diff --git a/app/build.gradle b/app/build.gradle index df7e404..a7c7034 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -43,7 +43,7 @@ dependencies { implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1' implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3' implementation 'androidx.navigation:navigation-ui-ktx:2.5.3' - implementation 'com.arthenica:ffmpeg-kit-full:5.1' + implementation 'com.arthenica:ffmpeg-kit-full:5.1.LTS' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.4' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ba47723..1794249 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,12 @@ xmlns:tools="http://schemas.android.com/tools" package="com.example.myapplication"> + + + + + + = mutableListOf() private lateinit var binding: ActivityMainBinding @@ -56,9 +62,7 @@ class MainActivity : AppCompatActivity() { //adds actions for when you press buttons override fun onOptionsItemSelected(item: MenuItem): Boolean { - val compressingRecycler = findViewById(R.id.compressing_recycler_view) as? RecyclerView - compressingRecycler?.adapter = CompressingAdapter(compressingItems) - compressingRecycler?.layoutManager = LinearLayoutManager(this) + return when (item.itemId) { @@ -74,12 +78,6 @@ class MainActivity : AppCompatActivity() { Toast.makeText(applicationContext, "Files", Toast.LENGTH_LONG).show() - if (fileName != null && fileDate != null) { - compressingItems.add(CompressingItem(fileName!!, 0.0, fileDate!!)) - } - - compressingItems.add(CompressingItem("Testing", 0.5, Date(1))) - return true } R.id.addYoutube ->{ @@ -90,13 +88,9 @@ class MainActivity : AppCompatActivity() { } } - private var fileName: String? = null - private var fileDate: Date? = null - private var fileStream: FileInputStream? = null - //grabs output from pressing files, used for grabbing URI private var resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == Activity.RESULT_OK) { + if (result.resultCode == RESULT_OK) { // There are no request codes val data: Uri? = result.data?.data @@ -105,16 +99,32 @@ class MainActivity : AppCompatActivity() { val fd = fileDescriptor?.fileDescriptor val inputStream = FileInputStream(fd) + val file = File(data.toString()) + Log.i("Tag", file.absolutePath) + + val fu = com.example.myapplication.utils.FileUtils(this) + + val test = "-i " + fu.getPath(data) + " -c:v libx264 -preset ultrafast -crf 28 -c:a aac -b:a 128k -movflags +faststart " + fu.getPath(data) + "_converted.mp4" + FFmpegKit.execute(test) + + val cursor = contentResolver.query(data, null, null, null, null) - val dateIndex = cursor?.getColumnIndex(MediaStore.MediaColumns.DATE_ADDED) - fileDate = Date(dateIndex?.toLong()!!) - val nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME) - fileName = cursor.getString(nameIndex) + cursor?.moveToFirst() - this.fileStream = inputStream + val fileDate = Date(System.currentTimeMillis()) + val fileName = cursor?.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME)) + + compressingItems.add(CompressingItem(fileName!!, 0.0, fileDate)) + + adapter.notifyDataSetChanged() Toast.makeText(applicationContext, data.toString(), Toast.LENGTH_LONG).show() } } + + companion object { + val compressingItems: MutableList = mutableListOf() + val adapter = CompressingAdapter(compressingItems) + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/ui/compressing/CompressingFragment.kt b/app/src/main/java/com/example/myapplication/ui/compressing/CompressingFragment.kt index ec5d3ef..30b2c95 100644 --- a/app/src/main/java/com/example/myapplication/ui/compressing/CompressingFragment.kt +++ b/app/src/main/java/com/example/myapplication/ui/compressing/CompressingFragment.kt @@ -6,12 +6,19 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.example.myapplication.MainActivity +import com.example.myapplication.MainActivity.Companion.compressingItems +import com.example.myapplication.R import com.example.myapplication.databinding.FragmentCompressingBinding class CompressingFragment : Fragment() { private var _binding: FragmentCompressingBinding? = null + + // This property is only valid between onCreateView and // onDestroyView. private val binding get() = _binding!! @@ -20,7 +27,15 @@ class CompressingFragment : Fragment() { ViewModelProvider(this)[CompressingViewModel::class.java] _binding = FragmentCompressingBinding.inflate(inflater, container, false) + + + val compressingRecycler = binding.root.findViewById(R.id.compressing_recycler_view) as? RecyclerView + compressingRecycler?.adapter = MainActivity.adapter + compressingRecycler?.layoutManager = LinearLayoutManager(binding.root.context) + return binding.root + + } override fun onDestroyView() { diff --git a/app/src/main/java/com/example/myapplication/utils/FileUtils.java b/app/src/main/java/com/example/myapplication/utils/FileUtils.java new file mode 100644 index 0000000..ea00cf3 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/utils/FileUtils.java @@ -0,0 +1,413 @@ +package com.example.myapplication.utils; + +import android.annotation.SuppressLint; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.provider.OpenableColumns; +import android.text.TextUtils; +import android.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.util.UUID; + +public class FileUtils { + public static String FALLBACK_COPY_FOLDER = "upload_part"; + + private static String TAG = "FileUtils"; + + private static Uri contentUri = null; + + Context context; + + public FileUtils(Context context) { + this.context = context; + } + + @SuppressLint("NewApi") + public String getPath(final Uri uri) { + // check here to KITKAT or new version + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + String selection = null; + String[] selectionArgs = null; + // DocumentProvider + + if (isKitKat) { + // ExternalStorageProvider + + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + String fullPath = getPathFromExtSD(split); + + if (fullPath == null || !fileExists(fullPath)) { + Log.d(TAG, "Copy files as a fallback"); + fullPath = copyFileToInternalStorage(uri, FALLBACK_COPY_FOLDER); + } + + if (fullPath != "") { + return fullPath; + } else { + return null; + } + } + + + // DownloadsProvider + + if (isDownloadsDocument(uri)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + final String id; + Cursor cursor = null; + try { + cursor = context.getContentResolver().query(uri, new String[] { + MediaStore.MediaColumns.DISPLAY_NAME + }, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + String fileName = cursor.getString(0); + String path = Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName; + if (!TextUtils.isEmpty(path)) { + return path; + } + } + } finally { + if (cursor != null) + cursor.close(); + } + id = DocumentsContract.getDocumentId(uri); + + if (!TextUtils.isEmpty(id)) { + if (id.startsWith("raw:")) { + return id.replaceFirst("raw:", ""); + } + String[] contentUriPrefixesToTry = new String[] { + "content://downloads/public_downloads", + "content://downloads/my_downloads" + }; + + for (String contentUriPrefix: contentUriPrefixesToTry) { + try { + final Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.valueOf(id)); + + return getDataColumn(context, contentUri, null, null); + } catch (NumberFormatException e) { + //In Android 8 and Android P the id is not a number + return uri.getPath().replaceFirst("^/document/raw:", "").replaceFirst("^raw:", ""); + } + } + } + } else { + final String id = DocumentsContract.getDocumentId(uri); + + if (id.startsWith("raw:")) { + return id.replaceFirst("raw:", ""); + } + try { + contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + + if (contentUri != null) + return getDataColumn(context, contentUri, null, null); + } + } + + + // MediaProvider + if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Log.d(TAG, "MEDIA DOCUMENT TYPE: " + type); + + Uri contentUri = null; + + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } else if ("document".equals(type)) { + contentUri = MediaStore.Files.getContentUri(MediaStore.getVolumeName(uri)); + } + + selection = "_id=?"; + selectionArgs = new String[] { + split[1] + }; + + + return getDataColumn(context, contentUri, selection, selectionArgs); + } + + if (isGoogleDriveUri(uri)) { + return getDriveFilePath(uri); + } + + if (isWhatsAppFile(uri)) { + return getFilePathForWhatsApp(uri); + } + + if ("content".equalsIgnoreCase(uri.getScheme())) { + if (isGooglePhotosUri(uri)) { + return uri.getLastPathSegment(); + } + + if (isGoogleDriveUri(uri)) { + return getDriveFilePath(uri); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // return getFilePathFromURI(context,uri); + return copyFileToInternalStorage(uri, FALLBACK_COPY_FOLDER); + // return getRealPathFromURI(context,uri); + } else { + return getDataColumn(context, uri, null, null); + } + + } + + if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + } else { + if (isWhatsAppFile(uri)) { + return getFilePathForWhatsApp(uri); + } + + if ("content".equalsIgnoreCase(uri.getScheme())) { + String[] projection = { + MediaStore.Images.Media.DATA + }; + Cursor cursor = null; + + try { + cursor = context.getContentResolver() + .query(uri, projection, selection, selectionArgs, null); + int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + + if (cursor.moveToFirst()) { + return cursor.getString(column_index); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + return copyFileToInternalStorage(uri, FALLBACK_COPY_FOLDER); + } + + private static boolean fileExists(String filePath) { + File file = new File(filePath); + + return file.exists(); + } + + private static String getPathFromExtSD(String[] pathData) { + final String type = pathData[0]; + final String relativePath = File.separator + pathData[1]; + String fullPath = ""; + + + Log.d(TAG, "MEDIA EXTSD TYPE: " + type); + Log.d(TAG, "Relative path: " + relativePath); + // on my Sony devices (4.4.4 & 5.1.1), `type` is a dynamic string + // something like "71F8-2C0A", some kind of unique id per storage + // don't know any API that can get the root path of that storage based on its id. + // + // so no "primary" type, but let the check here for other devices + if ("primary".equalsIgnoreCase(type)) { + fullPath = Environment.getExternalStorageDirectory() + relativePath; + if (fileExists(fullPath)) { + return fullPath; + } + } + + if ("home".equalsIgnoreCase(type)) { + fullPath = "/storage/emulated/0/Documents" + relativePath; + if (fileExists(fullPath)) { + return fullPath; + } + } + + // Environment.isExternalStorageRemovable() is `true` for external and internal storage + // so we cannot relay on it. + // + // instead, for each possible path, check if file exists + // we'll start with secondary storage as this could be our (physically) removable sd card + fullPath = System.getenv("SECONDARY_STORAGE") + relativePath; + if (fileExists(fullPath)) { + return fullPath; + } + + fullPath = System.getenv("EXTERNAL_STORAGE") + relativePath; + if (fileExists(fullPath)) { + return fullPath; + } + + return null; + } + + private String getDriveFilePath(Uri uri) { + Uri returnUri = uri; + Cursor returnCursor = context.getContentResolver().query(returnUri, null, null, null, null); + /* + * Get the column indexes of the data in the Cursor, + * * move to the first row in the Cursor, get the data, + * * and display it. + * */ + int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE); + returnCursor.moveToFirst(); + String name = (returnCursor.getString(nameIndex)); + String size = (Long.toString(returnCursor.getLong(sizeIndex))); + File file = new File(context.getCacheDir(), name); + try { + InputStream inputStream = context.getContentResolver().openInputStream(uri); + FileOutputStream outputStream = new FileOutputStream(file); + int read = 0; + int maxBufferSize = 1 * 1024 * 1024; + int bytesAvailable = inputStream.available(); + + //int bufferSize = 1024; + int bufferSize = Math.min(bytesAvailable, maxBufferSize); + + final byte[] buffers = new byte[bufferSize]; + while ((read = inputStream.read(buffers)) != -1) { + outputStream.write(buffers, 0, read); + } + Log.e(TAG, "Size " + file.length()); + inputStream.close(); + outputStream.close(); + Log.e(TAG, "Path " + file.getPath()); + Log.e(TAG, "Size " + file.length()); + } catch (Exception e) { + Log.e(TAG, e.getMessage()); + } + + return file.getPath(); + } + + /*** + * Used for Android Q+ + * @param uri + * @param newDirName if you want to create a directory, you can set this variable + * @return + */ + private String copyFileToInternalStorage(Uri uri, String newDirName) { + Uri returnUri = uri; + + Cursor returnCursor = context.getContentResolver().query(returnUri, new String[] { + OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE + }, null, null, null); + + + /* + * Get the column indexes of the data in the Cursor, + * * move to the first row in the Cursor, get the data, + * * and display it. + * */ + int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE); + returnCursor.moveToFirst(); + String name = (returnCursor.getString(nameIndex)); + String size = (Long.toString(returnCursor.getLong(sizeIndex))); + + File output; + if (!newDirName.equals("")) { + String random_collision_avoidance = UUID.randomUUID().toString(); + + File dir = new File(context.getFilesDir() + File.separator + newDirName + File.separator + random_collision_avoidance); + if (!dir.exists()) { + dir.mkdirs(); + } + output = new File(context.getFilesDir() + File.separator + newDirName + File.separator + random_collision_avoidance + File.separator + name); + } else { + output = new File(context.getFilesDir() + File.separator + name); + } + + try { + InputStream inputStream = context.getContentResolver().openInputStream(uri); + FileOutputStream outputStream = new FileOutputStream(output); + int read = 0; + int bufferSize = 1024; + final byte[] buffers = new byte[bufferSize]; + + while ((read = inputStream.read(buffers)) != -1) { + outputStream.write(buffers, 0, read); + } + + inputStream.close(); + outputStream.close(); + } catch (Exception e) { + Log.e(TAG, e.getMessage()); + } + + return output.getPath(); + } + + private String getFilePathForWhatsApp(Uri uri) { + return copyFileToInternalStorage(uri, "whatsapp"); + } + + private String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { + Cursor cursor = null; + final String column = "_data"; + final String[] projection = { + column + }; + + try { + cursor = context.getContentResolver().query(uri, projection, + selection, selectionArgs, null); + + if (cursor != null && cursor.moveToFirst()) { + final int index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(index); + } + } finally { + if (cursor != null) + cursor.close(); + } + + return null; + } + + private static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + private static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + private boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + private boolean isGooglePhotosUri(Uri uri) { + return "com.google.android.apps.photos.content".equals(uri.getAuthority()); + } + + public boolean isWhatsAppFile(Uri uri) { + return "com.whatsapp.provider.media".equals(uri.getAuthority()); + } + + private boolean isGoogleDriveUri(Uri uri) { + return "com.google.android.apps.docs.storage".equals(uri.getAuthority()) || "com.google.android.apps.docs.storage.legacy".equals(uri.getAuthority()); + } +} \ No newline at end of file