From 91c70b778ae1664d3cd7e2e940cda1b4db720965 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Fri, 2 Apr 2021 01:42:21 +0900 Subject: [PATCH 01/17] Move downloads to Environment downloads folder --- app/build.gradle | 4 +- .../activities/CameraActivity.java | 4 +- .../dialogs/CreateBackupDialogFragment.java | 144 ++++++++++----- .../dialogs/RestoreBackupDialogFragment.java | 164 ++++++++++++------ .../fragments/HashTagFragment.java | 3 - .../DownloadsPreferencesFragment.java | 130 +++++++------- .../instagrabber/managers/ThreadManager.java | 2 +- .../awais/instagrabber/utils/BitmapUtils.java | 13 +- .../instagrabber/utils/DirectoryUtils.java | 40 ++--- .../instagrabber/utils/DownloadUtils.java | 54 ++++-- .../instagrabber/utils/ExportImportUtils.java | 72 ++++---- .../instagrabber/utils/ResponseBodyUtils.java | 24 +-- .../java/awais/instagrabber/utils/Utils.java | 52 +++--- .../viewmodels/DirectThreadViewModel.java | 4 +- .../viewmodels/ImageEditViewModel.java | 4 +- .../main/res/layout/dialog_create_backup.xml | 2 +- .../main/res/layout/dialog_restore_backup.xml | 2 +- app/src/main/res/values/strings.xml | 2 + 18 files changed, 426 insertions(+), 294 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index f6096214..498d167a 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,13 +12,13 @@ def getGitHash = { -> } android { - compileSdkVersion 29 + compileSdkVersion 30 defaultConfig { applicationId 'me.austinhuang.instagrabber' minSdkVersion 21 - targetSdkVersion 29 + targetSdkVersion 30 versionCode 60 versionName '19.1.0' diff --git a/app/src/main/java/awais/instagrabber/activities/CameraActivity.java b/app/src/main/java/awais/instagrabber/activities/CameraActivity.java index 169efb27..9a76a9be 100644 --- a/app/src/main/java/awais/instagrabber/activities/CameraActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/CameraActivity.java @@ -33,7 +33,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import awais.instagrabber.databinding.ActivityCameraBinding; -import awais.instagrabber.utils.DirectoryUtils; +import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.PermissionUtils; import awais.instagrabber.utils.Utils; @@ -74,7 +74,7 @@ public class CameraActivity extends BaseLanguageActivity { setContentView(binding.getRoot()); Utils.transparentStatusBar(this, true, false); displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE); - outputDirectory = DirectoryUtils.getOutputMediaDirectory(this, "Camera"); + outputDirectory = DownloadUtils.getCameraDir(); cameraExecutor = Executors.newSingleThreadExecutor(); displayManager.registerDisplayListener(displayListener, null); binding.viewFinder.post(() -> { diff --git a/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java index f612b72c..42b0ebd1 100644 --- a/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java +++ b/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java @@ -2,8 +2,11 @@ package awais.instagrabber.dialogs; import android.app.Dialog; import android.content.Context; -import android.content.pm.PackageManager; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; import android.os.Bundle; +import android.provider.DocumentsContract; import android.text.Editable; import android.text.TextWatcher; import android.view.LayoutInflater; @@ -14,27 +17,25 @@ import android.view.inputmethod.InputMethodManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.FragmentTransaction; -import java.io.File; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import awais.instagrabber.databinding.DialogCreateBackupBinding; -import awais.instagrabber.utils.DirectoryChooser; +import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.ExportImportUtils; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; -import static awais.instagrabber.utils.Constants.FOLDER_PATH; -import static awais.instagrabber.utils.DownloadUtils.PERMS; +import static android.app.Activity.RESULT_OK; public class CreateBackupDialogFragment extends DialogFragment { + private static final String TAG = CreateBackupDialogFragment.class.getSimpleName(); private static final int STORAGE_PERM_REQUEST_CODE = 8020; private static final SimpleDateFormat BACKUP_FILE_DATE_TIME_FORMAT = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US); + private static final int CREATE_FILE_REQUEST_CODE = 1; private final OnResultListener onResultListener; private DialogCreateBackupBinding binding; @@ -113,60 +114,113 @@ public class CreateBackupDialogFragment extends DialogFragment { imm.hideSoftInputFromWindow(binding.etPassword.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN); }); binding.btnSaveTo.setOnClickListener(v -> { - if (ContextCompat.checkSelfPermission(context, PERMS[0]) == PackageManager.PERMISSION_GRANTED) { - showChooser(context); - } else { - requestPermissions(PERMS, STORAGE_PERM_REQUEST_CODE); - } + createFile(); + // if (ContextCompat.checkSelfPermission(context, PERMS[0]) == PackageManager.PERMISSION_GRANTED) { + // showChooser(context); + // } else { + // requestPermissions(PERMS, STORAGE_PERM_REQUEST_CODE); + // } }); } @Override public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (requestCode == STORAGE_PERM_REQUEST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - final Context context = getContext(); - if (context == null) return; - showChooser(context); - } + // if (requestCode == STORAGE_PERM_REQUEST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + // final Context context = getContext(); + // if (context == null) return; + // showChooser(context); + // } } - private void showChooser(@NonNull final Context context) { - final String folderPath = Utils.settingsHelper.getString(FOLDER_PATH); + @Override + public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { + if (data == null || data.getData() == null) return; + if (resultCode != RESULT_OK || requestCode != CREATE_FILE_REQUEST_CODE) return; + final Context context = getContext(); + if (context == null) return; final Editable passwordText = binding.etPassword.getText(); final String password = binding.cbPassword.isChecked() && passwordText != null && !TextUtils.isEmpty(passwordText.toString()) ? passwordText.toString().trim() : null; - final DirectoryChooser directoryChooser = new DirectoryChooser() - .setInitialDirectory(folderPath) - .setInteractionListener(path -> { - final Date now = new Date(); - final File file = new File(path, String.format("barinsta_%s.backup", BACKUP_FILE_DATE_TIME_FORMAT.format(now))); - int flags = 0; - if (binding.cbExportFavorites.isChecked()) { - flags |= ExportImportUtils.FLAG_FAVORITES; - } - if (binding.cbExportSettings.isChecked()) { - flags |= ExportImportUtils.FLAG_SETTINGS; - } - if (binding.cbExportLogins.isChecked()) { - flags |= ExportImportUtils.FLAG_COOKIES; - } - ExportImportUtils.exportData(context, flags, file, password, result -> { - if (onResultListener != null) { - onResultListener.onResult(result); - } - dismiss(); - }); - - }); - directoryChooser.setEnterTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); - directoryChooser.setExitTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); - directoryChooser.show(getChildFragmentManager(), "directory_chooser"); + int flags = 0; + if (binding.cbExportFavorites.isChecked()) { + flags |= ExportImportUtils.FLAG_FAVORITES; + } + if (binding.cbExportSettings.isChecked()) { + flags |= ExportImportUtils.FLAG_SETTINGS; + } + if (binding.cbExportLogins.isChecked()) { + flags |= ExportImportUtils.FLAG_COOKIES; + } + ExportImportUtils.exportData(context, flags, data.getData(), password, result -> { + if (onResultListener != null) { + onResultListener.onResult(result); + } + dismiss(); + }); + // try (final OutputStream stream = context.getContentResolver().openOutputStream(data.getData())) { + // } catch (Exception e) { + // Log.e(TAG, "onActivityResult: ", e); + // } } + // private void showChooser(@NonNull final Context context) { + // final String folderPath = Utils.settingsHelper.getString(FOLDER_PATH); + // final Editable passwordText = binding.etPassword.getText(); + // final String password = binding.cbPassword.isChecked() + // && passwordText != null + // && !TextUtils.isEmpty(passwordText.toString()) + // ? passwordText.toString().trim() + // : null; + // final DirectoryChooser directoryChooser = new DirectoryChooser() + // .setInitialDirectory(folderPath) + // .setInteractionListener(path -> { + // final Date now = new Date(); + // final File file = new File(path, String.format("barinsta_%s.backup", BACKUP_FILE_DATE_TIME_FORMAT.format(now))); + // int flags = 0; + // if (binding.cbExportFavorites.isChecked()) { + // flags |= ExportImportUtils.FLAG_FAVORITES; + // } + // if (binding.cbExportSettings.isChecked()) { + // flags |= ExportImportUtils.FLAG_SETTINGS; + // } + // if (binding.cbExportLogins.isChecked()) { + // flags |= ExportImportUtils.FLAG_COOKIES; + // } + // ExportImportUtils.exportData(context, flags, file, password, result -> { + // if (onResultListener != null) { + // onResultListener.onResult(result); + // } + // dismiss(); + // }); + // + // }); + // directoryChooser.setEnterTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); + // directoryChooser.setExitTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); + // directoryChooser.show(getChildFragmentManager(), "directory_chooser"); + // } + + private void createFile() { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("application/octet-stream"); + final Date now = new Date(); + final String fileName = String.format("barinsta_%s.backup", BACKUP_FILE_DATE_TIME_FORMAT.format(now)); + intent.putExtra(Intent.EXTRA_TITLE, fileName); + + // Optionally, specify a URI for the directory that should be opened in + // the system file picker when your app creates the document. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Uri.fromFile(DownloadUtils.getDownloadDir())); + } + + startActivityForResult(intent, CREATE_FILE_REQUEST_CODE); + } + + public interface OnResultListener { void onResult(boolean result); } diff --git a/app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java index 7967b9e2..78431b6f 100644 --- a/app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java +++ b/app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java @@ -1,12 +1,18 @@ package awais.instagrabber.dialogs; import android.app.Dialog; +import android.content.ContentResolver; import android.content.Context; -import android.content.pm.PackageManager; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.os.Looper; +import android.provider.MediaStore; import android.text.Editable; import android.text.TextWatcher; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -15,30 +21,28 @@ import android.view.inputmethod.InputMethodManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.FragmentTransaction; - -import java.io.File; import awais.instagrabber.databinding.DialogRestoreBackupBinding; -import awais.instagrabber.utils.DirectoryChooser; +import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.ExportImportUtils; import awais.instagrabber.utils.PasswordUtils.IncorrectPasswordException; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; -import static awais.instagrabber.utils.Constants.FOLDER_PATH; -import static awais.instagrabber.utils.DownloadUtils.PERMS; +import static android.app.Activity.RESULT_OK; public class RestoreBackupDialogFragment extends DialogFragment { + private static final String TAG = RestoreBackupDialogFragment.class.getSimpleName(); private static final int STORAGE_PERM_REQUEST_CODE = 8020; + private static final int OPEN_FILE_REQUEST_CODE = 1; private OnResultListener onResultListener; private DialogRestoreBackupBinding binding; - private File file; + // private File file; private boolean isEncrypted; + private Uri uri; public RestoreBackupDialogFragment() {} @@ -83,18 +87,62 @@ public class RestoreBackupDialogFragment extends DialogFragment { @Override public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (requestCode == STORAGE_PERM_REQUEST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - showChooser(); + // if (requestCode == STORAGE_PERM_REQUEST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + // showChooser(); + // } + } + + @Override + public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { + if (data == null || data.getData() == null) return; + if (resultCode != RESULT_OK || requestCode != OPEN_FILE_REQUEST_CODE) return; + final Context context = getContext(); + if (context == null) return; + isEncrypted = ExportImportUtils.isEncrypted(context, data.getData()); + if (isEncrypted) { + binding.passwordGroup.setVisibility(View.VISIBLE); + binding.passwordGroup.post(() -> { + binding.etPassword.requestFocus(); + binding.etPassword.post(() -> { + final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm == null) return; + imm.showSoftInput(binding.etPassword, InputMethodManager.SHOW_IMPLICIT); + }); + binding.btnRestore.setEnabled(!TextUtils.isEmpty(binding.etPassword.getText())); + }); + } else { + binding.passwordGroup.setVisibility(View.GONE); + binding.btnRestore.setEnabled(true); } + uri = data.getData(); + AppExecutors.getInstance().mainThread().execute(() -> { + Cursor c = null; + try { + String[] projection = {MediaStore.Files.FileColumns.DISPLAY_NAME}; + final ContentResolver contentResolver = context.getContentResolver(); + c = contentResolver.query(uri, projection, null, null, null); + if (c != null) { + while (c.moveToNext()) { + final String displayName = c.getString(0); + binding.filePath.setText(displayName); + } + } + } catch (Exception e) { + Log.e(TAG, "onActivityResult: ", e); + } finally { + if (c != null) { + c.close(); + } + } + }); } private void init() { final Context context = getContext(); - if (context == null) { - return; - } + if (context == null) return; binding.btnRestore.setEnabled(false); - binding.btnRestore.setOnClickListener(v -> new Handler().post(() -> { + binding.btnRestore.setOnClickListener(v -> new Handler(Looper.getMainLooper()).post(() -> { + if (uri == null) return; int flags = 0; if (binding.cbFavorites.isChecked()) { flags |= ExportImportUtils.FLAG_FAVORITES; @@ -111,7 +159,7 @@ public class RestoreBackupDialogFragment extends DialogFragment { ExportImportUtils.importData( context, flags, - file, + uri, !isEncrypted ? null : text.toString(), result -> { if (onResultListener != null) { @@ -137,46 +185,56 @@ public class RestoreBackupDialogFragment extends DialogFragment { @Override public void afterTextChanged(final Editable s) {} }); - if (ContextCompat.checkSelfPermission(context, PERMS[0]) == PackageManager.PERMISSION_GRANTED) { - showChooser(); - return; - } - requestPermissions(PERMS, STORAGE_PERM_REQUEST_CODE); - } + // if (ContextCompat.checkSelfPermission(context, PERMS[0]) == PackageManager.PERMISSION_GRANTED) { + // showChooser(); + // return; + // } + // requestPermissions(PERMS, STORAGE_PERM_REQUEST_CODE); + final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + // intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + // intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{ + // "application/pdf", // .pdf + // "application/vnd.oasis.opendocument.text", // .odt + // "text/plain" // .txt + // }); + startActivityForResult(intent, OPEN_FILE_REQUEST_CODE); - private void showChooser() { - final String folderPath = Utils.settingsHelper.getString(FOLDER_PATH); - final Context context = getContext(); - if (context == null) return; - final DirectoryChooser directoryChooser = new DirectoryChooser() - .setInitialDirectory(folderPath) - .setShowBackupFiles(true) - .setInteractionListener(file -> { - isEncrypted = ExportImportUtils.isEncrypted(file); - if (isEncrypted) { - binding.passwordGroup.setVisibility(View.VISIBLE); - binding.passwordGroup.post(() -> { - binding.etPassword.requestFocus(); - binding.etPassword.post(() -> { - final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - if (imm == null) return; - imm.showSoftInput(binding.etPassword, InputMethodManager.SHOW_IMPLICIT); - }); - binding.btnRestore.setEnabled(!TextUtils.isEmpty(binding.etPassword.getText())); - }); - } else { - binding.passwordGroup.setVisibility(View.GONE); - binding.btnRestore.setEnabled(true); - } - this.file = file; - binding.filePath.setText(file.getAbsolutePath()); - }); - directoryChooser.setEnterTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); - directoryChooser.setExitTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); - directoryChooser.setOnCancelListener(this::dismiss); - directoryChooser.show(getChildFragmentManager(), "directory_chooser"); } + // private void showChooser() { + // final String folderPath = Utils.settingsHelper.getString(FOLDER_PATH); + // final Context context = getContext(); + // if (context == null) return; + // final DirectoryChooser directoryChooser = new DirectoryChooser() + // .setInitialDirectory(folderPath) + // .setShowBackupFiles(true) + // .setInteractionListener(file -> { + // isEncrypted = ExportImportUtils.isEncrypted(file); + // if (isEncrypted) { + // binding.passwordGroup.setVisibility(View.VISIBLE); + // binding.passwordGroup.post(() -> { + // binding.etPassword.requestFocus(); + // binding.etPassword.post(() -> { + // final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + // if (imm == null) return; + // imm.showSoftInput(binding.etPassword, InputMethodManager.SHOW_IMPLICIT); + // }); + // binding.btnRestore.setEnabled(!TextUtils.isEmpty(binding.etPassword.getText())); + // }); + // } else { + // binding.passwordGroup.setVisibility(View.GONE); + // binding.btnRestore.setEnabled(true); + // } + // this.file = file; + // binding.filePath.setText(file.getAbsolutePath()); + // }); + // directoryChooser.setEnterTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); + // directoryChooser.setExitTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); + // directoryChooser.setOnCancelListener(this::dismiss); + // directoryChooser.show(getChildFragmentManager(), "directory_chooser"); + // } + public interface OnResultListener { void onResult(boolean result); } diff --git a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java index 81530dc5..3f974816 100644 --- a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java @@ -74,9 +74,6 @@ import static androidx.core.content.PermissionChecker.checkSelfPermission; import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; import static awais.instagrabber.utils.Utils.settingsHelper; -//import awaisomereport.LogCollector; -//import static awais.instagrabber.utils.Utils.logCollector; - public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "HashTagFragment"; private static final int STORAGE_PERM_REQUEST_CODE = 8020; diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java index c2637b78..285b4689 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java @@ -1,26 +1,14 @@ package awais.instagrabber.fragments.settings; import android.content.Context; -import android.view.View; import androidx.annotation.NonNull; -import androidx.appcompat.widget.AppCompatButton; -import androidx.appcompat.widget.AppCompatTextView; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; -import androidx.preference.PreferenceViewHolder; import androidx.preference.SwitchPreferenceCompat; -import com.google.android.material.switchmaterial.SwitchMaterial; - import awais.instagrabber.R; import awais.instagrabber.utils.Constants; -import awais.instagrabber.utils.DirectoryChooser; -import awais.instagrabber.utils.TextUtils; - -import static awais.instagrabber.utils.Constants.FOLDER_PATH; -import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; -import static awais.instagrabber.utils.Utils.settingsHelper; public class DownloadsPreferencesFragment extends BasePreferencesFragment { @Override @@ -28,7 +16,7 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment { final Context context = getContext(); if (context == null) return; screen.addPreference(getDownloadUserFolderPreference(context)); - screen.addPreference(getSaveToCustomFolderPreference(context)); + // screen.addPreference(getSaveToCustomFolderPreference(context)); } private Preference getDownloadUserFolderPreference(@NonNull final Context context) { @@ -39,63 +27,63 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment { return preference; } - private Preference getSaveToCustomFolderPreference(@NonNull final Context context) { - return new SaveToCustomFolderPreference(context, (resultCallback) -> new DirectoryChooser() - .setInitialDirectory(settingsHelper.getString(FOLDER_PATH)) - .setInteractionListener(file -> { - settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath()); - resultCallback.onResult(file.getAbsolutePath()); - }) - .show(getParentFragmentManager(), null)); - } - - public static class SaveToCustomFolderPreference extends Preference { - private AppCompatTextView customPathTextView; - private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener; - private final String key; + // private Preference getSaveToCustomFolderPreference(@NonNull final Context context) { + // return new SaveToCustomFolderPreference(context, (resultCallback) -> new DirectoryChooser() + // .setInitialDirectory(settingsHelper.getString(FOLDER_PATH)) + // .setInteractionListener(file -> { + // settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath()); + // resultCallback.onResult(file.getAbsolutePath()); + // }) + // .show(getParentFragmentManager(), null)); + // } - public SaveToCustomFolderPreference(final Context context, final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener) { - super(context); - this.onSelectFolderButtonClickListener = onSelectFolderButtonClickListener; - key = Constants.FOLDER_SAVE_TO; - setLayoutResource(R.layout.pref_custom_folder); - setKey(key); - setTitle(R.string.save_to_folder); - setIconSpaceReserved(false); - } - - @Override - public void onBindViewHolder(final PreferenceViewHolder holder) { - super.onBindViewHolder(holder); - final SwitchMaterial cbSaveTo = (SwitchMaterial) holder.findViewById(R.id.cbSaveTo); - final View buttonContainer = holder.findViewById(R.id.button_container); - customPathTextView = (AppCompatTextView) holder.findViewById(R.id.custom_path); - cbSaveTo.setOnCheckedChangeListener((buttonView, isChecked) -> { - settingsHelper.putBoolean(FOLDER_SAVE_TO, isChecked); - buttonContainer.setVisibility(isChecked ? View.VISIBLE : View.GONE); - final String customPath = settingsHelper.getString(FOLDER_PATH); - customPathTextView.setText(customPath); - }); - final boolean savedToEnabled = settingsHelper.getBoolean(key); - holder.itemView.setOnClickListener(v -> cbSaveTo.toggle()); - cbSaveTo.setChecked(savedToEnabled); - buttonContainer.setVisibility(savedToEnabled ? View.VISIBLE : View.GONE); - final AppCompatButton btnSaveTo = (AppCompatButton) holder.findViewById(R.id.btnSaveTo); - btnSaveTo.setOnClickListener(v -> { - if (onSelectFolderButtonClickListener == null) return; - onSelectFolderButtonClickListener.onClick(result -> { - if (TextUtils.isEmpty(result)) return; - customPathTextView.setText(result); - }); - }); - } - - public interface ResultCallback { - void onResult(String result); - } - - public interface OnSelectFolderButtonClickListener { - void onClick(ResultCallback resultCallback); - } - } + // public static class SaveToCustomFolderPreference extends Preference { + // private AppCompatTextView customPathTextView; + // private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener; + // private final String key; + // + // public SaveToCustomFolderPreference(final Context context, final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener) { + // super(context); + // this.onSelectFolderButtonClickListener = onSelectFolderButtonClickListener; + // key = Constants.FOLDER_SAVE_TO; + // setLayoutResource(R.layout.pref_custom_folder); + // setKey(key); + // setTitle(R.string.save_to_folder); + // setIconSpaceReserved(false); + // } + // + // @Override + // public void onBindViewHolder(final PreferenceViewHolder holder) { + // super.onBindViewHolder(holder); + // final SwitchMaterial cbSaveTo = (SwitchMaterial) holder.findViewById(R.id.cbSaveTo); + // final View buttonContainer = holder.findViewById(R.id.button_container); + // customPathTextView = (AppCompatTextView) holder.findViewById(R.id.custom_path); + // cbSaveTo.setOnCheckedChangeListener((buttonView, isChecked) -> { + // settingsHelper.putBoolean(FOLDER_SAVE_TO, isChecked); + // buttonContainer.setVisibility(isChecked ? View.VISIBLE : View.GONE); + // final String customPath = settingsHelper.getString(FOLDER_PATH); + // customPathTextView.setText(customPath); + // }); + // final boolean savedToEnabled = settingsHelper.getBoolean(key); + // holder.itemView.setOnClickListener(v -> cbSaveTo.toggle()); + // cbSaveTo.setChecked(savedToEnabled); + // buttonContainer.setVisibility(savedToEnabled ? View.VISIBLE : View.GONE); + // final AppCompatButton btnSaveTo = (AppCompatButton) holder.findViewById(R.id.btnSaveTo); + // btnSaveTo.setOnClickListener(v -> { + // if (onSelectFolderButtonClickListener == null) return; + // onSelectFolderButtonClickListener.onClick(result -> { + // if (TextUtils.isEmpty(result)) return; + // customPathTextView.setText(result); + // }); + // }); + // } + // + // public interface ResultCallback { + // void onResult(String result); + // } + // + // public interface OnSelectFolderButtonClickListener { + // void onClick(ResultCallback resultCallback); + // } + // } } diff --git a/app/src/main/java/awais/instagrabber/managers/ThreadManager.java b/app/src/main/java/awais/instagrabber/managers/ThreadManager.java index 8efff504..dab32b35 100644 --- a/app/src/main/java/awais/instagrabber/managers/ThreadManager.java +++ b/app/src/main/java/awais/instagrabber/managers/ThreadManager.java @@ -991,7 +991,7 @@ public final class ThreadManager { @NonNull final Uri uri) { try { final Pair dimensions = BitmapUtils.decodeDimensions(contentResolver, uri); - if (dimensions == null) { + if (dimensions == null || dimensions.first == null || dimensions.second == null) { data.postValue(Resource.error("Decoding dimensions failed", null)); return; } diff --git a/app/src/main/java/awais/instagrabber/utils/BitmapUtils.java b/app/src/main/java/awais/instagrabber/utils/BitmapUtils.java index c1241ebd..c0b4cb5f 100644 --- a/app/src/main/java/awais/instagrabber/utils/BitmapUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/BitmapUtils.java @@ -236,10 +236,15 @@ public final class BitmapUtils { @NonNull final Uri uri) throws FileNotFoundException { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; - BitmapFactory.decodeStream(contentResolver.openInputStream(uri), null, options); - return (options.outWidth == -1 || options.outHeight == -1) - ? null - : new Pair<>(options.outWidth, options.outHeight); + try (final InputStream inputStream = contentResolver.openInputStream(uri)) { + BitmapFactory.decodeStream(inputStream, null, options); + return (options.outWidth == -1 || options.outHeight == -1) + ? null + : new Pair<>(options.outWidth, options.outHeight); + } catch (IOException e) { + Log.e(TAG, "decodeDimensions: ", e); + } + return null; } public static File convertToJpegAndSaveToFile(@NonNull final Bitmap bitmap, @Nullable final File file) throws IOException { diff --git a/app/src/main/java/awais/instagrabber/utils/DirectoryUtils.java b/app/src/main/java/awais/instagrabber/utils/DirectoryUtils.java index 1ad6a531..205239c1 100644 --- a/app/src/main/java/awais/instagrabber/utils/DirectoryUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DirectoryUtils.java @@ -1,7 +1,5 @@ package awais.instagrabber.utils; -import android.content.Context; -import android.os.Build; import android.os.Environment; import java.io.File; @@ -10,8 +8,6 @@ import java.util.HashSet; import java.util.Set; import java.util.regex.Pattern; -import awais.instagrabber.R; - public class DirectoryUtils { private static final Pattern DIR_SEPORATOR = Pattern.compile("/"); @@ -73,22 +69,22 @@ public class DirectoryUtils { return rv; } - public static File getOutputMediaDirectory(final Context context, final String... dirs) { - if (context == null) return null; - final File[] externalMediaDirs = context.getExternalMediaDirs(); - if (externalMediaDirs == null || externalMediaDirs.length == 0) return context.getFilesDir(); - final File externalMediaDir = externalMediaDirs[0]; - File subDir = new File(externalMediaDir, context.getString(R.string.app_name)); - if (dirs != null) { - for (final String dir : dirs) { - subDir = new File(subDir, dir); - //noinspection ResultOfMethodCallIgnored - subDir.mkdirs(); - } - } - if (!subDir.exists()) { - return context.getFilesDir(); - } - return subDir; - } + // public static File getOutputMediaDirectory(final Context context, final String... dirs) { + // if (context == null) return null; + // final File[] externalMediaDirs = context.getExternalMediaDirs(); + // if (externalMediaDirs == null || externalMediaDirs.length == 0) return context.getFilesDir(); + // final File externalMediaDir = externalMediaDirs[0]; + // File subDir = new File(externalMediaDir, context.getString(R.string.app_name)); + // if (dirs != null) { + // for (final String dir : dirs) { + // subDir = new File(subDir, dir); + // //noinspection ResultOfMethodCallIgnored + // subDir.mkdirs(); + // } + // } + // if (!subDir.exists()) { + // return context.getFilesDir(); + // } + // return subDir; + // } } diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java index 85f45a21..a5933e48 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java @@ -41,26 +41,54 @@ import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.VideoVersion; import awais.instagrabber.workers.DownloadWorker; -import static awais.instagrabber.utils.Constants.FOLDER_PATH; -import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; - public final class DownloadUtils { - private static final String TAG = "DownloadUtils"; + private static final String TAG = DownloadUtils.class.getSimpleName(); public static final String WRITE_PERMISSION = Manifest.permission.WRITE_EXTERNAL_STORAGE; public static final String[] PERMS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; + public static final String DIR_BARINSTA = "Barinsta"; + public static final String DIR_DOWNLOADS = "Downloads"; + public static final String DIR_CAMERA = "Camera"; + public static final String DIR_EDIT = "Edit"; + + public static File getDownloadDir(final String... dirs) { + final File parent = new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS); + File subDir = new File(parent, DIR_BARINSTA); + if (dirs != null) { + for (final String dir : dirs) { + subDir = new File(subDir, dir); + //noinspection ResultOfMethodCallIgnored + subDir.mkdirs(); + } + } + return subDir; + } @NonNull - private static File getDownloadDir() { - File dir = new File(Environment.getExternalStorageDirectory(), "Download"); + public static File getDownloadDir() { + // final File parent = new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS); + // final File dir = new File(new File(parent, "barinsta"), "downloads"); + // if (!dir.exists()) { + // final boolean mkdirs = dir.mkdirs(); + // if (!mkdirs) { + // Log.e(TAG, "getDownloadDir: failed to create dir"); + // } + // } + // if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) { + // final String customPath = Utils.settingsHelper.getString(FOLDER_PATH); + // if (!TextUtils.isEmpty(customPath)) { + // dir = new File(customPath); + // } + // } + return getDownloadDir(DIR_DOWNLOADS); + } - if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) { - final String customPath = Utils.settingsHelper.getString(FOLDER_PATH); - if (!TextUtils.isEmpty(customPath)) { - dir = new File(customPath); - } - } - return dir; + public static File getCameraDir() { + return getDownloadDir(DIR_CAMERA); + } + + public static File getImageEditDir(final String sessionId) { + return getDownloadDir(DIR_EDIT, sessionId); } @Nullable diff --git a/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java b/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java index ca5ca21c..1110e58a 100755 --- a/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java @@ -2,6 +2,7 @@ package awais.instagrabber.utils; import android.content.Context; import android.content.SharedPreferences; +import android.net.Uri; import android.util.Base64; import android.util.Log; import android.util.Pair; @@ -20,9 +21,8 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; @@ -55,14 +55,15 @@ public final class ExportImportUtils { public static void importData(@NonNull final Context context, @ExportImportFlags final int flags, - @NonNull final File file, + @NonNull final Uri uri, final String password, final FetchListener fetchListener) throws IncorrectPasswordException { - try (final FileInputStream fis = new FileInputStream(file)) { - final int configType = fis.read(); + try (final InputStream stream = context.getContentResolver().openInputStream(uri)) { + if (stream == null) return; + final int configType = stream.read(); final StringBuilder builder = new StringBuilder(); int c; - while ((c = fis.read()) != -1) { + while ((c = stream.read()) != -1) { builder.append((char) c); } if (configType == 'A') { @@ -80,8 +81,8 @@ public final class ExportImportUtils { throw e; } catch (final Exception e) { if (fetchListener != null) fetchListener.onResult(false); -// if (logCollector != null) -// logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import::pass"); + // if (logCollector != null) + // logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import::pass"); if (BuildConfig.DEBUG) Log.e(TAG, "Error importing backup", e); } } else if (configType == 'Z') { @@ -99,7 +100,7 @@ public final class ExportImportUtils { throw e; } catch (final Exception e) { if (fetchListener != null) fetchListener.onResult(false); -// if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import"); + // if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import"); if (BuildConfig.DEBUG) Log.e(TAG, "", e); } } @@ -122,7 +123,7 @@ public final class ExportImportUtils { if (fetchListener != null) fetchListener.onResult(true); } catch (final Exception e) { if (fetchListener != null) fetchListener.onResult(false); -// if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "importJson"); + // if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "importJson"); if (BuildConfig.DEBUG) Log.e(TAG, "", e); } } @@ -212,9 +213,11 @@ public final class ExportImportUtils { } } - public static boolean isEncrypted(final File file) { - try (final FileInputStream fis = new FileInputStream(file)) { - final int configType = fis.read(); + public static boolean isEncrypted(@NonNull final Context context, + @NonNull final Uri uri) { + try (final InputStream stream = context.getContentResolver().openInputStream(uri)) { + if (stream == null) return false; + final int configType = stream.read(); if (configType == 'A') { return true; } @@ -226,7 +229,7 @@ public final class ExportImportUtils { public static void exportData(@NonNull final Context context, @ExportImportFlags final int flags, - @NonNull final File filePath, + @NonNull final Uri uri, final String password, final FetchListener fetchListener) { getExportString(flags, context, exportString -> { @@ -241,25 +244,30 @@ public final class ExportImportUtils { exportBytes = PasswordUtils.enc(exportString, bytes); } catch (final Exception e) { if (fetchListener != null) fetchListener.onResult(false); -// if (logCollector != null) -// logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::isPass"); + // if (logCollector != null) + // logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::isPass"); if (BuildConfig.DEBUG) Log.e(TAG, "", e); } } else { exportBytes = Base64.encode(exportString.getBytes(), Base64.DEFAULT | Base64.NO_WRAP | Base64.NO_PADDING); } if (exportBytes != null && exportBytes.length > 1) { - try (final FileOutputStream fos = new FileOutputStream(filePath)) { - fos.write(isPass ? 'A' : 'Z'); - fos.write(exportBytes); + try (final OutputStream stream = context.getContentResolver().openOutputStream(uri)) { + if (stream == null) return; + stream.write(isPass ? 'A' : 'Z'); + stream.write(exportBytes); if (fetchListener != null) fetchListener.onResult(true); - } catch (final Exception e) { + } catch (Exception e) { if (fetchListener != null) fetchListener.onResult(false); -// if (logCollector != null) -// logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::notPass"); - if (BuildConfig.DEBUG) Log.e(TAG, "", e); + // if (logCollector != null) + // logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::notPass"); + Log.e(TAG, "exportData", e); } - } else if (fetchListener != null) fetchListener.onResult(false); + return; + } + if (fetchListener != null) { + fetchListener.onResult(false); + } }); } @@ -324,7 +332,7 @@ public final class ExportImportUtils { }, AppExecutors.getInstance().tasksThread()); return; } catch (final Exception e) { -// if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getExportString"); + // if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getExportString"); if (BuildConfig.DEBUG) Log.e(TAG, "", e); } callback.onCreated(null); @@ -373,9 +381,9 @@ public final class ExportImportUtils { jsonArray.put(jsonObject); } } catch (Exception e) { -// if (logCollector != null) { -// logCollector.appendException(e, LogFile.UTILS_EXPORT, "getFavorites"); -// } + // if (logCollector != null) { + // logCollector.appendException(e, LogFile.UTILS_EXPORT, "getFavorites"); + // } if (BuildConfig.DEBUG) { Log.e(TAG, "Error exporting favorites", e); } @@ -409,9 +417,9 @@ public final class ExportImportUtils { jsonArray.put(jsonObject); } } catch (Exception e) { -// if (logCollector != null) { -// logCollector.appendException(e, LogFile.UTILS_EXPORT, "getCookies"); -// } + // if (logCollector != null) { + // logCollector.appendException(e, LogFile.UTILS_EXPORT, "getCookies"); + // } if (BuildConfig.DEBUG) { Log.e(TAG, "Error exporting accounts", e); } diff --git a/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java b/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java index 2722d11e..906d3945 100644 --- a/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java @@ -2,7 +2,6 @@ package awais.instagrabber.utils; import android.net.Uri; import android.util.Log; -import android.util.Pair; import androidx.annotation.Nullable; @@ -76,10 +75,10 @@ public final class ResponseBodyUtils { if (lastIndexMain >= 0) return sources[lastIndexMain]; else if (lastIndexBase >= 0) return sources[lastIndexBase]; } catch (final Exception e) { -// if (Utils.logCollector != null) -// Utils.logCollector.appendException(e, LogCollector.LogFile.UTILS, "getHighQualityPost", -// new Pair<>("resourcesNull", resources == null), -// new Pair<>("isVideo", isVideo)); + // if (Utils.logCollector != null) + // Utils.logCollector.appendException(e, LogCollector.LogFile.UTILS, "getHighQualityPost", + // new Pair<>("resourcesNull", resources == null), + // new Pair<>("isVideo", isVideo)); if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); } return null; @@ -94,9 +93,9 @@ public final class ResponseBodyUtils { src = getHighQualityPost(resources.getJSONObject("image_versions2").getJSONArray("candidates"), false, true, false); if (src == null) return resources.getString("display_url"); } catch (final Exception e) { -// if (Utils.logCollector != null) -// Utils.logCollector.appendException(e, LogCollector.LogFile.UTILS, "getHighQualityImage", -// new Pair<>("resourcesNull", resources == null)); + // if (Utils.logCollector != null) + // Utils.logCollector.appendException(e, LogCollector.LogFile.UTILS, "getHighQualityImage", + // new Pair<>("resourcesNull", resources == null)); if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); } return src; @@ -732,8 +731,8 @@ public final class ResponseBodyUtils { final List candidates = new ArrayList(); if (feedItem.has("display_resources") || feedItem.has("thumbnail_resources")) { final JSONArray displayResources = feedItem.has("display_resources") - ? feedItem.getJSONArray("display_resources") - : feedItem.getJSONArray("thumbnail_resources"); + ? feedItem.getJSONArray("display_resources") + : feedItem.getJSONArray("thumbnail_resources"); for (int i = 0; i < displayResources.length(); i++) { final JSONObject displayResource = displayResources.getJSONObject(i); candidates.add(new MediaCandidate( @@ -1090,9 +1089,10 @@ public final class ResponseBodyUtils { if (imageVersions2 == null) return null; final List candidates = imageVersions2.getCandidates(); if (candidates == null || candidates.isEmpty()) return null; - final List sortedCandidates = candidates.stream() + final List sortedCandidates = candidates + .stream() .sorted((c1, c2) -> Integer.compare(c2.getWidth(), c1.getWidth())) - .filter(c -> c.getWidth() < type.getValue()) + // .filter(c -> c.getWidth() < type.getValue()) .collect(Collectors.toList()); final MediaCandidate candidate = sortedCandidates.get(0); if (candidate == null) return null; diff --git a/app/src/main/java/awais/instagrabber/utils/Utils.java b/app/src/main/java/awais/instagrabber/utils/Utils.java index bf716e5d..5d5c4bca 100644 --- a/app/src/main/java/awais/instagrabber/utils/Utils.java +++ b/app/src/main/java/awais/instagrabber/utils/Utils.java @@ -50,19 +50,15 @@ import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Map; -//import javax.crypto.Mac; -//import javax.crypto.spec.SecretKeySpec; - import awais.instagrabber.R; import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.enums.FavoriteType; -//import awaisomereport.LogCollector; public final class Utils { private static final String TAG = "Utils"; private static final int VIDEO_CACHE_MAX_BYTES = 10 * 1024 * 1024; -// public static LogCollector logCollector; + // public static LogCollector logCollector; public static SettingsHelper settingsHelper; public static boolean sessionVolumeFull = false; public static final MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); @@ -93,34 +89,34 @@ public final class Utils { } public static Map sign(final Map form) { -// final String signed = sign(Constants.SIGNATURE_KEY, new JSONObject(form).toString()); -// if (signed == null) { -// return null; -// } + // final String signed = sign(Constants.SIGNATURE_KEY, new JSONObject(form).toString()); + // if (signed == null) { + // return null; + // } final Map map = new HashMap<>(); -// map.put("ig_sig_key_version", Constants.SIGNATURE_VERSION); -// map.put("signed_body", signed); + // map.put("ig_sig_key_version", Constants.SIGNATURE_VERSION); + // map.put("signed_body", signed); map.put("signed_body", "SIGNATURE." + new JSONObject(form).toString()); return map; } -// public static String sign(final String key, final String message) { -// try { -// final Mac hasher = Mac.getInstance("HmacSHA256"); -// hasher.init(new SecretKeySpec(key.getBytes(), "HmacSHA256")); -// byte[] hash = hasher.doFinal(message.getBytes()); -// final StringBuilder hexString = new StringBuilder(); -// for (byte b : hash) { -// final String hex = Integer.toHexString(0xff & b); -// if (hex.length() == 1) hexString.append('0'); -// hexString.append(hex); -// } -// return hexString.toString() + "." + message; -// } catch (Exception e) { -// Log.e(TAG, "Error signing", e); -// return null; -// } -// } + // public static String sign(final String key, final String message) { + // try { + // final Mac hasher = Mac.getInstance("HmacSHA256"); + // hasher.init(new SecretKeySpec(key.getBytes(), "HmacSHA256")); + // byte[] hash = hasher.doFinal(message.getBytes()); + // final StringBuilder hexString = new StringBuilder(); + // for (byte b : hash) { + // final String hex = Integer.toHexString(0xff & b); + // if (hex.length() == 1) hexString.append('0'); + // hexString.append(hex); + // } + // return hexString.toString() + "." + message; + // } catch (Exception e) { + // Log.e(TAG, "Error signing", e); + // return null; + // } + // } public static String getMimeType(@NonNull final Uri uri, final ContentResolver contentResolver) { String mimeType; diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java index 3329d6c8..3ad563c1 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java @@ -34,7 +34,7 @@ import awais.instagrabber.repositories.responses.directmessages.RankedRecipient; import awais.instagrabber.repositories.responses.giphy.GiphyGif; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; -import awais.instagrabber.utils.DirectoryUtils; +import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.MediaController; import awais.instagrabber.utils.MediaUtils; import awais.instagrabber.utils.TextUtils; @@ -72,7 +72,7 @@ public class DirectThreadViewModel extends AndroidViewModel { throw new IllegalArgumentException("User is not logged in!"); } contentResolver = application.getContentResolver(); - recordingsDir = DirectoryUtils.getOutputMediaDirectory(application, "Recordings"); + recordingsDir = DownloadUtils.getDownloadDir("Recordings"); final DirectMessagesManager messagesManager = DirectMessagesManager.getInstance(); threadManager = messagesManager.getThreadManager(threadId, pending, currentUser, contentResolver); threadManager.fetchPendingRequests(); diff --git a/app/src/main/java/awais/instagrabber/viewmodels/ImageEditViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/ImageEditViewModel.java index 9707e092..ecd12260 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/ImageEditViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/ImageEditViewModel.java @@ -24,7 +24,7 @@ import awais.instagrabber.fragments.imageedit.filters.filters.Filter; import awais.instagrabber.fragments.imageedit.filters.properties.Property; import awais.instagrabber.models.SavedImageEditState; import awais.instagrabber.utils.AppExecutors; -import awais.instagrabber.utils.DirectoryUtils; +import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.SerializablePair; import jp.co.cyberagent.android.gpuimage.GPUImage; import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter; @@ -56,7 +56,7 @@ public class ImageEditViewModel extends AndroidViewModel { public ImageEditViewModel(final Application application) { super(application); sessionId = SIMPLE_DATE_FORMAT.format(new Date()); - outputDir = DirectoryUtils.getOutputMediaDirectory(application, "Edit", sessionId); + outputDir = DownloadUtils.getImageEditDir(sessionId); destinationFile = new File(outputDir, RESULT + ".jpg"); destinationUri = Uri.fromFile(destinationFile); cropDestinationUri = Uri.fromFile(new File(outputDir, CROP + ".jpg")); diff --git a/app/src/main/res/layout/dialog_create_backup.xml b/app/src/main/res/layout/dialog_create_backup.xml index 78df89d5..56b4a71b 100644 --- a/app/src/main/res/layout/dialog_create_backup.xml +++ b/app/src/main/res/layout/dialog_create_backup.xml @@ -69,5 +69,5 @@ android:layout_height="wrap_content" android:layout_gravity="end" android:layout_marginTop="16dp" - android:text="@string/create_backup" /> + android:text="@string/backup" /> \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_restore_backup.xml b/app/src/main/res/layout/dialog_restore_backup.xml index 52686620..195c9625 100644 --- a/app/src/main/res/layout/dialog_restore_backup.xml +++ b/app/src/main/res/layout/dialog_restore_backup.xml @@ -127,7 +127,7 @@ android:layout_height="wrap_content" android:layout_gravity="end" android:layout_marginTop="16dp" - android:text="@string/restore_backup" + android:text="@string/restore" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/bottom_password_divider" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d3b7cf2b..bd79862c 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -477,4 +477,6 @@ Select an email app to send crash logs Copy caption Copy reply + Restore + Backup From 7c0acdbd6e2a95b932128ab191b9ebbace5b3c37 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Sat, 3 Apr 2021 19:53:01 +0900 Subject: [PATCH 02/17] Migrate File usage to DocumentFile --- .../instagrabber/InstaGrabberApplication.java | 2 + .../activities/CameraActivity.java | 64 ++-- .../viewholder/FeedGridItemViewHolder.java | 2 +- .../asyncs/DownloadedCheckerAsyncTask.java | 11 +- .../dialogs/CreateBackupDialogFragment.java | 3 +- .../dialogs/ProfilePicDialogFragment.java | 24 +- .../DownloadsPreferencesFragment.java | 167 ++++++---- .../services/DeleteImageIntentService.java | 12 +- .../awais/instagrabber/utils/BitmapUtils.java | 13 +- .../instagrabber/utils/DownloadUtils.java | 294 ++++++++++++------ .../instagrabber/utils/MediaUploader.java | 15 +- .../java/awais/instagrabber/utils/Utils.java | 54 ++++ .../instagrabber/utils/VoiceRecorder.java | 25 +- .../viewmodels/DirectThreadViewModel.java | 63 ++-- .../viewmodels/ImageEditViewModel.java | 26 +- .../instagrabber/workers/DownloadWorker.java | 111 ++++--- 16 files changed, 571 insertions(+), 315 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java b/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java index a033473d..c19e677e 100644 --- a/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java +++ b/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java @@ -14,6 +14,7 @@ import java.text.SimpleDateFormat; import java.util.UUID; import awais.instagrabber.utils.Constants; +import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.LocaleUtils; import awais.instagrabber.utils.SettingsHelper; import awais.instagrabber.utils.TextUtils; @@ -86,5 +87,6 @@ public final class InstaGrabberApplication extends Application { if (TextUtils.isEmpty(settingsHelper.getString(Constants.DEVICE_UUID))) { settingsHelper.putString(Constants.DEVICE_UUID, UUID.randomUUID().toString()); } + DownloadUtils.init(this); } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/activities/CameraActivity.java b/app/src/main/java/awais/instagrabber/activities/CameraActivity.java index 9a76a9be..3172439b 100644 --- a/app/src/main/java/awais/instagrabber/activities/CameraActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/CameraActivity.java @@ -5,12 +5,9 @@ import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.hardware.display.DisplayManager; -import android.media.MediaScannerConnection; -import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; -import android.webkit.MimeTypeMap; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -21,11 +18,13 @@ import androidx.camera.core.ImageCaptureException; import androidx.camera.core.Preview; import androidx.camera.lifecycle.ProcessCameraProvider; import androidx.core.content.ContextCompat; +import androidx.documentfile.provider.DocumentFile; -import com.google.common.io.Files; import com.google.common.util.concurrent.ListenableFuture; -import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.Locale; import java.util.concurrent.ExecutionException; @@ -45,7 +44,7 @@ public class CameraActivity extends BaseLanguageActivity { private ActivityCameraBinding binding; private ImageCapture imageCapture; - private File outputDirectory; + private DocumentFile outputDirectory; private ExecutorService cameraExecutor; private int displayId = -1; @@ -113,7 +112,13 @@ public class CameraActivity extends BaseLanguageActivity { } private void updateUi() { - binding.cameraCaptureButton.setOnClickListener(v -> takePhoto()); + binding.cameraCaptureButton.setOnClickListener(v -> { + try { + takePhoto(); + } catch (FileNotFoundException e) { + Log.e(TAG, "updateUi: ", e); + } + }); // Disable the button until the camera is set up binding.switchCamera.setEnabled(false); // Listener for button used to switch cameras. Only called if the button is enabled @@ -200,37 +205,44 @@ public class CameraActivity extends BaseLanguageActivity { preview.setSurfaceProvider(binding.viewFinder.getSurfaceProvider()); } - private void takePhoto() { + private void takePhoto() throws FileNotFoundException { if (imageCapture == null) return; - final File photoFile = new File(outputDirectory, SIMPLE_DATE_FORMAT.format(System.currentTimeMillis()) + ".jpg"); - final ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(photoFile).build(); + final String extension = "jpg"; + final String fileName = SIMPLE_DATE_FORMAT.format(System.currentTimeMillis()) + "." + extension; + // final File photoFile = new File(outputDirectory, fileName); + final String mimeType = Utils.mimeTypeMap.getMimeTypeFromExtension(extension); + final DocumentFile photoFile = outputDirectory.createFile(mimeType, fileName); + final OutputStream outputStream = getContentResolver().openOutputStream(photoFile.getUri()); + final ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(outputStream).build(); imageCapture.takePicture( outputFileOptions, cameraExecutor, new ImageCapture.OnImageSavedCallback() { @Override public void onImageSaved(@NonNull final ImageCapture.OutputFileResults outputFileResults) { - final Uri uri = Uri.fromFile(photoFile); - //noinspection UnstableApiUsage - final String mimeType = MimeTypeMap.getSingleton() - .getMimeTypeFromExtension(Files.getFileExtension(photoFile.getName())); - MediaScannerConnection.scanFile( - CameraActivity.this, - new String[]{photoFile.getAbsolutePath()}, - new String[]{mimeType}, - (path, uri1) -> { - Log.d(TAG, "onImageSaved: scan complete"); - final Intent intent = new Intent(); - intent.setData(uri1); - setResult(Activity.RESULT_OK, intent); - finish(); - }); - Log.d(TAG, "onImageSaved: " + uri); + if (outputStream != null) { + try { outputStream.close(); } catch (IOException ignored) {} + } + // final Uri uri = Uri.fromFile(photoFile); + // final String mimeType = MimeTypeMap.getSingleton() + // .getMimeTypeFromExtension(Files.getFileExtension(photoFile.getName())); + sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, photoFile.getUri())); + Utils.scanDocumentFile(CameraActivity.this, photoFile, (path, uri1) -> { + Log.d(TAG, "onImageSaved: scan complete"); + final Intent intent = new Intent(); + intent.setData(uri1); + setResult(Activity.RESULT_OK, intent); + finish(); + }); + Log.d(TAG, "onImageSaved: " + photoFile.getUri()); } @Override public void onError(@NonNull final ImageCaptureException exception) { Log.e(TAG, "onError: ", exception); + if (outputStream != null) { + try { outputStream.close(); } catch (IOException ignored) {} + } } } ); diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java index c6e1d571..3f71c624 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java @@ -102,7 +102,7 @@ public class FeedGridItemViewHolder extends RecyclerView.ViewHolder { binding.typeIcon.setVisibility(View.VISIBLE); binding.typeIcon.setImageResource(typeIconRes); } - final DownloadedCheckerAsyncTask task = new DownloadedCheckerAsyncTask(result -> { + final DownloadedCheckerAsyncTask task = new DownloadedCheckerAsyncTask(itemView.getContext(), result -> { final List checkList = result.get(media.getPk()); if (checkList == null || checkList.isEmpty()) { return; diff --git a/app/src/main/java/awais/instagrabber/asyncs/DownloadedCheckerAsyncTask.java b/app/src/main/java/awais/instagrabber/asyncs/DownloadedCheckerAsyncTask.java index 605bed3f..fd4ad6df 100644 --- a/app/src/main/java/awais/instagrabber/asyncs/DownloadedCheckerAsyncTask.java +++ b/app/src/main/java/awais/instagrabber/asyncs/DownloadedCheckerAsyncTask.java @@ -1,7 +1,9 @@ package awais.instagrabber.asyncs; +import android.content.Context; import android.os.AsyncTask; +import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -12,9 +14,12 @@ import awais.instagrabber.utils.DownloadUtils; public final class DownloadedCheckerAsyncTask extends AsyncTask>> { private static final String TAG = "DownloadedCheckerAsyncTask"; + private final WeakReference context; private final OnCheckResultListener listener; - public DownloadedCheckerAsyncTask(final OnCheckResultListener listener) { + public DownloadedCheckerAsyncTask(final Context context, + final OnCheckResultListener listener) { + this.context = new WeakReference<>(context); this.listener = listener; } @@ -25,7 +30,9 @@ public final class DownloadedCheckerAsyncTask extends AsyncTask> map = new HashMap<>(); for (final Media media : feedModels) { - map.put(media.getPk(), DownloadUtils.checkDownloaded(media)); + final Context context = this.context.get(); + if (context == null) return map; + map.put(media.getPk(), DownloadUtils.checkDownloaded(context, media)); } return map; } diff --git a/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java index 42b0ebd1..2cbd0224 100644 --- a/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java +++ b/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java @@ -3,7 +3,6 @@ package awais.instagrabber.dialogs; import android.app.Dialog; import android.content.Context; import android.content.Intent; -import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.DocumentsContract; @@ -214,7 +213,7 @@ public class CreateBackupDialogFragment extends DialogFragment { // Optionally, specify a URI for the directory that should be opened in // the system file picker when your app creates the document. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Uri.fromFile(DownloadUtils.getDownloadDir())); + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, DownloadUtils.getDownloadDir().getUri()); } startActivityForResult(intent, CREATE_FILE_REQUEST_CODE); diff --git a/app/src/main/java/awais/instagrabber/dialogs/ProfilePicDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/ProfilePicDialogFragment.java index affe5cba..9df1e552 100644 --- a/app/src/main/java/awais/instagrabber/dialogs/ProfilePicDialogFragment.java +++ b/app/src/main/java/awais/instagrabber/dialogs/ProfilePicDialogFragment.java @@ -7,7 +7,6 @@ import android.graphics.Color; import android.graphics.drawable.Animatable; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; -import android.os.Environment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -17,6 +16,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; +import androidx.documentfile.provider.DocumentFile; import androidx.fragment.app.DialogFragment; import com.facebook.drawee.backends.pipeline.Fresco; @@ -24,15 +24,13 @@ import com.facebook.drawee.controller.BaseControllerListener; import com.facebook.drawee.interfaces.DraweeController; import com.facebook.imagepipeline.image.ImageInfo; -import java.io.File; - -import awais.instagrabber.R; import awais.instagrabber.databinding.DialogProfilepicBinding; import awais.instagrabber.repositories.responses.User; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.TextUtils; +import awais.instagrabber.utils.Utils; import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.UserService; @@ -182,14 +180,18 @@ public class ProfilePicDialogFragment extends DialogFragment { private void downloadProfilePicture() { if (url == null) return; - final File dir = new File(Environment.getExternalStorageDirectory(), "Download"); + // final File dir = new File(Environment.getExternalStorageDirectory(), "Download"); final Context context = getContext(); if (context == null) return; - if (dir.exists() || dir.mkdirs()) { - final File saveFile = new File(dir, name + '_' + System.currentTimeMillis() + ".jpg"); - DownloadUtils.download(context, url, saveFile.getAbsolutePath()); - return; - } - Toast.makeText(context, R.string.downloader_error_creating_folder, Toast.LENGTH_SHORT).show(); + // if (dir.exists() || dir.mkdirs()) { + // + // } + final String fileName = name + '_' + System.currentTimeMillis() + ".jpg"; + // final File saveFile = new File(dir, fileName); + final DocumentFile downloadDir = DownloadUtils.getDownloadDir(); + final DocumentFile saveFile = downloadDir.createFile(Utils.mimeTypeMap.getMimeTypeFromExtension("jpg"), fileName); + DownloadUtils.download(context, url, saveFile); + // return; + // Toast.makeText(context, R.string.downloader_error_creating_folder, Toast.LENGTH_SHORT).show(); } } diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java index 285b4689..01cc0bd4 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java @@ -1,22 +1,43 @@ package awais.instagrabber.fragments.settings; import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.util.Log; +import android.view.View; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatButton; +import androidx.appcompat.widget.AppCompatTextView; +import androidx.documentfile.provider.DocumentFile; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; +import androidx.preference.PreferenceViewHolder; import androidx.preference.SwitchPreferenceCompat; +import com.google.android.material.switchmaterial.SwitchMaterial; + import awais.instagrabber.R; import awais.instagrabber.utils.Constants; +import awais.instagrabber.utils.TextUtils; + +import static android.app.Activity.RESULT_OK; +import static awais.instagrabber.utils.Constants.FOLDER_PATH; +import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; +import static awais.instagrabber.utils.Utils.settingsHelper; public class DownloadsPreferencesFragment extends BasePreferencesFragment { + private static final String TAG = DownloadsPreferencesFragment.class.getSimpleName(); + private static final int SELECT_DIR_REQUEST_CODE = 1; + private SaveToCustomFolderPreference.ResultCallback resultCallback; + @Override void setupPreferenceScreen(final PreferenceScreen screen) { final Context context = getContext(); if (context == null) return; screen.addPreference(getDownloadUserFolderPreference(context)); - // screen.addPreference(getSaveToCustomFolderPreference(context)); + screen.addPreference(getSaveToCustomFolderPreference(context)); } private Preference getDownloadUserFolderPreference(@NonNull final Context context) { @@ -27,63 +48,89 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment { return preference; } - // private Preference getSaveToCustomFolderPreference(@NonNull final Context context) { - // return new SaveToCustomFolderPreference(context, (resultCallback) -> new DirectoryChooser() - // .setInitialDirectory(settingsHelper.getString(FOLDER_PATH)) - // .setInteractionListener(file -> { - // settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath()); - // resultCallback.onResult(file.getAbsolutePath()); - // }) - // .show(getParentFragmentManager(), null)); - // } - - // public static class SaveToCustomFolderPreference extends Preference { - // private AppCompatTextView customPathTextView; - // private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener; - // private final String key; - // - // public SaveToCustomFolderPreference(final Context context, final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener) { - // super(context); - // this.onSelectFolderButtonClickListener = onSelectFolderButtonClickListener; - // key = Constants.FOLDER_SAVE_TO; - // setLayoutResource(R.layout.pref_custom_folder); - // setKey(key); - // setTitle(R.string.save_to_folder); - // setIconSpaceReserved(false); - // } - // - // @Override - // public void onBindViewHolder(final PreferenceViewHolder holder) { - // super.onBindViewHolder(holder); - // final SwitchMaterial cbSaveTo = (SwitchMaterial) holder.findViewById(R.id.cbSaveTo); - // final View buttonContainer = holder.findViewById(R.id.button_container); - // customPathTextView = (AppCompatTextView) holder.findViewById(R.id.custom_path); - // cbSaveTo.setOnCheckedChangeListener((buttonView, isChecked) -> { - // settingsHelper.putBoolean(FOLDER_SAVE_TO, isChecked); - // buttonContainer.setVisibility(isChecked ? View.VISIBLE : View.GONE); - // final String customPath = settingsHelper.getString(FOLDER_PATH); - // customPathTextView.setText(customPath); - // }); - // final boolean savedToEnabled = settingsHelper.getBoolean(key); - // holder.itemView.setOnClickListener(v -> cbSaveTo.toggle()); - // cbSaveTo.setChecked(savedToEnabled); - // buttonContainer.setVisibility(savedToEnabled ? View.VISIBLE : View.GONE); - // final AppCompatButton btnSaveTo = (AppCompatButton) holder.findViewById(R.id.btnSaveTo); - // btnSaveTo.setOnClickListener(v -> { - // if (onSelectFolderButtonClickListener == null) return; - // onSelectFolderButtonClickListener.onClick(result -> { - // if (TextUtils.isEmpty(result)) return; - // customPathTextView.setText(result); - // }); - // }); - // } - // - // public interface ResultCallback { - // void onResult(String result); - // } - // - // public interface OnSelectFolderButtonClickListener { - // void onClick(ResultCallback resultCallback); - // } - // } + private Preference getSaveToCustomFolderPreference(@NonNull final Context context) { + return new SaveToCustomFolderPreference(context, (resultCallback) -> { + // Choose a directory using the system's file picker. + final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + startActivityForResult(intent, SELECT_DIR_REQUEST_CODE); + this.resultCallback = resultCallback; + + // new DirectoryChooser() + // .setInitialDirectory(settingsHelper.getString(FOLDER_PATH)) + // .setInteractionListener(file -> { + // settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath()); + // resultCallback.onResult(file.getAbsolutePath()); + // }) + // .show(getParentFragmentManager(), null); + }); + } + + @Override + public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { + if (data == null || data.getData() == null) return; + if (resultCode != RESULT_OK || requestCode != SELECT_DIR_REQUEST_CODE) return; + final Context context = getContext(); + if (context == null) return; + final Uri dirUri = data.getData(); + Log.d(TAG, "onActivityResult: " + dirUri); + final int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + context.getContentResolver().takePersistableUriPermission(dirUri, takeFlags); + final DocumentFile root = DocumentFile.fromTreeUri(context, dirUri); + settingsHelper.putString(FOLDER_PATH, data.getData().toString()); + if (resultCallback != null) { + resultCallback.onResult(root.getName()); + resultCallback = null; + } + // Log.d(TAG, "onActivityResult: " + root); + } + + public static class SaveToCustomFolderPreference extends Preference { + private AppCompatTextView customPathTextView; + private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener; + private final String key; + + public SaveToCustomFolderPreference(final Context context, final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener) { + super(context); + this.onSelectFolderButtonClickListener = onSelectFolderButtonClickListener; + key = FOLDER_SAVE_TO; + setLayoutResource(R.layout.pref_custom_folder); + setKey(key); + setTitle(R.string.save_to_folder); + setIconSpaceReserved(false); + } + + @Override + public void onBindViewHolder(final PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + final SwitchMaterial cbSaveTo = (SwitchMaterial) holder.findViewById(R.id.cbSaveTo); + final View buttonContainer = holder.findViewById(R.id.button_container); + customPathTextView = (AppCompatTextView) holder.findViewById(R.id.custom_path); + cbSaveTo.setOnCheckedChangeListener((buttonView, isChecked) -> { + settingsHelper.putBoolean(FOLDER_SAVE_TO, isChecked); + buttonContainer.setVisibility(isChecked ? View.VISIBLE : View.GONE); + final String customPath = settingsHelper.getString(FOLDER_PATH); + customPathTextView.setText(customPath); + }); + final boolean savedToEnabled = settingsHelper.getBoolean(key); + holder.itemView.setOnClickListener(v -> cbSaveTo.toggle()); + cbSaveTo.setChecked(savedToEnabled); + buttonContainer.setVisibility(savedToEnabled ? View.VISIBLE : View.GONE); + final AppCompatButton btnSaveTo = (AppCompatButton) holder.findViewById(R.id.btnSaveTo); + btnSaveTo.setOnClickListener(v -> { + if (onSelectFolderButtonClickListener == null) return; + onSelectFolderButtonClickListener.onClick(result -> { + if (TextUtils.isEmpty(result)) return; + customPathTextView.setText(result); + }); + }); + } + + public interface ResultCallback { + void onResult(String result); + } + + public interface OnSelectFolderButtonClickListener { + void onClick(ResultCallback resultCallback); + } + } } diff --git a/app/src/main/java/awais/instagrabber/services/DeleteImageIntentService.java b/app/src/main/java/awais/instagrabber/services/DeleteImageIntentService.java index b9442527..ceeb2c7c 100644 --- a/app/src/main/java/awais/instagrabber/services/DeleteImageIntentService.java +++ b/app/src/main/java/awais/instagrabber/services/DeleteImageIntentService.java @@ -4,13 +4,14 @@ import android.app.IntentService; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.net.Uri; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.NotificationManagerCompat; +import androidx.documentfile.provider.DocumentFile; -import java.io.File; import java.util.Random; import awais.instagrabber.utils.TextUtils; @@ -39,7 +40,10 @@ public class DeleteImageIntentService extends IntentService { if (intent != null && Intent.ACTION_DELETE.equals(intent.getAction()) && intent.hasExtra(EXTRA_IMAGE_PATH)) { final String path = intent.getStringExtra(EXTRA_IMAGE_PATH); if (TextUtils.isEmpty(path)) return; - final File file = new File(path); + // final File file = new File(path); + final Uri parse = Uri.parse(path); + if (parse == null) return; + final DocumentFile file = DocumentFile.fromSingleUri(getApplicationContext(), parse); boolean deleted; if (file.exists()) { deleted = file.delete(); @@ -58,11 +62,11 @@ public class DeleteImageIntentService extends IntentService { @NonNull public static PendingIntent pendingIntent(@NonNull final Context context, - @NonNull final String imagePath, + @NonNull final DocumentFile imagePath, final int notificationId) { final Intent intent = new Intent(context, DeleteImageIntentService.class); intent.setAction(Intent.ACTION_DELETE); - intent.putExtra(EXTRA_IMAGE_PATH, imagePath); + intent.putExtra(EXTRA_IMAGE_PATH, imagePath.getUri().toString()); intent.putExtra(EXTRA_NOTIFICATION_ID, notificationId); return PendingIntent.getService(context, random.nextInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT); } diff --git a/app/src/main/java/awais/instagrabber/utils/BitmapUtils.java b/app/src/main/java/awais/instagrabber/utils/BitmapUtils.java index c0b4cb5f..01fef306 100644 --- a/app/src/main/java/awais/instagrabber/utils/BitmapUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/BitmapUtils.java @@ -11,14 +11,13 @@ import android.util.LruCache; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.util.Pair; +import androidx.documentfile.provider.DocumentFile; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import java.io.File; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -247,12 +246,14 @@ public final class BitmapUtils { return null; } - public static File convertToJpegAndSaveToFile(@NonNull final Bitmap bitmap, @Nullable final File file) throws IOException { - File tempFile = file; + public static DocumentFile convertToJpegAndSaveToFile(@NonNull final ContentResolver contentResolver, + @NonNull final Bitmap bitmap, + @Nullable final DocumentFile file) throws IOException { + DocumentFile tempFile = file; if (file == null) { - tempFile = DownloadUtils.getTempFile(); + tempFile = DownloadUtils.getTempFile(null, "jpg"); } - try (OutputStream output = new FileOutputStream(tempFile)) { + try (OutputStream output = contentResolver.openOutputStream(tempFile.getUri())) { final boolean compressResult = bitmap.compress(Bitmap.CompressFormat.JPEG, 100, output); if (!compressResult) { throw new RuntimeException("Compression failed!"); diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java index a5933e48..6f54dfcb 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java @@ -1,9 +1,11 @@ package awais.instagrabber.utils; import android.Manifest; +import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; -import android.os.Environment; +import android.net.Uri; +import android.provider.DocumentsContract; import android.util.Log; import android.webkit.MimeTypeMap; import android.widget.Toast; @@ -11,6 +13,8 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; +import androidx.core.util.Pair; +import androidx.documentfile.provider.DocumentFile; import androidx.work.Constraints; import androidx.work.Data; import androidx.work.NetworkType; @@ -21,9 +25,9 @@ import androidx.work.WorkRequest; import com.google.gson.Gson; import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; @@ -41,31 +45,50 @@ import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.VideoVersion; import awais.instagrabber.workers.DownloadWorker; +import static awais.instagrabber.utils.Constants.FOLDER_PATH; + public final class DownloadUtils { private static final String TAG = DownloadUtils.class.getSimpleName(); public static final String WRITE_PERMISSION = Manifest.permission.WRITE_EXTERNAL_STORAGE; public static final String[] PERMS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; - public static final String DIR_BARINSTA = "Barinsta"; - public static final String DIR_DOWNLOADS = "Downloads"; - public static final String DIR_CAMERA = "Camera"; - public static final String DIR_EDIT = "Edit"; - - public static File getDownloadDir(final String... dirs) { - final File parent = new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS); - File subDir = new File(parent, DIR_BARINSTA); + private static final String DIR_BARINSTA = "Barinsta"; + private static final String DIR_DOWNLOADS = "Downloads"; + private static final String DIR_CAMERA = "Camera"; + private static final String DIR_EDIT = "Edit"; + private static final String TEMP_DIR = "Temp"; + + private static DocumentFile root; + + public static void init(@NonNull final Context context) { + // if (!Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) return; + final String customPath = Utils.settingsHelper.getString(FOLDER_PATH); + if (TextUtils.isEmpty(customPath)) return; + // dir = new File(customPath); + root = DocumentFile.fromTreeUri(context, Uri.parse(customPath)); + Log.d(TAG, "init: " + root); + // final File parent = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + // final DocumentFile documentFile = DocumentFile.fromFile(parent); + // Log.d(TAG, "init: " + documentFile); + } + + public static DocumentFile getDownloadDir(final String... dirs) { + // final File parent = new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS); + // File subDir = new File(parent, DIR_BARINSTA); + DocumentFile subDir = root; if (dirs != null) { for (final String dir : dirs) { - subDir = new File(subDir, dir); - //noinspection ResultOfMethodCallIgnored - subDir.mkdirs(); + final DocumentFile subDirFile = subDir.findFile(dir); + if (subDirFile == null) { + subDir = subDir.createDirectory(dir); + } } } return subDir; } @NonNull - public static File getDownloadDir() { + public static DocumentFile getDownloadDir() { // final File parent = new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS); // final File dir = new File(new File(parent, "barinsta"), "downloads"); // if (!dir.exists()) { @@ -83,37 +106,63 @@ public final class DownloadUtils { return getDownloadDir(DIR_DOWNLOADS); } - public static File getCameraDir() { + public static DocumentFile getCameraDir() { return getDownloadDir(DIR_CAMERA); } - public static File getImageEditDir(final String sessionId) { + public static DocumentFile getImageEditDir(final String sessionId) { return getDownloadDir(DIR_EDIT, sessionId); } - @Nullable - private static File getDownloadDir(@NonNull final Context context, @Nullable final String username) { - return getDownloadDir(context, username, false); - } + // @Nullable + // private static DocumentFile getDownloadDir(@NonNull final Context context, @Nullable final String username) { + // return getDownloadDir(context, username, false); + // } @Nullable - private static File getDownloadDir(final Context context, - @Nullable final String username, - final boolean skipCreateDir) { - File dir = getDownloadDir(); - - if (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) && !TextUtils.isEmpty(username)) { - final String finaleUsername = username.startsWith("@") ? username.substring(1) : username; - dir = new File(dir, finaleUsername); + private static DocumentFile getDownloadDir(final Context context, + @Nullable final String username) { + final List userFolderPaths = getSubPathForUserFolder(username); + DocumentFile dir = root; + for (final String dirName : userFolderPaths) { + final DocumentFile file = dir.findFile(dirName); + if (file != null) { + dir = file; + continue; + } + dir = dir.createDirectory(dirName); + if (dir == null) break; } - - if (context != null && !skipCreateDir && !dir.exists() && !dir.mkdirs()) { + // final String joined = android.text.TextUtils.join("/", userFolderPaths); + // final Uri userFolderUri = DocumentsContract.buildDocumentUriUsingTree(root.getUri(), joined); + // final DocumentFile userFolder = DocumentFile.fromSingleUri(context, userFolderUri); + if (context != null && (dir == null || !dir.exists())) { Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show(); return null; } return dir; } + private static List getSubPathForUserFolder(final String username) { + final List list = new ArrayList<>(); + if (!Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) || TextUtils.isEmpty(username)) { + list.add(DIR_DOWNLOADS); + return list; + } + final String finalUsername = username.startsWith("@") ? username.substring(1) : username; + list.add(DIR_DOWNLOADS); + list.add(finalUsername); + return list; + } + + private static DocumentFile getTempDir() { + DocumentFile file = root.findFile(TEMP_DIR); + if (file == null) { + file = root.createDirectory(TEMP_DIR); + } + return file; + } + // public static void dmDownload(@NonNull final Context context, // @Nullable final String username, // final String modelId, @@ -126,59 +175,71 @@ public final class DownloadUtils { // } // } - private static void dmDownloadImpl(@NonNull final Context context, - @Nullable final String username, - final String modelId, - final String url) { - final File dir = getDownloadDir(context, username); - if (dir.exists() || dir.mkdirs()) { - download(context, - url, - getDownloadSaveFile(dir, modelId, url).getAbsolutePath()); - return; - } - Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show(); - } + // private static void dmDownloadImpl(@NonNull final Context context, + // @Nullable final String username, + // final String modelId, + // final String url) { + // final DocumentFile dir = getDownloadDir(context, username); + // if (dir != null && dir.exists()) { + // download(context, url, getDownloadSavePaths(dir, modelId, url)); + // return; + // } + // Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show(); + // } @NonNull - private static File getDownloadSaveFile(final File finalDir, - final String postId, - final String displayUrl) { - return getDownloadSaveFile(finalDir, postId, "", displayUrl); + private static Pair, String> getDownloadSavePaths(final List paths, + final String postId, + final String displayUrl) { + return getDownloadSavePaths(paths, postId, "", displayUrl); } - private static File getDownloadChildSaveFile(final File downloadDir, - final String postId, - final int childPosition, - final String url) { + private static Pair, String> getDownloadChildSaveFile(final List paths, + final String postId, + final int childPosition, + final String url) { final String sliderPostfix = "_slide_" + childPosition; - return getDownloadSaveFile(downloadDir, postId, sliderPostfix, url); + return getDownloadSavePaths(paths, postId, sliderPostfix, url); } - @NonNull - private static File getDownloadSaveFile(final File finalDir, - final String postId, - final String sliderPostfix, - final String displayUrl) { - final String fileName = postId + sliderPostfix + getFileExtensionFromUrl(displayUrl); - return new File(finalDir, fileName); + @Nullable + private static Pair, String> getDownloadSavePaths(final List paths, + final String postId, + final String sliderPostfix, + final String displayUrl) { + if (paths == null) return null; + final String extension = getFileExtensionFromUrl(displayUrl); + final String fileName = postId + sliderPostfix + extension; + // return new File(finalDir, fileName); + // DocumentFile file = finalDir.findFile(fileName); + // if (file == null) { + final String mimeType = Utils.mimeTypeMap.getMimeTypeFromExtension(extension.startsWith(".") ? extension.substring(1) : extension); + // file = finalDir.createFile(mimeType, fileName); + // } + paths.add(fileName); + return new Pair<>(paths, mimeType); } - @NonNull - public static File getTempFile() { + public static DocumentFile getTempFile() { return getTempFile(null, null); } - public static File getTempFile(final String fileName, final String extension) { - final File dir = getDownloadDir(); + public static DocumentFile getTempFile(final String fileName, final String extension) { + final DocumentFile dir = getTempDir(); String name = fileName; if (TextUtils.isEmpty(name)) { name = UUID.randomUUID().toString(); } + String mimeType = "application/octet-stream"; if (!TextUtils.isEmpty(extension)) { name += "." + extension; + mimeType = Utils.mimeTypeMap.getMimeTypeFromExtension(extension); + } + DocumentFile file = dir.findFile(name); + if (file == null) { + file = dir.createFile(mimeType, name); } - return new File(dir, name); + return file; } /** @@ -221,20 +282,21 @@ public final class DownloadUtils { return ""; } - public static List checkDownloaded(@NonNull final Media media) { + public static List checkDownloaded(@NonNull final Context context, + @NonNull final Media media) { final List checkList = new LinkedList<>(); final User user = media.getUser(); String username = "username"; if (user != null) { username = user.getUsername(); } - final File downloadDir = getDownloadDir(null, "@" + username, true); + final List userFolderPaths = getSubPathForUserFolder(username); switch (media.getMediaType()) { case MEDIA_TYPE_IMAGE: case MEDIA_TYPE_VIDEO: { final String url = ResponseBodyUtils.getImageUrl(media); - final File file = getDownloadSaveFile(downloadDir, media.getCode(), url); - checkList.add(file.exists()); + final Pair, String> pair = getDownloadSavePaths(userFolderPaths, media.getCode(), url); + checkList.add(checkPathExists(context, pair.first)); break; } case MEDIA_TYPE_SLIDER: @@ -243,8 +305,8 @@ public final class DownloadUtils { final Media child = sliderItems.get(i); if (child == null) continue; final String url = ResponseBodyUtils.getImageUrl(child); - final File file = getDownloadChildSaveFile(downloadDir, media.getCode(), i + 1, url); - checkList.add(file.exists()); + final Pair, String> pair = getDownloadChildSaveFile(userFolderPaths, media.getCode(), i + 1, url); + checkList.add(checkPathExists(context, pair.first)); } break; default: @@ -252,6 +314,14 @@ public final class DownloadUtils { return checkList; } + private static boolean checkPathExists(@NonNull final Context context, + @NonNull final List paths) { + final String joined = android.text.TextUtils.join("/", paths); + final Uri userFolderUri = DocumentsContract.buildDocumentUriUsingTree(root.getUri(), joined); + final DocumentFile userFolder = DocumentFile.fromSingleUri(context, userFolderUri); + return userFolder != null && userFolder.exists(); + } + public static void showDownloadDialog(@NonNull Context context, @NonNull final Media feedModel, final int childPosition) { @@ -286,15 +356,20 @@ public final class DownloadUtils { public static void download(@NonNull final Context context, @NonNull final StoryModel storyModel) { - final File downloadDir = getDownloadDir(context, "@" + storyModel.getUsername()); + final DocumentFile downloadDir = getDownloadDir(context, "@" + storyModel.getUsername()); final String url = storyModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO ? storyModel.getVideoUrl() : storyModel.getStoryUrl(); - final File saveFile = new File(downloadDir, - storyModel.getStoryMediaId() - + "_" + storyModel.getTimestamp() - + DownloadUtils.getFileExtensionFromUrl(url)); - download(context, url, saveFile.getAbsolutePath()); + final String extension = DownloadUtils.getFileExtensionFromUrl(url); + final String fileName = storyModel.getStoryMediaId() + "_" + storyModel.getTimestamp() + extension; + DocumentFile saveFile = downloadDir.findFile(fileName); + if (saveFile == null) { + saveFile = downloadDir.createFile( + Utils.mimeTypeMap.getMimeTypeFromExtension(extension.startsWith(".") ? extension.substring(1) : extension), + fileName); + } + // final File saveFile = new File(downloadDir, fileName); + download(context, url, saveFile); } public static void download(@NonNull final Context context, @@ -316,17 +391,19 @@ public final class DownloadUtils { private static void download(@NonNull final Context context, @NonNull final List feedModels, final int childPositionIfSingle) { - final Map map = new HashMap<>(); + final Map map = new HashMap<>(); for (final Media media : feedModels) { final User mediaUser = media.getUser(); - final File downloadDir = getDownloadDir(context, mediaUser == null ? "" : "@" + mediaUser.getUsername()); - if (downloadDir == null) return; + final List userFolderPaths = getSubPathForUserFolder(mediaUser == null ? "" : "@" + mediaUser.getUsername()); + // final DocumentFile downloadDir = getDownloadDir(context, mediaUser == null ? "" : "@" + mediaUser.getUsername()); switch (media.getMediaType()) { case MEDIA_TYPE_IMAGE: case MEDIA_TYPE_VIDEO: { final String url = getUrlOfType(media); - final File file = getDownloadSaveFile(downloadDir, media.getCode(), url); - map.put(url, file.getAbsolutePath()); + final Pair, String> pair = getDownloadSavePaths(userFolderPaths, media.getCode(), url); + final DocumentFile file = createFile(pair); + if (file == null) continue; + map.put(url, file); break; } case MEDIA_TYPE_VOICE: { @@ -335,28 +412,48 @@ public final class DownloadUtils { if (mediaUser != null) { fileName = mediaUser.getUsername() + "_" + fileName; } - final File file = getDownloadSaveFile(downloadDir, fileName, url); - map.put(url, file.getAbsolutePath()); + final Pair, String> pair = getDownloadSavePaths(userFolderPaths, fileName, url); + final DocumentFile file = createFile(pair); + if (file == null) continue; + map.put(url, file); break; } case MEDIA_TYPE_SLIDER: final List sliderItems = media.getCarouselMedia(); for (int i = 0; i < sliderItems.size(); i++) { - if (childPositionIfSingle >= 0 && feedModels.size() == 1 && i != childPositionIfSingle) { - continue; - } + if (childPositionIfSingle >= 0 && feedModels.size() == 1 && i != childPositionIfSingle) continue; final Media child = sliderItems.get(i); final String url = getUrlOfType(child); - final File file = getDownloadChildSaveFile(downloadDir, media.getCode(), i + 1, url); - map.put(url, file.getAbsolutePath()); + final Pair, String> pair = getDownloadChildSaveFile(userFolderPaths, media.getCode(), i + 1, url); + final DocumentFile file = createFile(pair); + if (file == null) continue; + map.put(url, file); } break; default: } } + if (map.isEmpty()) return; download(context, map); } + private static DocumentFile createFile(@NonNull final Pair, String> pair) { + if (pair.first == null || pair.second == null) return null; + DocumentFile dir = root; + final List first = pair.first; + for (int i = 0; i < first.size(); i++) { + final String name = first.get(i); + final DocumentFile file = dir.findFile(name); + if (file != null) { + dir = file; + continue; + } + dir = i == first.size() - 1 ? dir.createFile(pair.second, name) : dir.createDirectory(name); + if (dir == null) break; + } + return dir; + } + @Nullable private static String getUrlOfType(@NonNull final Media media) { switch (media.getMediaType()) { @@ -388,12 +485,13 @@ public final class DownloadUtils { public static void download(final Context context, final String url, - final String filePath) { + final DocumentFile filePath) { if (context == null || url == null || filePath == null) return; download(context, Collections.singletonMap(url, filePath)); } - private static void download(final Context context, final Map urlFilePathMap) { + private static void download(final Context context, final Map urlFilePathMap) { + if (context == null) return; final Constraints constraints = new Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build(); @@ -401,19 +499,25 @@ public final class DownloadUtils { .setUrlToFilePathMap(urlFilePathMap) .build(); final String requestJson = new Gson().toJson(request); - final File tempFile = getTempFile(); - try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile))) { + final DocumentFile tempFile = getTempFile(null, "json"); + if (tempFile == null) { + Log.e(TAG, "download: temp file is null"); + return; + } + final Uri uri = tempFile.getUri(); + final ContentResolver contentResolver = context.getContentResolver(); + if (contentResolver == null) return; + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(contentResolver.openOutputStream(uri)))) { writer.write(requestJson); } catch (IOException e) { Log.e(TAG, "download: Error writing request to file", e); - //noinspection ResultOfMethodCallIgnored tempFile.delete(); return; } final WorkRequest downloadWorkRequest = new OneTimeWorkRequest.Builder(DownloadWorker.class) .setInputData( new Data.Builder() - .putString(DownloadWorker.KEY_DOWNLOAD_REQUEST_JSON, tempFile.getAbsolutePath()) + .putString(DownloadWorker.KEY_DOWNLOAD_REQUEST_JSON, tempFile.getUri().toString()) .build() ) .setConstraints(constraints) diff --git a/app/src/main/java/awais/instagrabber/utils/MediaUploader.java b/app/src/main/java/awais/instagrabber/utils/MediaUploader.java index f9c47a91..30570663 100644 --- a/app/src/main/java/awais/instagrabber/utils/MediaUploader.java +++ b/app/src/main/java/awais/instagrabber/utils/MediaUploader.java @@ -6,11 +6,10 @@ import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.documentfile.provider.DocumentFile; import org.json.JSONObject; -import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Map; @@ -45,7 +44,7 @@ public final class MediaUploader { listener.onFailure(new RuntimeException("Bitmap result was null")); return; } - uploadPhoto(bitmap, listener); + uploadPhoto(contentResolver, bitmap, listener); } @Override @@ -55,13 +54,14 @@ public final class MediaUploader { }); } - private static void uploadPhoto(@NonNull final Bitmap bitmap, + private static void uploadPhoto(@NonNull final ContentResolver contentResolver, + @NonNull final Bitmap bitmap, @NonNull final OnMediaUploadCompleteListener listener) { appExecutors.tasksThread().submit(() -> { - final File file; + final DocumentFile file; final long byteLength; try { - file = BitmapUtils.convertToJpegAndSaveToFile(bitmap, null); + file = BitmapUtils.convertToJpegAndSaveToFile(contentResolver, bitmap, null); byteLength = file.length(); } catch (Exception e) { listener.onFailure(e); @@ -71,12 +71,11 @@ public final class MediaUploader { final Map headers = MediaUploadHelper.getUploadPhotoHeaders(options); final String url = HOST + "/rupload_igphoto/" + options.getName() + "/"; appExecutors.networkIO().execute(() -> { - try (FileInputStream input = new FileInputStream(file)) { + try (InputStream input = contentResolver.openInputStream(file.getUri())) { upload(input, url, headers, listener); } catch (IOException e) { listener.onFailure(e); } finally { - //noinspection ResultOfMethodCallIgnored file.delete(); } }); diff --git a/app/src/main/java/awais/instagrabber/utils/Utils.java b/app/src/main/java/awais/instagrabber/utils/Utils.java index 5d5c4bca..789133b8 100644 --- a/app/src/main/java/awais/instagrabber/utils/Utils.java +++ b/app/src/main/java/awais/instagrabber/utils/Utils.java @@ -15,8 +15,11 @@ import android.media.MediaScannerConnection; import android.media.MediaScannerConnection.OnScanCompletedListener; import android.net.Uri; import android.os.Build; +import android.os.Environment; import android.os.Handler; +import android.os.storage.StorageManager; import android.provider.Browser; +import android.provider.DocumentsContract; import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; @@ -35,6 +38,7 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; +import androidx.documentfile.provider.DocumentFile; import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat; import com.google.android.exoplayer2.database.ExoDatabaseProvider; @@ -46,6 +50,8 @@ import org.json.JSONObject; import java.io.File; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Map; @@ -71,6 +77,7 @@ public final class Utils { public static Handler applicationHandler; public static String cacheDir; private static int defaultStatusBarColor; + private static Object[] volumes; public static int convertDpToPx(final float dp) { return Math.round((dp * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT); @@ -367,4 +374,51 @@ public final class Utils { if (window == null) return; window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } + + public static void scanDocumentFile(@NonNull final Context context, + @NonNull final DocumentFile documentFile, + @NonNull final OnScanCompletedListener callback) { + if (!documentFile.isFile()) return; + File file = null; + try { + file = getDocumentFileRealPath(context, documentFile); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + Log.e(TAG, "scanDocumentFile: ", e); + } + if (file == null) return; + MediaScannerConnection.scanFile(context, + new String[]{file.getAbsolutePath()}, + new String[]{documentFile.getType()}, + callback); + } + + private static File getDocumentFileRealPath(Context context, DocumentFile documentFile) + throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + final String docId = DocumentsContract.getDocumentId(documentFile.getUri()); + final String[] split = docId.split(":"); + final String type = split[0]; + + if (type.equalsIgnoreCase("primary")) { + return new File(Environment.getExternalStorageDirectory(), split[1]); + } else { + if (volumes == null) { + StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); + Method getVolumeListMethod = sm.getClass().getMethod("getVolumeList"); + volumes = (Object[]) getVolumeListMethod.invoke(sm); + } + + for (Object volume : volumes) { + Method getUuidMethod = volume.getClass().getMethod("getUuid"); + String uuid = (String) getUuidMethod.invoke(volume); + + if (uuid != null && uuid.equalsIgnoreCase(type)) { + Method getPathMethod = volume.getClass().getMethod("getPath"); + String path = (String) getPathMethod.invoke(volume); + return new File(path, split[1]); + } + } + } + + return null; + } } diff --git a/app/src/main/java/awais/instagrabber/utils/VoiceRecorder.java b/app/src/main/java/awais/instagrabber/utils/VoiceRecorder.java index a22f5055..d264226e 100644 --- a/app/src/main/java/awais/instagrabber/utils/VoiceRecorder.java +++ b/app/src/main/java/awais/instagrabber/utils/VoiceRecorder.java @@ -1,14 +1,16 @@ package awais.instagrabber.utils; +import android.app.Application; import android.media.MediaRecorder; import android.os.Handler; import android.os.Message; +import android.os.ParcelFileDescriptor; import android.util.Log; import android.webkit.MimeTypeMap; import androidx.annotation.NonNull; +import androidx.documentfile.provider.DocumentFile; -import java.io.File; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; @@ -27,20 +29,20 @@ public class VoiceRecorder { private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(FILE_FORMAT, Locale.US); private final List waveform = new ArrayList<>(); - private final File recordingsDir; + private final DocumentFile recordingsDir; private final VoiceRecorderCallback callback; private MediaRecorder recorder; - private File audioTempFile; + private DocumentFile audioTempFile; private MaxAmpHandler maxAmpHandler; private boolean stopped; - public VoiceRecorder(@NonNull final File recordingsDir, final VoiceRecorderCallback callback) { + public VoiceRecorder(@NonNull final DocumentFile recordingsDir, final VoiceRecorderCallback callback) { this.recordingsDir = recordingsDir; this.callback = callback; } - public void startRecording() { + public void startRecording(final Application application) { stopped = false; try { recorder = new MediaRecorder(); @@ -48,7 +50,8 @@ public class VoiceRecorder { recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); deleteTempAudioFile(); audioTempFile = getAudioRecordFile(); - recorder.setOutputFile(audioTempFile.getAbsolutePath()); + final ParcelFileDescriptor parcelFileDescriptor = application.getContentResolver().openFileDescriptor(audioTempFile.getUri(), "rwt"); + recorder.setOutputFile(parcelFileDescriptor.getFileDescriptor()); recorder.setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC); recorder.setAudioEncodingBitRate(AUDIO_BIT_RATE); recorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE); @@ -140,9 +143,9 @@ public class VoiceRecorder { // } @NonNull - private File getAudioRecordFile() { + private DocumentFile getAudioRecordFile() { final String name = String.format("%s-%s.%s", FILE_PREFIX, SIMPLE_DATE_FORMAT.format(new Date()), EXTENSION); - return new File(recordingsDir, name); + return recordingsDir.createFile(MIME_TYPE, name); } private void deleteTempAudioFile() { @@ -160,11 +163,11 @@ public class VoiceRecorder { public static class VoiceRecordingResult { private final String mimeType; - private final File file; + private final DocumentFile file; private final List waveform; private final int samplingFreq = 10; - public VoiceRecordingResult(final String mimeType, final File file, final List waveform) { + public VoiceRecordingResult(final String mimeType, final DocumentFile file, final List waveform) { this.mimeType = mimeType; this.file = file; this.waveform = waveform; @@ -174,7 +177,7 @@ public class VoiceRecorder { return mimeType; } - public File getFile() { + public DocumentFile getFile() { return file; } diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java index 3ad563c1..440d4765 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java @@ -2,18 +2,17 @@ package awais.instagrabber.viewmodels; import android.app.Application; import android.content.ContentResolver; -import android.media.MediaScannerConnection; import android.net.Uri; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.documentfile.provider.DocumentFile; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.Transformations; -import java.io.File; import java.util.List; import java.util.Map; import java.util.Objects; @@ -38,6 +37,7 @@ import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.MediaController; import awais.instagrabber.utils.MediaUtils; import awais.instagrabber.utils.TextUtils; +import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.VoiceRecorder; import static awais.instagrabber.utils.Utils.settingsHelper; @@ -47,7 +47,7 @@ public class DirectThreadViewModel extends AndroidViewModel { // private static final String ERROR_INVALID_THREAD = "Invalid thread"; private final ContentResolver contentResolver; - private final File recordingsDir; + private final DocumentFile recordingsDir; private final Application application; private final long viewerId; private final String threadId; @@ -166,37 +166,32 @@ public class DirectThreadViewModel extends AndroidViewModel { @Override public void onComplete(final VoiceRecorder.VoiceRecordingResult result) { Log.d(TAG, "onComplete: recording complete. Scanning file..."); - MediaScannerConnection.scanFile( - application, - new String[]{result.getFile().getAbsolutePath()}, - new String[]{result.getMimeType()}, - (path, uri) -> { - if (uri == null) { - final String msg = "Scan failed!"; - Log.e(TAG, msg); - data.postValue(Resource.error(msg, null)); - return; - } - Log.d(TAG, "onComplete: scan complete"); - MediaUtils.getVoiceInfo(contentResolver, uri, new MediaUtils.OnInfoLoadListener() { - @Override - public void onLoad(@Nullable final MediaUtils.VideoInfo videoInfo) { - if (videoInfo == null) return; - threadManager.sendVoice(data, - uri, - result.getWaveform(), - result.getSamplingFreq(), - videoInfo == null ? 0 : videoInfo.duration, - videoInfo == null ? 0 : videoInfo.size); - } - - @Override - public void onFailure(final Throwable t) { - data.postValue(Resource.error(t.getMessage(), null)); - } - }); + Utils.scanDocumentFile(application, result.getFile(), (path, uri) -> { + if (uri == null) { + final String msg = "Scan failed!"; + Log.e(TAG, msg); + data.postValue(Resource.error(msg, null)); + return; + } + Log.d(TAG, "onComplete: scan complete"); + MediaUtils.getVoiceInfo(contentResolver, uri, new MediaUtils.OnInfoLoadListener() { + @Override + public void onLoad(@Nullable final MediaUtils.VideoInfo videoInfo) { + if (videoInfo == null) return; + threadManager.sendVoice(data, + uri, + result.getWaveform(), + result.getSamplingFreq(), + videoInfo == null ? 0 : videoInfo.duration, + videoInfo == null ? 0 : videoInfo.size); } - ); + + @Override + public void onFailure(final Throwable t) { + data.postValue(Resource.error(t.getMessage(), null)); + } + }); + }); } @Override @@ -204,7 +199,7 @@ public class DirectThreadViewModel extends AndroidViewModel { } }); - voiceRecorder.startRecording(); + voiceRecorder.startRecording(application); return data; } diff --git a/app/src/main/java/awais/instagrabber/viewmodels/ImageEditViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/ImageEditViewModel.java index ecd12260..7a8613ea 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/ImageEditViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/ImageEditViewModel.java @@ -5,6 +5,7 @@ import android.graphics.RectF; import android.net.Uri; import androidx.annotation.NonNull; +import androidx.documentfile.provider.DocumentFile; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; @@ -26,6 +27,7 @@ import awais.instagrabber.models.SavedImageEditState; import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.SerializablePair; +import awais.instagrabber.utils.Utils; import jp.co.cyberagent.android.gpuimage.GPUImage; import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter; import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilterGroup; @@ -35,6 +37,7 @@ public class ImageEditViewModel extends AndroidViewModel { private static final String RESULT = "result"; private static final String FILE_FORMAT = "yyyyMMddHHmmssSSS"; private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(FILE_FORMAT, Locale.US); + private static final String MIME_TYPE = Utils.mimeTypeMap.getMimeTypeFromExtension("jpg"); private Uri originalUri; private SavedImageEditState savedImageEditState; @@ -48,18 +51,18 @@ public class ImageEditViewModel extends AndroidViewModel { private final MutableLiveData isCropped = new MutableLiveData<>(false); private final MutableLiveData isTuned = new MutableLiveData<>(false); private final MutableLiveData isFiltered = new MutableLiveData<>(false); - private final File outputDir; + private final DocumentFile outputDir; private List> tuningFilters; private Filter appliedFilter; - private final File destinationFile; + private final DocumentFile destinationFile; public ImageEditViewModel(final Application application) { super(application); sessionId = SIMPLE_DATE_FORMAT.format(new Date()); outputDir = DownloadUtils.getImageEditDir(sessionId); - destinationFile = new File(outputDir, RESULT + ".jpg"); - destinationUri = Uri.fromFile(destinationFile); - cropDestinationUri = Uri.fromFile(new File(outputDir, CROP + ".jpg")); + destinationFile = outputDir.createFile(MIME_TYPE, RESULT + ".jpg"); + destinationUri = destinationFile.getUri(); + cropDestinationUri = outputDir.createFile(MIME_TYPE, CROP + ".jpg").getUri(); } public String getSessionId() { @@ -159,16 +162,15 @@ public class ImageEditViewModel extends AndroidViewModel { delete(outputDir); } - private void delete(@NonNull final File file) { + private void delete(@NonNull final DocumentFile file) { if (file.isDirectory()) { - final File[] files = file.listFiles(); + final DocumentFile[] files = file.listFiles(); if (files != null) { - for (File f : files) { + for (DocumentFile f : files) { delete(f); } } } - //noinspection ResultOfMethodCallIgnored file.delete(); } @@ -206,9 +208,9 @@ public class ImageEditViewModel extends AndroidViewModel { return new SerializablePair<>(type, propertyValueMap); } - public File getDestinationFile() { - return destinationFile; - } + // public File getDestinationFile() { + // return destinationFile; + // } public enum Tab { RESULT, diff --git a/app/src/main/java/awais/instagrabber/workers/DownloadWorker.java b/app/src/main/java/awais/instagrabber/workers/DownloadWorker.java index b0dae625..12669ed0 100644 --- a/app/src/main/java/awais/instagrabber/workers/DownloadWorker.java +++ b/app/src/main/java/awais/instagrabber/workers/DownloadWorker.java @@ -8,7 +8,6 @@ import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.MediaMetadataRetriever; -import android.media.MediaScannerConnection; import android.net.Uri; import android.os.Build; import android.os.Handler; @@ -18,7 +17,7 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; -import androidx.core.content.FileProvider; +import androidx.documentfile.provider.DocumentFile; import androidx.work.Data; import androidx.work.ForegroundInfo; import androidx.work.Worker; @@ -30,10 +29,8 @@ import com.google.gson.JsonSyntaxException; import org.apache.commons.imaging.formats.jpeg.iptc.JpegIptcRewriter; import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.InputStream; +import java.io.OutputStream; import java.net.URL; import java.net.URLConnection; import java.util.Collection; @@ -44,6 +41,7 @@ import java.util.Map; import java.util.Scanner; import java.util.Set; import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; import awais.instagrabber.BuildConfig; import awais.instagrabber.R; @@ -52,10 +50,11 @@ import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; -//import awaisomereport.LogCollector; import static awais.instagrabber.utils.Constants.DOWNLOAD_CHANNEL_ID; import static awais.instagrabber.utils.Constants.NOTIF_GROUP_NAME; + +//import awaisomereport.LogCollector; //import static awais.instagrabber.utils.Utils.logCollector; public class DownloadWorker extends Worker { @@ -85,8 +84,21 @@ public class DownloadWorker extends Worker { .build()); } final String downloadRequestString; - final File requestFile = new File(downloadRequestFilePath); - try (Scanner scanner = new Scanner(requestFile)) { + // final File requestFile = new File(downloadRequestFilePath); + final Uri requestFile = Uri.parse(downloadRequestFilePath); + if (requestFile == null) { + return Result.failure(new Data.Builder() + .putString("error", "requestFile is null") + .build()); + } + final Context context = getApplicationContext(); + final ContentResolver contentResolver = context.getContentResolver(); + if (contentResolver == null) { + return Result.failure(new Data.Builder() + .putString("error", "contentResolver is null") + .build()); + } + try (Scanner scanner = new Scanner(contentResolver.openInputStream(requestFile))) { downloadRequestString = scanner.useDelimiter("\\A").next(); } catch (Exception e) { Log.e(TAG, "doWork: ", e); @@ -116,7 +128,7 @@ public class DownloadWorker extends Worker { final Map urlToFilePathMap = downloadRequest.getUrlToFilePathMap(); download(urlToFilePathMap); new Handler(Looper.getMainLooper()).postDelayed(() -> showSummary(urlToFilePathMap), 500); - final boolean deleted = requestFile.delete(); + final boolean deleted = DocumentFile.fromSingleUri(context, requestFile).delete(); if (!deleted) { Log.w(TAG, "doWork: requestFile not deleted!"); } @@ -131,7 +143,9 @@ public class DownloadWorker extends Worker { for (final Map.Entry urlAndFilePath : entries) { final String url = urlAndFilePath.getKey(); updateDownloadProgress(notificationId, count, total, 0); - download(notificationId, count, total, url, urlAndFilePath.getValue()); + final String uriString = urlAndFilePath.getValue(); + final DocumentFile file = DocumentFile.fromSingleUri(getApplicationContext(), Uri.parse(uriString)); + download(notificationId, count, total, url, file); count++; } } @@ -144,17 +158,24 @@ public class DownloadWorker extends Worker { final int position, final int total, final String url, - final String filePath) { - final boolean isJpg = filePath.endsWith("jpg"); + final DocumentFile filePath) { + final Context context = getApplicationContext(); + if (context == null) return; + final ContentResolver contentResolver = context.getContentResolver(); + if (contentResolver == null) return; + final String filePathType = filePath.getType(); + if (filePathType == null) return; + // final String extension = Utils.mimeTypeMap.getExtensionFromMimeType(filePathType); + final boolean isJpg = filePathType.startsWith("image"); // extension.endsWith("jpg"); // using temp file approach to remove IPTC so that download progress can be reported - final File outFile = isJpg ? DownloadUtils.getTempFile() : new File(filePath); + final DocumentFile outFile = isJpg ? DownloadUtils.getTempFile(null, "jpg") : filePath; try { final URLConnection urlConnection = new URL(url).openConnection(); final long fileSize = Build.VERSION.SDK_INT >= 24 ? urlConnection.getContentLengthLong() : urlConnection.getContentLength(); float totalRead = 0; try (final BufferedInputStream bis = new BufferedInputStream(urlConnection.getInputStream()); - final FileOutputStream fos = new FileOutputStream(outFile)) { + final OutputStream fos = contentResolver.openOutputStream(outFile.getUri())) { final byte[] buffer = new byte[0x2000]; int count; while ((count = bis.read(buffer, 0, 0x2000)) != -1) { @@ -167,18 +188,17 @@ public class DownloadWorker extends Worker { } fos.flush(); } catch (final Exception e) { - Log.e(TAG, "Error while writing data from url: " + url + " to file: " + outFile.getAbsolutePath(), e); + Log.e(TAG, "Error while writing data from url: " + url + " to file: " + outFile.getName(), e); } if (isJpg) { - final File finalFile = new File(filePath); - try (FileInputStream fis = new FileInputStream(outFile); - FileOutputStream fos = new FileOutputStream(finalFile)) { + try (final InputStream fis = contentResolver.openInputStream(outFile.getUri()); + final OutputStream fos = contentResolver.openOutputStream(filePath.getUri())) { final JpegIptcRewriter jpegIptcRewriter = new JpegIptcRewriter(); jpegIptcRewriter.removeIPTC(fis, fos); } catch (Exception e) { Log.e(TAG, "Error while removing iptc: url: " + url - + ", tempFile: " + outFile.getAbsolutePath() - + ", finalFile: " + finalFile.getAbsolutePath(), e); + + ", tempFile: " + outFile + + ", finalFile: " + filePath, e); } final boolean deleted = outFile.delete(); if (!deleted) { @@ -243,57 +263,56 @@ public class DownloadWorker extends Worker { private void showSummary(final Map urlToFilePathMap) { final Context context = getApplicationContext(); - final Collection filePaths = urlToFilePathMap.values(); + final Collection filePaths = urlToFilePathMap.values() + .stream() + .map(s -> DocumentFile.fromSingleUri(context, Uri.parse(s))) + .collect(Collectors.toList()); final List notifications = new LinkedList<>(); final List notificationIds = new LinkedList<>(); int count = 1; - for (final String filePath : filePaths) { - final File file = new File(filePath); - context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file))); - MediaScannerConnection.scanFile(context, new String[]{file.getAbsolutePath()}, null, null); - final Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file); + for (final DocumentFile filePath : filePaths) { + // final File file = new File(filePath); + context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, filePath.getUri())); + Utils.scanDocumentFile(context, filePath, (path, uri) -> {}); + // final Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file); final ContentResolver contentResolver = context.getContentResolver(); Bitmap bitmap = null; - final String mimeType = Utils.getMimeType(uri, contentResolver); + final String mimeType = filePath.getType(); // Utils.getMimeType(uri, contentResolver); if (!TextUtils.isEmpty(mimeType)) { if (mimeType.startsWith("image")) { - try (final InputStream inputStream = contentResolver.openInputStream(uri)) { + try (final InputStream inputStream = contentResolver.openInputStream(filePath.getUri())) { bitmap = BitmapFactory.decodeStream(inputStream); } catch (final Exception e) { -// if (logCollector != null) -// logCollector.appendException(e, LogCollector.LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_1"); if (BuildConfig.DEBUG) Log.e(TAG, "", e); } } else if (mimeType.startsWith("video")) { final MediaMetadataRetriever retriever = new MediaMetadataRetriever(); try { try { - retriever.setDataSource(context, uri); + retriever.setDataSource(context, filePath.getUri()); } catch (final Exception e) { - retriever.setDataSource(file.getAbsolutePath()); + // retriever.setDataSource(file.getAbsolutePath()); + Log.e(TAG, "showSummary: ", e); } bitmap = retriever.getFrameAtTime(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) try { retriever.close(); } catch (final Exception e) { -// if (logCollector != null) -// logCollector.appendException(e, LogCollector.LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_2"); + Log.e(TAG, "showSummary: ", e); } } catch (final Exception e) { - if (BuildConfig.DEBUG) Log.e(TAG, "", e); -// if (logCollector != null) -// logCollector.appendException(e, LogCollector.LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_3"); + Log.e(TAG, "", e); } } } final String downloadComplete = context.getString(R.string.downloader_complete); - final Intent intent = new Intent(Intent.ACTION_VIEW, uri) + final Intent intent = new Intent(Intent.ACTION_VIEW, filePath.getUri()) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_FROM_BACKGROUND | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - .putExtra(Intent.EXTRA_STREAM, uri); + .putExtra(Intent.EXTRA_STREAM, filePath.getUri()); final PendingIntent pendingIntent = PendingIntent.getActivity( context, DOWNLOAD_NOTIFICATION_INTENT_REQUEST_CODE, @@ -357,16 +376,22 @@ public class DownloadWorker extends Worker { public static class Builder { private Map urlToFilePathMap; - public Builder setUrlToFilePathMap(final Map urlToFilePathMap) { - this.urlToFilePathMap = urlToFilePathMap; + public Builder setUrlToFilePathMap(final Map urlToFilePathMap) { + if (urlToFilePathMap == null) { + return this; + } + this.urlToFilePathMap = urlToFilePathMap.entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, + o -> o.getValue().getUri().toString())); return this; } - public Builder addUrl(@NonNull final String url, @NonNull final String filePath) { + public Builder addUrl(@NonNull final String url, @NonNull final DocumentFile filePath) { if (urlToFilePathMap == null) { urlToFilePathMap = new HashMap<>(); } - urlToFilePathMap.put(url, filePath); + urlToFilePathMap.put(url, filePath.getUri().toString()); return this; } From 7577e82ac13848916e63919e28904f1b5fe6ed66 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Sun, 4 Apr 2021 09:27:59 +0900 Subject: [PATCH 03/17] Add back username prepend logic --- .../instagrabber/utils/DownloadUtils.java | 62 ++++++++++++++----- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java index 6f54dfcb..df8fef91 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java @@ -191,25 +191,36 @@ public final class DownloadUtils { private static Pair, String> getDownloadSavePaths(final List paths, final String postId, final String displayUrl) { - return getDownloadSavePaths(paths, postId, "", displayUrl); + return getDownloadSavePaths(paths, postId, "", displayUrl, ""); + } + + @NonNull + private static Pair, String> getDownloadSavePaths(final List paths, + final String postId, + final String displayUrl, + final String username) { + return getDownloadSavePaths(paths, postId, "", displayUrl, username); } private static Pair, String> getDownloadChildSaveFile(final List paths, final String postId, final int childPosition, - final String url) { + final String url, + final String username) { final String sliderPostfix = "_slide_" + childPosition; - return getDownloadSavePaths(paths, postId, sliderPostfix, url); + return getDownloadSavePaths(paths, postId, sliderPostfix, url, username); } @Nullable private static Pair, String> getDownloadSavePaths(final List paths, final String postId, final String sliderPostfix, - final String displayUrl) { + final String displayUrl, + final String username) { if (paths == null) return null; final String extension = getFileExtensionFromUrl(displayUrl); - final String fileName = postId + sliderPostfix + extension; + final String usernamePrepend = TextUtils.isEmpty(username) ? "" : (username + "_"); + final String fileName = usernamePrepend + postId + sliderPostfix + extension; // return new File(finalDir, fileName); // DocumentFile file = finalDir.findFile(fileName); // if (file == null) { @@ -295,8 +306,9 @@ public final class DownloadUtils { case MEDIA_TYPE_IMAGE: case MEDIA_TYPE_VIDEO: { final String url = ResponseBodyUtils.getImageUrl(media); - final Pair, String> pair = getDownloadSavePaths(userFolderPaths, media.getCode(), url); - checkList.add(checkPathExists(context, pair.first)); + final Pair, String> file = getDownloadSavePaths(userFolderPaths, media.getCode(), url, ""); + final Pair, String> usernameFile = getDownloadSavePaths(userFolderPaths, media.getCode(), url, username); + checkList.add(checkPathExists(context, file.first) || checkPathExists(context, usernameFile.first)); break; } case MEDIA_TYPE_SLIDER: @@ -305,8 +317,9 @@ public final class DownloadUtils { final Media child = sliderItems.get(i); if (child == null) continue; final String url = ResponseBodyUtils.getImageUrl(child); - final Pair, String> pair = getDownloadChildSaveFile(userFolderPaths, media.getCode(), i + 1, url); - checkList.add(checkPathExists(context, pair.first)); + final Pair, String> file = getDownloadChildSaveFile(userFolderPaths, media.getCode(), i + 1, url, ""); + final Pair, String> usernameFile = getDownloadChildSaveFile(userFolderPaths, media.getCode(), i + 1, url, username); + checkList.add(checkPathExists(context, file.first) || checkPathExists(context, usernameFile.first)); } break; default: @@ -356,12 +369,16 @@ public final class DownloadUtils { public static void download(@NonNull final Context context, @NonNull final StoryModel storyModel) { - final DocumentFile downloadDir = getDownloadDir(context, "@" + storyModel.getUsername()); + final DocumentFile downloadDir = getDownloadDir(context, storyModel.getUsername()); final String url = storyModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO ? storyModel.getVideoUrl() : storyModel.getStoryUrl(); final String extension = DownloadUtils.getFileExtensionFromUrl(url); - final String fileName = storyModel.getStoryMediaId() + "_" + storyModel.getTimestamp() + extension; + final String baseFileName = storyModel.getStoryMediaId() + "_" + + storyModel.getTimestamp() + extension; + final String usernamePrepend = Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) + && storyModel.getUsername() != null ? storyModel.getUsername() + "_" : ""; + final String fileName = usernamePrepend + baseFileName; DocumentFile saveFile = downloadDir.findFile(fileName); if (saveFile == null) { saveFile = downloadDir.createFile( @@ -394,13 +411,24 @@ public final class DownloadUtils { final Map map = new HashMap<>(); for (final Media media : feedModels) { final User mediaUser = media.getUser(); - final List userFolderPaths = getSubPathForUserFolder(mediaUser == null ? "" : "@" + mediaUser.getUsername()); - // final DocumentFile downloadDir = getDownloadDir(context, mediaUser == null ? "" : "@" + mediaUser.getUsername()); + final String username = mediaUser == null ? "" : mediaUser.getUsername(); + final List userFolderPaths = getSubPathForUserFolder(username); + // final DocumentFile downloadDir = getDownloadDir(context, mediaUser == null ? "" : mediaUser.getUsername()); switch (media.getMediaType()) { case MEDIA_TYPE_IMAGE: case MEDIA_TYPE_VIDEO: { final String url = getUrlOfType(media); - final Pair, String> pair = getDownloadSavePaths(userFolderPaths, media.getCode(), url); + String fileName = media.getId(); + if (mediaUser != null && TextUtils.isEmpty(media.getCode())) { + fileName = mediaUser.getUsername() + "_" + fileName; + } + if (!TextUtils.isEmpty(media.getCode())) { + fileName = media.getCode(); + if (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) && mediaUser != null) { + fileName = mediaUser.getUsername() + "_" + fileName; + } + } + final Pair, String> pair = getDownloadSavePaths(userFolderPaths, fileName, url); final DocumentFile file = createFile(pair); if (file == null) continue; map.put(url, file); @@ -424,7 +452,11 @@ public final class DownloadUtils { if (childPositionIfSingle >= 0 && feedModels.size() == 1 && i != childPositionIfSingle) continue; final Media child = sliderItems.get(i); final String url = getUrlOfType(child); - final Pair, String> pair = getDownloadChildSaveFile(userFolderPaths, media.getCode(), i + 1, url); + final String usernamePrepend = Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) && mediaUser != null + ? mediaUser.getUsername() + : ""; + final Pair, String> pair = getDownloadChildSaveFile(userFolderPaths, media.getCode(), i + 1, url, + usernamePrepend); final DocumentFile file = createFile(pair); if (file == null) continue; map.put(url, file); From 9b691a453e8b1794396cb160ed551608128fa885 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Sun, 4 Apr 2021 23:53:10 +0900 Subject: [PATCH 04/17] Remove read/write storage permissions. Add an activity to select directory. --- app/src/main/AndroidManifest.xml | 5 +- .../instagrabber/InstaGrabberApplication.java | 6 +- .../activities/DirectDownload.java | 13 +- .../activities/DirectorySelectActivity.java | 128 ++++++++++++++++++ .../instagrabber/activities/MainActivity.java | 26 +++- .../dialogs/CreateBackupDialogFragment.java | 2 +- .../dialogs/ProfilePicDialogFragment.java | 11 +- .../fragments/CollectionPostsFragment.java | 46 +++---- .../fragments/HashTagFragment.java | 83 ++++++------ .../fragments/LocationFragment.java | 87 ++++++------ .../fragments/PostViewV2Fragment.java | 13 +- .../fragments/SavedViewerFragment.java | 29 ++-- .../fragments/StoryViewerFragment.java | 18 ++- .../fragments/TopicPostsFragment.java | 32 ++--- .../DirectMessageThreadFragment.java | 15 +- .../fragments/main/FeedFragment.java | 30 ++-- .../fragments/main/ProfileFragment.java | 29 ++-- .../DownloadsPreferencesFragment.java | 124 +++++++++++------ .../awais/instagrabber/utils/Constants.java | 20 +-- .../instagrabber/utils/DirectoryChooser.java | 24 ++-- .../instagrabber/utils/DownloadUtils.java | 121 +++++++++++++---- .../java/awais/instagrabber/utils/Utils.java | 89 ++++++++++-- .../viewmodels/DirectThreadViewModel.java | 2 +- .../res/layout/activity_directory_select.xml | 39 ++++++ 24 files changed, 652 insertions(+), 340 deletions(-) create mode 100644 app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java create mode 100644 app/src/main/res/layout/activity_directory_select.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a958a9a3..48326842 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,8 +4,8 @@ package="awais.instagrabber"> - - + + @@ -133,6 +133,7 @@ + requestListeners = new HashSet<>(); // requestListeners.add(new RequestLoggingListener()); final ImagePipelineConfig imagePipelineConfig = ImagePipelineConfig @@ -90,6 +87,5 @@ public final class InstaGrabberApplication extends Application { if (TextUtils.isEmpty(settingsHelper.getString(Constants.DEVICE_UUID))) { settingsHelper.putString(Constants.DEVICE_UUID, UUID.randomUUID().toString()); } - DownloadUtils.init(this); } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/activities/DirectDownload.java b/app/src/main/java/awais/instagrabber/activities/DirectDownload.java index e2d30743..94e02280 100644 --- a/app/src/main/java/awais/instagrabber/activities/DirectDownload.java +++ b/app/src/main/java/awais/instagrabber/activities/DirectDownload.java @@ -1,6 +1,5 @@ package awais.instagrabber.activities; -import android.Manifest; import android.app.Notification; import android.content.Context; import android.content.Intent; @@ -14,10 +13,8 @@ import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; -import androidx.core.app.ActivityCompat; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; -import androidx.core.content.ContextCompat; import awais.instagrabber.R; import awais.instagrabber.asyncs.PostFetcher; @@ -74,11 +71,11 @@ public final class DirectDownload extends AppCompatActivity { } private synchronized void checkPermissions() { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - doDownload(); - return; - } - ActivityCompat.requestPermissions(this, DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); + // if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + doDownload(); + // return; + // } + // ActivityCompat.requestPermissions(this, DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); } @Override diff --git a/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java b/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java new file mode 100644 index 00000000..c45338a0 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java @@ -0,0 +1,128 @@ +package awais.instagrabber.activities; + +import android.content.Intent; +import android.content.UriPermission; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Parcelable; +import android.provider.DocumentsContract; +import android.util.Log; + +import androidx.annotation.Nullable; +import androidx.documentfile.provider.DocumentFile; + +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import awais.instagrabber.databinding.ActivityDirectorySelectBinding; +import awais.instagrabber.utils.AppExecutors; +import awais.instagrabber.utils.Constants; +import awais.instagrabber.utils.Utils; + +public class DirectorySelectActivity extends BaseLanguageActivity { + private static final String TAG = DirectorySelectActivity.class.getSimpleName(); + + public static final int SELECT_DIR_REQUEST_CODE = 1090; + + private Uri initialUri; + private ActivityDirectorySelectBinding binding; + + @Override + protected void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding = ActivityDirectorySelectBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + binding.selectDir.setOnClickListener(v -> openDirectoryChooser()); + setInitialUri(); + } + + private void setInitialUri() { + AppExecutors.getInstance().mainThread().execute(() -> { + final Intent intent = getIntent(); + if (intent == null) { + setMessage(); + return; + } + final Parcelable initialUriParcelable = intent.getParcelableExtra(Constants.EXTRA_INITIAL_URI); + if (!(initialUriParcelable instanceof Uri)) { + setMessage(); + return; + } + initialUri = (Uri) initialUriParcelable; + setMessage(); + }); + } + + private void setMessage() { + if (initialUri == null) { + // default message + binding.message.setText("Select a directory which Barinsta will use for downloads and temp files"); + return; + } + + if (!initialUri.toString().startsWith("content")) { + final String message = String.format("Android has changed the way apps can access files and directories on storage.\n\n" + + "Please re-select the directory '%s' after clicking the button below", + initialUri.toString()); + binding.message.setText(message); + return; + } + + final List existingPermissions = getContentResolver().getPersistedUriPermissions(); + final boolean anyMatch = existingPermissions.stream().anyMatch(uriPermission -> uriPermission.getUri().equals(initialUri)); + if (!anyMatch) { + // permission revoked message + final String message = "Permissions for the previously selected directory '%s' were revoked by the system.\n\n" + + "Re-select the directory or select a new directory."; + final DocumentFile documentFile = DocumentFile.fromSingleUri(this, initialUri); + String path; + try { + path = URLDecoder.decode(initialUri.toString(), StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException e) { + path = initialUri.toString(); + } + if (documentFile != null) { + try { + final File file = Utils.getDocumentFileRealPath(this, documentFile); + if (file != null) { + path = file.getAbsolutePath(); + } + } catch (Exception e) { + Log.e(TAG, "setMessage: ", e); + } + } + binding.message.setText(String.format(message, path)); + } + } + + private void openDirectoryChooser() { + final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && initialUri != null) { + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initialUri); + } + startActivityForResult(intent, SELECT_DIR_REQUEST_CODE); + } + + @Override + protected void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode != SELECT_DIR_REQUEST_CODE) return; + if (resultCode != RESULT_OK) { + // Show error + return; + } + if (data == null || data.getData() == null) { + // show error + return; + } + try { + Utils.setupSelectedDir(this, data); + } catch (Exception e) { + // show error + } + } +} diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.java b/app/src/main/java/awais/instagrabber/activities/MainActivity.java index 3459d632..fb2d8e6c 100644 --- a/app/src/main/java/awais/instagrabber/activities/MainActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/MainActivity.java @@ -79,6 +79,7 @@ import awais.instagrabber.services.DMSyncAlarmReceiver; import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; +import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.FlavorTown; import awais.instagrabber.utils.IntentUtils; import awais.instagrabber.utils.TextUtils; @@ -92,6 +93,7 @@ import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; +import static awais.instagrabber.utils.Constants.EXTRA_INITIAL_URI; import static awais.instagrabber.utils.NavigationExtensions.setupWithNavController; import static awais.instagrabber.utils.Utils.settingsHelper; @@ -134,6 +136,16 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage @Override protected void onCreate(@Nullable final Bundle savedInstanceState) { + try { + DownloadUtils.init(this); + } catch (DownloadUtils.ReselectDocumentTreeException e) { + super.onCreate(savedInstanceState); + final Intent intent = new Intent(this, DirectorySelectActivity.class); + intent.putExtra(EXTRA_INITIAL_URI, e.getInitialUri()); + startActivity(intent); + finish(); + return; + } RetrofitFactory.setup(this); super.onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(getLayoutInflater()); @@ -221,7 +233,9 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage @Override protected void onSaveInstanceState(@NonNull final Bundle outState) { outState.putString(FIRST_FRAGMENT_GRAPH_INDEX_KEY, String.valueOf(firstFragmentGraphIndex)); - outState.putString(LAST_SELECT_NAV_MENU_ID, String.valueOf(binding.bottomNavView.getSelectedItemId())); + if (binding != null) { + outState.putString(LAST_SELECT_NAV_MENU_ID, String.valueOf(binding.bottomNavView.getSelectedItemId())); + } super.onSaveInstanceState(outState); } @@ -265,7 +279,9 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage Log.e(TAG, "onDestroy: ", e); } unbindActivityCheckerService(); - RetrofitFactory.getInstance().destroy(); + try { + RetrofitFactory.getInstance().destroy(); + } catch (Exception ignored) {} } @Override @@ -916,9 +932,9 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage return currentTabs; } -// public boolean isNavRootInCurrentTabs(@IdRes final int navRootId) { -// return showBottomViewDestinations.stream().anyMatch(id -> id == navRootId); -// } + // public boolean isNavRootInCurrentTabs(@IdRes final int navRootId) { + // return showBottomViewDestinations.stream().anyMatch(id -> id == navRootId); + // } private void setNavBarDMUnreadCountBadge(final int unseenCount) { final BadgeDrawable badge = binding.bottomNavView.getOrCreateBadge(R.id.direct_messages_nav_graph); diff --git a/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java index 2cbd0224..132d75a4 100644 --- a/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java +++ b/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java @@ -213,7 +213,7 @@ public class CreateBackupDialogFragment extends DialogFragment { // Optionally, specify a URI for the directory that should be opened in // the system file picker when your app creates the document. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, DownloadUtils.getDownloadDir().getUri()); + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, DownloadUtils.getBackupsDir().getUri()); } startActivityForResult(intent, CREATE_FILE_REQUEST_CODE); diff --git a/app/src/main/java/awais/instagrabber/dialogs/ProfilePicDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/ProfilePicDialogFragment.java index 9df1e552..eb8a0247 100644 --- a/app/src/main/java/awais/instagrabber/dialogs/ProfilePicDialogFragment.java +++ b/app/src/main/java/awais/instagrabber/dialogs/ProfilePicDialogFragment.java @@ -15,7 +15,6 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; import androidx.documentfile.provider.DocumentFile; import androidx.fragment.app.DialogFragment; @@ -109,11 +108,11 @@ public class ProfilePicDialogFragment extends DialogFragment { binding.download.setOnClickListener(v -> { final Context context = getContext(); if (context == null) return; - if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) { - downloadProfilePicture(); - return; - } - requestPermissions(DownloadUtils.PERMS, 8020); + // if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) { + downloadProfilePicture(); + // return; + // } + // requestPermissions(DownloadUtils.PERMS, 8020); }); } diff --git a/app/src/main/java/awais/instagrabber/fragments/CollectionPostsFragment.java b/app/src/main/java/awais/instagrabber/fragments/CollectionPostsFragment.java index 9a850045..ab7190c8 100644 --- a/app/src/main/java/awais/instagrabber/fragments/CollectionPostsFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/CollectionPostsFragment.java @@ -27,7 +27,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.coordinatorlayout.widget.CoordinatorLayout; -import androidx.core.content.PermissionChecker; import androidx.core.graphics.ColorUtils; import androidx.fragment.app.Fragment; import androidx.navigation.NavController; @@ -65,9 +64,6 @@ import awais.instagrabber.utils.Utils; import awais.instagrabber.webservices.CollectionService; import awais.instagrabber.webservices.ServiceCallback; -import static androidx.core.content.PermissionChecker.checkSelfPermission; -import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; - public class CollectionPostsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "CollectionPostsFragment"; private static final int STORAGE_PERM_REQUEST_CODE = 8020; @@ -105,12 +101,12 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay if (CollectionPostsFragment.this.selectedFeedModels == null) return false; final Context context = getContext(); if (context == null) return false; - if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { - DownloadUtils.download(context, ImmutableList.copyOf(CollectionPostsFragment.this.selectedFeedModels)); - binding.posts.endSelection(); - return true; - } - requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); + // if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { + DownloadUtils.download(context, ImmutableList.copyOf(CollectionPostsFragment.this.selectedFeedModels)); + binding.posts.endSelection(); + // return true; + // } + // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); } return false; } @@ -140,13 +136,13 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay public void onDownloadClick(final Media feedModel, final int childPosition) { final Context context = getContext(); if (context == null) return; - if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { - DownloadUtils.showDownloadDialog(context, feedModel, childPosition); - return; - } - downloadFeedModel = feedModel; - downloadChildPosition = -1; - requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); + // if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { + DownloadUtils.showDownloadDialog(context, feedModel, childPosition); + // return; + // } + // downloadFeedModel = feedModel; + // downloadChildPosition = -1; + // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); } @Override @@ -288,8 +284,7 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay if (item.getItemId() == R.id.layout) { showPostsLayoutPreferences(); return true; - } - else if (item.getItemId() == R.id.delete) { + } else if (item.getItemId() == R.id.delete) { final Context context = getContext(); new AlertDialog.Builder(context) .setTitle(R.string.delete_collection) @@ -309,15 +304,13 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay Log.e(TAG, "Error deleting collection", t); try { Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); - } - catch(final Throwable e) {} + } catch (final Throwable e) {} } }); }) .setNegativeButton(R.string.cancel, null) .show(); - } - else if (item.getItemId() == R.id.edit) { + } else if (item.getItemId() == R.id.edit) { final Context context = getContext(); final EditText input = new EditText(context); new AlertDialog.Builder(context) @@ -339,8 +332,7 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay Log.e(TAG, "Error editing collection", t); try { Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); - } - catch(final Throwable e) {} + } catch (final Throwable e) {} } }); }) @@ -443,8 +435,8 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay private void setupCover() { final String coverUrl = ResponseBodyUtils.getImageUrl(savedCollection.getCoverMedias() == null - ? savedCollection.getCoverMedia() - : savedCollection.getCoverMedias().get(0)); + ? savedCollection.getCoverMedia() + : savedCollection.getCoverMedias().get(0)); final DraweeController controller = Fresco .newDraweeControllerBuilder() .setOldController(binding.cover.getController()) diff --git a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java index 3f974816..d43101c7 100644 --- a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java @@ -24,7 +24,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.coordinatorlayout.widget.CoordinatorLayout; -import androidx.core.content.PermissionChecker; import androidx.fragment.app.Fragment; import androidx.navigation.NavController; import androidx.navigation.NavDirections; @@ -70,8 +69,6 @@ import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.StoriesService; import awais.instagrabber.webservices.TagsService; -import static androidx.core.content.PermissionChecker.checkSelfPermission; -import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; import static awais.instagrabber.utils.Utils.settingsHelper; public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { @@ -121,13 +118,13 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe if (HashTagFragment.this.selectedFeedModels == null) return false; final Context context = getContext(); if (context == null) return false; - if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { - DownloadUtils.download(context, ImmutableList.copyOf(HashTagFragment.this.selectedFeedModels)); - binding.posts.endSelection(); - return true; - } - requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); + // if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { + DownloadUtils.download(context, ImmutableList.copyOf(HashTagFragment.this.selectedFeedModels)); + binding.posts.endSelection(); return true; + // } + // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); + // return true; } return false; } @@ -157,13 +154,13 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe public void onDownloadClick(final Media feedModel, final int childPosition) { final Context context = getContext(); if (context == null) return; - if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { - DownloadUtils.showDownloadDialog(context, feedModel, childPosition); - return; - } - downloadFeedModel = feedModel; - downloadChildPosition = childPosition; - requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); + // if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { + DownloadUtils.showDownloadDialog(context, feedModel, childPosition); + // return; + // } + // downloadFeedModel = feedModel; + // downloadChildPosition = childPosition; + // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); } @Override @@ -400,8 +397,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe try { Toast.makeText(getContext(), R.string.error_loading_hashtag, Toast.LENGTH_SHORT).show(); binding.swipeRefreshLayout.setEnabled(false); - } - catch (Exception ignored) {} + } catch (Exception ignored) {} return; } setTitle(); @@ -428,31 +424,32 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe userId, deviceUuid, new ServiceCallback() { - @Override - public void onSuccess(final Boolean result) { - hashtagDetailsBinding.btnFollowTag.setClickable(true); - if (!result) { - Log.e(TAG, "onSuccess: result is false"); - Snackbar.make(root, R.string.downloader_unknown_error, BaseTransientBottomBar.LENGTH_LONG) - .show(); - return; - } - hashtagDetailsBinding.btnFollowTag.setText(R.string.unfollow); - hashtagDetailsBinding.btnFollowTag.setChipIconResource(R.drawable.ic_outline_person_add_disabled_24); - } - - @Override - public void onFailure(@NonNull final Throwable t) { - hashtagDetailsBinding.btnFollowTag.setClickable(true); - Log.e(TAG, "onFailure: ", t); - final String message = t.getMessage(); - Snackbar.make(root, - message != null ? message - : getString(R.string.downloader_unknown_error), - BaseTransientBottomBar.LENGTH_LONG) - .show(); - } - }); + @Override + public void onSuccess(final Boolean result) { + hashtagDetailsBinding.btnFollowTag.setClickable(true); + if (!result) { + Log.e(TAG, "onSuccess: result is false"); + Snackbar.make(root, R.string.downloader_unknown_error, BaseTransientBottomBar.LENGTH_LONG) + .show(); + return; + } + hashtagDetailsBinding.btnFollowTag.setText(R.string.unfollow); + hashtagDetailsBinding.btnFollowTag + .setChipIconResource(R.drawable.ic_outline_person_add_disabled_24); + } + + @Override + public void onFailure(@NonNull final Throwable t) { + hashtagDetailsBinding.btnFollowTag.setClickable(true); + Log.e(TAG, "onFailure: ", t); + final String message = t.getMessage(); + Snackbar.make(root, + message != null ? message + : getString(R.string.downloader_unknown_error), + BaseTransientBottomBar.LENGTH_LONG) + .show(); + } + }); return; } }); diff --git a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java index c42809c5..c8e15e1e 100644 --- a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java @@ -22,7 +22,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.coordinatorlayout.widget.CoordinatorLayout; -import androidx.core.content.PermissionChecker; import androidx.fragment.app.Fragment; import androidx.navigation.NavController; import androidx.navigation.NavDirections; @@ -66,13 +65,8 @@ import awais.instagrabber.webservices.LocationService; import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.StoriesService; -import static androidx.core.content.PermissionChecker.checkSelfPermission; -import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; import static awais.instagrabber.utils.Utils.settingsHelper; -//import awaisomereport.LogCollector; -//import static awais.instagrabber.utils.Utils.logCollector; - public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "LocationFragment"; private static final int STORAGE_PERM_REQUEST_CODE = 8020; @@ -118,12 +112,12 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR if (LocationFragment.this.selectedFeedModels == null) return false; final Context context = getContext(); if (context == null) return false; - if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { - DownloadUtils.download(context, ImmutableList.copyOf(LocationFragment.this.selectedFeedModels)); - binding.posts.endSelection(); - return true; - } - requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); + // if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { + DownloadUtils.download(context, ImmutableList.copyOf(LocationFragment.this.selectedFeedModels)); + binding.posts.endSelection(); + return true; + // } + // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); } return false; } @@ -153,13 +147,13 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR public void onDownloadClick(final Media feedModel, final int childPosition) { final Context context = getContext(); if (context == null) return; - if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { - DownloadUtils.showDownloadDialog(context, feedModel, childPosition); - return; - } - downloadFeedModel = feedModel; - downloadChildPosition = childPosition; - requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); + // if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { + DownloadUtils.showDownloadDialog(context, feedModel, childPosition); + // return; + // } + // downloadFeedModel = feedModel; + // downloadChildPosition = childPosition; + // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); } @Override @@ -399,8 +393,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR try { Toast.makeText(getContext(), R.string.error_loading_location, Toast.LENGTH_SHORT).show(); binding.swipeRefreshLayout.setEnabled(false); - } - catch (Exception ignored) {} + } catch (Exception ignored) {} return; } setTitle(); @@ -409,16 +402,16 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR final long locationId = locationModel.getPk(); // binding.swipeRefreshLayout.setRefreshing(true); locationDetailsBinding.mainLocationImage.setImageURI("res:/" + R.drawable.ic_location); -// final String postCount = String.valueOf(locationModel.getCount()); -// final SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_count_inline, -// locationModel.getPostCount() > 2000000000L -// ? 2000000000 -// : locationModel.getPostCount().intValue(), -// postCount)); -// span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); -// span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); -// locationDetailsBinding.mainLocPostCount.setText(span); -// locationDetailsBinding.mainLocPostCount.setVisibility(View.VISIBLE); + // final String postCount = String.valueOf(locationModel.getCount()); + // final SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_count_inline, + // locationModel.getPostCount() > 2000000000L + // ? 2000000000 + // : locationModel.getPostCount().intValue(), + // postCount)); + // span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); + // span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); + // locationDetailsBinding.mainLocPostCount.setText(span); + // locationDetailsBinding.mainLocPostCount.setVisibility(View.VISIBLE); locationDetailsBinding.locationFullName.setText(locationModel.getName()); CharSequence biography = locationModel.getAddress() + "\n" + locationModel.getCity(); // binding.locationBiography.setCaptionIsExpandable(true); @@ -431,22 +424,22 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR } else { locationDetailsBinding.locationBiography.setVisibility(View.VISIBLE); locationDetailsBinding.locationBiography.setText(biography); -// locationDetailsBinding.locationBiography.addOnHashtagListener(autoLinkItem -> { -// final NavController navController = NavHostFragment.findNavController(this); -// final Bundle bundle = new Bundle(); -// final String originalText = autoLinkItem.getOriginalText().trim(); -// bundle.putString(ARG_HASHTAG, originalText); -// navController.navigate(R.id.action_global_hashTagFragment, bundle); -// }); -// locationDetailsBinding.locationBiography.addOnMentionClickListener(autoLinkItem -> { -// final String originalText = autoLinkItem.getOriginalText().trim(); -// navigateToProfile(originalText); -// }); -// locationDetailsBinding.locationBiography.addOnEmailClickListener(autoLinkItem -> Utils.openEmailAddress(context, -// autoLinkItem.getOriginalText() -// .trim())); -// locationDetailsBinding.locationBiography -// .addOnURLClickListener(autoLinkItem -> Utils.openURL(context, autoLinkItem.getOriginalText().trim())); + // locationDetailsBinding.locationBiography.addOnHashtagListener(autoLinkItem -> { + // final NavController navController = NavHostFragment.findNavController(this); + // final Bundle bundle = new Bundle(); + // final String originalText = autoLinkItem.getOriginalText().trim(); + // bundle.putString(ARG_HASHTAG, originalText); + // navController.navigate(R.id.action_global_hashTagFragment, bundle); + // }); + // locationDetailsBinding.locationBiography.addOnMentionClickListener(autoLinkItem -> { + // final String originalText = autoLinkItem.getOriginalText().trim(); + // navigateToProfile(originalText); + // }); + // locationDetailsBinding.locationBiography.addOnEmailClickListener(autoLinkItem -> Utils.openEmailAddress(context, + // autoLinkItem.getOriginalText() + // .trim())); + // locationDetailsBinding.locationBiography + // .addOnURLClickListener(autoLinkItem -> Utils.openURL(context, autoLinkItem.getOriginalText().trim())); locationDetailsBinding.locationBiography.setOnLongClickListener(v -> { Utils.copyText(context, biography); return true; diff --git a/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java b/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java index 0d0c7865..9a349418 100644 --- a/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java @@ -43,7 +43,6 @@ import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.view.ContextThemeWrapper; import androidx.appcompat.widget.PopupMenu; import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.core.content.PermissionChecker; import androidx.core.view.ViewCompat; import androidx.core.widget.NestedScrollView; import androidx.fragment.app.DialogFragment; @@ -100,9 +99,7 @@ import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; import awais.instagrabber.viewmodels.PostViewV2ViewModel; -import static androidx.core.content.PermissionChecker.checkSelfPermission; import static awais.instagrabber.fragments.HashTagFragment.ARG_HASHTAG; -import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; import static awais.instagrabber.utils.Utils.settingsHelper; public class PostViewV2Fragment extends SharedElementTransitionDialogFragment implements EditTextDialogFragment.EditTextDialogFragmentCallback { @@ -622,11 +619,11 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment im private void setupDownload() { binding.download.setOnClickListener(v -> { - if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { - DownloadUtils.showDownloadDialog(context, viewModel.getMedia(), sliderPosition); - return; - } - requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); + // if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { + DownloadUtils.showDownloadDialog(context, viewModel.getMedia(), sliderPosition); + // return; + // } + // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); }); binding.download.setOnLongClickListener(v -> { Utils.displayToastAboveView(context, v, getString(R.string.action_download)); diff --git a/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java index b6ee5431..456c82d1 100644 --- a/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java @@ -18,7 +18,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; -import androidx.core.content.PermissionChecker; import androidx.fragment.app.Fragment; import androidx.navigation.NavController; import androidx.navigation.NavDirections; @@ -45,8 +44,6 @@ import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; -import static androidx.core.content.PermissionChecker.checkSelfPermission; -import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; import static awais.instagrabber.utils.Utils.settingsHelper; public final class SavedViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { @@ -86,12 +83,12 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL if (SavedViewerFragment.this.selectedFeedModels == null) return false; final Context context = getContext(); if (context == null) return false; - if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { - DownloadUtils.download(context, ImmutableList.copyOf(SavedViewerFragment.this.selectedFeedModels)); - binding.posts.endSelection(); - return true; - } - requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); + // if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { + DownloadUtils.download(context, ImmutableList.copyOf(SavedViewerFragment.this.selectedFeedModels)); + binding.posts.endSelection(); + // return true; + // } + // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); } return false; } @@ -121,13 +118,13 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL public void onDownloadClick(final Media feedModel, final int childPosition) { final Context context = getContext(); if (context == null) return; - if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { - DownloadUtils.showDownloadDialog(context, feedModel, childPosition); - return; - } - downloadFeedModel = feedModel; - downloadChildPosition = childPosition; - requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); + // if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { + DownloadUtils.showDownloadDialog(context, feedModel, childPosition); + // return; + // } + // downloadFeedModel = feedModel; + // downloadChildPosition = childPosition; + // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); } @Override diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java index 6de66afe..60e62f80 100644 --- a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java @@ -29,8 +29,6 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; import androidx.core.view.GestureDetectorCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModel; @@ -210,10 +208,10 @@ public class StoryViewerFragment extends Fragment { if (context == null) return false; int itemId = item.getItemId(); if (itemId == R.id.action_download) { - if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) - downloadStory(); - else - ActivityCompat.requestPermissions(requireActivity(), DownloadUtils.PERMS, 8020); + // if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) + downloadStory(); + // else + // ActivityCompat.requestPermissions(requireActivity(), DownloadUtils.PERMS, 8020); return true; } if (itemId == R.id.action_dms) { @@ -415,10 +413,10 @@ public class StoryViewerFragment extends Fragment { return true; } } catch (final Exception e) { -// if (logCollector != null) -// logCollector.appendException(e, LogCollector.LogFile.ACTIVITY_STORY_VIEWER, "setupListeners", -// new Pair<>("swipeEvent", swipeEvent), -// new Pair<>("diffX", diffX)); + // if (logCollector != null) + // logCollector.appendException(e, LogCollector.LogFile.ACTIVITY_STORY_VIEWER, "setupListeners", + // new Pair<>("swipeEvent", swipeEvent), + // new Pair<>("diffX", diffX)); if (BuildConfig.DEBUG) Log.e(TAG, "Error", e); } return false; diff --git a/app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java b/app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java index 9b680d64..3a87b4e6 100644 --- a/app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java @@ -23,7 +23,6 @@ import androidx.activity.OnBackPressedDispatcher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.coordinatorlayout.widget.CoordinatorLayout; -import androidx.core.content.PermissionChecker; import androidx.core.graphics.ColorUtils; import androidx.fragment.app.Fragment; import androidx.navigation.NavController; @@ -51,17 +50,14 @@ import awais.instagrabber.databinding.FragmentTopicPostsBinding; import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; import awais.instagrabber.fragments.main.DiscoverFragmentDirections; import awais.instagrabber.models.PostsLayoutPreferences; -import awais.instagrabber.repositories.responses.discover.TopicCluster; import awais.instagrabber.repositories.responses.Media; +import awais.instagrabber.repositories.responses.discover.TopicCluster; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.Utils; import awais.instagrabber.webservices.DiscoverService; -import static androidx.core.content.PermissionChecker.checkSelfPermission; -import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; - public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final int STORAGE_PERM_REQUEST_CODE = 8020; private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030; @@ -97,12 +93,12 @@ public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.O if (TopicPostsFragment.this.selectedFeedModels == null) return false; final Context context = getContext(); if (context == null) return false; - if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { - DownloadUtils.download(context, ImmutableList.copyOf(TopicPostsFragment.this.selectedFeedModels)); - binding.posts.endSelection(); - return true; - } - requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); + // if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { + DownloadUtils.download(context, ImmutableList.copyOf(TopicPostsFragment.this.selectedFeedModels)); + binding.posts.endSelection(); + return true; + // } + // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); } return false; } @@ -132,13 +128,13 @@ public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.O public void onDownloadClick(final Media feedModel, final int childPosition) { final Context context = getContext(); if (context == null) return; - if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { - DownloadUtils.showDownloadDialog(context, feedModel, childPosition); - return; - } - downloadFeedModel = feedModel; - downloadChildPosition = -1; - requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); + // if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { + DownloadUtils.showDownloadDialog(context, feedModel, childPosition); + // return; + // } + // downloadFeedModel = feedModel; + // downloadChildPosition = -1; + // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); } @Override diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java index a6b1cb4c..ac3d5078 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java @@ -30,7 +30,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.LiveData; import androidx.lifecycle.MediatorLiveData; @@ -1337,13 +1336,13 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); return; } - if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) { - DownloadUtils.download(context, media); - Toast.makeText(context, R.string.downloader_downloading_media, Toast.LENGTH_SHORT).show(); - return; - } - tempMedia = media; - requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); + // if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) { + DownloadUtils.download(context, media); + Toast.makeText(context, R.string.downloader_downloading_media, Toast.LENGTH_SHORT).show(); + // return; + // } + // tempMedia = media; + // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); } @NonNull diff --git a/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java index bfc1dee3..e5567ef2 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java @@ -19,7 +19,6 @@ import androidx.activity.OnBackPressedDispatcher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.coordinatorlayout.widget.CoordinatorLayout; -import androidx.core.content.PermissionChecker; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.navigation.NavController; @@ -55,9 +54,6 @@ import awais.instagrabber.viewmodels.FeedStoriesViewModel; import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.StoriesService; -import static androidx.core.content.PermissionChecker.checkSelfPermission; -import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; - public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "FeedFragment"; private static final int STORAGE_PERM_REQUEST_CODE = 8020; @@ -123,13 +119,13 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre public void onDownloadClick(final Media feedModel, final int childPosition) { final Context context = getContext(); if (context == null) return; - if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { - DownloadUtils.showDownloadDialog(context, feedModel, childPosition); - return; - } - downloadFeedModel = feedModel; - downloadChildPosition = childPosition; - requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); + // if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { + DownloadUtils.showDownloadDialog(context, feedModel, childPosition); + // return; + // } + // downloadFeedModel = feedModel; + // downloadChildPosition = childPosition; + // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); } @Override @@ -204,13 +200,13 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre if (FeedFragment.this.selectedFeedModels == null) return false; final Context context = getContext(); if (context == null) return false; - if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { - DownloadUtils.download(context, ImmutableList.copyOf(FeedFragment.this.selectedFeedModels)); - binding.feedRecyclerView.endSelection(); - return true; - } - requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); + // if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { + DownloadUtils.download(context, ImmutableList.copyOf(FeedFragment.this.selectedFeedModels)); + binding.feedRecyclerView.endSelection(); return true; + // } + // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); + // return true; } return false; } diff --git a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java index cc2ce13a..3da981d0 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java @@ -28,7 +28,6 @@ import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.content.res.AppCompatResources; import androidx.coordinatorlayout.widget.CoordinatorLayout; -import androidx.core.content.PermissionChecker; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; @@ -98,9 +97,7 @@ import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.StoriesService; import awais.instagrabber.webservices.UserService; -import static androidx.core.content.PermissionChecker.checkSelfPermission; import static awais.instagrabber.fragments.HashTagFragment.ARG_HASHTAG; -import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "ProfileFragment"; @@ -164,13 +161,13 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe if (ProfileFragment.this.selectedFeedModels == null) return false; final Context context = getContext(); if (context == null) return false; - if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { - DownloadUtils.download(context, ImmutableList.copyOf(ProfileFragment.this.selectedFeedModels)); - binding.postsRecyclerView.endSelection(); - return true; - } - requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); + // if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { + DownloadUtils.download(context, ImmutableList.copyOf(ProfileFragment.this.selectedFeedModels)); + binding.postsRecyclerView.endSelection(); return true; + // } + // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); + // return true; } return false; } @@ -200,13 +197,13 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe public void onDownloadClick(final Media feedModel, final int childPosition) { final Context context = getContext(); if (context == null) return; - if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { - DownloadUtils.showDownloadDialog(context, feedModel, childPosition); - return; - } - downloadFeedModel = feedModel; - downloadChildPosition = childPosition; - requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); + // if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { + DownloadUtils.showDownloadDialog(context, feedModel, childPosition); + // return; + // } + // downloadFeedModel = feedModel; + // downloadChildPosition = childPosition; + // requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); } @Override diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java index 8a2a03b3..254d3479 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java @@ -1,13 +1,11 @@ package awais.instagrabber.fragments.settings; import android.content.Context; -import android.content.Intent; import android.net.Uri; import android.util.Log; import android.view.View; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.appcompat.widget.AppCompatButton; import androidx.appcompat.widget.AppCompatTextView; import androidx.documentfile.provider.DocumentFile; @@ -21,15 +19,14 @@ import com.google.android.material.switchmaterial.SwitchMaterial; import awais.instagrabber.R; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.TextUtils; +import awais.instagrabber.utils.Utils; -import static android.app.Activity.RESULT_OK; import static awais.instagrabber.utils.Constants.FOLDER_PATH; import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; import static awais.instagrabber.utils.Utils.settingsHelper; public class DownloadsPreferencesFragment extends BasePreferencesFragment { private static final String TAG = DownloadsPreferencesFragment.class.getSimpleName(); - private static final int SELECT_DIR_REQUEST_CODE = 1; private SaveToCustomFolderPreference.ResultCallback resultCallback; @Override @@ -37,7 +34,7 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment { final Context context = getContext(); if (context == null) return; screen.addPreference(getDownloadUserFolderPreference(context)); - screen.addPreference(getSaveToCustomFolderPreference(context)); + // screen.addPreference(getSaveToCustomFolderPreference(context)); screen.addPreference(getPrependUsernameToFilenamePreference(context)); } @@ -49,41 +46,63 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment { return preference; } - private Preference getSaveToCustomFolderPreference(@NonNull final Context context) { - return new SaveToCustomFolderPreference(context, (resultCallback) -> { - // Choose a directory using the system's file picker. - final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - startActivityForResult(intent, SELECT_DIR_REQUEST_CODE); - this.resultCallback = resultCallback; - - // new DirectoryChooser() - // .setInitialDirectory(settingsHelper.getString(FOLDER_PATH)) - // .setInteractionListener(file -> { - // settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath()); - // resultCallback.onResult(file.getAbsolutePath()); - // }) - // .show(getParentFragmentManager(), null); - }); - } - - @Override - public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { - if (data == null || data.getData() == null) return; - if (resultCode != RESULT_OK || requestCode != SELECT_DIR_REQUEST_CODE) return; - final Context context = getContext(); - if (context == null) return; - final Uri dirUri = data.getData(); - Log.d(TAG, "onActivityResult: " + dirUri); - final int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - context.getContentResolver().takePersistableUriPermission(dirUri, takeFlags); - final DocumentFile root = DocumentFile.fromTreeUri(context, dirUri); - settingsHelper.putString(FOLDER_PATH, data.getData().toString()); - if (resultCallback != null) { - resultCallback.onResult(root.getName()); - resultCallback = null; - } - // Log.d(TAG, "onActivityResult: " + root); - } + // private Preference getSaveToCustomFolderPreference(@NonNull final Context context) { + // return new SaveToCustomFolderPreference(context, checked -> { + // try { + // DownloadUtils.init(context); + // } catch (DownloadUtils.ReselectDocumentTreeException e) { + // if (!checked) return; + // startDocumentSelector(e.getInitialUri()); + // } catch (Exception e) { + // Log.e(TAG, "getSaveToCustomFolderPreference: ", e); + // } + // }, (resultCallback) -> { + // // Choose a directory using the system's file picker. + // startDocumentSelector(null); + // this.resultCallback = resultCallback; + // + // // new DirectoryChooser() + // // .setInitialDirectory(settingsHelper.getString(FOLDER_PATH)) + // // .setInteractionListener(file -> { + // // settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath()); + // // resultCallback.onResult(file.getAbsolutePath()); + // // }) + // // .show(getParentFragmentManager(), null); + // }); + // } + + // private void startDocumentSelector(final Uri initialUri) { + // final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && initialUri != null) { + // intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initialUri); + // } + // startActivityForResult(intent, SELECT_DIR_REQUEST_CODE); + // } + + // @Override + // public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { + // if (requestCode != SELECT_DIR_REQUEST_CODE) return; + // final Context context = getContext(); + // if (context == null) return; + // if (resultCode != RESULT_OK) { + // try { + // DownloadUtils.init(context, true); + // } catch (Exception ignored) {} + // return; + // } + // if (data == null || data.getData() == null) return; + // Utils.setupSelectedDir(context, data); + // if (resultCallback != null) { + // try { + // final DocumentFile root = DocumentFile.fromTreeUri(context, data.getData()); + // resultCallback.onResult(Utils.getDocumentFileRealPath(context, root).getAbsolutePath()); + // } catch (Exception e) { + // Log.e(TAG, "onActivityResult: ", e); + // } + // resultCallback = null; + // } + // // Log.d(TAG, "onActivityResult: " + root); + // } private Preference getPrependUsernameToFilenamePreference(@NonNull final Context context) { final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context); @@ -95,11 +114,15 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment { public static class SaveToCustomFolderPreference extends Preference { private AppCompatTextView customPathTextView; + private final OnSaveToChangeListener onSaveToChangeListener; private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener; private final String key; - public SaveToCustomFolderPreference(final Context context, final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener) { + public SaveToCustomFolderPreference(final Context context, + final OnSaveToChangeListener onSaveToChangeListener, + final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener) { super(context); + this.onSaveToChangeListener = onSaveToChangeListener; this.onSelectFolderButtonClickListener = onSelectFolderButtonClickListener; key = FOLDER_SAVE_TO; setLayoutResource(R.layout.pref_custom_folder); @@ -117,8 +140,21 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment { cbSaveTo.setOnCheckedChangeListener((buttonView, isChecked) -> { settingsHelper.putBoolean(FOLDER_SAVE_TO, isChecked); buttonContainer.setVisibility(isChecked ? View.VISIBLE : View.GONE); - final String customPath = settingsHelper.getString(FOLDER_PATH); + final Context context = getContext(); + String customPath = settingsHelper.getString(FOLDER_PATH); + if (!TextUtils.isEmpty(customPath) && customPath.startsWith("content") && context != null) { + final Uri uri = Uri.parse(customPath); + final DocumentFile documentFile = DocumentFile.fromSingleUri(context, uri); + try { + customPath = Utils.getDocumentFileRealPath(context, documentFile).getAbsolutePath(); + } catch (Exception e) { + Log.e(TAG, "onBindViewHolder: ", e); + } + } customPathTextView.setText(customPath); + if (onSaveToChangeListener != null) { + onSaveToChangeListener.onChange(isChecked); + } }); final boolean savedToEnabled = settingsHelper.getBoolean(key); holder.itemView.setOnClickListener(v -> cbSaveTo.toggle()); @@ -141,5 +177,9 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment { public interface OnSelectFolderButtonClickListener { void onClick(ResultCallback resultCallback); } + + public interface OnSaveToChangeListener { + void onChange(boolean checked); + } } } diff --git a/app/src/main/java/awais/instagrabber/utils/Constants.java b/app/src/main/java/awais/instagrabber/utils/Constants.java index a2a5fd13..69f63f23 100644 --- a/app/src/main/java/awais/instagrabber/utils/Constants.java +++ b/app/src/main/java/awais/instagrabber/utils/Constants.java @@ -70,15 +70,15 @@ public final class Constants { public static final int DM_CHECK_NOTIFICATION_ID = 11; // see https://github.com/dilame/instagram-private-api/blob/master/src/core/constants.ts -// public static final String SUPPORTED_CAPABILITIES = "[ { \"name\": \"SUPPORTED_SDK_VERSIONS\", \"value\":" + -// " \"13.0,14.0,15.0,16.0,17.0,18.0,19.0,20.0,21.0,22.0,23.0,24.0,25.0,26.0,27.0,28.0,29.0,30.0,31.0," + -// "32.0,33.0,34.0,35.0,36.0,37.0,38.0,39.0,40.0,41.0,42.0,43.0,44.0,45.0,46.0,47.0,48.0,49.0,50.0,51.0," + -// "52.0,53.0,54.0,55.0,56.0,57.0,58.0,59.0,60.0,61.0,62.0,63.0,64.0,65.0,66.0\" }, { \"name\": \"FACE_TRACKER_VERSION\", " + -// "\"value\": 12 }, { \"name\": \"segmentation\", \"value\": \"segmentation_enabled\" }, { \"name\": \"COMPRESSION\", " + -// "\"value\": \"ETC2_COMPRESSION\" }, { \"name\": \"world_tracker\", \"value\": \"world_tracker_enabled\" }, { \"name\": " + -// "\"gyroscope\", \"value\": \"gyroscope_enabled\" } ]"; -// public static final String SIGNATURE_VERSION = "4"; -// public static final String SIGNATURE_KEY = "9193488027538fd3450b83b7d05286d4ca9599a0f7eeed90d8c85925698a05dc"; + // public static final String SUPPORTED_CAPABILITIES = "[ { \"name\": \"SUPPORTED_SDK_VERSIONS\", \"value\":" + + // " \"13.0,14.0,15.0,16.0,17.0,18.0,19.0,20.0,21.0,22.0,23.0,24.0,25.0,26.0,27.0,28.0,29.0,30.0,31.0," + + // "32.0,33.0,34.0,35.0,36.0,37.0,38.0,39.0,40.0,41.0,42.0,43.0,44.0,45.0,46.0,47.0,48.0,49.0,50.0,51.0," + + // "52.0,53.0,54.0,55.0,56.0,57.0,58.0,59.0,60.0,61.0,62.0,63.0,64.0,65.0,66.0\" }, { \"name\": \"FACE_TRACKER_VERSION\", " + + // "\"value\": 12 }, { \"name\": \"segmentation\", \"value\": \"segmentation_enabled\" }, { \"name\": \"COMPRESSION\", " + + // "\"value\": \"ETC2_COMPRESSION\" }, { \"name\": \"world_tracker\", \"value\": \"world_tracker_enabled\" }, { \"name\": " + + // "\"gyroscope\", \"value\": \"gyroscope_enabled\" } ]"; + // public static final String SIGNATURE_VERSION = "4"; + // public static final String SIGNATURE_KEY = "9193488027538fd3450b83b7d05286d4ca9599a0f7eeed90d8c85925698a05dc"; public static final String BREADCRUMB_KEY = "iN4$aGr0m"; public static final int LOGIN_RESULT_CODE = 5000; public static final String SKIPPED_VERSION = "skipped_version"; @@ -123,4 +123,6 @@ public final class Constants { public static final String DM_THREAD_ACTION_EXTRA_THREAD_TITLE = "thread_title"; public static final String X_IG_APP_ID = "936619743392459"; + + public static final String EXTRA_INITIAL_URI = "initial_uri"; } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/DirectoryChooser.java b/app/src/main/java/awais/instagrabber/utils/DirectoryChooser.java index d349e490..1ea880a5 100755 --- a/app/src/main/java/awais/instagrabber/utils/DirectoryChooser.java +++ b/app/src/main/java/awais/instagrabber/utils/DirectoryChooser.java @@ -3,7 +3,6 @@ package awais.instagrabber.utils; import android.app.Activity; import android.app.Dialog; import android.content.Context; -import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Environment; @@ -11,19 +10,14 @@ import android.os.FileObserver; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; -import com.google.android.material.snackbar.BaseTransientBottomBar; -import com.google.android.material.snackbar.Snackbar; - import java.io.File; import java.util.ArrayList; import java.util.Collections; @@ -95,15 +89,15 @@ public final class DirectoryChooser extends DialogFragment { if (context == null) context = getContext(); if (context == null) context = getActivity(); if (context == null) return; - if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) != PackageManager.PERMISSION_GRANTED) { - final String text = "Storage permissions denied!"; - if (container == null) { - Toast.makeText(context, text, Toast.LENGTH_LONG).show(); - } else { - Snackbar.make(container, text, BaseTransientBottomBar.LENGTH_LONG).show(); - } - dismiss(); - } + // if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) != PackageManager.PERMISSION_GRANTED) { + // final String text = "Storage permissions denied!"; + // if (container == null) { + // Toast.makeText(context, text, Toast.LENGTH_LONG).show(); + // } else { + // Snackbar.make(container, text, BaseTransientBottomBar.LENGTH_LONG).show(); + // } + // dismiss(); + // } final View.OnClickListener clickListener = v -> { if (v == binding.btnConfirm) { if (interactionListener != null && isValidFile(selectedDir)) diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java index df8fef91..88be98db 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java @@ -1,9 +1,9 @@ package awais.instagrabber.utils; -import android.Manifest; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; +import android.content.UriPermission; import android.net.Uri; import android.provider.DocumentsContract; import android.util.Log; @@ -49,32 +49,65 @@ import static awais.instagrabber.utils.Constants.FOLDER_PATH; public final class DownloadUtils { private static final String TAG = DownloadUtils.class.getSimpleName(); - - public static final String WRITE_PERMISSION = Manifest.permission.WRITE_EXTERNAL_STORAGE; - public static final String[] PERMS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; - private static final String DIR_BARINSTA = "Barinsta"; + // private static final String DIR_BARINSTA = "Barinsta"; private static final String DIR_DOWNLOADS = "Downloads"; private static final String DIR_CAMERA = "Camera"; private static final String DIR_EDIT = "Edit"; - private static final String TEMP_DIR = "Temp"; + private static final String DIR_RECORDINGS = "Recordings"; + private static final String DIR_TEMP = "Temp"; + private static final String DIR_BACKUPS = "Backups"; + + // public static final String WRITE_PERMISSION = Manifest.permission.WRITE_EXTERNAL_STORAGE; + // public static final String[] PERMS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; private static DocumentFile root; + // private static DocumentFile DOWNLOADS_DIR_FILE; - public static void init(@NonNull final Context context) { - // if (!Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) return; + public static void init(@NonNull final Context context) throws ReselectDocumentTreeException { + // if (DOWNLOADS_DIR_FILE == null) { + // final Uri uri = Utils.getSafUris(context, new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS))[0]; + // DOWNLOADS_DIR_FILE = DocumentFile.fromTreeUri(context, uri); + // } + // if (!Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) { + // root = DOWNLOADS_DIR_FILE; // DocumentFile.fromFile(DOWNLOADS_DIR_FILE); + // return; + // } final String customPath = Utils.settingsHelper.getString(FOLDER_PATH); - if (TextUtils.isEmpty(customPath)) return; - // dir = new File(customPath); - root = DocumentFile.fromTreeUri(context, Uri.parse(customPath)); + if (TextUtils.isEmpty(customPath)) { + throw new ReselectDocumentTreeException(); + // root = DOWNLOADS_DIR_FILE; // DocumentFile.fromFile(DOWNLOADS_DIR_FILE); + // return; + } + if (!customPath.startsWith("content")) { + // if (customPath.equals(DOWNLOADS_DIR_FILE.getAbsolutePath())) { + // throw new ReselectDocumentTreeException(); + // } + // reselect the folder in selector view + throw new ReselectDocumentTreeException(Uri.parse(customPath)); + } + final Uri uri = Uri.parse(customPath); + final List existingPermissions = context.getContentResolver().getPersistedUriPermissions(); + if (existingPermissions.isEmpty()) { + // reselect the folder in selector view + throw new ReselectDocumentTreeException(uri); + } + final boolean anyMatch = existingPermissions.stream().anyMatch(uriPermission -> uriPermission.getUri().equals(uri)); + if (!anyMatch) { + // reselect the folder in selector view + throw new ReselectDocumentTreeException(uri); + } + root = DocumentFile.fromTreeUri(context, uri); Log.d(TAG, "init: " + root); // final File parent = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); // final DocumentFile documentFile = DocumentFile.fromFile(parent); // Log.d(TAG, "init: " + documentFile); } + @Nullable public static DocumentFile getDownloadDir(final String... dirs) { - // final File parent = new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS); - // File subDir = new File(parent, DIR_BARINSTA); + if (root == null) { + return null; + } DocumentFile subDir = root; if (dirs != null) { for (final String dir : dirs) { @@ -87,7 +120,7 @@ public final class DownloadUtils { return subDir; } - @NonNull + @Nullable public static DocumentFile getDownloadDir() { // final File parent = new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS); // final File dir = new File(new File(parent, "barinsta"), "downloads"); @@ -106,14 +139,26 @@ public final class DownloadUtils { return getDownloadDir(DIR_DOWNLOADS); } + @Nullable public static DocumentFile getCameraDir() { return getDownloadDir(DIR_CAMERA); } + @Nullable public static DocumentFile getImageEditDir(final String sessionId) { return getDownloadDir(DIR_EDIT, sessionId); } + @Nullable + public static DocumentFile getRecordingsDir() { + return getDownloadDir(DIR_RECORDINGS); + } + + @Nullable + public static DocumentFile getBackupsDir() { + return getDownloadDir(DIR_BACKUPS); + } + // @Nullable // private static DocumentFile getDownloadDir(@NonNull final Context context, @Nullable final String username) { // return getDownloadDir(context, username, false); @@ -156,9 +201,9 @@ public final class DownloadUtils { } private static DocumentFile getTempDir() { - DocumentFile file = root.findFile(TEMP_DIR); + DocumentFile file = root.findFile(DIR_TEMP); if (file == null) { - file = root.createDirectory(TEMP_DIR); + file = root.createDirectory(DIR_TEMP); } return file; } @@ -202,11 +247,11 @@ public final class DownloadUtils { return getDownloadSavePaths(paths, postId, "", displayUrl, username); } - private static Pair, String> getDownloadChildSaveFile(final List paths, - final String postId, - final int childPosition, - final String url, - final String username) { + private static Pair, String> getDownloadChildSavePaths(final List paths, + final String postId, + final int childPosition, + final String url, + final String username) { final String sliderPostfix = "_slide_" + childPosition; return getDownloadSavePaths(paths, postId, sliderPostfix, url, username); } @@ -231,9 +276,9 @@ public final class DownloadUtils { return new Pair<>(paths, mimeType); } - public static DocumentFile getTempFile() { - return getTempFile(null, null); - } + // public static DocumentFile getTempFile() { + // return getTempFile(null, null); + // } public static DocumentFile getTempFile(final String fileName, final String extension) { final DocumentFile dir = getTempDir(); @@ -317,8 +362,8 @@ public final class DownloadUtils { final Media child = sliderItems.get(i); if (child == null) continue; final String url = ResponseBodyUtils.getImageUrl(child); - final Pair, String> file = getDownloadChildSaveFile(userFolderPaths, media.getCode(), i + 1, url, ""); - final Pair, String> usernameFile = getDownloadChildSaveFile(userFolderPaths, media.getCode(), i + 1, url, username); + final Pair, String> file = getDownloadChildSavePaths(userFolderPaths, media.getCode(), i + 1, url, ""); + final Pair, String> usernameFile = getDownloadChildSavePaths(userFolderPaths, media.getCode(), i + 1, url, username); checkList.add(checkPathExists(context, file.first) || checkPathExists(context, usernameFile.first)); } break; @@ -329,6 +374,7 @@ public final class DownloadUtils { private static boolean checkPathExists(@NonNull final Context context, @NonNull final List paths) { + if (root == null) return false; final String joined = android.text.TextUtils.join("/", paths); final Uri userFolderUri = DocumentsContract.buildDocumentUriUsingTree(root.getUri(), joined); final DocumentFile userFolder = DocumentFile.fromSingleUri(context, userFolderUri); @@ -455,8 +501,8 @@ public final class DownloadUtils { final String usernamePrepend = Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) && mediaUser != null ? mediaUser.getUsername() : ""; - final Pair, String> pair = getDownloadChildSaveFile(userFolderPaths, media.getCode(), i + 1, url, - usernamePrepend); + final Pair, String> pair = getDownloadChildSavePaths(userFolderPaths, media.getCode(), i + 1, url, + usernamePrepend); final DocumentFile file = createFile(pair); if (file == null) continue; map.put(url, file); @@ -469,7 +515,9 @@ public final class DownloadUtils { download(context, map); } + @Nullable private static DocumentFile createFile(@NonNull final Pair, String> pair) { + if (root == null) return null; if (pair.first == null || pair.second == null) return null; DocumentFile dir = root; final List first = pair.first; @@ -558,4 +606,21 @@ public final class DownloadUtils { WorkManager.getInstance(context) .enqueue(downloadWorkRequest); } + + + public static class ReselectDocumentTreeException extends Exception { + private final Uri initialUri; + + public ReselectDocumentTreeException() { + initialUri = null; + } + + public ReselectDocumentTreeException(final Uri initialUri) { + this.initialUri = initialUri; + } + + public Uri getInitialUri() { + return initialUri; + } + } } diff --git a/app/src/main/java/awais/instagrabber/utils/Utils.java b/app/src/main/java/awais/instagrabber/utils/Utils.java index b7c8e429..e9832715 100644 --- a/app/src/main/java/awais/instagrabber/utils/Utils.java +++ b/app/src/main/java/awais/instagrabber/utils/Utils.java @@ -36,6 +36,7 @@ import android.widget.Toast; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; @@ -70,6 +71,8 @@ import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.Tab; import awais.instagrabber.models.enums.FavoriteType; +import static awais.instagrabber.utils.Constants.FOLDER_PATH; + public final class Utils { private static final String TAG = "Utils"; private static final int VIDEO_CACHE_MAX_BYTES = 10 * 1024 * 1024; @@ -520,7 +523,8 @@ public final class Utils { callback); } - private static File getDocumentFileRealPath(Context context, DocumentFile documentFile) + public static File getDocumentFileRealPath(@NonNull final Context context, + @NonNull final DocumentFile documentFile) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { final String docId = DocumentsContract.getDocumentId(documentFile.getUri()); final String[] split = docId.split(":"); @@ -530,18 +534,19 @@ public final class Utils { return new File(Environment.getExternalStorageDirectory(), split[1]); } else { if (volumes == null) { - StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); - Method getVolumeListMethod = sm.getClass().getMethod("getVolumeList"); + final StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); + if (sm == null) return null; + final Method getVolumeListMethod = sm.getClass().getMethod("getVolumeList"); volumes = (Object[]) getVolumeListMethod.invoke(sm); } - + if (volumes == null) return null; for (Object volume : volumes) { - Method getUuidMethod = volume.getClass().getMethod("getUuid"); - String uuid = (String) getUuidMethod.invoke(volume); + final Method getUuidMethod = volume.getClass().getMethod("getUuid"); + final String uuid = (String) getUuidMethod.invoke(volume); if (uuid != null && uuid.equalsIgnoreCase(type)) { - Method getPathMethod = volume.getClass().getMethod("getPath"); - String path = (String) getPathMethod.invoke(volume); + final Method getPathMethod = volume.getClass().getMethod("getPath"); + final String path = (String) getPathMethod.invoke(volume); return new File(path, split[1]); } } @@ -549,4 +554,72 @@ public final class Utils { return null; } + + public static void setupSelectedDir(@NonNull final Context context, + @NonNull final Intent intent) throws DownloadUtils.ReselectDocumentTreeException { + final Uri dirUri = intent.getData(); + Log.d(TAG, "onActivityResult: " + dirUri); + if (dirUri == null) return; + final int takeFlags = intent.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + context.getContentResolver().takePersistableUriPermission(dirUri, takeFlags); + settingsHelper.putString(FOLDER_PATH, dirUri.toString()); + // re-init DownloadUtils + DownloadUtils.init(context); + } + + /** + * Ing.N.Nyerges 2019 V2.0 + *

+ * Storage Access Framework(SAF) Uri's creator from File (java.IO), + * for removable external storages + * + * @param context Application Context + * @param file File path + file name + * @return Uri[]: + * uri[0] = SAF TREE Uri + * uri[1] = SAF DOCUMENT Uri + */ + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public static Uri[] getSafUris(Context context, File file) { + + Uri[] uri = new Uri[2]; + String scheme = "content"; + String authority = "com.android.externalstorage.documents"; + + // Separate each element of the File path + // File format: "/storage/XXXX-XXXX/sub-folder1/sub-folder2..../filename" + // (XXXX-XXXX is external removable number + String[] ele = file.getPath().split(File.separator); + // ele[0] = not used (empty) + // ele[1] = not used (storage name) + // ele[2] = storage number + // ele[3 to (n-1)] = folders + // ele[n] = file name + + // Construct folders strings using SAF format + StringBuilder folders = new StringBuilder(); + if (ele.length > 4) { + folders.append(ele[3]); + for (int i = 4; i < ele.length - 1; ++i) folders.append("%2F").append(ele[i]); + } + + String common = ele[2] + "%3A" + folders.toString(); + + // Construct TREE Uri + Uri.Builder builder = new Uri.Builder(); + builder.scheme(scheme); + builder.authority(authority); + builder.encodedPath("/tree/" + common); + uri[0] = builder.build(); + + // Construct DOCUMENT Uri + builder = new Uri.Builder(); + builder.scheme(scheme); + builder.authority(authority); + if (ele.length > 4) common = common + "%2F"; + builder.encodedPath("/document/" + common + file.getName()); + uri[1] = builder.build(); + + return uri; + } } diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java index 440d4765..fd23f755 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java @@ -72,7 +72,7 @@ public class DirectThreadViewModel extends AndroidViewModel { throw new IllegalArgumentException("User is not logged in!"); } contentResolver = application.getContentResolver(); - recordingsDir = DownloadUtils.getDownloadDir("Recordings"); + recordingsDir = DownloadUtils.getRecordingsDir(); final DirectMessagesManager messagesManager = DirectMessagesManager.getInstance(); threadManager = messagesManager.getThreadManager(threadId, pending, currentUser, contentResolver); threadManager.fetchPendingRequests(); diff --git a/app/src/main/res/layout/activity_directory_select.xml b/app/src/main/res/layout/activity_directory_select.xml new file mode 100644 index 00000000..01f81a9f --- /dev/null +++ b/app/src/main/res/layout/activity_directory_select.xml @@ -0,0 +1,39 @@ + + + + + + + + + + \ No newline at end of file From 8030c7f22066fbdd118a6cf86aa5d71a05bb514f Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Mon, 5 Apr 2021 21:01:33 +0900 Subject: [PATCH 05/17] Fix downloaded check logic --- .../viewholder/FeedGridItemViewHolder.java | 2 +- .../asyncs/DownloadedCheckerAsyncTask.java | 11 +----- .../instagrabber/utils/DownloadUtils.java | 37 +++++++++++-------- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java index 3f71c624..c6e1d571 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java @@ -102,7 +102,7 @@ public class FeedGridItemViewHolder extends RecyclerView.ViewHolder { binding.typeIcon.setVisibility(View.VISIBLE); binding.typeIcon.setImageResource(typeIconRes); } - final DownloadedCheckerAsyncTask task = new DownloadedCheckerAsyncTask(itemView.getContext(), result -> { + final DownloadedCheckerAsyncTask task = new DownloadedCheckerAsyncTask(result -> { final List checkList = result.get(media.getPk()); if (checkList == null || checkList.isEmpty()) { return; diff --git a/app/src/main/java/awais/instagrabber/asyncs/DownloadedCheckerAsyncTask.java b/app/src/main/java/awais/instagrabber/asyncs/DownloadedCheckerAsyncTask.java index fd4ad6df..605bed3f 100644 --- a/app/src/main/java/awais/instagrabber/asyncs/DownloadedCheckerAsyncTask.java +++ b/app/src/main/java/awais/instagrabber/asyncs/DownloadedCheckerAsyncTask.java @@ -1,9 +1,7 @@ package awais.instagrabber.asyncs; -import android.content.Context; import android.os.AsyncTask; -import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -14,12 +12,9 @@ import awais.instagrabber.utils.DownloadUtils; public final class DownloadedCheckerAsyncTask extends AsyncTask>> { private static final String TAG = "DownloadedCheckerAsyncTask"; - private final WeakReference context; private final OnCheckResultListener listener; - public DownloadedCheckerAsyncTask(final Context context, - final OnCheckResultListener listener) { - this.context = new WeakReference<>(context); + public DownloadedCheckerAsyncTask(final OnCheckResultListener listener) { this.listener = listener; } @@ -30,9 +25,7 @@ public final class DownloadedCheckerAsyncTask extends AsyncTask> map = new HashMap<>(); for (final Media media : feedModels) { - final Context context = this.context.get(); - if (context == null) return map; - map.put(media.getPk(), DownloadUtils.checkDownloaded(context, media)); + map.put(media.getPk(), DownloadUtils.checkDownloaded(media)); } return map; } diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java index 88be98db..e9776e4f 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java @@ -5,7 +5,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.UriPermission; import android.net.Uri; -import android.provider.DocumentsContract; import android.util.Log; import android.webkit.MimeTypeMap; import android.widget.Toast; @@ -338,8 +337,7 @@ public final class DownloadUtils { return ""; } - public static List checkDownloaded(@NonNull final Context context, - @NonNull final Media media) { + public static List checkDownloaded(@NonNull final Media media) { final List checkList = new LinkedList<>(); final User user = media.getUser(); String username = "username"; @@ -351,9 +349,10 @@ public final class DownloadUtils { case MEDIA_TYPE_IMAGE: case MEDIA_TYPE_VIDEO: { final String url = ResponseBodyUtils.getImageUrl(media); - final Pair, String> file = getDownloadSavePaths(userFolderPaths, media.getCode(), url, ""); - final Pair, String> usernameFile = getDownloadSavePaths(userFolderPaths, media.getCode(), url, username); - checkList.add(checkPathExists(context, file.first) || checkPathExists(context, usernameFile.first)); + final Pair, String> file = getDownloadSavePaths(new ArrayList<>(userFolderPaths), media.getCode(), url, ""); + final Pair, String> usernameFile = getDownloadSavePaths(new ArrayList<>(userFolderPaths), media.getCode(), url, + username); + checkList.add(checkPathExists(file.first) || checkPathExists(usernameFile.first)); break; } case MEDIA_TYPE_SLIDER: @@ -362,9 +361,11 @@ public final class DownloadUtils { final Media child = sliderItems.get(i); if (child == null) continue; final String url = ResponseBodyUtils.getImageUrl(child); - final Pair, String> file = getDownloadChildSavePaths(userFolderPaths, media.getCode(), i + 1, url, ""); - final Pair, String> usernameFile = getDownloadChildSavePaths(userFolderPaths, media.getCode(), i + 1, url, username); - checkList.add(checkPathExists(context, file.first) || checkPathExists(context, usernameFile.first)); + final Pair, String> file = getDownloadChildSavePaths(new ArrayList<>(userFolderPaths), media.getCode(), i + 1, url, + ""); + final Pair, String> usernameFile = getDownloadChildSavePaths(new ArrayList<>(userFolderPaths), media.getCode(), + i + 1, url, username); + checkList.add(checkPathExists(file.first) || checkPathExists(usernameFile.first)); } break; default: @@ -372,13 +373,16 @@ public final class DownloadUtils { return checkList; } - private static boolean checkPathExists(@NonNull final Context context, - @NonNull final List paths) { + private static boolean checkPathExists(@NonNull final List paths) { if (root == null) return false; - final String joined = android.text.TextUtils.join("/", paths); - final Uri userFolderUri = DocumentsContract.buildDocumentUriUsingTree(root.getUri(), joined); - final DocumentFile userFolder = DocumentFile.fromSingleUri(context, userFolderUri); - return userFolder != null && userFolder.exists(); + DocumentFile dir = root; + for (final String path : paths) { + dir = dir.findFile(path); + if (dir == null || !dir.exists()) { + return false; + } + } + return true; } public static void showDownloadDialog(@NonNull Context context, @@ -501,7 +505,8 @@ public final class DownloadUtils { final String usernamePrepend = Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) && mediaUser != null ? mediaUser.getUsername() : ""; - final Pair, String> pair = getDownloadChildSavePaths(userFolderPaths, media.getCode(), i + 1, url, + final Pair, String> pair = getDownloadChildSavePaths(new ArrayList<>(userFolderPaths), media.getCode(), i + 1, + url, usernamePrepend); final DocumentFile file = createFile(pair); if (file == null) continue; From bca38a2645da761ed4086c9de192d7bea13bedf4 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Tue, 6 Apr 2021 00:51:01 +0900 Subject: [PATCH 06/17] Fix audio recording in dm --- app/src/main/AndroidManifest.xml | 1 - .../MediaPickerBottomDialogFragment.java | 17 +- .../instagrabber/utils/DownloadUtils.java | 9 +- .../instagrabber/utils/MediaController.java | 270 +++++++++--------- .../awais/instagrabber/utils/MediaUtils.java | 38 ++- .../instagrabber/utils/PermissionUtils.java | 23 +- .../java/awais/instagrabber/utils/Utils.java | 8 +- .../instagrabber/utils/VoiceRecorder.java | 16 +- .../viewmodels/DirectThreadViewModel.java | 6 +- 9 files changed, 198 insertions(+), 190 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 48326842..e2e15e36 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,7 +20,6 @@ android:fullBackupContent="@xml/backup_descriptor" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" - android:requestLegacyExternalStorage="true" android:supportsRtl="true" android:theme="@style/AppTheme.Launcher" tools:ignore="UnusedAttribute"> diff --git a/app/src/main/java/awais/instagrabber/dialogs/MediaPickerBottomDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/MediaPickerBottomDialogFragment.java index d36cb9dd..15474032 100644 --- a/app/src/main/java/awais/instagrabber/dialogs/MediaPickerBottomDialogFragment.java +++ b/app/src/main/java/awais/instagrabber/dialogs/MediaPickerBottomDialogFragment.java @@ -26,7 +26,6 @@ import awais.instagrabber.R; import awais.instagrabber.adapters.MediaItemsAdapter; import awais.instagrabber.databinding.LayoutMediaPickerBinding; import awais.instagrabber.utils.MediaController; -import awais.instagrabber.utils.PermissionUtils; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.viewmodels.MediaPickerViewModel; @@ -121,10 +120,10 @@ public class MediaPickerBottomDialogFragment extends BottomSheetDialogFragment { if (requestCode == ATTACH_MEDIA_REQUEST_CODE) { final Context context = getContext(); if (context == null) return; - final boolean hasAttachMediaPerms = PermissionUtils.hasAttachMediaPerms(context); - if (hasAttachMediaPerms) { - viewModel.loadMedia(context); - } + // final boolean hasAttachMediaPerms = PermissionUtils.hasAttachMediaPerms(context); + // if (hasAttachMediaPerms) { + viewModel.loadMedia(context); + // } } } @@ -133,10 +132,10 @@ public class MediaPickerBottomDialogFragment extends BottomSheetDialogFragment { setupAlbumPicker(); final Context context = getContext(); if (context == null) return; - if (!PermissionUtils.hasAttachMediaPerms(context)) { - PermissionUtils.requestAttachMediaPerms(this, ATTACH_MEDIA_REQUEST_CODE); - return; - } + // if (!PermissionUtils.hasAttachMediaPerms(context)) { + // PermissionUtils.requestAttachMediaPerms(this, ATTACH_MEDIA_REQUEST_CODE); + // return; + // } viewModel.loadMedia(context); } diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java index e9776e4f..3432ec3f 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java @@ -52,7 +52,7 @@ public final class DownloadUtils { private static final String DIR_DOWNLOADS = "Downloads"; private static final String DIR_CAMERA = "Camera"; private static final String DIR_EDIT = "Edit"; - private static final String DIR_RECORDINGS = "Recordings"; + private static final String DIR_RECORDINGS = "Sent Recordings"; private static final String DIR_TEMP = "Temp"; private static final String DIR_BACKUPS = "Backups"; @@ -96,7 +96,7 @@ public final class DownloadUtils { throw new ReselectDocumentTreeException(uri); } root = DocumentFile.fromTreeUri(context, uri); - Log.d(TAG, "init: " + root); + // Log.d(TAG, "init: " + root); // final File parent = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); // final DocumentFile documentFile = DocumentFile.fromFile(parent); // Log.d(TAG, "init: " + documentFile); @@ -111,9 +111,8 @@ public final class DownloadUtils { if (dirs != null) { for (final String dir : dirs) { final DocumentFile subDirFile = subDir.findFile(dir); - if (subDirFile == null) { - subDir = subDir.createDirectory(dir); - } + final boolean exists = subDirFile != null && subDirFile.exists(); + subDir = exists ? subDirFile : subDir.createDirectory(dir); } } return subDir; diff --git a/app/src/main/java/awais/instagrabber/utils/MediaController.java b/app/src/main/java/awais/instagrabber/utils/MediaController.java index 237dd85a..9ca8e318 100644 --- a/app/src/main/java/awais/instagrabber/utils/MediaController.java +++ b/app/src/main/java/awais/instagrabber/utils/MediaController.java @@ -87,83 +87,83 @@ public class MediaController { Cursor cursor = null; try { - if (PermissionUtils.hasAttachMediaPerms(context)) { - cursor = MediaStore.Images.Media.query(context.getContentResolver(), - MediaStore.Images.Media.EXTERNAL_CONTENT_URI, - PROJECTION_PHOTOS, - null, - null, - (Build.VERSION.SDK_INT > 28 - ? MediaStore.Images.Media.DATE_TAKEN - : MediaStore.Images.Media.DATE_MODIFIED) + " DESC"); - if (cursor != null) { - int imageIdColumn = cursor.getColumnIndex(MediaStore.Images.Media._ID); - int bucketIdColumn = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID); - int bucketNameColumn = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME); - int dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA); - int dateColumn = cursor.getColumnIndex(Build.VERSION.SDK_INT > 28 ? MediaStore.Images.Media.DATE_TAKEN - : MediaStore.Images.Media.DATE_MODIFIED); - int orientationColumn = cursor.getColumnIndex(MediaStore.Images.Media.ORIENTATION); - int widthColumn = cursor.getColumnIndex(MediaStore.Images.Media.WIDTH); - int heightColumn = cursor.getColumnIndex(MediaStore.Images.Media.HEIGHT); - int sizeColumn = cursor.getColumnIndex(MediaStore.Images.Media.SIZE); - - while (cursor.moveToNext()) { - String path = cursor.getString(dataColumn); - if (TextUtils.isEmpty(path)) { - continue; - } + // if (PermissionUtils.hasAttachMediaPerms(context)) { + cursor = MediaStore.Images.Media.query(context.getContentResolver(), + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + PROJECTION_PHOTOS, + null, + null, + (Build.VERSION.SDK_INT > 28 + ? MediaStore.Images.Media.DATE_TAKEN + : MediaStore.Images.Media.DATE_MODIFIED) + " DESC"); + if (cursor != null) { + int imageIdColumn = cursor.getColumnIndex(MediaStore.Images.Media._ID); + int bucketIdColumn = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID); + int bucketNameColumn = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME); + int dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA); + int dateColumn = cursor.getColumnIndex(Build.VERSION.SDK_INT > 28 ? MediaStore.Images.Media.DATE_TAKEN + : MediaStore.Images.Media.DATE_MODIFIED); + int orientationColumn = cursor.getColumnIndex(MediaStore.Images.Media.ORIENTATION); + int widthColumn = cursor.getColumnIndex(MediaStore.Images.Media.WIDTH); + int heightColumn = cursor.getColumnIndex(MediaStore.Images.Media.HEIGHT); + int sizeColumn = cursor.getColumnIndex(MediaStore.Images.Media.SIZE); + + while (cursor.moveToNext()) { + String path = cursor.getString(dataColumn); + if (TextUtils.isEmpty(path)) { + continue; + } - int imageId = cursor.getInt(imageIdColumn); - int bucketId = cursor.getInt(bucketIdColumn); - String bucketName = cursor.getString(bucketNameColumn); - long dateTaken = cursor.getLong(dateColumn); - int orientation = cursor.getInt(orientationColumn); - int width = cursor.getInt(widthColumn); - int height = cursor.getInt(heightColumn); - long size = cursor.getLong(sizeColumn); + int imageId = cursor.getInt(imageIdColumn); + int bucketId = cursor.getInt(bucketIdColumn); + String bucketName = cursor.getString(bucketNameColumn); + long dateTaken = cursor.getLong(dateColumn); + int orientation = cursor.getInt(orientationColumn); + int width = cursor.getInt(widthColumn); + int height = cursor.getInt(heightColumn); + long size = cursor.getLong(sizeColumn); - MediaEntry mediaEntry = new MediaEntry(bucketId, imageId, dateTaken, path, orientation, -1, false, width, height, size); + MediaEntry mediaEntry = new MediaEntry(bucketId, imageId, dateTaken, path, orientation, -1, false, width, height, size); - if (allPhotosAlbum == null) { - allPhotosAlbum = new AlbumEntry(0, context.getString(R.string.all_photos), mediaEntry); - photoAlbumsSorted.add(0, allPhotosAlbum); - } - if (allMediaAlbum == null) { - allMediaAlbum = new AlbumEntry(0, context.getString(R.string.all_media), mediaEntry); - mediaAlbumsSorted.add(0, allMediaAlbum); - } - allPhotosAlbum.addPhoto(mediaEntry); - allMediaAlbum.addPhoto(mediaEntry); - - AlbumEntry albumEntry = mediaAlbums.get(bucketId); - if (albumEntry == null) { - albumEntry = new AlbumEntry(bucketId, bucketName, mediaEntry); - mediaAlbums.put(bucketId, albumEntry); - if (mediaCameraAlbumId == null && cameraFolder != null && path.startsWith(cameraFolder)) { - mediaAlbumsSorted.add(0, albumEntry); - mediaCameraAlbumId = bucketId; - } else { - mediaAlbumsSorted.add(albumEntry); - } + if (allPhotosAlbum == null) { + allPhotosAlbum = new AlbumEntry(0, context.getString(R.string.all_photos), mediaEntry); + photoAlbumsSorted.add(0, allPhotosAlbum); + } + if (allMediaAlbum == null) { + allMediaAlbum = new AlbumEntry(0, context.getString(R.string.all_media), mediaEntry); + mediaAlbumsSorted.add(0, allMediaAlbum); + } + allPhotosAlbum.addPhoto(mediaEntry); + allMediaAlbum.addPhoto(mediaEntry); + + AlbumEntry albumEntry = mediaAlbums.get(bucketId); + if (albumEntry == null) { + albumEntry = new AlbumEntry(bucketId, bucketName, mediaEntry); + mediaAlbums.put(bucketId, albumEntry); + if (mediaCameraAlbumId == null && cameraFolder != null && path.startsWith(cameraFolder)) { + mediaAlbumsSorted.add(0, albumEntry); + mediaCameraAlbumId = bucketId; + } else { + mediaAlbumsSorted.add(albumEntry); } - albumEntry.addPhoto(mediaEntry); - - albumEntry = photoAlbums.get(bucketId); - if (albumEntry == null) { - albumEntry = new AlbumEntry(bucketId, bucketName, mediaEntry); - photoAlbums.put(bucketId, albumEntry); - if (photoCameraAlbumId == null && cameraFolder != null && path.startsWith(cameraFolder)) { - photoAlbumsSorted.add(0, albumEntry); - photoCameraAlbumId = bucketId; - } else { - photoAlbumsSorted.add(albumEntry); - } + } + albumEntry.addPhoto(mediaEntry); + + albumEntry = photoAlbums.get(bucketId); + if (albumEntry == null) { + albumEntry = new AlbumEntry(bucketId, bucketName, mediaEntry); + photoAlbums.put(bucketId, albumEntry); + if (photoCameraAlbumId == null && cameraFolder != null && path.startsWith(cameraFolder)) { + photoAlbumsSorted.add(0, albumEntry); + photoCameraAlbumId = bucketId; + } else { + photoAlbumsSorted.add(albumEntry); } - albumEntry.addPhoto(mediaEntry); } + albumEntry.addPhoto(mediaEntry); } } + // } } catch (Throwable e) { Log.e(TAG, "loadGalleryAlbums: ", e); } finally { @@ -177,79 +177,79 @@ public class MediaController { } try { - if (PermissionUtils.hasAttachMediaPerms(context)) { - cursor = MediaStore.Images.Media.query(context.getContentResolver(), - MediaStore.Video.Media.EXTERNAL_CONTENT_URI, - PROJECTION_VIDEO, - MediaStore.Video.Media.MIME_TYPE + "=?", - new String[]{"video/mp4"}, - (Build.VERSION.SDK_INT > 28 - ? MediaStore.Video.Media.DATE_TAKEN - : MediaStore.Video.Media.DATE_MODIFIED) + " DESC"); - if (cursor != null) { - int imageIdColumn = cursor.getColumnIndex(MediaStore.Video.Media._ID); - int bucketIdColumn = cursor.getColumnIndex(MediaStore.Video.Media.BUCKET_ID); - int bucketNameColumn = cursor.getColumnIndex(MediaStore.Video.Media.BUCKET_DISPLAY_NAME); - int dataColumn = cursor.getColumnIndex(MediaStore.Video.Media.DATA); - int dateColumn = cursor.getColumnIndex(Build.VERSION.SDK_INT > 28 ? MediaStore.Video.Media.DATE_TAKEN - : MediaStore.Video.Media.DATE_MODIFIED); - int durationColumn = cursor.getColumnIndex(MediaStore.Video.Media.DURATION); - int widthColumn = cursor.getColumnIndex(MediaStore.Video.Media.WIDTH); - int heightColumn = cursor.getColumnIndex(MediaStore.Video.Media.HEIGHT); - int sizeColumn = cursor.getColumnIndex(MediaStore.Video.Media.SIZE); - - while (cursor.moveToNext()) { - String path = cursor.getString(dataColumn); - if (TextUtils.isEmpty(path)) { - continue; - } + // if (PermissionUtils.hasAttachMediaPerms(context)) { + cursor = MediaStore.Images.Media.query(context.getContentResolver(), + MediaStore.Video.Media.EXTERNAL_CONTENT_URI, + PROJECTION_VIDEO, + MediaStore.Video.Media.MIME_TYPE + "=?", + new String[]{"video/mp4"}, + (Build.VERSION.SDK_INT > 28 + ? MediaStore.Video.Media.DATE_TAKEN + : MediaStore.Video.Media.DATE_MODIFIED) + " DESC"); + if (cursor != null) { + int imageIdColumn = cursor.getColumnIndex(MediaStore.Video.Media._ID); + int bucketIdColumn = cursor.getColumnIndex(MediaStore.Video.Media.BUCKET_ID); + int bucketNameColumn = cursor.getColumnIndex(MediaStore.Video.Media.BUCKET_DISPLAY_NAME); + int dataColumn = cursor.getColumnIndex(MediaStore.Video.Media.DATA); + int dateColumn = cursor.getColumnIndex(Build.VERSION.SDK_INT > 28 ? MediaStore.Video.Media.DATE_TAKEN + : MediaStore.Video.Media.DATE_MODIFIED); + int durationColumn = cursor.getColumnIndex(MediaStore.Video.Media.DURATION); + int widthColumn = cursor.getColumnIndex(MediaStore.Video.Media.WIDTH); + int heightColumn = cursor.getColumnIndex(MediaStore.Video.Media.HEIGHT); + int sizeColumn = cursor.getColumnIndex(MediaStore.Video.Media.SIZE); + + while (cursor.moveToNext()) { + String path = cursor.getString(dataColumn); + if (TextUtils.isEmpty(path)) { + continue; + } - int imageId = cursor.getInt(imageIdColumn); - int bucketId = cursor.getInt(bucketIdColumn); - String bucketName = cursor.getString(bucketNameColumn); - long dateTaken = cursor.getLong(dateColumn); - long duration = cursor.getLong(durationColumn); - int width = cursor.getInt(widthColumn); - int height = cursor.getInt(heightColumn); - long size = cursor.getLong(sizeColumn); - - MediaEntry mediaEntry = new MediaEntry(bucketId, imageId, dateTaken, path, -1, duration, true, width, height, size); - - if (allVideosAlbum == null) { - allVideosAlbum = new AlbumEntry(0, context.getString(R.string.all_videos), mediaEntry); - allVideosAlbum.videoOnly = true; - int index = 0; - if (allMediaAlbum != null) { - index++; - } - if (allPhotosAlbum != null) { - index++; - } - mediaAlbumsSorted.add(index, allVideosAlbum); + int imageId = cursor.getInt(imageIdColumn); + int bucketId = cursor.getInt(bucketIdColumn); + String bucketName = cursor.getString(bucketNameColumn); + long dateTaken = cursor.getLong(dateColumn); + long duration = cursor.getLong(durationColumn); + int width = cursor.getInt(widthColumn); + int height = cursor.getInt(heightColumn); + long size = cursor.getLong(sizeColumn); + + MediaEntry mediaEntry = new MediaEntry(bucketId, imageId, dateTaken, path, -1, duration, true, width, height, size); + + if (allVideosAlbum == null) { + allVideosAlbum = new AlbumEntry(0, context.getString(R.string.all_videos), mediaEntry); + allVideosAlbum.videoOnly = true; + int index = 0; + if (allMediaAlbum != null) { + index++; } - if (allMediaAlbum == null) { - allMediaAlbum = new AlbumEntry(0, context.getString(R.string.all_media), mediaEntry); - mediaAlbumsSorted.add(0, allMediaAlbum); + if (allPhotosAlbum != null) { + index++; } - allVideosAlbum.addPhoto(mediaEntry); - allMediaAlbum.addPhoto(mediaEntry); - - AlbumEntry albumEntry = mediaAlbums.get(bucketId); - if (albumEntry == null) { - albumEntry = new AlbumEntry(bucketId, bucketName, mediaEntry); - mediaAlbums.put(bucketId, albumEntry); - if (mediaCameraAlbumId == null && cameraFolder != null && path.startsWith(cameraFolder)) { - mediaAlbumsSorted.add(0, albumEntry); - mediaCameraAlbumId = bucketId; - } else { - mediaAlbumsSorted.add(albumEntry); - } + mediaAlbumsSorted.add(index, allVideosAlbum); + } + if (allMediaAlbum == null) { + allMediaAlbum = new AlbumEntry(0, context.getString(R.string.all_media), mediaEntry); + mediaAlbumsSorted.add(0, allMediaAlbum); + } + allVideosAlbum.addPhoto(mediaEntry); + allMediaAlbum.addPhoto(mediaEntry); + + AlbumEntry albumEntry = mediaAlbums.get(bucketId); + if (albumEntry == null) { + albumEntry = new AlbumEntry(bucketId, bucketName, mediaEntry); + mediaAlbums.put(bucketId, albumEntry); + if (mediaCameraAlbumId == null && cameraFolder != null && path.startsWith(cameraFolder)) { + mediaAlbumsSorted.add(0, albumEntry); + mediaCameraAlbumId = bucketId; + } else { + mediaAlbumsSorted.add(albumEntry); } - - albumEntry.addPhoto(mediaEntry); } + + albumEntry.addPhoto(mediaEntry); } } + // } } catch (Throwable e) { Log.e(TAG, "loadGalleryAlbums: ", e); } finally { diff --git a/app/src/main/java/awais/instagrabber/utils/MediaUtils.java b/app/src/main/java/awais/instagrabber/utils/MediaUtils.java index c7ee7967..e329573b 100644 --- a/app/src/main/java/awais/instagrabber/utils/MediaUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/MediaUtils.java @@ -2,13 +2,17 @@ package awais.instagrabber.utils; import android.content.ContentResolver; import android.database.Cursor; +import android.media.MediaMetadataRetriever; import android.net.Uri; +import android.os.ParcelFileDescriptor; import android.provider.MediaStore; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.io.FileDescriptor; + public final class MediaUtils { private static final String TAG = MediaUtils.class.getSimpleName(); private static final String[] PROJECTION_VIDEO = { @@ -64,34 +68,24 @@ public final class MediaUtils { @NonNull final Uri uri, @NonNull final OnInfoLoadListener listener) { AppExecutors.getInstance().tasksThread().submit(() -> { - try (Cursor cursor = MediaStore.Video.query(contentResolver, uri, PROJECTION_AUDIO)) { - if (cursor == null) { - if (listener != null) { - listener.onLoad(null); - } - return; - } - int durationColumn = cursor.getColumnIndex(MediaStore.Audio.Media.DURATION); - int sizeColumn = cursor.getColumnIndex(MediaStore.Audio.Media.SIZE); - if (cursor.moveToNext()) { - if (listener != null) { - listener.onLoad(new VideoInfo( - cursor.getLong(durationColumn), - 0, - 0, - cursor.getLong(sizeColumn) - )); - } + try (ParcelFileDescriptor parcelFileDescriptor = contentResolver.openFileDescriptor(uri, "r")) { + final FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); + final MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever(); + mediaMetadataRetriever.setDataSource(fileDescriptor); + final String duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); + if (listener != null) { + listener.onLoad(new VideoInfo( + Long.parseLong(duration), + 0, + 0, + 0 + )); } } catch (Exception e) { Log.e(TAG, "getVoiceInfo: ", e); if (listener != null) { listener.onFailure(e); } - return; - } - if (listener != null) { - listener.onLoad(null); } }); } diff --git a/app/src/main/java/awais/instagrabber/utils/PermissionUtils.java b/app/src/main/java/awais/instagrabber/utils/PermissionUtils.java index b0ec5f61..aaf510d5 100644 --- a/app/src/main/java/awais/instagrabber/utils/PermissionUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/PermissionUtils.java @@ -11,32 +11,31 @@ import androidx.core.content.PermissionChecker; import androidx.fragment.app.Fragment; import static android.Manifest.permission.CAMERA; -import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.RECORD_AUDIO; -import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static androidx.core.content.PermissionChecker.checkSelfPermission; public class PermissionUtils { - public static final String[] AUDIO_RECORD_PERMS = new String[]{WRITE_EXTERNAL_STORAGE, RECORD_AUDIO}; - public static final String[] ATTACH_MEDIA_PERMS = new String[]{READ_EXTERNAL_STORAGE}; + public static final String[] AUDIO_RECORD_PERMS = new String[]{RECORD_AUDIO}; + // public static final String[] ATTACH_MEDIA_PERMS = new String[]{READ_EXTERNAL_STORAGE}; public static final String[] CAMERA_PERMS = new String[]{CAMERA}; public static boolean hasAudioRecordPerms(@NonNull final Context context) { - return checkSelfPermission(context, WRITE_EXTERNAL_STORAGE) == PermissionChecker.PERMISSION_GRANTED - && checkSelfPermission(context, RECORD_AUDIO) == PermissionChecker.PERMISSION_GRANTED; + return // checkSelfPermission(context, WRITE_EXTERNAL_STORAGE) == PermissionChecker.PERMISSION_GRANTED + // && + checkSelfPermission(context, RECORD_AUDIO) == PermissionChecker.PERMISSION_GRANTED; } public static void requestAudioRecordPerms(final Fragment fragment, final int requestCode) { fragment.requestPermissions(AUDIO_RECORD_PERMS, requestCode); } - public static boolean hasAttachMediaPerms(@NonNull final Context context) { - return checkSelfPermission(context, READ_EXTERNAL_STORAGE) == PermissionChecker.PERMISSION_GRANTED; - } + // public static boolean hasAttachMediaPerms(@NonNull final Context context) { + // return checkSelfPermission(context, READ_EXTERNAL_STORAGE) == PermissionChecker.PERMISSION_GRANTED; + // } - public static void requestAttachMediaPerms(final Fragment fragment, final int requestCode) { - fragment.requestPermissions(ATTACH_MEDIA_PERMS, requestCode); - } + // public static void requestAttachMediaPerms(final Fragment fragment, final int requestCode) { + // fragment.requestPermissions(ATTACH_MEDIA_PERMS, requestCode); + // } public static boolean hasCameraPerms(final Context context) { return ContextCompat.checkSelfPermission(context, CAMERA) == PackageManager.PERMISSION_GRANTED; diff --git a/app/src/main/java/awais/instagrabber/utils/Utils.java b/app/src/main/java/awais/instagrabber/utils/Utils.java index e9832715..502de24a 100644 --- a/app/src/main/java/awais/instagrabber/utils/Utils.java +++ b/app/src/main/java/awais/instagrabber/utils/Utils.java @@ -509,7 +509,11 @@ public final class Utils { public static void scanDocumentFile(@NonNull final Context context, @NonNull final DocumentFile documentFile, @NonNull final OnScanCompletedListener callback) { - if (!documentFile.isFile()) return; + if (!documentFile.isFile() || !documentFile.exists()) { + Log.d(TAG, "scanDocumentFile: " + documentFile); + callback.onScanCompleted(null, null); + return; + } File file = null; try { file = getDocumentFileRealPath(context, documentFile); @@ -532,6 +536,8 @@ public final class Utils { if (type.equalsIgnoreCase("primary")) { return new File(Environment.getExternalStorageDirectory(), split[1]); + } else if (type.equalsIgnoreCase("raw")) { + return new File(split[1]); } else { if (volumes == null) { final StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); diff --git a/app/src/main/java/awais/instagrabber/utils/VoiceRecorder.java b/app/src/main/java/awais/instagrabber/utils/VoiceRecorder.java index d264226e..a8c2bf1a 100644 --- a/app/src/main/java/awais/instagrabber/utils/VoiceRecorder.java +++ b/app/src/main/java/awais/instagrabber/utils/VoiceRecorder.java @@ -11,6 +11,7 @@ import android.webkit.MimeTypeMap; import androidx.annotation.NonNull; import androidx.documentfile.provider.DocumentFile; +import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; @@ -44,13 +45,14 @@ public class VoiceRecorder { public void startRecording(final Application application) { stopped = false; + ParcelFileDescriptor parcelFileDescriptor = null; try { recorder = new MediaRecorder(); recorder.setAudioSource(MediaRecorder.AudioSource.MIC); recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); deleteTempAudioFile(); audioTempFile = getAudioRecordFile(); - final ParcelFileDescriptor parcelFileDescriptor = application.getContentResolver().openFileDescriptor(audioTempFile.getUri(), "rwt"); + parcelFileDescriptor = application.getContentResolver().openFileDescriptor(audioTempFile.getUri(), "rwt"); recorder.setOutputFile(parcelFileDescriptor.getFileDescriptor()); recorder.setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC); recorder.setAudioEncodingBitRate(AUDIO_BIT_RATE); @@ -66,6 +68,12 @@ public class VoiceRecorder { } catch (Exception e) { Log.e(TAG, "Audio recording failed", e); deleteTempAudioFile(); + } finally { + if (parcelFileDescriptor != null) { + try { + parcelFileDescriptor.close(); + } catch (IOException ignored) {} + } } } @@ -145,7 +153,11 @@ public class VoiceRecorder { @NonNull private DocumentFile getAudioRecordFile() { final String name = String.format("%s-%s.%s", FILE_PREFIX, SIMPLE_DATE_FORMAT.format(new Date()), EXTENSION); - return recordingsDir.createFile(MIME_TYPE, name); + DocumentFile file = recordingsDir.findFile(name); + if (file == null || !file.exists()) { + file = recordingsDir.createFile(MIME_TYPE, name); + } + return file; } private void deleteTempAudioFile() { diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java index fd23f755..fdb286ed 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java @@ -174,16 +174,16 @@ public class DirectThreadViewModel extends AndroidViewModel { return; } Log.d(TAG, "onComplete: scan complete"); - MediaUtils.getVoiceInfo(contentResolver, uri, new MediaUtils.OnInfoLoadListener() { + MediaUtils.getVoiceInfo(contentResolver, result.getFile().getUri(), new MediaUtils.OnInfoLoadListener() { @Override public void onLoad(@Nullable final MediaUtils.VideoInfo videoInfo) { if (videoInfo == null) return; threadManager.sendVoice(data, - uri, + result.getFile().getUri(), result.getWaveform(), result.getSamplingFreq(), videoInfo == null ? 0 : videoInfo.duration, - videoInfo == null ? 0 : videoInfo.size); + result.getFile().length()); } @Override From 6005e91d8a61b7e0296733eb0acf4f1d0be0b6b5 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Tue, 6 Apr 2021 00:58:42 +0900 Subject: [PATCH 07/17] Media picker will require Read storage permission --- app/src/main/AndroidManifest.xml | 2 +- .../MediaPickerBottomDialogFragment.java | 17 +- .../instagrabber/utils/MediaController.java | 270 +++++++++--------- .../instagrabber/utils/PermissionUtils.java | 19 +- 4 files changed, 154 insertions(+), 154 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e2e15e36..e6f9c562 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,7 +5,7 @@ - + diff --git a/app/src/main/java/awais/instagrabber/dialogs/MediaPickerBottomDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/MediaPickerBottomDialogFragment.java index 15474032..d36cb9dd 100644 --- a/app/src/main/java/awais/instagrabber/dialogs/MediaPickerBottomDialogFragment.java +++ b/app/src/main/java/awais/instagrabber/dialogs/MediaPickerBottomDialogFragment.java @@ -26,6 +26,7 @@ import awais.instagrabber.R; import awais.instagrabber.adapters.MediaItemsAdapter; import awais.instagrabber.databinding.LayoutMediaPickerBinding; import awais.instagrabber.utils.MediaController; +import awais.instagrabber.utils.PermissionUtils; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.viewmodels.MediaPickerViewModel; @@ -120,10 +121,10 @@ public class MediaPickerBottomDialogFragment extends BottomSheetDialogFragment { if (requestCode == ATTACH_MEDIA_REQUEST_CODE) { final Context context = getContext(); if (context == null) return; - // final boolean hasAttachMediaPerms = PermissionUtils.hasAttachMediaPerms(context); - // if (hasAttachMediaPerms) { - viewModel.loadMedia(context); - // } + final boolean hasAttachMediaPerms = PermissionUtils.hasAttachMediaPerms(context); + if (hasAttachMediaPerms) { + viewModel.loadMedia(context); + } } } @@ -132,10 +133,10 @@ public class MediaPickerBottomDialogFragment extends BottomSheetDialogFragment { setupAlbumPicker(); final Context context = getContext(); if (context == null) return; - // if (!PermissionUtils.hasAttachMediaPerms(context)) { - // PermissionUtils.requestAttachMediaPerms(this, ATTACH_MEDIA_REQUEST_CODE); - // return; - // } + if (!PermissionUtils.hasAttachMediaPerms(context)) { + PermissionUtils.requestAttachMediaPerms(this, ATTACH_MEDIA_REQUEST_CODE); + return; + } viewModel.loadMedia(context); } diff --git a/app/src/main/java/awais/instagrabber/utils/MediaController.java b/app/src/main/java/awais/instagrabber/utils/MediaController.java index 9ca8e318..237dd85a 100644 --- a/app/src/main/java/awais/instagrabber/utils/MediaController.java +++ b/app/src/main/java/awais/instagrabber/utils/MediaController.java @@ -87,83 +87,83 @@ public class MediaController { Cursor cursor = null; try { - // if (PermissionUtils.hasAttachMediaPerms(context)) { - cursor = MediaStore.Images.Media.query(context.getContentResolver(), - MediaStore.Images.Media.EXTERNAL_CONTENT_URI, - PROJECTION_PHOTOS, - null, - null, - (Build.VERSION.SDK_INT > 28 - ? MediaStore.Images.Media.DATE_TAKEN - : MediaStore.Images.Media.DATE_MODIFIED) + " DESC"); - if (cursor != null) { - int imageIdColumn = cursor.getColumnIndex(MediaStore.Images.Media._ID); - int bucketIdColumn = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID); - int bucketNameColumn = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME); - int dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA); - int dateColumn = cursor.getColumnIndex(Build.VERSION.SDK_INT > 28 ? MediaStore.Images.Media.DATE_TAKEN - : MediaStore.Images.Media.DATE_MODIFIED); - int orientationColumn = cursor.getColumnIndex(MediaStore.Images.Media.ORIENTATION); - int widthColumn = cursor.getColumnIndex(MediaStore.Images.Media.WIDTH); - int heightColumn = cursor.getColumnIndex(MediaStore.Images.Media.HEIGHT); - int sizeColumn = cursor.getColumnIndex(MediaStore.Images.Media.SIZE); - - while (cursor.moveToNext()) { - String path = cursor.getString(dataColumn); - if (TextUtils.isEmpty(path)) { - continue; - } + if (PermissionUtils.hasAttachMediaPerms(context)) { + cursor = MediaStore.Images.Media.query(context.getContentResolver(), + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + PROJECTION_PHOTOS, + null, + null, + (Build.VERSION.SDK_INT > 28 + ? MediaStore.Images.Media.DATE_TAKEN + : MediaStore.Images.Media.DATE_MODIFIED) + " DESC"); + if (cursor != null) { + int imageIdColumn = cursor.getColumnIndex(MediaStore.Images.Media._ID); + int bucketIdColumn = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID); + int bucketNameColumn = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME); + int dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA); + int dateColumn = cursor.getColumnIndex(Build.VERSION.SDK_INT > 28 ? MediaStore.Images.Media.DATE_TAKEN + : MediaStore.Images.Media.DATE_MODIFIED); + int orientationColumn = cursor.getColumnIndex(MediaStore.Images.Media.ORIENTATION); + int widthColumn = cursor.getColumnIndex(MediaStore.Images.Media.WIDTH); + int heightColumn = cursor.getColumnIndex(MediaStore.Images.Media.HEIGHT); + int sizeColumn = cursor.getColumnIndex(MediaStore.Images.Media.SIZE); + + while (cursor.moveToNext()) { + String path = cursor.getString(dataColumn); + if (TextUtils.isEmpty(path)) { + continue; + } - int imageId = cursor.getInt(imageIdColumn); - int bucketId = cursor.getInt(bucketIdColumn); - String bucketName = cursor.getString(bucketNameColumn); - long dateTaken = cursor.getLong(dateColumn); - int orientation = cursor.getInt(orientationColumn); - int width = cursor.getInt(widthColumn); - int height = cursor.getInt(heightColumn); - long size = cursor.getLong(sizeColumn); + int imageId = cursor.getInt(imageIdColumn); + int bucketId = cursor.getInt(bucketIdColumn); + String bucketName = cursor.getString(bucketNameColumn); + long dateTaken = cursor.getLong(dateColumn); + int orientation = cursor.getInt(orientationColumn); + int width = cursor.getInt(widthColumn); + int height = cursor.getInt(heightColumn); + long size = cursor.getLong(sizeColumn); - MediaEntry mediaEntry = new MediaEntry(bucketId, imageId, dateTaken, path, orientation, -1, false, width, height, size); + MediaEntry mediaEntry = new MediaEntry(bucketId, imageId, dateTaken, path, orientation, -1, false, width, height, size); - if (allPhotosAlbum == null) { - allPhotosAlbum = new AlbumEntry(0, context.getString(R.string.all_photos), mediaEntry); - photoAlbumsSorted.add(0, allPhotosAlbum); - } - if (allMediaAlbum == null) { - allMediaAlbum = new AlbumEntry(0, context.getString(R.string.all_media), mediaEntry); - mediaAlbumsSorted.add(0, allMediaAlbum); - } - allPhotosAlbum.addPhoto(mediaEntry); - allMediaAlbum.addPhoto(mediaEntry); - - AlbumEntry albumEntry = mediaAlbums.get(bucketId); - if (albumEntry == null) { - albumEntry = new AlbumEntry(bucketId, bucketName, mediaEntry); - mediaAlbums.put(bucketId, albumEntry); - if (mediaCameraAlbumId == null && cameraFolder != null && path.startsWith(cameraFolder)) { - mediaAlbumsSorted.add(0, albumEntry); - mediaCameraAlbumId = bucketId; - } else { - mediaAlbumsSorted.add(albumEntry); + if (allPhotosAlbum == null) { + allPhotosAlbum = new AlbumEntry(0, context.getString(R.string.all_photos), mediaEntry); + photoAlbumsSorted.add(0, allPhotosAlbum); } - } - albumEntry.addPhoto(mediaEntry); - - albumEntry = photoAlbums.get(bucketId); - if (albumEntry == null) { - albumEntry = new AlbumEntry(bucketId, bucketName, mediaEntry); - photoAlbums.put(bucketId, albumEntry); - if (photoCameraAlbumId == null && cameraFolder != null && path.startsWith(cameraFolder)) { - photoAlbumsSorted.add(0, albumEntry); - photoCameraAlbumId = bucketId; - } else { - photoAlbumsSorted.add(albumEntry); + if (allMediaAlbum == null) { + allMediaAlbum = new AlbumEntry(0, context.getString(R.string.all_media), mediaEntry); + mediaAlbumsSorted.add(0, allMediaAlbum); + } + allPhotosAlbum.addPhoto(mediaEntry); + allMediaAlbum.addPhoto(mediaEntry); + + AlbumEntry albumEntry = mediaAlbums.get(bucketId); + if (albumEntry == null) { + albumEntry = new AlbumEntry(bucketId, bucketName, mediaEntry); + mediaAlbums.put(bucketId, albumEntry); + if (mediaCameraAlbumId == null && cameraFolder != null && path.startsWith(cameraFolder)) { + mediaAlbumsSorted.add(0, albumEntry); + mediaCameraAlbumId = bucketId; + } else { + mediaAlbumsSorted.add(albumEntry); + } + } + albumEntry.addPhoto(mediaEntry); + + albumEntry = photoAlbums.get(bucketId); + if (albumEntry == null) { + albumEntry = new AlbumEntry(bucketId, bucketName, mediaEntry); + photoAlbums.put(bucketId, albumEntry); + if (photoCameraAlbumId == null && cameraFolder != null && path.startsWith(cameraFolder)) { + photoAlbumsSorted.add(0, albumEntry); + photoCameraAlbumId = bucketId; + } else { + photoAlbumsSorted.add(albumEntry); + } } + albumEntry.addPhoto(mediaEntry); } - albumEntry.addPhoto(mediaEntry); } } - // } } catch (Throwable e) { Log.e(TAG, "loadGalleryAlbums: ", e); } finally { @@ -177,79 +177,79 @@ public class MediaController { } try { - // if (PermissionUtils.hasAttachMediaPerms(context)) { - cursor = MediaStore.Images.Media.query(context.getContentResolver(), - MediaStore.Video.Media.EXTERNAL_CONTENT_URI, - PROJECTION_VIDEO, - MediaStore.Video.Media.MIME_TYPE + "=?", - new String[]{"video/mp4"}, - (Build.VERSION.SDK_INT > 28 - ? MediaStore.Video.Media.DATE_TAKEN - : MediaStore.Video.Media.DATE_MODIFIED) + " DESC"); - if (cursor != null) { - int imageIdColumn = cursor.getColumnIndex(MediaStore.Video.Media._ID); - int bucketIdColumn = cursor.getColumnIndex(MediaStore.Video.Media.BUCKET_ID); - int bucketNameColumn = cursor.getColumnIndex(MediaStore.Video.Media.BUCKET_DISPLAY_NAME); - int dataColumn = cursor.getColumnIndex(MediaStore.Video.Media.DATA); - int dateColumn = cursor.getColumnIndex(Build.VERSION.SDK_INT > 28 ? MediaStore.Video.Media.DATE_TAKEN - : MediaStore.Video.Media.DATE_MODIFIED); - int durationColumn = cursor.getColumnIndex(MediaStore.Video.Media.DURATION); - int widthColumn = cursor.getColumnIndex(MediaStore.Video.Media.WIDTH); - int heightColumn = cursor.getColumnIndex(MediaStore.Video.Media.HEIGHT); - int sizeColumn = cursor.getColumnIndex(MediaStore.Video.Media.SIZE); - - while (cursor.moveToNext()) { - String path = cursor.getString(dataColumn); - if (TextUtils.isEmpty(path)) { - continue; - } + if (PermissionUtils.hasAttachMediaPerms(context)) { + cursor = MediaStore.Images.Media.query(context.getContentResolver(), + MediaStore.Video.Media.EXTERNAL_CONTENT_URI, + PROJECTION_VIDEO, + MediaStore.Video.Media.MIME_TYPE + "=?", + new String[]{"video/mp4"}, + (Build.VERSION.SDK_INT > 28 + ? MediaStore.Video.Media.DATE_TAKEN + : MediaStore.Video.Media.DATE_MODIFIED) + " DESC"); + if (cursor != null) { + int imageIdColumn = cursor.getColumnIndex(MediaStore.Video.Media._ID); + int bucketIdColumn = cursor.getColumnIndex(MediaStore.Video.Media.BUCKET_ID); + int bucketNameColumn = cursor.getColumnIndex(MediaStore.Video.Media.BUCKET_DISPLAY_NAME); + int dataColumn = cursor.getColumnIndex(MediaStore.Video.Media.DATA); + int dateColumn = cursor.getColumnIndex(Build.VERSION.SDK_INT > 28 ? MediaStore.Video.Media.DATE_TAKEN + : MediaStore.Video.Media.DATE_MODIFIED); + int durationColumn = cursor.getColumnIndex(MediaStore.Video.Media.DURATION); + int widthColumn = cursor.getColumnIndex(MediaStore.Video.Media.WIDTH); + int heightColumn = cursor.getColumnIndex(MediaStore.Video.Media.HEIGHT); + int sizeColumn = cursor.getColumnIndex(MediaStore.Video.Media.SIZE); + + while (cursor.moveToNext()) { + String path = cursor.getString(dataColumn); + if (TextUtils.isEmpty(path)) { + continue; + } - int imageId = cursor.getInt(imageIdColumn); - int bucketId = cursor.getInt(bucketIdColumn); - String bucketName = cursor.getString(bucketNameColumn); - long dateTaken = cursor.getLong(dateColumn); - long duration = cursor.getLong(durationColumn); - int width = cursor.getInt(widthColumn); - int height = cursor.getInt(heightColumn); - long size = cursor.getLong(sizeColumn); - - MediaEntry mediaEntry = new MediaEntry(bucketId, imageId, dateTaken, path, -1, duration, true, width, height, size); - - if (allVideosAlbum == null) { - allVideosAlbum = new AlbumEntry(0, context.getString(R.string.all_videos), mediaEntry); - allVideosAlbum.videoOnly = true; - int index = 0; - if (allMediaAlbum != null) { - index++; + int imageId = cursor.getInt(imageIdColumn); + int bucketId = cursor.getInt(bucketIdColumn); + String bucketName = cursor.getString(bucketNameColumn); + long dateTaken = cursor.getLong(dateColumn); + long duration = cursor.getLong(durationColumn); + int width = cursor.getInt(widthColumn); + int height = cursor.getInt(heightColumn); + long size = cursor.getLong(sizeColumn); + + MediaEntry mediaEntry = new MediaEntry(bucketId, imageId, dateTaken, path, -1, duration, true, width, height, size); + + if (allVideosAlbum == null) { + allVideosAlbum = new AlbumEntry(0, context.getString(R.string.all_videos), mediaEntry); + allVideosAlbum.videoOnly = true; + int index = 0; + if (allMediaAlbum != null) { + index++; + } + if (allPhotosAlbum != null) { + index++; + } + mediaAlbumsSorted.add(index, allVideosAlbum); } - if (allPhotosAlbum != null) { - index++; + if (allMediaAlbum == null) { + allMediaAlbum = new AlbumEntry(0, context.getString(R.string.all_media), mediaEntry); + mediaAlbumsSorted.add(0, allMediaAlbum); } - mediaAlbumsSorted.add(index, allVideosAlbum); - } - if (allMediaAlbum == null) { - allMediaAlbum = new AlbumEntry(0, context.getString(R.string.all_media), mediaEntry); - mediaAlbumsSorted.add(0, allMediaAlbum); - } - allVideosAlbum.addPhoto(mediaEntry); - allMediaAlbum.addPhoto(mediaEntry); - - AlbumEntry albumEntry = mediaAlbums.get(bucketId); - if (albumEntry == null) { - albumEntry = new AlbumEntry(bucketId, bucketName, mediaEntry); - mediaAlbums.put(bucketId, albumEntry); - if (mediaCameraAlbumId == null && cameraFolder != null && path.startsWith(cameraFolder)) { - mediaAlbumsSorted.add(0, albumEntry); - mediaCameraAlbumId = bucketId; - } else { - mediaAlbumsSorted.add(albumEntry); + allVideosAlbum.addPhoto(mediaEntry); + allMediaAlbum.addPhoto(mediaEntry); + + AlbumEntry albumEntry = mediaAlbums.get(bucketId); + if (albumEntry == null) { + albumEntry = new AlbumEntry(bucketId, bucketName, mediaEntry); + mediaAlbums.put(bucketId, albumEntry); + if (mediaCameraAlbumId == null && cameraFolder != null && path.startsWith(cameraFolder)) { + mediaAlbumsSorted.add(0, albumEntry); + mediaCameraAlbumId = bucketId; + } else { + mediaAlbumsSorted.add(albumEntry); + } } - } - albumEntry.addPhoto(mediaEntry); + albumEntry.addPhoto(mediaEntry); + } } } - // } } catch (Throwable e) { Log.e(TAG, "loadGalleryAlbums: ", e); } finally { diff --git a/app/src/main/java/awais/instagrabber/utils/PermissionUtils.java b/app/src/main/java/awais/instagrabber/utils/PermissionUtils.java index aaf510d5..8b92fe1d 100644 --- a/app/src/main/java/awais/instagrabber/utils/PermissionUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/PermissionUtils.java @@ -11,31 +11,30 @@ import androidx.core.content.PermissionChecker; import androidx.fragment.app.Fragment; import static android.Manifest.permission.CAMERA; +import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.RECORD_AUDIO; import static androidx.core.content.PermissionChecker.checkSelfPermission; public class PermissionUtils { public static final String[] AUDIO_RECORD_PERMS = new String[]{RECORD_AUDIO}; - // public static final String[] ATTACH_MEDIA_PERMS = new String[]{READ_EXTERNAL_STORAGE}; + public static final String[] ATTACH_MEDIA_PERMS = new String[]{READ_EXTERNAL_STORAGE}; public static final String[] CAMERA_PERMS = new String[]{CAMERA}; public static boolean hasAudioRecordPerms(@NonNull final Context context) { - return // checkSelfPermission(context, WRITE_EXTERNAL_STORAGE) == PermissionChecker.PERMISSION_GRANTED - // && - checkSelfPermission(context, RECORD_AUDIO) == PermissionChecker.PERMISSION_GRANTED; + return checkSelfPermission(context, RECORD_AUDIO) == PermissionChecker.PERMISSION_GRANTED; } public static void requestAudioRecordPerms(final Fragment fragment, final int requestCode) { fragment.requestPermissions(AUDIO_RECORD_PERMS, requestCode); } - // public static boolean hasAttachMediaPerms(@NonNull final Context context) { - // return checkSelfPermission(context, READ_EXTERNAL_STORAGE) == PermissionChecker.PERMISSION_GRANTED; - // } + public static boolean hasAttachMediaPerms(@NonNull final Context context) { + return checkSelfPermission(context, READ_EXTERNAL_STORAGE) == PermissionChecker.PERMISSION_GRANTED; + } - // public static void requestAttachMediaPerms(final Fragment fragment, final int requestCode) { - // fragment.requestPermissions(ATTACH_MEDIA_PERMS, requestCode); - // } + public static void requestAttachMediaPerms(final Fragment fragment, final int requestCode) { + fragment.requestPermissions(ATTACH_MEDIA_PERMS, requestCode); + } public static boolean hasCameraPerms(final Context context) { return ContextCompat.checkSelfPermission(context, CAMERA) == PackageManager.PERMISSION_GRANTED; From 5a8c3bb2017e15d3df4e004c374b15ed29ed5364 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Thu, 8 Apr 2021 07:51:37 +0900 Subject: [PATCH 08/17] Fix crop for scoped storage --- app/build.gradle | 2 +- .../instagrabber/fragments/imageedit/ImageEditFragment.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 3048fb50..265ddc67 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -157,7 +157,7 @@ dependencies { implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'org.apache.commons:commons-imaging:1.0-alpha2' - implementation 'com.github.ammargitham:uCrop:2.3-native-beta-2' + implementation 'com.github.ammargitham:uCrop:2.3-beta' implementation 'com.github.ammargitham:android-gpuimage:2.1.1-beta4' debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7' diff --git a/app/src/main/java/awais/instagrabber/fragments/imageedit/ImageEditFragment.java b/app/src/main/java/awais/instagrabber/fragments/imageedit/ImageEditFragment.java index 4a2c4881..e3b76ec1 100644 --- a/app/src/main/java/awais/instagrabber/fragments/imageedit/ImageEditFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/imageedit/ImageEditFragment.java @@ -225,6 +225,11 @@ public class ImageEditFragment extends Fragment { @Override public void onCropFinish(final UCropFragment.UCropResult result) { Log.d(TAG, "onCropFinish: " + result.mResultCode); + if (result.mResultCode == UCrop.RESULT_ERROR) { + final Throwable t = UCrop.getError(result.mResultData); + Log.e(TAG, "onCropFinish: ", t); + return; + } if (result.mResultCode == AppCompatActivity.RESULT_OK) { final Intent resultData = result.mResultData; final Bundle extras = resultData.getExtras(); From 94b6e778bd2da172e2cc2788ad46f35c36a2f3f3 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Thu, 8 Apr 2021 08:10:37 +0900 Subject: [PATCH 09/17] Fix leak and add null checks --- .../instagrabber/activities/MainActivity.java | 1 + .../instagrabber/utils/DownloadUtils.java | 56 +++++++++++-------- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.java b/app/src/main/java/awais/instagrabber/activities/MainActivity.java index fb2d8e6c..0acbd2a2 100644 --- a/app/src/main/java/awais/instagrabber/activities/MainActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/MainActivity.java @@ -282,6 +282,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage try { RetrofitFactory.getInstance().destroy(); } catch (Exception ignored) {} + DownloadUtils.destroy(); } @Override diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java index 3432ec3f..9b4e86aa 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java @@ -56,11 +56,7 @@ public final class DownloadUtils { private static final String DIR_TEMP = "Temp"; private static final String DIR_BACKUPS = "Backups"; - // public static final String WRITE_PERMISSION = Manifest.permission.WRITE_EXTERNAL_STORAGE; - // public static final String[] PERMS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; - private static DocumentFile root; - // private static DocumentFile DOWNLOADS_DIR_FILE; public static void init(@NonNull final Context context) throws ReselectDocumentTreeException { // if (DOWNLOADS_DIR_FILE == null) { @@ -102,6 +98,10 @@ public final class DownloadUtils { // Log.d(TAG, "init: " + documentFile); } + public static void destroy() { + root = null; + } + @Nullable public static DocumentFile getDownloadDir(final String... dirs) { if (root == null) { @@ -110,6 +110,7 @@ public final class DownloadUtils { DocumentFile subDir = root; if (dirs != null) { for (final String dir : dirs) { + if (subDir == null || TextUtils.isEmpty(dir)) continue; final DocumentFile subDirFile = subDir.findFile(dir); final boolean exists = subDirFile != null && subDirFile.exists(); subDir = exists ? subDirFile : subDir.createDirectory(dir); @@ -230,14 +231,12 @@ public final class DownloadUtils { // Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show(); // } - @NonNull private static Pair, String> getDownloadSavePaths(final List paths, final String postId, final String displayUrl) { return getDownloadSavePaths(paths, postId, "", displayUrl, ""); } - @NonNull private static Pair, String> getDownloadSavePaths(final List paths, final String postId, final String displayUrl, @@ -254,7 +253,6 @@ public final class DownloadUtils { return getDownloadSavePaths(paths, postId, sliderPostfix, url, username); } - @Nullable private static Pair, String> getDownloadSavePaths(final List paths, final String postId, final String sliderPostfix, @@ -287,7 +285,10 @@ public final class DownloadUtils { String mimeType = "application/octet-stream"; if (!TextUtils.isEmpty(extension)) { name += "." + extension; - mimeType = Utils.mimeTypeMap.getMimeTypeFromExtension(extension); + final String mimeType1 = Utils.mimeTypeMap.getMimeTypeFromExtension(extension); + if (mimeType1 != null) { + mimeType = mimeType1; + } } DocumentFile file = dir.findFile(name); if (file == null) { @@ -349,9 +350,14 @@ public final class DownloadUtils { case MEDIA_TYPE_VIDEO: { final String url = ResponseBodyUtils.getImageUrl(media); final Pair, String> file = getDownloadSavePaths(new ArrayList<>(userFolderPaths), media.getCode(), url, ""); - final Pair, String> usernameFile = getDownloadSavePaths(new ArrayList<>(userFolderPaths), media.getCode(), url, - username); - checkList.add(checkPathExists(file.first) || checkPathExists(usernameFile.first)); + final boolean fileExists = file.first != null && checkPathExists(file.first); + boolean usernameFileExists = false; + if (!fileExists) { + final Pair, String> usernameFile = getDownloadSavePaths( + new ArrayList<>(userFolderPaths), media.getCode(), url, username); + usernameFileExists = usernameFile.first != null && checkPathExists(usernameFile.first); + } + checkList.add(fileExists || usernameFileExists); break; } case MEDIA_TYPE_SLIDER: @@ -360,11 +366,16 @@ public final class DownloadUtils { final Media child = sliderItems.get(i); if (child == null) continue; final String url = ResponseBodyUtils.getImageUrl(child); - final Pair, String> file = getDownloadChildSavePaths(new ArrayList<>(userFolderPaths), media.getCode(), i + 1, url, - ""); - final Pair, String> usernameFile = getDownloadChildSavePaths(new ArrayList<>(userFolderPaths), media.getCode(), - i + 1, url, username); - checkList.add(checkPathExists(file.first) || checkPathExists(usernameFile.first)); + final Pair, String> file = getDownloadChildSavePaths( + new ArrayList<>(userFolderPaths), media.getCode(), i + 1, url, ""); + final boolean fileExists = file.first != null && checkPathExists(file.first); + boolean usernameFileExists = false; + if (!fileExists) { + final Pair, String> usernameFile = getDownloadChildSavePaths( + new ArrayList<>(userFolderPaths), media.getCode(), i + 1, url, username); + usernameFileExists = usernameFile.first != null && checkPathExists(usernameFile.first); + } + checkList.add(fileExists || usernameFileExists); } break; default: @@ -419,6 +430,7 @@ public final class DownloadUtils { public static void download(@NonNull final Context context, @NonNull final StoryModel storyModel) { final DocumentFile downloadDir = getDownloadDir(context, storyModel.getUsername()); + if (downloadDir == null) return; final String url = storyModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO ? storyModel.getVideoUrl() : storyModel.getStoryUrl(); @@ -430,9 +442,9 @@ public final class DownloadUtils { final String fileName = usernamePrepend + baseFileName; DocumentFile saveFile = downloadDir.findFile(fileName); if (saveFile == null) { - saveFile = downloadDir.createFile( - Utils.mimeTypeMap.getMimeTypeFromExtension(extension.startsWith(".") ? extension.substring(1) : extension), - fileName); + final String mimeType = Utils.mimeTypeMap.getMimeTypeFromExtension(extension.startsWith(".") ? extension.substring(1) : extension); + if (mimeType == null) return; + saveFile = downloadDir.createFile(mimeType, fileName); } // final File saveFile = new File(downloadDir, fileName); download(context, url, saveFile); @@ -504,9 +516,8 @@ public final class DownloadUtils { final String usernamePrepend = Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) && mediaUser != null ? mediaUser.getUsername() : ""; - final Pair, String> pair = getDownloadChildSavePaths(new ArrayList<>(userFolderPaths), media.getCode(), i + 1, - url, - usernamePrepend); + final Pair, String> pair = getDownloadChildSavePaths( + new ArrayList<>(userFolderPaths), media.getCode(), i + 1, url, usernamePrepend); final DocumentFile file = createFile(pair); if (file == null) continue; map.put(url, file); @@ -611,7 +622,6 @@ public final class DownloadUtils { .enqueue(downloadWorkRequest); } - public static class ReselectDocumentTreeException extends Exception { private final Uri initialUri; From ef847321dcdec32b46f15e560289c275fb0334af Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Thu, 8 Apr 2021 09:02:18 +0900 Subject: [PATCH 10/17] No more scanning --- .../activities/CameraActivity.java | 35 ++--- .../imageedit/ImageEditFragment.java | 7 +- .../awais/instagrabber/utils/MediaUtils.java | 51 +++---- .../java/awais/instagrabber/utils/Utils.java | 124 +++++------------- .../viewmodels/DirectThreadViewModel.java | 47 +++---- .../instagrabber/workers/DownloadWorker.java | 5 +- 6 files changed, 89 insertions(+), 180 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/activities/CameraActivity.java b/app/src/main/java/awais/instagrabber/activities/CameraActivity.java index 3172439b..2d987b19 100644 --- a/app/src/main/java/awais/instagrabber/activities/CameraActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/CameraActivity.java @@ -22,7 +22,6 @@ import androidx.documentfile.provider.DocumentFile; import com.google.common.util.concurrent.ListenableFuture; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.text.SimpleDateFormat; @@ -115,7 +114,7 @@ public class CameraActivity extends BaseLanguageActivity { binding.cameraCaptureButton.setOnClickListener(v -> { try { takePhoto(); - } catch (FileNotFoundException e) { + } catch (IOException e) { Log.e(TAG, "updateUi: ", e); } }); @@ -205,14 +204,19 @@ public class CameraActivity extends BaseLanguageActivity { preview.setSurfaceProvider(binding.viewFinder.getSurfaceProvider()); } - private void takePhoto() throws FileNotFoundException { + private void takePhoto() throws IOException { if (imageCapture == null) return; final String extension = "jpg"; final String fileName = SIMPLE_DATE_FORMAT.format(System.currentTimeMillis()) + "." + extension; // final File photoFile = new File(outputDirectory, fileName); - final String mimeType = Utils.mimeTypeMap.getMimeTypeFromExtension(extension); + final String mimeType = "image/jpg"; final DocumentFile photoFile = outputDirectory.createFile(mimeType, fileName); + if (photoFile == null) { + Log.e(TAG, "takePhoto: photoFile is null!"); + return; + } final OutputStream outputStream = getContentResolver().openOutputStream(photoFile.getUri()); + if (outputStream == null) return; final ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(outputStream).build(); imageCapture.takePicture( outputFileOptions, @@ -220,29 +224,18 @@ public class CameraActivity extends BaseLanguageActivity { new ImageCapture.OnImageSavedCallback() { @Override public void onImageSaved(@NonNull final ImageCapture.OutputFileResults outputFileResults) { - if (outputStream != null) { - try { outputStream.close(); } catch (IOException ignored) {} - } - // final Uri uri = Uri.fromFile(photoFile); - // final String mimeType = MimeTypeMap.getSingleton() - // .getMimeTypeFromExtension(Files.getFileExtension(photoFile.getName())); - sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, photoFile.getUri())); - Utils.scanDocumentFile(CameraActivity.this, photoFile, (path, uri1) -> { - Log.d(TAG, "onImageSaved: scan complete"); - final Intent intent = new Intent(); - intent.setData(uri1); - setResult(Activity.RESULT_OK, intent); - finish(); - }); + try { outputStream.close(); } catch (IOException ignored) {} + final Intent intent = new Intent(); + intent.setData(photoFile.getUri()); + setResult(Activity.RESULT_OK, intent); + finish(); Log.d(TAG, "onImageSaved: " + photoFile.getUri()); } @Override public void onError(@NonNull final ImageCaptureException exception) { Log.e(TAG, "onError: ", exception); - if (outputStream != null) { - try { outputStream.close(); } catch (IOException ignored) {} - } + try { outputStream.close(); } catch (IOException ignored) {} } } ); diff --git a/app/src/main/java/awais/instagrabber/fragments/imageedit/ImageEditFragment.java b/app/src/main/java/awais/instagrabber/fragments/imageedit/ImageEditFragment.java index e3b76ec1..718d12f3 100644 --- a/app/src/main/java/awais/instagrabber/fragments/imageedit/ImageEditFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/imageedit/ImageEditFragment.java @@ -34,7 +34,6 @@ import com.yalantis.ucrop.UCropActivity; import com.yalantis.ucrop.UCropFragment; import com.yalantis.ucrop.UCropFragmentCallback; -import java.io.File; import java.util.List; import awais.instagrabber.R; @@ -42,7 +41,6 @@ import awais.instagrabber.databinding.FragmentImageEditBinding; import awais.instagrabber.fragments.imageedit.filters.filters.Filter; import awais.instagrabber.models.SavedImageEditState; import awais.instagrabber.utils.AppExecutors; -import awais.instagrabber.utils.Utils; import awais.instagrabber.viewmodels.ImageEditViewModel; public class ImageEditFragment extends Fragment { @@ -183,11 +181,12 @@ public class ImageEditFragment extends Fragment { if (context == null) return; final Uri resultUri = viewModel.getResultUri().getValue(); if (resultUri == null) return; - Utils.mediaScanFile(context, new File(resultUri.toString()), (path, uri) -> AppExecutors.getInstance().mainThread().execute(() -> { + AppExecutors.getInstance().mainThread().execute(() -> { final NavController navController = NavHostFragment.findNavController(this); setNavControllerResult(navController, resultUri); navController.navigateUp(); - })); + }); + // Utils.mediaScanFile(context, new File(resultUri.toString()), (path, uri) -> ); }); } diff --git a/app/src/main/java/awais/instagrabber/utils/MediaUtils.java b/app/src/main/java/awais/instagrabber/utils/MediaUtils.java index e329573b..ef68bc46 100644 --- a/app/src/main/java/awais/instagrabber/utils/MediaUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/MediaUtils.java @@ -32,9 +32,7 @@ public final class MediaUtils { AppExecutors.getInstance().tasksThread().submit(() -> { try (Cursor cursor = MediaStore.Video.query(contentResolver, uri, PROJECTION_VIDEO)) { if (cursor == null) { - if (listener != null) { - listener.onLoad(null); - } + listener.onLoad(null); return; } int durationColumn = cursor.getColumnIndex(MediaStore.Video.Media.DURATION); @@ -42,24 +40,16 @@ public final class MediaUtils { int heightColumn = cursor.getColumnIndex(MediaStore.Video.Media.HEIGHT); int sizeColumn = cursor.getColumnIndex(MediaStore.Video.Media.SIZE); if (cursor.moveToNext()) { - if (listener != null) { - listener.onLoad(new VideoInfo( - cursor.getLong(durationColumn), - cursor.getInt(widthColumn), - cursor.getInt(heightColumn), - cursor.getLong(sizeColumn) - )); - } + listener.onLoad(new VideoInfo( + cursor.getLong(durationColumn), + cursor.getInt(widthColumn), + cursor.getInt(heightColumn), + cursor.getLong(sizeColumn) + )); } } catch (Exception e) { Log.e(TAG, "getVideoInfo: ", e); - if (listener != null) { - listener.onFailure(e); - } - return; - } - if (listener != null) { - listener.onLoad(null); + listener.onFailure(e); } }); } @@ -69,23 +59,24 @@ public final class MediaUtils { @NonNull final OnInfoLoadListener listener) { AppExecutors.getInstance().tasksThread().submit(() -> { try (ParcelFileDescriptor parcelFileDescriptor = contentResolver.openFileDescriptor(uri, "r")) { + if (parcelFileDescriptor == null) { + listener.onLoad(null); + return; + } final FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); final MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever(); mediaMetadataRetriever.setDataSource(fileDescriptor); - final String duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); - if (listener != null) { - listener.onLoad(new VideoInfo( - Long.parseLong(duration), - 0, - 0, - 0 - )); - } + String duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); + if (TextUtils.isEmpty(duration)) duration = "0"; + listener.onLoad(new VideoInfo( + Long.parseLong(duration), + 0, + 0, + 0 + )); } catch (Exception e) { Log.e(TAG, "getVoiceInfo: ", e); - if (listener != null) { - listener.onFailure(e); - } + listener.onFailure(e); } }); } diff --git a/app/src/main/java/awais/instagrabber/utils/Utils.java b/app/src/main/java/awais/instagrabber/utils/Utils.java index 502de24a..3a98d1f0 100644 --- a/app/src/main/java/awais/instagrabber/utils/Utils.java +++ b/app/src/main/java/awais/instagrabber/utils/Utils.java @@ -12,8 +12,6 @@ import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.media.MediaScannerConnection; -import android.media.MediaScannerConnection.OnScanCompletedListener; import android.net.Uri; import android.os.Build; import android.os.Environment; @@ -36,7 +34,6 @@ import android.widget.Toast; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; @@ -48,7 +45,6 @@ import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvicto import com.google.android.exoplayer2.upstream.cache.SimpleCache; import com.google.common.collect.ImmutableList; import com.google.common.collect.Ordering; -import com.google.common.io.Files; import org.json.JSONObject; @@ -340,18 +336,18 @@ public final class Utils { window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } - public static void mediaScanFile(@NonNull final Context context, - @NonNull File file, - @NonNull final OnScanCompletedListener callback) { - //noinspection UnstableApiUsage - final String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(Files.getFileExtension(file.getName())); - MediaScannerConnection.scanFile( - context, - new String[]{file.getAbsolutePath()}, - new String[]{mimeType}, - callback - ); - } + // public static void mediaScanFile(@NonNull final Context context, + // @NonNull File file, + // @NonNull final OnScanCompletedListener callback) { + // //noinspection UnstableApiUsage + // final String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(Files.getFileExtension(file.getName())); + // MediaScannerConnection.scanFile( + // context, + // new String[]{file.getAbsolutePath()}, + // new String[]{mimeType}, + // callback + // ); + // } public static void hideKeyboard(final View view) { if (view == null) return; @@ -506,26 +502,26 @@ public final class Utils { return tabOrderString.contains(navRootString); } - public static void scanDocumentFile(@NonNull final Context context, - @NonNull final DocumentFile documentFile, - @NonNull final OnScanCompletedListener callback) { - if (!documentFile.isFile() || !documentFile.exists()) { - Log.d(TAG, "scanDocumentFile: " + documentFile); - callback.onScanCompleted(null, null); - return; - } - File file = null; - try { - file = getDocumentFileRealPath(context, documentFile); - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { - Log.e(TAG, "scanDocumentFile: ", e); - } - if (file == null) return; - MediaScannerConnection.scanFile(context, - new String[]{file.getAbsolutePath()}, - new String[]{documentFile.getType()}, - callback); - } + // public static void scanDocumentFile(@NonNull final Context context, + // @NonNull final DocumentFile documentFile, + // @NonNull final OnScanCompletedListener callback) { + // if (!documentFile.isFile() || !documentFile.exists()) { + // Log.d(TAG, "scanDocumentFile: " + documentFile); + // callback.onScanCompleted(null, null); + // return; + // } + // File file = null; + // try { + // file = getDocumentFileRealPath(context, documentFile); + // } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + // Log.e(TAG, "scanDocumentFile: ", e); + // } + // if (file == null) return; + // MediaScannerConnection.scanFile(context, + // new String[]{file.getAbsolutePath()}, + // new String[]{documentFile.getType()}, + // callback); + // } public static File getDocumentFileRealPath(@NonNull final Context context, @NonNull final DocumentFile documentFile) @@ -572,60 +568,4 @@ public final class Utils { // re-init DownloadUtils DownloadUtils.init(context); } - - /** - * Ing.N.Nyerges 2019 V2.0 - *

- * Storage Access Framework(SAF) Uri's creator from File (java.IO), - * for removable external storages - * - * @param context Application Context - * @param file File path + file name - * @return Uri[]: - * uri[0] = SAF TREE Uri - * uri[1] = SAF DOCUMENT Uri - */ - @RequiresApi(api = Build.VERSION_CODES.KITKAT) - public static Uri[] getSafUris(Context context, File file) { - - Uri[] uri = new Uri[2]; - String scheme = "content"; - String authority = "com.android.externalstorage.documents"; - - // Separate each element of the File path - // File format: "/storage/XXXX-XXXX/sub-folder1/sub-folder2..../filename" - // (XXXX-XXXX is external removable number - String[] ele = file.getPath().split(File.separator); - // ele[0] = not used (empty) - // ele[1] = not used (storage name) - // ele[2] = storage number - // ele[3 to (n-1)] = folders - // ele[n] = file name - - // Construct folders strings using SAF format - StringBuilder folders = new StringBuilder(); - if (ele.length > 4) { - folders.append(ele[3]); - for (int i = 4; i < ele.length - 1; ++i) folders.append("%2F").append(ele[i]); - } - - String common = ele[2] + "%3A" + folders.toString(); - - // Construct TREE Uri - Uri.Builder builder = new Uri.Builder(); - builder.scheme(scheme); - builder.authority(authority); - builder.encodedPath("/tree/" + common); - uri[0] = builder.build(); - - // Construct DOCUMENT Uri - builder = new Uri.Builder(); - builder.scheme(scheme); - builder.authority(authority); - if (ele.length > 4) common = common + "%2F"; - builder.encodedPath("/document/" + common + file.getName()); - uri[1] = builder.build(); - - return uri; - } } diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java index fdb286ed..97b285c9 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java @@ -3,7 +3,6 @@ package awais.instagrabber.viewmodels; import android.app.Application; import android.content.ContentResolver; import android.net.Uri; -import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -37,7 +36,6 @@ import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.MediaController; import awais.instagrabber.utils.MediaUtils; import awais.instagrabber.utils.TextUtils; -import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.VoiceRecorder; import static awais.instagrabber.utils.Utils.settingsHelper; @@ -165,39 +163,28 @@ public class DirectThreadViewModel extends AndroidViewModel { @Override public void onComplete(final VoiceRecorder.VoiceRecordingResult result) { - Log.d(TAG, "onComplete: recording complete. Scanning file..."); - Utils.scanDocumentFile(application, result.getFile(), (path, uri) -> { - if (uri == null) { - final String msg = "Scan failed!"; - Log.e(TAG, msg); - data.postValue(Resource.error(msg, null)); - return; + // Log.d(TAG, "onComplete: recording complete. Scanning file..."); + MediaUtils.getVoiceInfo(contentResolver, result.getFile().getUri(), new MediaUtils.OnInfoLoadListener() { + @Override + public void onLoad(@Nullable final MediaUtils.VideoInfo videoInfo) { + if (videoInfo == null) return; + threadManager.sendVoice(data, + result.getFile().getUri(), + result.getWaveform(), + result.getSamplingFreq(), + videoInfo.duration, + result.getFile().length()); + } + + @Override + public void onFailure(final Throwable t) { + data.postValue(Resource.error(t.getMessage(), null)); } - Log.d(TAG, "onComplete: scan complete"); - MediaUtils.getVoiceInfo(contentResolver, result.getFile().getUri(), new MediaUtils.OnInfoLoadListener() { - @Override - public void onLoad(@Nullable final MediaUtils.VideoInfo videoInfo) { - if (videoInfo == null) return; - threadManager.sendVoice(data, - result.getFile().getUri(), - result.getWaveform(), - result.getSamplingFreq(), - videoInfo == null ? 0 : videoInfo.duration, - result.getFile().length()); - } - - @Override - public void onFailure(final Throwable t) { - data.postValue(Resource.error(t.getMessage(), null)); - } - }); }); } @Override - public void onCancel() { - - } + public void onCancel() {} }); voiceRecorder.startRecording(application); return data; diff --git a/app/src/main/java/awais/instagrabber/workers/DownloadWorker.java b/app/src/main/java/awais/instagrabber/workers/DownloadWorker.java index 12669ed0..238f10af 100644 --- a/app/src/main/java/awais/instagrabber/workers/DownloadWorker.java +++ b/app/src/main/java/awais/instagrabber/workers/DownloadWorker.java @@ -49,7 +49,6 @@ import awais.instagrabber.services.DeleteImageIntentService; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.TextUtils; -import awais.instagrabber.utils.Utils; import static awais.instagrabber.utils.Constants.DOWNLOAD_CHANNEL_ID; import static awais.instagrabber.utils.Constants.NOTIF_GROUP_NAME; @@ -272,8 +271,8 @@ public class DownloadWorker extends Worker { int count = 1; for (final DocumentFile filePath : filePaths) { // final File file = new File(filePath); - context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, filePath.getUri())); - Utils.scanDocumentFile(context, filePath, (path, uri) -> {}); + // context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, filePath.getUri())); + // Utils.scanDocumentFile(context, filePath, (path, uri) -> {}); // final Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file); final ContentResolver contentResolver = context.getContentResolver(); Bitmap bitmap = null; From 7b60258959a7b1f8056891dcf014c8cd0008611f Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Thu, 8 Apr 2021 23:48:44 +0900 Subject: [PATCH 11/17] Improve folder selection UI/UX --- .../activities/DirectorySelectActivity.java | 135 ++++---- .../dialogs/ConfirmDialogFragment.java | 56 +++- .../DownloadsPreferencesFragment.java | 306 ++++++++++-------- .../instagrabber/utils/DownloadUtils.java | 16 +- .../DirectorySelectActivityViewModel.java | 109 +++++++ .../res/layout/activity_directory_select.xml | 55 +++- app/src/main/res/values/strings.xml | 8 + 7 files changed, 465 insertions(+), 220 deletions(-) create mode 100644 app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.java diff --git a/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java b/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java index c45338a0..e76426c0 100644 --- a/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java @@ -1,102 +1,64 @@ package awais.instagrabber.activities; import android.content.Intent; -import android.content.UriPermission; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Parcelable; import android.provider.DocumentsContract; import android.util.Log; +import android.view.View; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.documentfile.provider.DocumentFile; +import androidx.lifecycle.ViewModelProvider; -import java.io.File; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.util.List; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import awais.instagrabber.R; import awais.instagrabber.databinding.ActivityDirectorySelectBinding; +import awais.instagrabber.dialogs.ConfirmDialogFragment; import awais.instagrabber.utils.AppExecutors; -import awais.instagrabber.utils.Constants; -import awais.instagrabber.utils.Utils; +import awais.instagrabber.viewmodels.DirectorySelectActivityViewModel; public class DirectorySelectActivity extends BaseLanguageActivity { private static final String TAG = DirectorySelectActivity.class.getSimpleName(); - - public static final int SELECT_DIR_REQUEST_CODE = 1090; + public static final int SELECT_DIR_REQUEST_CODE = 0x01; + private static final int ERROR_REQUEST_CODE = 0x02; private Uri initialUri; private ActivityDirectorySelectBinding binding; + private DirectorySelectActivityViewModel viewModel; @Override protected void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = ActivityDirectorySelectBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); + viewModel = new ViewModelProvider(this).get(DirectorySelectActivityViewModel.class); + setupObservers(); binding.selectDir.setOnClickListener(v -> openDirectoryChooser()); - setInitialUri(); + AppExecutors.getInstance().mainThread().execute(() -> viewModel.setInitialUri(getIntent())); } - private void setInitialUri() { - AppExecutors.getInstance().mainThread().execute(() -> { - final Intent intent = getIntent(); - if (intent == null) { - setMessage(); - return; - } - final Parcelable initialUriParcelable = intent.getParcelableExtra(Constants.EXTRA_INITIAL_URI); - if (!(initialUriParcelable instanceof Uri)) { - setMessage(); + private void setupObservers() { + viewModel.getMessage().observe(this, message -> binding.message.setText(message)); + viewModel.getPrevUri().observe(this, prevUri -> { + if (prevUri == null) { + binding.prevUri.setVisibility(View.GONE); + binding.message2.setVisibility(View.GONE); return; } - initialUri = (Uri) initialUriParcelable; - setMessage(); + binding.prevUri.setText(prevUri); + binding.prevUri.setVisibility(View.VISIBLE); + binding.message2.setVisibility(View.VISIBLE); + }); + viewModel.getDirSuccess().observe(this, success -> binding.selectDir.setVisibility(success ? View.GONE : View.VISIBLE)); + viewModel.isLoading().observe(this, loading -> { + binding.message.setVisibility(loading ? View.GONE : View.VISIBLE); + binding.loadingIndicator.setVisibility(loading ? View.VISIBLE : View.GONE); }); - } - - private void setMessage() { - if (initialUri == null) { - // default message - binding.message.setText("Select a directory which Barinsta will use for downloads and temp files"); - return; - } - - if (!initialUri.toString().startsWith("content")) { - final String message = String.format("Android has changed the way apps can access files and directories on storage.\n\n" + - "Please re-select the directory '%s' after clicking the button below", - initialUri.toString()); - binding.message.setText(message); - return; - } - - final List existingPermissions = getContentResolver().getPersistedUriPermissions(); - final boolean anyMatch = existingPermissions.stream().anyMatch(uriPermission -> uriPermission.getUri().equals(initialUri)); - if (!anyMatch) { - // permission revoked message - final String message = "Permissions for the previously selected directory '%s' were revoked by the system.\n\n" + - "Re-select the directory or select a new directory."; - final DocumentFile documentFile = DocumentFile.fromSingleUri(this, initialUri); - String path; - try { - path = URLDecoder.decode(initialUri.toString(), StandardCharsets.UTF_8.toString()); - } catch (UnsupportedEncodingException e) { - path = initialUri.toString(); - } - if (documentFile != null) { - try { - final File file = Utils.getDocumentFileRealPath(this, documentFile); - if (file != null) { - path = file.getAbsolutePath(); - } - } catch (Exception e) { - Log.e(TAG, "setMessage: ", e); - } - } - binding.message.setText(String.format(message, path)); - } } private void openDirectoryChooser() { @@ -112,17 +74,42 @@ public class DirectorySelectActivity extends BaseLanguageActivity { super.onActivityResult(requestCode, resultCode, data); if (requestCode != SELECT_DIR_REQUEST_CODE) return; if (resultCode != RESULT_OK) { - // Show error + showErrorDialog(getString(R.string.select_a_folder)); return; } if (data == null || data.getData() == null) { - // show error + showErrorDialog(getString(R.string.select_a_folder)); return; } - try { - Utils.setupSelectedDir(this, data); - } catch (Exception e) { - // show error - } + AppExecutors.getInstance().mainThread().execute(() -> { + try { + viewModel.setupSelectedDir(data); + final Intent intent = new Intent(this, MainActivity.class); + startActivity(intent); + finish(); + } catch (Exception e) { + // Should not come to this point. + // If it does, we have to show this error to the user so that they can report it. + try (final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw)) { + e.printStackTrace(pw); + showErrorDialog("Please report this error to the developers:\n\n" + sw.toString()); + } catch (IOException ioException) { + Log.e(TAG, "onActivityResult: ", ioException); + } + } + }, 500); + } + + private void showErrorDialog(@NonNull final String message) { + final ConfirmDialogFragment dialogFragment = ConfirmDialogFragment.newInstance( + ERROR_REQUEST_CODE, + R.string.error, + message, + R.string.ok, + 0, + 0 + ); + dialogFragment.show(getSupportFragmentManager(), ConfirmDialogFragment.class.getSimpleName()); } } diff --git a/app/src/main/java/awais/instagrabber/dialogs/ConfirmDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/ConfirmDialogFragment.java index 5b8eb3c9..076809e3 100644 --- a/app/src/main/java/awais/instagrabber/dialogs/ConfirmDialogFragment.java +++ b/app/src/main/java/awais/instagrabber/dialogs/ConfirmDialogFragment.java @@ -9,6 +9,7 @@ import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; import com.google.android.material.dialog.MaterialAlertDialogBuilder; @@ -28,13 +29,37 @@ public class ConfirmDialogFragment extends DialogFragment { @StringRes final int positiveText, @StringRes final int negativeText, @StringRes final int neutralText) { + return newInstance(requestCode, title, (Integer) message, positiveText, negativeText, neutralText); + } + + @NonNull + public static ConfirmDialogFragment newInstance(final int requestCode, + @StringRes final int title, + final String message, + @StringRes final int positiveText, + @StringRes final int negativeText, + @StringRes final int neutralText) { + return newInstance(requestCode, title, (Object) message, positiveText, negativeText, neutralText); + } + + @NonNull + private static ConfirmDialogFragment newInstance(final int requestCode, + @StringRes final int title, + final Object message, + @StringRes final int positiveText, + @StringRes final int negativeText, + @StringRes final int neutralText) { Bundle args = new Bundle(); args.putInt("requestCode", requestCode); if (title != 0) { args.putInt("title", title); } - if (message != 0) { - args.putInt("message", message); + if (message != null) { + if (message instanceof Integer) { + args.putInt("message", (int) message); + } else if (message instanceof String) { + args.putString("message", (String) message); + } } if (positiveText != 0) { args.putInt("positive", positiveText); @@ -48,6 +73,7 @@ public class ConfirmDialogFragment extends DialogFragment { ConfirmDialogFragment fragment = new ConfirmDialogFragment(); fragment.setArguments(args); return fragment; + } public ConfirmDialogFragment() {} @@ -55,11 +81,16 @@ public class ConfirmDialogFragment extends DialogFragment { @Override public void onAttach(@NonNull final Context context) { super.onAttach(context); + this.context = context; final Fragment parentFragment = getParentFragment(); if (parentFragment instanceof ConfirmDialogFragmentCallback) { callback = (ConfirmDialogFragmentCallback) parentFragment; + return; + } + final FragmentActivity fragmentActivity = getActivity(); + if (fragmentActivity instanceof ConfirmDialogFragmentCallback) { + callback = (ConfirmDialogFragmentCallback) fragmentActivity; } - this.context = context; } @NonNull @@ -67,7 +98,7 @@ public class ConfirmDialogFragment extends DialogFragment { public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { final Bundle arguments = getArguments(); int title = 0; - int message = 0; + String message = null; int neutralButtonText = 0; int negativeButtonText = 0; @@ -75,7 +106,7 @@ public class ConfirmDialogFragment extends DialogFragment { final int requestCode; if (arguments != null) { title = arguments.getInt("title", 0); - message = arguments.getInt("message", 0); + message = getMessage(arguments); positiveButtonText = arguments.getInt("positive", defaultPositiveButtonText); negativeButtonText = arguments.getInt("negative", 0); neutralButtonText = arguments.getInt("neutral", 0); @@ -92,7 +123,7 @@ public class ConfirmDialogFragment extends DialogFragment { if (title != 0) { builder.setTitle(title); } - if (message != 0) { + if (message != null) { builder.setMessage(message); } if (negativeButtonText != 0) { @@ -110,6 +141,19 @@ public class ConfirmDialogFragment extends DialogFragment { return builder.create(); } + private String getMessage(@NonNull final Bundle arguments) { + String message = null; + final Object messageObject = arguments.get("message"); + if (messageObject != null) { + if (messageObject instanceof Integer) { + message = getString((int) messageObject); + } else if (messageObject instanceof String) { + message = (String) messageObject; + } + } + return message; + } + public interface ConfirmDialogFragmentCallback { void onPositiveButtonClicked(int requestCode); diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java index 254d3479..9313c79b 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java @@ -1,108 +1,144 @@ package awais.instagrabber.fragments.settings; import android.content.Context; +import android.content.Intent; import android.net.Uri; +import android.os.Build; +import android.provider.DocumentsContract; import android.util.Log; -import android.view.View; import androidx.annotation.NonNull; -import androidx.appcompat.widget.AppCompatButton; -import androidx.appcompat.widget.AppCompatTextView; -import androidx.documentfile.provider.DocumentFile; +import androidx.annotation.Nullable; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; -import androidx.preference.PreferenceViewHolder; import androidx.preference.SwitchPreferenceCompat; -import com.google.android.material.switchmaterial.SwitchMaterial; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import awais.instagrabber.R; +import awais.instagrabber.dialogs.ConfirmDialogFragment; +import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.Constants; +import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; +import static android.app.Activity.RESULT_OK; +import static awais.instagrabber.activities.DirectorySelectActivity.SELECT_DIR_REQUEST_CODE; +import static awais.instagrabber.utils.Constants.DOWNLOAD_USER_FOLDER; import static awais.instagrabber.utils.Constants.FOLDER_PATH; -import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; import static awais.instagrabber.utils.Utils.settingsHelper; public class DownloadsPreferencesFragment extends BasePreferencesFragment { private static final String TAG = DownloadsPreferencesFragment.class.getSimpleName(); - private SaveToCustomFolderPreference.ResultCallback resultCallback; + // private SaveToCustomFolderPreference.ResultCallback resultCallback; @Override void setupPreferenceScreen(final PreferenceScreen screen) { final Context context = getContext(); if (context == null) return; screen.addPreference(getDownloadUserFolderPreference(context)); - // screen.addPreference(getSaveToCustomFolderPreference(context)); screen.addPreference(getPrependUsernameToFilenamePreference(context)); + screen.addPreference(getSaveToCustomFolderPreference(context)); } private Preference getDownloadUserFolderPreference(@NonNull final Context context) { final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context); - preference.setKey(Constants.DOWNLOAD_USER_FOLDER); + preference.setKey(DOWNLOAD_USER_FOLDER); preference.setTitle(R.string.download_user_folder); preference.setIconSpaceReserved(false); return preference; } - // private Preference getSaveToCustomFolderPreference(@NonNull final Context context) { - // return new SaveToCustomFolderPreference(context, checked -> { - // try { - // DownloadUtils.init(context); - // } catch (DownloadUtils.ReselectDocumentTreeException e) { - // if (!checked) return; - // startDocumentSelector(e.getInitialUri()); - // } catch (Exception e) { - // Log.e(TAG, "getSaveToCustomFolderPreference: ", e); - // } - // }, (resultCallback) -> { - // // Choose a directory using the system's file picker. - // startDocumentSelector(null); - // this.resultCallback = resultCallback; - // - // // new DirectoryChooser() - // // .setInitialDirectory(settingsHelper.getString(FOLDER_PATH)) - // // .setInteractionListener(file -> { - // // settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath()); - // // resultCallback.onResult(file.getAbsolutePath()); - // // }) - // // .show(getParentFragmentManager(), null); - // }); - // } + private Preference getSaveToCustomFolderPreference(@NonNull final Context context) { + final Preference preference = new Preference(context); + preference.setKey(FOLDER_PATH); + preference.setIconSpaceReserved(false); + preference.setTitle(R.string.barinsta_folder); + preference.setSummaryProvider(p -> { + final String currentValue = settingsHelper.getString(FOLDER_PATH); + if (TextUtils.isEmpty(currentValue)) return ""; + String path; + try { + path = URLDecoder.decode(currentValue, StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException e) { + path = currentValue; + } + return path; + }); + preference.setOnPreferenceClickListener(p -> { + openDirectoryChooser(DownloadUtils.getRootDirUri()); + return true; + }); + return preference; + // return new SaveToCustomFolderPreference(context, checked -> { + // try { + // DownloadUtils.init(context); + // } catch (DownloadUtils.ReselectDocumentTreeException e) { + // if (!checked) return; + // startDocumentSelector(e.getInitialUri()); + // } catch (Exception e) { + // Log.e(TAG, "getSaveToCustomFolderPreference: ", e); + // } + // }, (resultCallback) -> { + // // Choose a directory using the system's file picker. + // startDocumentSelector(null); + // this.resultCallback = resultCallback; + // + // // new DirectoryChooser() + // // .setInitialDirectory(settingsHelper.getString(FOLDER_PATH)) + // // .setInteractionListener(file -> { + // // settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath()); + // // resultCallback.onResult(file.getAbsolutePath()); + // // }) + // // .show(getParentFragmentManager(), null); + // }); + } - // private void startDocumentSelector(final Uri initialUri) { - // final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && initialUri != null) { - // intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initialUri); - // } - // startActivityForResult(intent, SELECT_DIR_REQUEST_CODE); - // } + private void openDirectoryChooser(final Uri initialUri) { + final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && initialUri != null) { + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initialUri); + } + startActivityForResult(intent, SELECT_DIR_REQUEST_CODE); + } - // @Override - // public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { - // if (requestCode != SELECT_DIR_REQUEST_CODE) return; - // final Context context = getContext(); - // if (context == null) return; - // if (resultCode != RESULT_OK) { - // try { - // DownloadUtils.init(context, true); - // } catch (Exception ignored) {} - // return; - // } - // if (data == null || data.getData() == null) return; - // Utils.setupSelectedDir(context, data); - // if (resultCallback != null) { - // try { - // final DocumentFile root = DocumentFile.fromTreeUri(context, data.getData()); - // resultCallback.onResult(Utils.getDocumentFileRealPath(context, root).getAbsolutePath()); - // } catch (Exception e) { - // Log.e(TAG, "onActivityResult: ", e); - // } - // resultCallback = null; - // } - // // Log.d(TAG, "onActivityResult: " + root); - // } + @Override + public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { + if (requestCode != SELECT_DIR_REQUEST_CODE) return; + if (resultCode != RESULT_OK) return; + if (data == null || data.getData() == null) return; + final Context context = getContext(); + if (context == null) return; + AppExecutors.getInstance().mainThread().execute(() -> { + try { + Utils.setupSelectedDir(context, data); + } catch (Exception e) { + // Should not come to this point. + // If it does, we have to show this error to the user so that they can report it. + try (final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw)) { + e.printStackTrace(pw); + final ConfirmDialogFragment dialogFragment = ConfirmDialogFragment.newInstance( + 123, + R.string.error, + "Please report this error to the developers:\n\n" + sw.toString(), + R.string.ok, + 0, + 0 + ); + dialogFragment.show(getChildFragmentManager(), ConfirmDialogFragment.class.getSimpleName()); + } catch (IOException ioException) { + Log.e(TAG, "onActivityResult: ", ioException); + } + } + }, 500); + } private Preference getPrependUsernameToFilenamePreference(@NonNull final Context context) { final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context); @@ -112,74 +148,74 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment { return preference; } - public static class SaveToCustomFolderPreference extends Preference { - private AppCompatTextView customPathTextView; - private final OnSaveToChangeListener onSaveToChangeListener; - private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener; - private final String key; - - public SaveToCustomFolderPreference(final Context context, - final OnSaveToChangeListener onSaveToChangeListener, - final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener) { - super(context); - this.onSaveToChangeListener = onSaveToChangeListener; - this.onSelectFolderButtonClickListener = onSelectFolderButtonClickListener; - key = FOLDER_SAVE_TO; - setLayoutResource(R.layout.pref_custom_folder); - setKey(key); - setTitle(R.string.save_to_folder); - setIconSpaceReserved(false); - } - - @Override - public void onBindViewHolder(final PreferenceViewHolder holder) { - super.onBindViewHolder(holder); - final SwitchMaterial cbSaveTo = (SwitchMaterial) holder.findViewById(R.id.cbSaveTo); - final View buttonContainer = holder.findViewById(R.id.button_container); - customPathTextView = (AppCompatTextView) holder.findViewById(R.id.custom_path); - cbSaveTo.setOnCheckedChangeListener((buttonView, isChecked) -> { - settingsHelper.putBoolean(FOLDER_SAVE_TO, isChecked); - buttonContainer.setVisibility(isChecked ? View.VISIBLE : View.GONE); - final Context context = getContext(); - String customPath = settingsHelper.getString(FOLDER_PATH); - if (!TextUtils.isEmpty(customPath) && customPath.startsWith("content") && context != null) { - final Uri uri = Uri.parse(customPath); - final DocumentFile documentFile = DocumentFile.fromSingleUri(context, uri); - try { - customPath = Utils.getDocumentFileRealPath(context, documentFile).getAbsolutePath(); - } catch (Exception e) { - Log.e(TAG, "onBindViewHolder: ", e); - } - } - customPathTextView.setText(customPath); - if (onSaveToChangeListener != null) { - onSaveToChangeListener.onChange(isChecked); - } - }); - final boolean savedToEnabled = settingsHelper.getBoolean(key); - holder.itemView.setOnClickListener(v -> cbSaveTo.toggle()); - cbSaveTo.setChecked(savedToEnabled); - buttonContainer.setVisibility(savedToEnabled ? View.VISIBLE : View.GONE); - final AppCompatButton btnSaveTo = (AppCompatButton) holder.findViewById(R.id.btnSaveTo); - btnSaveTo.setOnClickListener(v -> { - if (onSelectFolderButtonClickListener == null) return; - onSelectFolderButtonClickListener.onClick(result -> { - if (TextUtils.isEmpty(result)) return; - customPathTextView.setText(result); - }); - }); - } - - public interface ResultCallback { - void onResult(String result); - } - - public interface OnSelectFolderButtonClickListener { - void onClick(ResultCallback resultCallback); - } - - public interface OnSaveToChangeListener { - void onChange(boolean checked); - } - } + // public static class SaveToCustomFolderPreference extends Preference { + // private AppCompatTextView customPathTextView; + // private final OnSaveToChangeListener onSaveToChangeListener; + // private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener; + // private final String key; + // + // public SaveToCustomFolderPreference(final Context context, + // final OnSaveToChangeListener onSaveToChangeListener, + // final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener) { + // super(context); + // this.onSaveToChangeListener = onSaveToChangeListener; + // this.onSelectFolderButtonClickListener = onSelectFolderButtonClickListener; + // key = FOLDER_SAVE_TO; + // setLayoutResource(R.layout.pref_custom_folder); + // setKey(key); + // setTitle(R.string.save_to_folder); + // setIconSpaceReserved(false); + // } + // + // @Override + // public void onBindViewHolder(final PreferenceViewHolder holder) { + // super.onBindViewHolder(holder); + // final SwitchMaterial cbSaveTo = (SwitchMaterial) holder.findViewById(R.id.cbSaveTo); + // final View buttonContainer = holder.findViewById(R.id.button_container); + // customPathTextView = (AppCompatTextView) holder.findViewById(R.id.custom_path); + // cbSaveTo.setOnCheckedChangeListener((buttonView, isChecked) -> { + // settingsHelper.putBoolean(FOLDER_SAVE_TO, isChecked); + // buttonContainer.setVisibility(isChecked ? View.VISIBLE : View.GONE); + // final Context context = getContext(); + // String customPath = settingsHelper.getString(FOLDER_PATH); + // if (!TextUtils.isEmpty(customPath) && customPath.startsWith("content") && context != null) { + // final Uri uri = Uri.parse(customPath); + // final DocumentFile documentFile = DocumentFile.fromSingleUri(context, uri); + // try { + // customPath = Utils.getDocumentFileRealPath(context, documentFile).getAbsolutePath(); + // } catch (Exception e) { + // Log.e(TAG, "onBindViewHolder: ", e); + // } + // } + // customPathTextView.setText(customPath); + // if (onSaveToChangeListener != null) { + // onSaveToChangeListener.onChange(isChecked); + // } + // }); + // final boolean savedToEnabled = settingsHelper.getBoolean(key); + // holder.itemView.setOnClickListener(v -> cbSaveTo.toggle()); + // cbSaveTo.setChecked(savedToEnabled); + // buttonContainer.setVisibility(savedToEnabled ? View.VISIBLE : View.GONE); + // final AppCompatButton btnSaveTo = (AppCompatButton) holder.findViewById(R.id.btnSaveTo); + // btnSaveTo.setOnClickListener(v -> { + // if (onSelectFolderButtonClickListener == null) return; + // onSelectFolderButtonClickListener.onClick(result -> { + // if (TextUtils.isEmpty(result)) return; + // customPathTextView.setText(result); + // }); + // }); + // } + // + // public interface ResultCallback { + // void onResult(String result); + // } + // + // public interface OnSelectFolderButtonClickListener { + // void onClick(ResultCallback resultCallback); + // } + // + // public interface OnSaveToChangeListener { + // void onChange(boolean checked); + // } + // } } diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java index 9b4e86aa..9ceaa508 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java @@ -69,7 +69,7 @@ public final class DownloadUtils { // } final String customPath = Utils.settingsHelper.getString(FOLDER_PATH); if (TextUtils.isEmpty(customPath)) { - throw new ReselectDocumentTreeException(); + throw new ReselectDocumentTreeException("folder path is null or empty"); // root = DOWNLOADS_DIR_FILE; // DocumentFile.fromFile(DOWNLOADS_DIR_FILE); // return; } @@ -92,6 +92,10 @@ public final class DownloadUtils { throw new ReselectDocumentTreeException(uri); } root = DocumentFile.fromTreeUri(context, uri); + if (root == null || !root.exists() || root.lastModified() == 0) { + root = null; + throw new ReselectDocumentTreeException(uri); + } // Log.d(TAG, "init: " + root); // final File parent = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); // final DocumentFile documentFile = DocumentFile.fromFile(parent); @@ -622,6 +626,11 @@ public final class DownloadUtils { .enqueue(downloadWorkRequest); } + @Nullable + public static Uri getRootDirUri() { + return root != null ? root.getUri() : null; + } + public static class ReselectDocumentTreeException extends Exception { private final Uri initialUri; @@ -629,6 +638,11 @@ public final class DownloadUtils { initialUri = null; } + public ReselectDocumentTreeException(final String message) { + super(message); + initialUri = null; + } + public ReselectDocumentTreeException(final Uri initialUri) { this.initialUri = initialUri; } diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.java new file mode 100644 index 00000000..3fc940ae --- /dev/null +++ b/app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.java @@ -0,0 +1,109 @@ +package awais.instagrabber.viewmodels; + +import android.app.Application; +import android.content.Intent; +import android.content.UriPermission; +import android.net.Uri; +import android.os.Parcelable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.documentfile.provider.DocumentFile; +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import awais.instagrabber.R; +import awais.instagrabber.utils.Constants; +import awais.instagrabber.utils.DownloadUtils; +import awais.instagrabber.utils.Utils; + +public class DirectorySelectActivityViewModel extends AndroidViewModel { + private static final String TAG = DirectorySelectActivityViewModel.class.getSimpleName(); + + private final MutableLiveData message = new MutableLiveData<>(); + private final MutableLiveData prevUri = new MutableLiveData<>(); + private final MutableLiveData loading = new MutableLiveData<>(false); + private final MutableLiveData dirSuccess = new MutableLiveData<>(false); + + public DirectorySelectActivityViewModel(final Application application) { + super(application); + } + + public LiveData getMessage() { + return message; + } + + public LiveData getPrevUri() { + return prevUri; + } + + public LiveData isLoading() { + return loading; + } + + public LiveData getDirSuccess() { + return dirSuccess; + } + + public void setInitialUri(final Intent intent) { + if (intent == null) { + setMessage(null); + return; + } + final Parcelable initialUriParcelable = intent.getParcelableExtra(Constants.EXTRA_INITIAL_URI); + if (!(initialUriParcelable instanceof Uri)) { + setMessage(null); + return; + } + setMessage((Uri) initialUriParcelable); + } + + private void setMessage(@Nullable final Uri initialUri) { + if (initialUri == null) { + // default message + message.postValue(getApplication().getString(R.string.dir_select_default_message)); + prevUri.postValue(null); + return; + } + if (!initialUri.toString().startsWith("content")) { + message.postValue(getApplication().getString(R.string.dir_select_reselect_message)); + prevUri.postValue(initialUri.toString()); + return; + } + final List existingPermissions = getApplication().getContentResolver().getPersistedUriPermissions(); + final boolean anyMatch = existingPermissions.stream().anyMatch(uriPermission -> uriPermission.getUri().equals(initialUri)); + final DocumentFile documentFile = DocumentFile.fromSingleUri(getApplication(), initialUri); + String path; + try { + path = URLDecoder.decode(initialUri.toString(), StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException e) { + path = initialUri.toString(); + } + if (!anyMatch) { + message.postValue(getApplication().getString(R.string.dir_select_permission_revoked_message)); + prevUri.postValue(path); + return; + } + if (documentFile == null || !documentFile.exists() || documentFile.lastModified() == 0) { + message.postValue(getApplication().getString(R.string.dir_select_folder_not_exist)); + prevUri.postValue(path); + } + } + + public void setupSelectedDir(@NonNull final Intent data) throws DownloadUtils.ReselectDocumentTreeException { + loading.postValue(true); + try { + Utils.setupSelectedDir(getApplication(), data); + message.postValue(getApplication().getString(R.string.dir_select_success_message)); + dirSuccess.postValue(true); + } finally { + loading.postValue(false); + } + } +} diff --git a/app/src/main/res/layout/activity_directory_select.xml b/app/src/main/res/layout/activity_directory_select.xml index 01f81a9f..5d5a0110 100644 --- a/app/src/main/res/layout/activity_directory_select.xml +++ b/app/src/main/res/layout/activity_directory_select.xml @@ -1,6 +1,7 @@ @@ -19,7 +20,55 @@ android:id="@+id/message" android:layout_width="0dp" android:layout_height="wrap_content" + android:layout_marginBottom="8dp" + android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" + app:layout_constraintBottom_toTopOf="@id/prev_uri" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed" + app:layout_goneMarginBottom="0dp" + tools:text="@string/dir_select_permission_revoked_message" + tools:visibility="visible" /> + + + + + + @@ -29,11 +78,9 @@ style="@style/Widget.MaterialComponents.Button.OutlinedButton" android:layout_width="0dp" android:layout_height="wrap_content" - android:text="Select Directory" + android:text="@string/select_folder" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/message" - app:layout_constraintVertical_bias="1" /> + app:layout_constraintStart_toStartOf="parent" /> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3adc84b8..87f48b79 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -495,4 +495,12 @@ Copy reply Restore Backup + Select a folder where Barinsta can store downloads and temporary files.\n\nYou can change this later in More > Settings > Downloads. + Android has changed the way apps can access files and directories on storage. Currently Barinsta does not have permission to access the following folder: + Permissions for the previously selected folder were revoked by the system: + The previously selected folder does not exist now: + Re-select the directory or select a new directory by clicking the button below. + No folder selected! + Success! Please wait. Starting app… + Barinsta folder From 9ea7c2c3af7209952fee0da1381632017e81feef Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Fri, 9 Apr 2021 20:18:41 +0900 Subject: [PATCH 12/17] Set new uri path to a different PreferenceKey to maintain ability to downgrade. --- .../DownloadsPreferencesFragment.java | 6 ++-- .../fragments/settings/PreferenceKeys.java | 1 + .../instagrabber/utils/DownloadUtils.java | 29 ++++--------------- .../instagrabber/utils/SettingsHelper.java | 3 +- .../java/awais/instagrabber/utils/Utils.java | 4 +-- .../DirectorySelectActivityViewModel.java | 16 +++++----- 6 files changed, 23 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java index 9313c79b..9609ca20 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java @@ -30,8 +30,8 @@ import awais.instagrabber.utils.Utils; import static android.app.Activity.RESULT_OK; import static awais.instagrabber.activities.DirectorySelectActivity.SELECT_DIR_REQUEST_CODE; +import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_BARINSTA_DIR_URI; import static awais.instagrabber.utils.Constants.DOWNLOAD_USER_FOLDER; -import static awais.instagrabber.utils.Constants.FOLDER_PATH; import static awais.instagrabber.utils.Utils.settingsHelper; public class DownloadsPreferencesFragment extends BasePreferencesFragment { @@ -57,11 +57,11 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment { private Preference getSaveToCustomFolderPreference(@NonNull final Context context) { final Preference preference = new Preference(context); - preference.setKey(FOLDER_PATH); + preference.setKey(PREF_BARINSTA_DIR_URI); preference.setIconSpaceReserved(false); preference.setTitle(R.string.barinsta_folder); preference.setSummaryProvider(p -> { - final String currentValue = settingsHelper.getString(FOLDER_PATH); + final String currentValue = settingsHelper.getString(PREF_BARINSTA_DIR_URI); if (TextUtils.isEmpty(currentValue)) return ""; String path; try { diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.java b/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.java index 5287a1a5..64cc9e0f 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.java @@ -7,4 +7,5 @@ public final class PreferenceKeys { public static final String PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER = "enable_dm_auto_refresh_freq_number"; public static final String PREF_ENABLE_SENTRY = "enable_sentry"; public static final String PREF_TAB_ORDER = "tab_order"; + public static final String PREF_BARINSTA_DIR_URI = "barinsta_dir_uri"; } diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java index 9ceaa508..e9a8f31a 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java @@ -44,7 +44,7 @@ import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.VideoVersion; import awais.instagrabber.workers.DownloadWorker; -import static awais.instagrabber.utils.Constants.FOLDER_PATH; +import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_BARINSTA_DIR_URI; public final class DownloadUtils { private static final String TAG = DownloadUtils.class.getSimpleName(); @@ -59,28 +59,15 @@ public final class DownloadUtils { private static DocumentFile root; public static void init(@NonNull final Context context) throws ReselectDocumentTreeException { - // if (DOWNLOADS_DIR_FILE == null) { - // final Uri uri = Utils.getSafUris(context, new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS))[0]; - // DOWNLOADS_DIR_FILE = DocumentFile.fromTreeUri(context, uri); - // } - // if (!Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) { - // root = DOWNLOADS_DIR_FILE; // DocumentFile.fromFile(DOWNLOADS_DIR_FILE); - // return; - // } - final String customPath = Utils.settingsHelper.getString(FOLDER_PATH); - if (TextUtils.isEmpty(customPath)) { + final String barinstaDirUri = Utils.settingsHelper.getString(PREF_BARINSTA_DIR_URI); + if (TextUtils.isEmpty(barinstaDirUri)) { throw new ReselectDocumentTreeException("folder path is null or empty"); - // root = DOWNLOADS_DIR_FILE; // DocumentFile.fromFile(DOWNLOADS_DIR_FILE); - // return; } - if (!customPath.startsWith("content")) { - // if (customPath.equals(DOWNLOADS_DIR_FILE.getAbsolutePath())) { - // throw new ReselectDocumentTreeException(); - // } + if (!barinstaDirUri.startsWith("content")) { // reselect the folder in selector view - throw new ReselectDocumentTreeException(Uri.parse(customPath)); + throw new ReselectDocumentTreeException(Uri.parse(barinstaDirUri)); } - final Uri uri = Uri.parse(customPath); + final Uri uri = Uri.parse(barinstaDirUri); final List existingPermissions = context.getContentResolver().getPersistedUriPermissions(); if (existingPermissions.isEmpty()) { // reselect the folder in selector view @@ -96,10 +83,6 @@ public final class DownloadUtils { root = null; throw new ReselectDocumentTreeException(uri); } - // Log.d(TAG, "init: " + root); - // final File parent = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); - // final DocumentFile documentFile = DocumentFile.fromFile(parent); - // Log.d(TAG, "init: " + documentFile); } public static void destroy() { diff --git a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java index fc568d9e..8c08420f 100755 --- a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java +++ b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java @@ -11,6 +11,7 @@ import androidx.appcompat.app.AppCompatDelegate; import java.util.HashSet; import java.util.Set; +import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_BARINSTA_DIR_URI; import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH; import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER; import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT; @@ -158,7 +159,7 @@ public final class SettingsHelper { CUSTOM_DATE_TIME_FORMAT, DEVICE_UUID, SKIPPED_VERSION, DEFAULT_TAB, PREF_DARK_THEME, PREF_LIGHT_THEME, PREF_POSTS_LAYOUT, PREF_PROFILE_POSTS_LAYOUT, PREF_TOPIC_POSTS_LAYOUT, PREF_HASHTAG_POSTS_LAYOUT, PREF_LOCATION_POSTS_LAYOUT, PREF_LIKED_POSTS_LAYOUT, PREF_TAGGED_POSTS_LAYOUT, PREF_SAVED_POSTS_LAYOUT, - STORY_SORT, PREF_EMOJI_VARIANTS, PREF_REACTIONS, PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT, PREF_TAB_ORDER}) + STORY_SORT, PREF_EMOJI_VARIANTS, PREF_REACTIONS, PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT, PREF_TAB_ORDER, PREF_BARINSTA_DIR_URI}) public @interface StringSettings {} @StringDef({DOWNLOAD_USER_FOLDER, DOWNLOAD_PREPEND_USER_NAME, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS, diff --git a/app/src/main/java/awais/instagrabber/utils/Utils.java b/app/src/main/java/awais/instagrabber/utils/Utils.java index 3a98d1f0..c2a55c33 100644 --- a/app/src/main/java/awais/instagrabber/utils/Utils.java +++ b/app/src/main/java/awais/instagrabber/utils/Utils.java @@ -67,7 +67,7 @@ import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.Tab; import awais.instagrabber.models.enums.FavoriteType; -import static awais.instagrabber.utils.Constants.FOLDER_PATH; +import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_BARINSTA_DIR_URI; public final class Utils { private static final String TAG = "Utils"; @@ -564,7 +564,7 @@ public final class Utils { if (dirUri == null) return; final int takeFlags = intent.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); context.getContentResolver().takePersistableUriPermission(dirUri, takeFlags); - settingsHelper.putString(FOLDER_PATH, dirUri.toString()); + settingsHelper.putString(PREF_BARINSTA_DIR_URI, dirUri.toString()); // re-init DownloadUtils DownloadUtils.init(context); } diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.java index 3fc940ae..a1bfdccf 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.java @@ -21,6 +21,7 @@ import java.util.List; import awais.instagrabber.R; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.DownloadUtils; +import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; public class DirectorySelectActivityViewModel extends AndroidViewModel { @@ -66,14 +67,15 @@ public class DirectorySelectActivityViewModel extends AndroidViewModel { private void setMessage(@Nullable final Uri initialUri) { if (initialUri == null) { - // default message - message.postValue(getApplication().getString(R.string.dir_select_default_message)); - prevUri.postValue(null); - return; - } - if (!initialUri.toString().startsWith("content")) { + final String prevVersionFolderPath = Utils.settingsHelper.getString(Constants.FOLDER_PATH); + if (TextUtils.isEmpty(prevVersionFolderPath)) { + // default message + message.postValue(getApplication().getString(R.string.dir_select_default_message)); + prevUri.postValue(null); + return; + } message.postValue(getApplication().getString(R.string.dir_select_reselect_message)); - prevUri.postValue(initialUri.toString()); + prevUri.postValue(prevVersionFolderPath); return; } final List existingPermissions = getApplication().getContentResolver().getPersistedUriPermissions(); From b287f964154a49f661e39b967471aba4abeb2b16 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sun, 13 Jun 2021 17:21:34 -0400 Subject: [PATCH 13/17] made pr buildable (not functional) --- .../instagrabber/activities/CameraActivity.kt | 42 +++-- .../activities/DirectorySelectActivity.java | 4 +- .../dialogs/CreateBackupDialogFragment.java | 3 +- .../dialogs/RestoreBackupDialogFragment.java | 2 +- .../imageedit/ImageEditFragment.java | 5 +- .../fragments/settings/PreferenceKeys.kt | 1 + .../awais/instagrabber/utils/BitmapUtils.kt | 7 +- .../awais/instagrabber/utils/Constants.kt | 1 + .../instagrabber/utils/DownloadUtils.java | 10 +- .../awais/instagrabber/utils/MediaUploader.kt | 12 +- .../awais/instagrabber/utils/MediaUtils.java | 2 +- .../instagrabber/utils/VoiceRecorder.java | 5 +- .../viewmodels/DirectThreadViewModel.kt | 39 ++--- .../DirectorySelectActivityViewModel.java | 4 +- .../instagrabber/workers/DownloadWorker.kt | 144 +++++++++++------- 15 files changed, 156 insertions(+), 125 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/activities/CameraActivity.kt b/app/src/main/java/awais/instagrabber/activities/CameraActivity.kt index cfdf4516..1eca69d8 100644 --- a/app/src/main/java/awais/instagrabber/activities/CameraActivity.kt +++ b/app/src/main/java/awais/instagrabber/activities/CameraActivity.kt @@ -4,22 +4,19 @@ import android.content.Intent import android.content.res.Configuration import android.hardware.display.DisplayManager import android.hardware.display.DisplayManager.DisplayListener -import android.media.MediaScannerConnection -import android.net.Uri import android.os.Bundle import android.util.Log import android.view.LayoutInflater -import android.webkit.MimeTypeMap import androidx.camera.core.* import androidx.camera.lifecycle.ProcessCameraProvider import androidx.core.content.ContextCompat +import androidx.documentfile.provider.DocumentFile import awais.instagrabber.databinding.ActivityCameraBinding -import awais.instagrabber.utils.DirectoryUtils +import awais.instagrabber.utils.DownloadUtils import awais.instagrabber.utils.PermissionUtils import awais.instagrabber.utils.Utils import awais.instagrabber.utils.extensions.TAG -import com.google.common.io.Files -import java.io.File +import java.io.IOException import java.text.SimpleDateFormat import java.util.* import java.util.concurrent.ExecutionException @@ -28,10 +25,10 @@ import java.util.concurrent.Executors class CameraActivity : BaseLanguageActivity() { private lateinit var binding: ActivityCameraBinding - private lateinit var outputDirectory: File private lateinit var displayManager: DisplayManager private lateinit var cameraExecutor: ExecutorService + private var outputDirectory: DocumentFile? = null private var imageCapture: ImageCapture? = null private var displayId = -1 private var cameraProvider: ProcessCameraProvider? = null @@ -55,7 +52,7 @@ class CameraActivity : BaseLanguageActivity() { setContentView(binding.root) Utils.transparentStatusBar(this, true, false) displayManager = getSystemService(DISPLAY_SERVICE) as DisplayManager - outputDirectory = DirectoryUtils.getOutputMediaDirectory(this, "Camera") + outputDirectory = DownloadUtils.getCameraDir() cameraExecutor = Executors.newSingleThreadExecutor() displayManager.registerDisplayListener(displayListener, null) binding.viewFinder.post { @@ -176,33 +173,28 @@ class CameraActivity : BaseLanguageActivity() { private fun takePhoto() { if (imageCapture == null) return - val photoFile = File(outputDirectory, simpleDateFormat.format(System.currentTimeMillis()) + ".jpg") - val outputFileOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build() + val fileName = simpleDateFormat.format(System.currentTimeMillis()) + ".jpg" + val mimeType = "image/jpg" + val photoFile = outputDirectory?.createFile(mimeType, fileName)?.let { it } ?: return + val outputStream = contentResolver.openOutputStream(photoFile.uri)?.let { it } ?: return + val outputFileOptions = ImageCapture.OutputFileOptions.Builder(outputStream).build() imageCapture?.takePicture( outputFileOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback { @Suppress("UnstableApiUsage") override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { - val uri = Uri.fromFile(photoFile) - val mimeType = MimeTypeMap.getSingleton() - .getMimeTypeFromExtension(Files.getFileExtension(photoFile.name)) - MediaScannerConnection.scanFile( - this@CameraActivity, - arrayOf(photoFile.absolutePath), - arrayOf(mimeType) - ) { _: String?, uri1: Uri? -> - Log.d(TAG, "onImageSaved: scan complete") - val intent = Intent() - intent.data = uri1 - setResult(RESULT_OK, intent) - finish() - } - Log.d(TAG, "onImageSaved: $uri") + try { outputStream.close() } catch (ignored: IOException) {} + val intent = Intent() + intent.data = photoFile.uri + setResult(RESULT_OK, intent) + finish() + Log.d(TAG, "onImageSaved: " + photoFile.uri) } override fun onError(exception: ImageCaptureException) { Log.e(TAG, "onError: ", exception) + try { outputStream.close() } catch (ignored: IOException) {} } } ) diff --git a/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java b/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java index e76426c0..7fffbfec 100644 --- a/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java @@ -39,7 +39,7 @@ public class DirectorySelectActivity extends BaseLanguageActivity { viewModel = new ViewModelProvider(this).get(DirectorySelectActivityViewModel.class); setupObservers(); binding.selectDir.setOnClickListener(v -> openDirectoryChooser()); - AppExecutors.getInstance().mainThread().execute(() -> viewModel.setInitialUri(getIntent())); + AppExecutors.INSTANCE.getMainThread().execute(() -> viewModel.setInitialUri(getIntent())); } private void setupObservers() { @@ -81,7 +81,7 @@ public class DirectorySelectActivity extends BaseLanguageActivity { showErrorDialog(getString(R.string.select_a_folder)); return; } - AppExecutors.getInstance().mainThread().execute(() -> { + AppExecutors.INSTANCE.getMainThread().execute(() -> { try { viewModel.setupSelectedDir(data); final Intent intent = new Intent(this, MainActivity.class); diff --git a/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java index ba3f5202..bbf2e35e 100644 --- a/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java +++ b/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java @@ -207,8 +207,7 @@ public class CreateBackupDialogFragment extends DialogFragment { Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("application/octet-stream"); - final Date now = new Date(); - final String fileName = String.format("barinsta_%s.backup", BACKUP_FILE_DATE_TIME_FORMAT.format(now)); + final String fileName = String.format("barinsta_%s.backup", LocalDateTime.now().format(BACKUP_FILE_DATE_TIME_FORMAT)); intent.putExtra(Intent.EXTRA_TITLE, fileName); // Optionally, specify a URI for the directory that should be opened in diff --git a/app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java index 78431b6f..c4d8ea87 100644 --- a/app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java +++ b/app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java @@ -115,7 +115,7 @@ public class RestoreBackupDialogFragment extends DialogFragment { binding.btnRestore.setEnabled(true); } uri = data.getData(); - AppExecutors.getInstance().mainThread().execute(() -> { + AppExecutors.INSTANCE.getMainThread().execute(() -> { Cursor c = null; try { String[] projection = {MediaStore.Files.FileColumns.DISPLAY_NAME}; diff --git a/app/src/main/java/awais/instagrabber/fragments/imageedit/ImageEditFragment.java b/app/src/main/java/awais/instagrabber/fragments/imageedit/ImageEditFragment.java index 7586bdce..034b6706 100644 --- a/app/src/main/java/awais/instagrabber/fragments/imageedit/ImageEditFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/imageedit/ImageEditFragment.java @@ -183,11 +183,12 @@ public class ImageEditFragment extends Fragment { if (context == null) return; final Uri resultUri = viewModel.getResultUri().getValue(); if (resultUri == null) return; - Utils.mediaScanFile(context, new File(resultUri.toString()), (path, uri) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { + AppExecutors.INSTANCE.getMainThread().execute(() -> { final NavController navController = NavHostFragment.findNavController(this); setNavControllerResult(navController, resultUri); navController.navigateUp(); - })); + }); + // Utils.mediaScanFile(context, new File(resultUri.toString()), (path, uri) -> ); }); } diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt b/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt index bdc8a77c..bf1b7edf 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt +++ b/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt @@ -19,6 +19,7 @@ object PreferenceKeys { const val APP_THEME = "app_theme_v19" const val APP_LANGUAGE = "app_language_v19" const val STORY_SORT = "story_sort" + const val PREF_BARINSTA_DIR_URI = "barinsta_dir_uri" // set string prefs const val KEYWORD_FILTERS = "keyword_filters" diff --git a/app/src/main/java/awais/instagrabber/utils/BitmapUtils.kt b/app/src/main/java/awais/instagrabber/utils/BitmapUtils.kt index 379ea8db..4d6abced 100644 --- a/app/src/main/java/awais/instagrabber/utils/BitmapUtils.kt +++ b/app/src/main/java/awais/instagrabber/utils/BitmapUtils.kt @@ -8,6 +8,7 @@ import android.net.Uri import android.util.Log import android.util.LruCache import androidx.core.util.Pair +import androidx.documentfile.provider.DocumentFile import awais.instagrabber.utils.extensions.TAG import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -192,9 +193,9 @@ object BitmapUtils { } @Throws(IOException::class) - fun convertToJpegAndSaveToFile(bitmap: Bitmap, file: File?): File { - val tempFile = file ?: DownloadUtils.getTempFile() - FileOutputStream(tempFile).use { output -> + fun convertToJpegAndSaveToFile(contentResolver: ContentResolver, bitmap: Bitmap, file: DocumentFile?): DocumentFile { + val tempFile = file ?: DownloadUtils.getTempFile(null, "jpg") + contentResolver.openOutputStream(tempFile.uri).use { output -> val compressResult = bitmap.compress(Bitmap.CompressFormat.JPEG, 100, output) if (!compressResult) { throw RuntimeException("Compression failed!") diff --git a/app/src/main/java/awais/instagrabber/utils/Constants.kt b/app/src/main/java/awais/instagrabber/utils/Constants.kt index 8cf96e03..82b1f87e 100644 --- a/app/src/main/java/awais/instagrabber/utils/Constants.kt +++ b/app/src/main/java/awais/instagrabber/utils/Constants.kt @@ -89,4 +89,5 @@ object Constants { const val DM_THREAD_ACTION_EXTRA_THREAD_ID = "thread_id" const val DM_THREAD_ACTION_EXTRA_THREAD_TITLE = "thread_title" const val X_IG_APP_ID = "936619743392459" + const val EXTRA_INITIAL_URI = "initial_uri" } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java index 819c2b2c..755b6198 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java @@ -4,6 +4,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.UriPermission; +import android.Manifest; import android.net.Uri; import android.util.Log; import android.webkit.MimeTypeMap; @@ -59,6 +60,9 @@ public final class DownloadUtils { private static DocumentFile root; + public static final String WRITE_PERMISSION = Manifest.permission.WRITE_EXTERNAL_STORAGE; + public static final String[] PERMS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; + public static void init(@NonNull final Context context) throws ReselectDocumentTreeException { final String barinstaDirUri = Utils.settingsHelper.getString(PREF_BARINSTA_DIR_URI); if (TextUtils.isEmpty(barinstaDirUri)) { @@ -177,7 +181,7 @@ public final class DownloadUtils { private static List getSubPathForUserFolder(final String username) { final List list = new ArrayList<>(); - if (!Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) || TextUtils.isEmpty(username)) { + if (!Utils.settingsHelper.getBoolean(PreferenceKeys.DOWNLOAD_USER_FOLDER) || TextUtils.isEmpty(username)) { list.add(DIR_DOWNLOADS); return list; } @@ -425,7 +429,7 @@ public final class DownloadUtils { final String extension = DownloadUtils.getFileExtensionFromUrl(url); final String baseFileName = storyModel.getStoryMediaId() + "_" + storyModel.getTimestamp() + extension; - final String usernamePrepend = Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) + final String usernamePrepend = Utils.settingsHelper.getBoolean(PreferenceKeys.DOWNLOAD_PREPEND_USER_NAME) && storyModel.getUsername() != null ? storyModel.getUsername() + "_" : ""; final String fileName = usernamePrepend + baseFileName; DocumentFile saveFile = downloadDir.findFile(fileName); @@ -501,7 +505,7 @@ public final class DownloadUtils { if (childPositionIfSingle >= 0 && feedModels.size() == 1 && i != childPositionIfSingle) continue; final Media child = sliderItems.get(i); final String url = getUrlOfType(child); - final String usernamePrepend = Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) && mediaUser != null + final String usernamePrepend = Utils.settingsHelper.getBoolean(PreferenceKeys.DOWNLOAD_PREPEND_USER_NAME) && mediaUser != null ? mediaUser.getUsername() : ""; final Pair, String> pair = getDownloadChildSavePaths( diff --git a/app/src/main/java/awais/instagrabber/utils/MediaUploader.kt b/app/src/main/java/awais/instagrabber/utils/MediaUploader.kt index 911b30c8..65fff446 100644 --- a/app/src/main/java/awais/instagrabber/utils/MediaUploader.kt +++ b/app/src/main/java/awais/instagrabber/utils/MediaUploader.kt @@ -3,6 +3,7 @@ package awais.instagrabber.utils import android.content.ContentResolver import android.graphics.Bitmap import android.net.Uri +import androidx.documentfile.provider.DocumentFile import awais.instagrabber.models.UploadVideoOptions import awais.instagrabber.webservices.interceptors.AddCookiesInterceptor import kotlinx.coroutines.Dispatchers @@ -12,8 +13,6 @@ import okio.BufferedSink import okio.Okio import org.json.JSONObject import ru.gildor.coroutines.okhttp.await -import java.io.File -import java.io.FileInputStream import java.io.IOException import java.io.InputStream @@ -29,20 +28,23 @@ object MediaUploader { ): MediaUploadResponse = withContext(Dispatchers.IO) { val bitmapResult = BitmapUtils.loadBitmap(contentResolver, uri, 1000f, false) val bitmap = bitmapResult?.bitmap ?: throw IOException("bitmap is null") - uploadPhoto(bitmap) + uploadPhoto(contentResolver, bitmap) } @Suppress("BlockingMethodInNonBlockingContext") private suspend fun uploadPhoto( + contentResolver: ContentResolver, bitmap: Bitmap, ): MediaUploadResponse = withContext(Dispatchers.IO) { - val file: File = BitmapUtils.convertToJpegAndSaveToFile(bitmap, null) + val file: DocumentFile = BitmapUtils.convertToJpegAndSaveToFile(contentResolver, bitmap, null) val byteLength: Long = file.length() val options = createUploadPhotoOptions(byteLength) val headers = getUploadPhotoHeaders(options) val url = HOST + "/rupload_igphoto/" + options.name + "/" try { - FileInputStream(file).use { input -> upload(input, url, headers) } + contentResolver.openInputStream(file.uri).use { input -> + upload(input!!, url, headers) + } } finally { file.delete() } diff --git a/app/src/main/java/awais/instagrabber/utils/MediaUtils.java b/app/src/main/java/awais/instagrabber/utils/MediaUtils.java index f4084946..0d58a76f 100644 --- a/app/src/main/java/awais/instagrabber/utils/MediaUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/MediaUtils.java @@ -57,7 +57,7 @@ public final class MediaUtils { public static void getVoiceInfo(@NonNull final ContentResolver contentResolver, @NonNull final Uri uri, @NonNull final OnInfoLoadListener listener) { - AppExecutors.getInstance().tasksThread().submit(() -> { + AppExecutors.INSTANCE.getTasksThread().submit(() -> { try (ParcelFileDescriptor parcelFileDescriptor = contentResolver.openFileDescriptor(uri, "r")) { if (parcelFileDescriptor == null) { listener.onLoad(null); diff --git a/app/src/main/java/awais/instagrabber/utils/VoiceRecorder.java b/app/src/main/java/awais/instagrabber/utils/VoiceRecorder.java index 75ec561f..266d814c 100644 --- a/app/src/main/java/awais/instagrabber/utils/VoiceRecorder.java +++ b/app/src/main/java/awais/instagrabber/utils/VoiceRecorder.java @@ -1,6 +1,7 @@ package awais.instagrabber.utils; import android.app.Application; +import android.content.ContentResolver; import android.media.MediaRecorder; import android.os.Handler; import android.os.Message; @@ -44,7 +45,7 @@ public class VoiceRecorder { this.callback = callback; } - public void startRecording(final Application application) { + public void startRecording(final ContentResolver contentResolver) { stopped = false; ParcelFileDescriptor parcelFileDescriptor = null; try { @@ -53,7 +54,7 @@ public class VoiceRecorder { recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); deleteTempAudioFile(); audioTempFile = getAudioRecordFile(); - parcelFileDescriptor = application.getContentResolver().openFileDescriptor(audioTempFile.getUri(), "rwt"); + parcelFileDescriptor = contentResolver.openFileDescriptor(audioTempFile.getUri(), "rwt"); recorder.setOutputFile(parcelFileDescriptor.getFileDescriptor()); recorder.setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC); recorder.setAudioEncodingBitRate(AUDIO_BIT_RATE); diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.kt b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.kt index f7626af3..70407c06 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.kt +++ b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.kt @@ -1,10 +1,10 @@ package awais.instagrabber.viewmodels +import android.R.attr import android.app.Application import android.content.ContentResolver -import android.media.MediaScannerConnection import android.net.Uri -import android.util.Log +import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.* import awais.instagrabber.customviews.emoji.Emoji import awais.instagrabber.managers.DirectMessagesManager @@ -23,10 +23,9 @@ import awais.instagrabber.utils.MediaUtils.OnInfoLoadListener import awais.instagrabber.utils.MediaUtils.VideoInfo import awais.instagrabber.utils.VoiceRecorder.VoiceRecorderCallback import awais.instagrabber.utils.VoiceRecorder.VoiceRecordingResult -import awais.instagrabber.utils.extensions.TAG -import java.io.File import java.util.* + class DirectThreadViewModel( application: Application, val threadId: String, @@ -37,7 +36,7 @@ class DirectThreadViewModel( // private static final String ERROR_INVALID_THREAD = "Invalid thread"; private val contentResolver: ContentResolver = application.contentResolver - private val recordingsDir: File = DirectoryUtils.getOutputMediaDirectory(application, "Recordings") + private val recordingsDir: DocumentFile? = DownloadUtils.getRecordingsDir() private var voiceRecorder: VoiceRecorder? = null private lateinit var threadManager: ThreadManager @@ -87,33 +86,24 @@ class DirectThreadViewModel( fun startRecording(): LiveData> { val data = MutableLiveData>() - voiceRecorder = VoiceRecorder(recordingsDir, object : VoiceRecorderCallback { + voiceRecorder = VoiceRecorder(recordingsDir!!, object : VoiceRecorderCallback { override fun onStart() {} override fun onComplete(result: VoiceRecordingResult) { - Log.d(TAG, "onComplete: recording complete. Scanning file...") - MediaScannerConnection.scanFile( - getApplication(), - arrayOf(result.file.absolutePath), - arrayOf(result.mimeType) - ) { _: String?, uri: Uri? -> - if (uri == null) { - val msg = "Scan failed!" - Log.e(TAG, msg) - data.postValue(error(msg, null)) - return@scanFile - } - Log.d(TAG, "onComplete: scan complete") - MediaUtils.getVoiceInfo(contentResolver, uri, object : OnInfoLoadListener { + // Log.d(TAG, "onComplete: recording complete. Scanning file..."); + MediaUtils.getVoiceInfo( + contentResolver, + result.file.uri, + object : OnInfoLoadListener { override fun onLoad(videoInfo: VideoInfo?) { if (videoInfo == null) return threadManager.sendVoice( data, - uri, + result.file.uri, result.waveform, result.samplingFreq, videoInfo.duration, - videoInfo.size, - viewModelScope, + result.file.length(), + viewModelScope ) } @@ -121,12 +111,11 @@ class DirectThreadViewModel( data.postValue(error(t.message, null)) } }) - } } override fun onCancel() {} }) - voiceRecorder?.startRecording() + voiceRecorder?.startRecording(contentResolver) return data } diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.java index a1bfdccf..03e0420f 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.java @@ -24,6 +24,8 @@ import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; +import static awais.instagrabber.fragments.settings.PreferenceKeys.FOLDER_PATH; + public class DirectorySelectActivityViewModel extends AndroidViewModel { private static final String TAG = DirectorySelectActivityViewModel.class.getSimpleName(); @@ -67,7 +69,7 @@ public class DirectorySelectActivityViewModel extends AndroidViewModel { private void setMessage(@Nullable final Uri initialUri) { if (initialUri == null) { - final String prevVersionFolderPath = Utils.settingsHelper.getString(Constants.FOLDER_PATH); + final String prevVersionFolderPath = Utils.settingsHelper.getString(FOLDER_PATH); if (TextUtils.isEmpty(prevVersionFolderPath)) { // default message message.postValue(getApplication().getString(R.string.dir_select_default_message)); diff --git a/app/src/main/java/awais/instagrabber/workers/DownloadWorker.kt b/app/src/main/java/awais/instagrabber/workers/DownloadWorker.kt index a749cc07..b182c514 100644 --- a/app/src/main/java/awais/instagrabber/workers/DownloadWorker.kt +++ b/app/src/main/java/awais/instagrabber/workers/DownloadWorker.kt @@ -6,8 +6,8 @@ import android.content.ContentResolver import android.content.Context import android.content.Intent import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.media.MediaMetadataRetriever -import android.media.MediaScannerConnection import android.net.Uri import android.os.Build import android.os.Handler @@ -15,7 +15,7 @@ import android.os.Looper import android.util.Log import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat -import androidx.core.content.FileProvider +import androidx.documentfile.provider.DocumentFile import androidx.work.CoroutineWorker import androidx.work.Data import androidx.work.ForegroundInfo @@ -37,13 +37,12 @@ import kotlinx.coroutines.withContext import org.apache.commons.imaging.formats.jpeg.iptc.JpegIptcRewriter import java.io.BufferedInputStream import java.io.File -import java.io.FileInputStream -import java.io.FileOutputStream import java.net.URL import java.util.* import java.util.concurrent.ExecutionException import kotlin.math.abs + class DownloadWorker(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) { private val notificationManager: NotificationManagerCompat = NotificationManagerCompat.from(context) @@ -89,15 +88,15 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti return Result.success() } - private suspend fun download(urlToFilePathMap: Map) { + private suspend fun download(urlToFilePathMap: Map) { val notificationId = notificationId val entries = urlToFilePathMap.entries var count = 1 val total = urlToFilePathMap.size - for ((url, value) in entries) { + for ((url, file) in entries) { updateDownloadProgress(notificationId, count, total, 0f) withContext(Dispatchers.IO) { - download(notificationId, count, total, url, value) + download(notificationId, count, total, url, file) } count++ } @@ -111,47 +110,49 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti position: Int, total: Int, url: String, - filePath: String, + filePath: DocumentFile, ) { - val isJpg = filePath.endsWith("jpg") + val context = applicationContext.let { it } + val contentResolver = context.contentResolver?.let { it } ?: return + val filePathType = filePath.type?.let { it } ?: return + val isJpg = filePathType.startsWith("image") // using temp file approach to remove IPTC so that download progress can be reported - val outFile = if (isJpg) DownloadUtils.getTempFile() else File(filePath) + val outFile = if (isJpg) DownloadUtils.getTempFile(null, "jpg") else filePath try { val urlConnection = URL(url).openConnection() val fileSize = if (Build.VERSION.SDK_INT >= 24) urlConnection.contentLengthLong else urlConnection.contentLength.toLong() var totalRead = 0f try { BufferedInputStream(urlConnection.getInputStream()).use { bis -> - FileOutputStream(outFile).use { fos -> + contentResolver.openOutputStream(outFile.uri).use { fos -> val buffer = ByteArray(0x2000) var count: Int while (bis.read(buffer, 0, 0x2000).also { count = it } != -1) { totalRead += count - fos.write(buffer, 0, count) + fos!!.write(buffer, 0, count) setProgressAsync(Data.Builder().putString(URL, url) .putFloat(PROGRESS, totalRead * 100f / fileSize) .build()) updateDownloadProgress(notificationId, position, total, totalRead * 100f / fileSize) } - fos.flush() + fos!!.flush() } } } catch (e: Exception) { - Log.e(TAG, "Error while writing data from url: " + url + " to file: " + outFile.absolutePath, e) + Log.e(TAG, "Error while writing data from url: " + url + " to file: " + outFile.name, e) } if (isJpg) { - val finalFile = File(filePath) try { - FileInputStream(outFile).use { fis -> - FileOutputStream(finalFile).use { fos -> + contentResolver.openInputStream(outFile.uri).use { fis -> + contentResolver.openOutputStream(filePath.uri).use { fos -> val jpegIptcRewriter = JpegIptcRewriter() jpegIptcRewriter.removeIPTC(fis, fos) } } } catch (e: Exception) { Log.e(TAG, "Error while removing iptc: url: " + url - + ", tempFile: " + outFile.absolutePath - + ", finalFile: " + finalFile.absolutePath, e) + + ", tempFile: " + outFile.name + + ", finalFile: " + filePath.name, e) } val deleted = outFile.delete() if (!deleted) { @@ -218,53 +219,90 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti return builder.build() } - private fun showSummary(urlToFilePathMap: Map?) { + private fun showSummary(urlToFilePathMap: Map?) { val context = applicationContext val filePaths = urlToFilePathMap!!.values val notifications: MutableList = LinkedList() val notificationIds: MutableList = LinkedList() var count = 1 - for (filePath in filePaths) { - val file = File(filePath) - context.sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file))) - MediaScannerConnection.scanFile(context, arrayOf(file.absolutePath), null, null) - val uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file) + for (filePath: DocumentFile in filePaths) { + // final File file = new File(filePath); + // context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, filePath.getUri())); + // Utils.scanDocumentFile(context, filePath, (path, uri) -> {}); + // final Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file); val contentResolver = context.contentResolver - val bitmap = getThumbnail(context, file, uri, contentResolver) + var bitmap: Bitmap? = null + val mimeType = filePath.type // Utils.getMimeType(uri, contentResolver); + if (!isEmpty(mimeType)) { + if (mimeType!!.startsWith("image")) { + try { + contentResolver.openInputStream(filePath.uri).use { inputStream -> + bitmap = BitmapFactory.decodeStream(inputStream) + } + } catch (e: java.lang.Exception) { + if (BuildConfig.DEBUG) Log.e(TAG, "", e) + } + } else if (mimeType.startsWith("video")) { + val retriever = MediaMetadataRetriever() + try { + try { + retriever.setDataSource(context, filePath.uri) + } catch (e: java.lang.Exception) { + // retriever.setDataSource(file.getAbsolutePath()); + Log.e(TAG, "showSummary: ", e) + } + bitmap = retriever.frameAtTime + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) try { + retriever.close() + } catch (e: java.lang.Exception) { + Log.e(TAG, "showSummary: ", e) + } + } catch (e: java.lang.Exception) { + Log.e(TAG, "", e) + } + } + } val downloadComplete = context.getString(R.string.downloader_complete) - val intent = Intent(Intent.ACTION_VIEW, uri) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK - or Intent.FLAG_FROM_BACKGROUND - or Intent.FLAG_GRANT_READ_URI_PERMISSION - or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - .putExtra(Intent.EXTRA_STREAM, uri) + val intent = Intent(Intent.ACTION_VIEW, filePath.uri) + .addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK + or Intent.FLAG_FROM_BACKGROUND + or Intent.FLAG_GRANT_READ_URI_PERMISSION + or Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ) + .putExtra(Intent.EXTRA_STREAM, filePath.uri) val pendingIntent = PendingIntent.getActivity( context, DOWNLOAD_NOTIFICATION_INTENT_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_ONE_SHOT ) - val notificationId = notificationId + count + val notificationId: Int = notificationId + count notificationIds.add(notificationId) count++ - val builder: NotificationCompat.Builder = NotificationCompat.Builder(context, DOWNLOAD_CHANNEL_ID) - .setSmallIcon(R.drawable.ic_download) - .setContentText(null) - .setContentTitle(downloadComplete) - .setWhen(System.currentTimeMillis()) - .setOnlyAlertOnce(true) - .setAutoCancel(true) - .setGroup(NOTIF_GROUP_NAME + "_" + id) - .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) - .setContentIntent(pendingIntent) - .addAction(R.drawable.ic_delete, - context.getString(R.string.delete), - DeleteImageIntentService.pendingIntent(context, filePath, notificationId)) + val builder: NotificationCompat.Builder = + NotificationCompat.Builder(context, DOWNLOAD_CHANNEL_ID) + .setSmallIcon(R.drawable.ic_download) + .setContentText(null) + .setContentTitle(downloadComplete) + .setWhen(System.currentTimeMillis()) + .setOnlyAlertOnce(true) + .setAutoCancel(true) + .setGroup(NOTIF_GROUP_NAME + "_" + id) + .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) + .setContentIntent(pendingIntent) + .addAction( + R.drawable.ic_delete, + context.getString(R.string.delete), + DeleteImageIntentService.pendingIntent(context, filePath, notificationId) + ) if (bitmap != null) { builder.setLargeIcon(bitmap) - .setStyle(NotificationCompat.BigPictureStyle() - .bigPicture(bitmap) - .bigLargeIcon(null)) + .setStyle( + NotificationCompat.BigPictureStyle() + .bigPicture(bitmap) + .bigLargeIcon(null) + ) .setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL) } notifications.add(builder) @@ -344,16 +382,16 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti return bitmap } - class DownloadRequest private constructor(val urlToFilePathMap: Map) { + class DownloadRequest private constructor(val urlToFilePathMap: Map) { class Builder { - private var urlToFilePathMap: MutableMap = mutableMapOf() - fun setUrlToFilePathMap(urlToFilePathMap: MutableMap): Builder { + private var urlToFilePathMap: MutableMap = mutableMapOf() + fun setUrlToFilePathMap(urlToFilePathMap: MutableMap): Builder { this.urlToFilePathMap = urlToFilePathMap return this } - fun addUrl(url: String, filePath: String): Builder { + fun addUrl(url: String, filePath: DocumentFile): Builder { urlToFilePathMap[url] = filePath return this } From 2038f57472dead1bd501a573c7cd4e90ad3e8c7a Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sun, 13 Jun 2021 17:58:25 -0400 Subject: [PATCH 14/17] preference and post support (not functional) --- .../fragments/PostViewV2Fragment.java | 17 +- .../DownloadsPreferencesFragment.java | 238 +++++++++++++----- 2 files changed, 183 insertions(+), 72 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java b/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java index 6efd3711..873448da 100644 --- a/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java @@ -105,7 +105,7 @@ import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; import awais.instagrabber.viewmodels.PostViewV2ViewModel; -import static androidx.core.content.PermissionChecker.checkSelfPermission; +//import static androidx.core.content.PermissionChecker.checkSelfPermission; import static awais.instagrabber.fragments.HashTagFragment.ARG_HASHTAG; import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_SHOWN_COUNT_TOOLTIP; import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; @@ -119,6 +119,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme private static final int STORAGE_PERM_REQUEST_CODE = 8020; private DialogPostViewBinding binding; + private Context context; private boolean detailsVisible = true; private boolean video; private VideoPlayerViewHelper videoPlayerViewHelper; @@ -211,6 +212,12 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme init(); } + @Override + public void onAttach(@NonNull final Context context) { + super.onAttach(context); + this.context = context; + } + @Override public void onPause() { super.onPause(); @@ -454,13 +461,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme private void setupDownload() { bottom.download.setOnClickListener(v -> { - final Context context = getContext(); - if (context == null) return; - if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { - DownloadUtils.showDownloadDialog(context, viewModel.getMedia(), sliderPosition); - return; - } - requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); + DownloadUtils.showDownloadDialog(context, viewModel.getMedia(), sliderPosition); }); TooltipCompat.setTooltipText(bottom.download, getString(R.string.action_download)); } diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java index 6a18c69a..3f76ffcc 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java @@ -1,27 +1,41 @@ package awais.instagrabber.fragments.settings; import android.content.Context; -import android.view.View; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.provider.DocumentsContract; +import android.util.Log; import androidx.annotation.NonNull; -import androidx.appcompat.widget.AppCompatButton; -import androidx.appcompat.widget.AppCompatTextView; +import androidx.annotation.Nullable; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; -import androidx.preference.PreferenceViewHolder; import androidx.preference.SwitchPreferenceCompat; -import com.google.android.material.switchmaterial.SwitchMaterial; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import awais.instagrabber.R; -import awais.instagrabber.utils.DirectoryChooser; +import awais.instagrabber.dialogs.ConfirmDialogFragment; +import awais.instagrabber.utils.AppExecutors; +import awais.instagrabber.utils.Constants; +import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.TextUtils; +import awais.instagrabber.utils.Utils; -import static awais.instagrabber.fragments.settings.PreferenceKeys.FOLDER_PATH; -import static awais.instagrabber.fragments.settings.PreferenceKeys.FOLDER_SAVE_TO; +import static android.app.Activity.RESULT_OK; +import static awais.instagrabber.activities.DirectorySelectActivity.SELECT_DIR_REQUEST_CODE; import static awais.instagrabber.utils.Utils.settingsHelper; public class DownloadsPreferencesFragment extends BasePreferencesFragment { + private static final String TAG = DownloadsPreferencesFragment.class.getSimpleName(); + // private SaveToCustomFolderPreference.ResultCallback resultCallback; + @Override void setupPreferenceScreen(final PreferenceScreen screen) { final Context context = getContext(); @@ -40,13 +54,88 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment { } private Preference getSaveToCustomFolderPreference(@NonNull final Context context) { - return new SaveToCustomFolderPreference(context, (resultCallback) -> new DirectoryChooser() - .setInitialDirectory(settingsHelper.getString(FOLDER_PATH)) - .setInteractionListener(file -> { - settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath()); - resultCallback.onResult(file.getAbsolutePath()); - }) - .show(getParentFragmentManager(), null)); + final Preference preference = new Preference(context); + preference.setKey(PreferenceKeys.PREF_BARINSTA_DIR_URI); + preference.setIconSpaceReserved(false); + preference.setTitle(R.string.barinsta_folder); + preference.setSummaryProvider(p -> { + final String currentValue = settingsHelper.getString(PreferenceKeys.PREF_BARINSTA_DIR_URI); + if (TextUtils.isEmpty(currentValue)) return ""; + String path; + try { + path = URLDecoder.decode(currentValue, StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException e) { + path = currentValue; + } + return path; + }); + preference.setOnPreferenceClickListener(p -> { + openDirectoryChooser(DownloadUtils.getRootDirUri()); + return true; + }); + return preference; + // return new SaveToCustomFolderPreference(context, checked -> { + // try { + // DownloadUtils.init(context); + // } catch (DownloadUtils.ReselectDocumentTreeException e) { + // if (!checked) return; + // startDocumentSelector(e.getInitialUri()); + // } catch (Exception e) { + // Log.e(TAG, "getSaveToCustomFolderPreference: ", e); + // } + // }, (resultCallback) -> { + // // Choose a directory using the system's file picker. + // startDocumentSelector(null); + // this.resultCallback = resultCallback; + // + // // new DirectoryChooser() + // // .setInitialDirectory(settingsHelper.getString(FOLDER_PATH)) + // // .setInteractionListener(file -> { + // // settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath()); + // // resultCallback.onResult(file.getAbsolutePath()); + // // }) + // // .show(getParentFragmentManager(), null); + // }); + } + + private void openDirectoryChooser(final Uri initialUri) { + final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && initialUri != null) { + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initialUri); + } + startActivityForResult(intent, SELECT_DIR_REQUEST_CODE); + } + + @Override + public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { + if (requestCode != SELECT_DIR_REQUEST_CODE) return; + if (resultCode != RESULT_OK) return; + if (data == null || data.getData() == null) return; + final Context context = getContext(); + if (context == null) return; + AppExecutors.INSTANCE.getMainThread().execute(() -> { + try { + Utils.setupSelectedDir(context, data); + } catch (Exception e) { + // Should not come to this point. + // If it does, we have to show this error to the user so that they can report it. + try (final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw)) { + e.printStackTrace(pw); + final ConfirmDialogFragment dialogFragment = ConfirmDialogFragment.newInstance( + 123, + R.string.error, + "Please report this error to the developers:\n\n" + sw.toString(), + R.string.ok, + 0, + 0 + ); + dialogFragment.show(getChildFragmentManager(), ConfirmDialogFragment.class.getSimpleName()); + } catch (IOException ioException) { + Log.e(TAG, "onActivityResult: ", ioException); + } + } + }, 500); } private Preference getPrependUsernameToFilenamePreference(@NonNull final Context context) { @@ -57,53 +146,74 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment { return preference; } - public static class SaveToCustomFolderPreference extends Preference { - private AppCompatTextView customPathTextView; - private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener; - private final String key; - - public SaveToCustomFolderPreference(final Context context, final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener) { - super(context); - this.onSelectFolderButtonClickListener = onSelectFolderButtonClickListener; - key = PreferenceKeys.FOLDER_SAVE_TO; - setLayoutResource(R.layout.pref_custom_folder); - setKey(key); - setTitle(R.string.save_to_folder); - setIconSpaceReserved(false); - } - - @Override - public void onBindViewHolder(final PreferenceViewHolder holder) { - super.onBindViewHolder(holder); - final SwitchMaterial cbSaveTo = (SwitchMaterial) holder.findViewById(R.id.cbSaveTo); - final View buttonContainer = holder.findViewById(R.id.button_container); - customPathTextView = (AppCompatTextView) holder.findViewById(R.id.custom_path); - cbSaveTo.setOnCheckedChangeListener((buttonView, isChecked) -> { - settingsHelper.putBoolean(FOLDER_SAVE_TO, isChecked); - buttonContainer.setVisibility(isChecked ? View.VISIBLE : View.GONE); - final String customPath = settingsHelper.getString(FOLDER_PATH); - customPathTextView.setText(customPath); - }); - final boolean savedToEnabled = settingsHelper.getBoolean(key); - holder.itemView.setOnClickListener(v -> cbSaveTo.toggle()); - cbSaveTo.setChecked(savedToEnabled); - buttonContainer.setVisibility(savedToEnabled ? View.VISIBLE : View.GONE); - final AppCompatButton btnSaveTo = (AppCompatButton) holder.findViewById(R.id.btnSaveTo); - btnSaveTo.setOnClickListener(v -> { - if (onSelectFolderButtonClickListener == null) return; - onSelectFolderButtonClickListener.onClick(result -> { - if (TextUtils.isEmpty(result)) return; - customPathTextView.setText(result); - }); - }); - } - - public interface ResultCallback { - void onResult(String result); - } - - public interface OnSelectFolderButtonClickListener { - void onClick(ResultCallback resultCallback); - } - } + // public static class SaveToCustomFolderPreference extends Preference { + // private AppCompatTextView customPathTextView; + // private final OnSaveToChangeListener onSaveToChangeListener; + // private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener; + // private final String key; + // + // public SaveToCustomFolderPreference(final Context context, + // final OnSaveToChangeListener onSaveToChangeListener, + // final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener) { + // super(context); + // this.onSaveToChangeListener = onSaveToChangeListener; + // this.onSelectFolderButtonClickListener = onSelectFolderButtonClickListener; + // key = FOLDER_SAVE_TO; + // setLayoutResource(R.layout.pref_custom_folder); + // setKey(key); + // setTitle(R.string.save_to_folder); + // setIconSpaceReserved(false); + // } + // + // @Override + // public void onBindViewHolder(final PreferenceViewHolder holder) { + // super.onBindViewHolder(holder); + // final SwitchMaterial cbSaveTo = (SwitchMaterial) holder.findViewById(R.id.cbSaveTo); + // final View buttonContainer = holder.findViewById(R.id.button_container); + // customPathTextView = (AppCompatTextView) holder.findViewById(R.id.custom_path); + // cbSaveTo.setOnCheckedChangeListener((buttonView, isChecked) -> { + // settingsHelper.putBoolean(FOLDER_SAVE_TO, isChecked); + // buttonContainer.setVisibility(isChecked ? View.VISIBLE : View.GONE); + // final Context context = getContext(); + // String customPath = settingsHelper.getString(FOLDER_PATH); + // if (!TextUtils.isEmpty(customPath) && customPath.startsWith("content") && context != null) { + // final Uri uri = Uri.parse(customPath); + // final DocumentFile documentFile = DocumentFile.fromSingleUri(context, uri); + // try { + // customPath = Utils.getDocumentFileRealPath(context, documentFile).getAbsolutePath(); + // } catch (Exception e) { + // Log.e(TAG, "onBindViewHolder: ", e); + // } + // } + // customPathTextView.setText(customPath); + // if (onSaveToChangeListener != null) { + // onSaveToChangeListener.onChange(isChecked); + // } + // }); + // final boolean savedToEnabled = settingsHelper.getBoolean(key); + // holder.itemView.setOnClickListener(v -> cbSaveTo.toggle()); + // cbSaveTo.setChecked(savedToEnabled); + // buttonContainer.setVisibility(savedToEnabled ? View.VISIBLE : View.GONE); + // final AppCompatButton btnSaveTo = (AppCompatButton) holder.findViewById(R.id.btnSaveTo); + // btnSaveTo.setOnClickListener(v -> { + // if (onSelectFolderButtonClickListener == null) return; + // onSelectFolderButtonClickListener.onClick(result -> { + // if (TextUtils.isEmpty(result)) return; + // customPathTextView.setText(result); + // }); + // }); + // } + // + // public interface ResultCallback { + // void onResult(String result); + // } + // + // public interface OnSelectFolderButtonClickListener { + // void onClick(ResultCallback resultCallback); + // } + // + // public interface OnSaveToChangeListener { + // void onChange(boolean checked); + // } + // } } From 6ed9e8458fb1117208530ee486c15c9731eae97a Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sun, 13 Jun 2021 20:51:22 -0400 Subject: [PATCH 15/17] initialize downloadutils on launch --- .../awais/instagrabber/activities/MainActivity.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.kt b/app/src/main/java/awais/instagrabber/activities/MainActivity.kt index 90be552a..adb1e728 100644 --- a/app/src/main/java/awais/instagrabber/activities/MainActivity.kt +++ b/app/src/main/java/awais/instagrabber/activities/MainActivity.kt @@ -8,6 +8,7 @@ import android.content.ComponentName import android.content.Intent import android.content.ServiceConnection import android.os.* +import android.provider.DocumentsContract.EXTRA_INITIAL_URI import android.text.Editable import android.util.Log import android.view.Menu @@ -53,6 +54,7 @@ import awais.instagrabber.services.ActivityCheckerService import awais.instagrabber.services.DMSyncAlarmReceiver import awais.instagrabber.utils.* import awais.instagrabber.utils.AppExecutors.tasksThread +import awais.instagrabber.utils.DownloadUtils.ReselectDocumentTreeException import awais.instagrabber.utils.TextUtils.isEmpty import awais.instagrabber.utils.TextUtils.shortcodeToId import awais.instagrabber.utils.emoji.EmojiParser @@ -73,6 +75,7 @@ import kotlinx.coroutines.withContext import java.util.* import java.util.stream.Collectors + class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedListener { private lateinit var binding: ActivityMainBinding @@ -107,6 +110,16 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL private val graphQLRepository: GraphQLRepository by lazy { GraphQLRepository.getInstance() } override fun onCreate(savedInstanceState: Bundle?) { + try { + DownloadUtils.init(this) + } catch (e: ReselectDocumentTreeException) { + super.onCreate(savedInstanceState) + val intent = Intent(this, DirectorySelectActivity::class.java) + intent.putExtra(EXTRA_INITIAL_URI, e.initialUri) + startActivity(intent) + finish() + return + } super.onCreate(savedInstanceState) instance = this binding = ActivityMainBinding.inflate(layoutInflater) From c47272792e566046f5b80045f95e8efb2d98bd06 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sun, 13 Jun 2021 22:09:07 -0400 Subject: [PATCH 16/17] resolve gson crash --- .../instagrabber/workers/DownloadWorker.kt | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/workers/DownloadWorker.kt b/app/src/main/java/awais/instagrabber/workers/DownloadWorker.kt index b182c514..dd6f1352 100644 --- a/app/src/main/java/awais/instagrabber/workers/DownloadWorker.kt +++ b/app/src/main/java/awais/instagrabber/workers/DownloadWorker.kt @@ -40,9 +40,10 @@ import java.io.File import java.net.URL import java.util.* import java.util.concurrent.ExecutionException +import java.util.stream.Collectors +import kotlin.collections.Map import kotlin.math.abs - class DownloadWorker(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) { private val notificationManager: NotificationManagerCompat = NotificationManagerCompat.from(context) @@ -88,15 +89,16 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti return Result.success() } - private suspend fun download(urlToFilePathMap: Map) { + private suspend fun download(urlToFilePathMap: Map) { val notificationId = notificationId val entries = urlToFilePathMap.entries var count = 1 val total = urlToFilePathMap.size - for ((url, file) in entries) { + for ((url, uriString) in entries) { updateDownloadProgress(notificationId, count, total, 0f) withContext(Dispatchers.IO) { - download(notificationId, count, total, url, file) + val file = DocumentFile.fromSingleUri(applicationContext, Uri.parse(uriString)) + download(notificationId, count, total, url, file!!) } count++ } @@ -219,9 +221,9 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti return builder.build() } - private fun showSummary(urlToFilePathMap: Map?) { + private fun showSummary(urlToFilePathMap: Map) { val context = applicationContext - val filePaths = urlToFilePathMap!!.values + val filePaths = urlToFilePathMap.mapNotNull { DocumentFile.fromSingleUri(context, Uri.parse(it.value)) } val notifications: MutableList = LinkedList() val notificationIds: MutableList = LinkedList() var count = 1 @@ -382,17 +384,19 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti return bitmap } - class DownloadRequest private constructor(val urlToFilePathMap: Map) { + class DownloadRequest private constructor(val urlToFilePathMap: Map) { class Builder { - private var urlToFilePathMap: MutableMap = mutableMapOf() + private var urlToFilePathMap: MutableMap = mutableMapOf() fun setUrlToFilePathMap(urlToFilePathMap: MutableMap): Builder { this.urlToFilePathMap = urlToFilePathMap + .mapValues { it.value.uri.toString() } + .toMutableMap() return this } fun addUrl(url: String, filePath: DocumentFile): Builder { - urlToFilePathMap[url] = filePath + urlToFilePathMap[url] = filePath.uri.toString() return this } From bf22b867231924f9ae8aaa7c57f1c69becd64620 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sun, 13 Jun 2021 22:22:53 -0400 Subject: [PATCH 17/17] fix downloadworker; now functional --- .../java/awais/instagrabber/workers/DownloadWorker.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/workers/DownloadWorker.kt b/app/src/main/java/awais/instagrabber/workers/DownloadWorker.kt index dd6f1352..27c47c3a 100644 --- a/app/src/main/java/awais/instagrabber/workers/DownloadWorker.kt +++ b/app/src/main/java/awais/instagrabber/workers/DownloadWorker.kt @@ -55,9 +55,14 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti .build()) } val downloadRequestString: String - val requestFile = File(downloadRequestFilePath) + val requestFile = Uri.parse(downloadRequestFilePath) + val context = applicationContext + val contentResolver = context.contentResolver ?: return Result.failure(Data.Builder() + .putString("error", "contentResolver is null") + .build()) try { - downloadRequestString = requestFile.bufferedReader().use { it.readText() } + val scanner = Scanner(contentResolver.openInputStream(requestFile)) + downloadRequestString = scanner.useDelimiter("\\A").next() } catch (e: Exception) { Log.e(TAG, "doWork: ", e) return Result.failure(Data.Builder() @@ -82,7 +87,7 @@ class DownloadWorker(context: Context, workerParams: WorkerParameters) : Corouti val urlToFilePathMap = downloadRequest.urlToFilePathMap download(urlToFilePathMap) Handler(Looper.getMainLooper()).postDelayed({ showSummary(urlToFilePathMap) }, 500) - val deleted = requestFile.delete() + val deleted = DocumentFile.fromSingleUri(context, requestFile)!!.delete() if (!deleted) { Log.w(TAG, "doWork: requestFile not deleted!") }