From 916536e5cd4ac31192b69717046e653dedabe9b1 Mon Sep 17 00:00:00 2001 From: zhwanng <48609908+zhwanng@users.noreply.github.com> Date: Fri, 3 Nov 2023 16:05:17 +0800 Subject: [PATCH] fix bugs 1. [fix] https://github.com/haiwen/seadroid/issues/985 2.[modify] WRITE_EXTERNAL_STORAGE Modified to MANAGE_EXTERNAL_STORAGE in BrowserActivity 3. fix other bugs --- .gitignore | 1 - app/build.gradle | 4 +- app/src/main/AndroidManifest.xml | 4 +- .../com/seafile/seadroid2/SeafConnection.java | 2 +- .../seafile/seadroid2/data/DataManager.java | 16 +- .../seadroid2/data/DatabaseHelper.java | 6 +- .../folderbackup/FolderBackupService.java | 8 +- .../seadroid2/transfer/TransferTask.java | 3 + .../ui/activity/BrowserActivity.java | 53 +++++-- .../seadroid2/ui/adapter/GalleryAdapter.java | 5 +- .../seadroid2/ui/fragment/ReposFragment.java | 2 - .../ui/fragment/SettingsFragment.java | 26 ++-- .../com/seafile/seadroid2/util/FileUtils.java | 141 ++++++++++++++++++ app/src/main/res/values-fr/strings.xml | 43 +++++- app/src/main/res/values-zh-rCN/strings.xml | 28 ++-- app/src/main/res/values/strings.xml | 1 + gradle.properties | 15 ++ 17 files changed, 296 insertions(+), 62 deletions(-) create mode 100644 app/src/main/java/com/seafile/seadroid2/util/FileUtils.java create mode 100644 gradle.properties diff --git a/.gitignore b/.gitignore index d230d5a47..0423c756f 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,6 @@ lint.xml # Gradle files .gradle/ **/build/ -gradle.properties # Local configuration file (sdk path, etc) local.properties diff --git a/app/build.gradle b/app/build.gradle index ac3b0a4a5..7bdc699ff 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId 'com.seafile.seadroid2' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 138 - versionName "2.3.5" + versionCode 139 + versionName "2.3.6" multiDexEnabled true resValue "string", "authorities", applicationId + '.cameraupload.provider' resValue "string", "account_type", "com.seafile.seadroid2.account.api2" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 198ca1ba1..7594ed365 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -33,9 +33,11 @@ - + + " + parentPath); - Utils.utilsLogInfo(false, "filePath ->" + fb.getFilePath()); + Utils.utilsLogInfo(false, "localPath ->" + fb.getFilePath()); FolderBackupInfo fileInfo = databaseHelper.getBackupFileInfo( repoConfig.getRepoID(), @@ -208,10 +208,14 @@ private void startBackupFolder(String parentPath, String filePath) { String.valueOf(fb.getSimpleSize())); // if (fileInfo != null && !TextUtils.isEmpty(fileInfo.filePath)) { - Utils.utilsLogInfo(false, "file not exists ->" + fileInfo.filePath); + Utils.utilsLogInfo(false, "db exists ->" + fileInfo.filePath); + Utils.utilsLogInfo(false, "---------------"); continue; } + Utils.utilsLogInfo(false, "uploadFile ->" + fb.getFilePath()); + Utils.utilsLogInfo(false, "---------------"); + int taskID = txService.addTaskToSourceQue(Utils.TRANSFER_FOLDER_TAG, currentAccount, repoConfig.getRepoID(), repoConfig.getRepoName(), parentPath, fb.getFilePath(), false, false); diff --git a/app/src/main/java/com/seafile/seadroid2/transfer/TransferTask.java b/app/src/main/java/com/seafile/seadroid2/transfer/TransferTask.java index 048ffb8b4..137946bb6 100644 --- a/app/src/main/java/com/seafile/seadroid2/transfer/TransferTask.java +++ b/app/src/main/java/com/seafile/seadroid2/transfer/TransferTask.java @@ -1,6 +1,8 @@ package com.seafile.seadroid2.transfer; import android.os.AsyncTask; +import android.os.FileUtils; + import com.seafile.seadroid2.SeafException; import com.seafile.seadroid2.account.Account; @@ -28,6 +30,7 @@ public TransferTask(String source, int taskID, Account account, String repoName, this.account = account; this.repoName = repoName; this.repoID = repoID; + this.path = path; this.state = TaskState.INIT; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/BrowserActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/activity/BrowserActivity.java index c056d43ff..f5ccb7305 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/BrowserActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/activity/BrowserActivity.java @@ -15,8 +15,10 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; +import android.os.Environment; import android.os.IBinder; import android.provider.MediaStore; +import android.provider.Settings; import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; @@ -25,6 +27,7 @@ import android.view.View; import android.view.Window; import android.widget.FrameLayout; +import android.widget.Toast; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.Toolbar; @@ -39,6 +42,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.viewpager.widget.ViewPager; +import com.blankj.utilcode.util.ToastUtils; import com.google.android.material.snackbar.Snackbar; import com.google.android.material.tabs.TabLayout; import com.google.common.collect.Lists; @@ -406,15 +410,13 @@ public void onPageScrollStateChanged(int state) { requestServerInfo(); - requestReadExternalStoragePermission(); +// requestReadExternalStoragePermission(); + checkAndRequestManageStoragePermission(); + Utils.startCameraSyncJob(this); syncCamera(); } - public FrameLayout getmContainer() { - return mContainer; - } - private void finishAndStartAccountsActivity() { Intent newIntent = new Intent(this, AccountsActivity.class); newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -566,18 +568,49 @@ public void onTaskSuccess() { return super.onOptionsItemSelected(item); } + private void checkAndRequestManageStoragePermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (!Environment.isExternalStorageManager()) { + new AlertDialog.Builder(this) + .setMessage(R.string.permission_manage_exteral_storage_rationale) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); + intent.setData(Uri.parse("package:" + BrowserActivity.this.getPackageName())); + startActivity(intent); + } + }) + .show(); + } + + } else { + requestReadExternalStoragePermission(); + } + } + /** - * If the user is running Android 6.0 (API level 23) or later, the user has to grant your app its permissions while they are running the app + *

+ * If the user is running Android 6.0 (API level 23) or later, + * the user has to grant your app its permissions while they are running the app. + *

*

* Requests the WRITE_EXTERNAL_STORAGE permission. * If the permission has been denied previously, a SnackBar will prompt the user to grant the * permission, otherwise it is requested directly. + *

*/ private void requestReadExternalStoragePermission() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { // Should we show an explanation? - if (ActivityCompat.shouldShowRequestPermissionRationale(this, - Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { Snackbar.make(mLayout, R.string.permission_read_exteral_storage_rationale, @@ -603,6 +636,7 @@ public void onClick(View view) { } } + /** * Callback received when a permissions request has been completed. */ @@ -1648,8 +1682,7 @@ public void downloadFile(String dir, String fileName) { filePath); if (!txService.hasDownloadNotifProvider()) { - DownloadNotificationProvider provider = new DownloadNotificationProvider(txService.getDownloadTaskManager(), - txService); + DownloadNotificationProvider provider = new DownloadNotificationProvider(txService.getDownloadTaskManager(), txService); txService.saveDownloadNotifProvider(provider); } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/adapter/GalleryAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/adapter/GalleryAdapter.java index fe66540bf..722e8935f 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/adapter/GalleryAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/adapter/GalleryAdapter.java @@ -1,8 +1,6 @@ package com.seafile.seadroid2.ui.adapter; import android.graphics.Bitmap; -import androidx.annotation.Nullable; -import androidx.viewpager.widget.PagerAdapter; import android.util.DisplayMetrics; import android.view.LayoutInflater; import android.view.View; @@ -10,6 +8,9 @@ import android.widget.ImageView; import android.widget.ProgressBar; +import androidx.annotation.Nullable; +import androidx.viewpager.widget.PagerAdapter; + import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.GlideException; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/fragment/ReposFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/fragment/ReposFragment.java index dfe0dad26..f2768e770 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/fragment/ReposFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/fragment/ReposFragment.java @@ -444,7 +444,6 @@ public void navToDirectory(boolean forceRefresh, boolean restorePosition) { if (nav.getDirPath().equals(BrowserActivity.ACTIONBAR_PARENT_PATH)) { mActivity.setUpButtonTitle(nav.getRepoName()); } else - mActivity.setUpButtonTitle(nav.getDirPath().substring( nav.getDirPath().lastIndexOf(BrowserActivity.ACTIONBAR_PARENT_PATH) + 1)); } @@ -477,7 +476,6 @@ public void startTimer() { isTimerStarted = true; Log.d(DEBUG_TAG, "timer started"); mTimer.postDelayed(new Runnable() { - @Override public void run() { if (mActivity == null) return; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/fragment/SettingsFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/fragment/SettingsFragment.java index 1a54d9b4b..02836c1f3 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/fragment/SettingsFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/fragment/SettingsFragment.java @@ -1,17 +1,22 @@ package com.seafile.seadroid2.ui.fragment; +import android.Manifest; import android.app.Activity; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; +import android.os.Environment; import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceCategory; import android.preference.PreferenceScreen; + import androidx.appcompat.app.AlertDialog; + import android.text.Html; import android.text.TextUtils; import android.util.Log; @@ -319,8 +324,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { cFolderBackupCategory.removePreference(cBackupFolderState); SettingsManager.instance().saveFolderAutomaticBackup(false); } else { - - XXPermissions.with(getActivity()).permission(Permission.MANAGE_EXTERNAL_STORAGE).request(new OnPermissionCallback() { + XXPermissions.with(requireContext()).permission(Permission.MANAGE_EXTERNAL_STORAGE).request(new OnPermissionCallback() { @Override public void onGranted(List permissions, boolean all) { @@ -334,10 +338,10 @@ public void onGranted(List permissions, boolean all) { @Override public void onDenied(List permissions, boolean never) { if (never) { - Toast.makeText(getActivity(), mActivity.getString(R.string.authorization_storage_permission), Toast.LENGTH_LONG).show(); + Toast.makeText(requireContext(), mActivity.getString(R.string.authorization_storage_permission), Toast.LENGTH_LONG).show(); XXPermissions.startPermissionActivity(getActivity(), permissions); } else { - Toast.makeText(getActivity(), mActivity.getString(R.string.get_storage_permission_failed), Toast.LENGTH_LONG).show(); + Toast.makeText(requireContext(), mActivity.getString(R.string.get_storage_permission_failed), Toast.LENGTH_LONG).show(); ((CheckBoxPreference) findPreference(SettingsManager.FOLDER_BACKUP_SWITCH_KEY)).setChecked(false); } } @@ -358,11 +362,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { cUploadCategory.removePreference(cUploadAdvancedScreen); cameraManager.disableCameraUpload(); } else { -// Intent intent = new Intent(mActivity, CameraUploadConfigActivity.class); -// intent.putExtra(CAMERA_UPLOAD_BOTH_PAGES, true); -// startActivityForResult(intent, CHOOSE_CAMERA_UPLOAD_REQUEST); - XXPermissions.with(getActivity()).permission(Permission.MANAGE_EXTERNAL_STORAGE).request(new OnPermissionCallback() { - + XXPermissions.with(requireContext()).permission(Permission.MANAGE_EXTERNAL_STORAGE).request(new OnPermissionCallback() { @Override public void onGranted(List permissions, boolean all) { if (all) { @@ -375,11 +375,11 @@ public void onGranted(List permissions, boolean all) { @Override public void onDenied(List permissions, boolean never) { if (never) { - Toast.makeText(getActivity(), mActivity.getString(R.string.authorization_storage_permission), Toast.LENGTH_LONG).show(); - XXPermissions.startPermissionActivity(getActivity(), permissions); + Toast.makeText(requireContext(), mActivity.getString(R.string.authorization_storage_permission), Toast.LENGTH_LONG).show(); + XXPermissions.startPermissionActivity(requireContext(), permissions); } else { - Toast.makeText(getActivity(), mActivity.getString(R.string.get_storage_permission_failed), Toast.LENGTH_LONG).show(); - ((CheckBoxPreference) findPreference(SettingsManager.FOLDER_BACKUP_SWITCH_KEY)).setChecked(false); + Toast.makeText(requireContext(), mActivity.getString(R.string.get_storage_permission_failed), Toast.LENGTH_LONG).show(); + ((CheckBoxPreference) findPreference(SettingsManager.CAMERA_UPLOAD_SWITCH_KEY)).setChecked(false); } } }); diff --git a/app/src/main/java/com/seafile/seadroid2/util/FileUtils.java b/app/src/main/java/com/seafile/seadroid2/util/FileUtils.java new file mode 100644 index 000000000..ca12102b3 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/util/FileUtils.java @@ -0,0 +1,141 @@ +package com.seafile.seadroid2.util; + +import android.text.TextUtils; + +import java.nio.charset.StandardCharsets; + +public class FileUtils { + + /** + * build valid file path and name. + * + *

+ * " * < ? / : > convert to _ + *

+ * + * for example: + *

+ * /a'"'a/b'*'b/c'<'c/d'>'d/e'?'e/f':'f/g'/'g/ + *

+ * convert to -> + *

+ * /a_a/b_b/c_c/d_d/e_e/f_f/g_g/ + *

+ * + * and so on. + */ + public static String buildValidFilePathName(String fileName) { + + fileName = buildValidFatFilename(fileName); + fileName = buildValidExtFilename(fileName); + return fileName; + } + + + /** + * Check if given filename is valid for an ext4 filesystem. + */ + public static boolean isValidExtFilename(String name) { + return (name != null) && name.equals(buildValidExtFilename(name)); + } + + /** + * Mutate the given filename to make it valid for an ext4 filesystem, + * replacing any invalid characters with "_". + */ + public static String buildValidExtFilename(String name) { + if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) { + return "(invalid)"; + } + final StringBuilder res = new StringBuilder(name.length()); + for (int i = 0; i < name.length(); i++) { + final char c = name.charAt(i); + if (isValidExtFilenameChar(c)) { + res.append(c); + } else { + res.append('_'); + } + } + trimFilename(res, 255); + return res.toString(); + } + + private static boolean isValidExtFilenameChar(char c) { + switch (c) { + case '\0': + //case '/': + return false; + default: + return true; + } + } + + /** + * Check if given filename is valid for a FAT filesystem. + */ + public static boolean isValidFatFilename(String name) { + return (name != null) && name.equals(buildValidFatFilename(name)); + } + + /** + * Mutate the given filename to make it valid for a FAT filesystem, + * replacing any invalid characters with "_". + */ + public static String buildValidFatFilename(String name) { + if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) { + return "(invalid)"; + } + final StringBuilder res = new StringBuilder(name.length()); + for (int i = 0; i < name.length(); i++) { + final char c = name.charAt(i); + if (isValidFatFilenameChar(c)) { + res.append(c); + } else { + res.append('_'); + } + } + // Even though vfat allows 255 UCS-2 chars, we might eventually write to + // ext4 through a FUSE layer, so use that limit. + trimFilename(res, 255); + return res.toString(); + } + + private static boolean isValidFatFilenameChar(char c) { + if ((0x00 <= c && c <= 0x1f)) { + return false; + } + switch (c) { + case '"': + case '*': + //case '/': + case ':': + case '<': + case '>': + case '?': + case '\\': + case '|': + case 0x7F: + return false; + default: + return true; + } + } + + public static String trimFilename(String str, int maxBytes) { + final StringBuilder res = new StringBuilder(str); + trimFilename(res, maxBytes); + return res.toString(); + } + + private static void trimFilename(StringBuilder res, int maxBytes) { + byte[] raw = res.toString().getBytes(StandardCharsets.UTF_8); + if (raw.length > maxBytes) { + maxBytes -= 3; + while (raw.length > maxBytes) { + res.deleteCharAt(res.length() / 2); + raw = res.toString().getBytes(StandardCharsets.UTF_8); + } + res.insert(res.length() / 2, "..."); + } + } +} diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index fb7038663..b34a574f7 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -182,6 +182,7 @@ Aucun téléchargement en cours Note : Le nom de serveur peut contenir un port. Par exemple, www.exemple.com:8000 Copier le lien + Vous n\'êtes pas autorisé à effectuer cette action. Partager un lien vers ce fichier Partager un lien vers ce dossier Saisir le mot de passe partagé @@ -376,12 +377,12 @@ Erreur d\'authentification. %s (renommé par Seafile) À PROPOS + Politique de confidentialité Version À propos de l\'auteur - Seafile client pour Android +
Client Seafile pour Android

%s

Contactez-nous

@@ -391,8 +392,8 @@ E-mail : support@seafile.com

Contributions

https://github.com/haiwen/seadroid.git

Copyright ©2013-2023 Seafile Ltd.
-]]>
- +]]> +
Fonctionnalités avancées Stockage du cache Taille du cache @@ -560,4 +561,38 @@ E-mail : support@seafile.com
Un mot de passe est requis. Le mot de passe est trop faible. + Dossier de sauvegarde + Activer le dossier de sauvegarde + Le service de sauvegarde est démarré + Le service de sauvegarde est arrêté + Sélectionner un dossier + Taille %s + fichier\%s\ | dossier\%s\ + Stockage interne + Veuillez sélectionner un dossier de sauvegarde + Sélectionner un dossier de sauvegarde + Sélectionner une bibliothèque de sauvegarde + Le dossier est sauvegardé + Échec de l\'obtention de l\'autorisation du stockage + L\'autorisation est refusée de manière permanente, veuillez accorder manuellement l\'autorisation d\'accès au stockage. + 0 + Sélectionner un dossier de sauvegarde + Mode de sauvegarde + WIFI + Sauvegarde de la bibliothèque vers + Ajouter un dossier de sauvegarde + Annuler la sauvegarde + Effectué + Informations sur la sauvegarde des dossiers + WIFI ou données mobiles + Choisir un mode + Sélectionner une bibliothèque + Mettre à jour la bibliothèque de sauvegarde + L\'application a cessé de fonctionner + + Ajouter + Modifier + Mise à jour + Nettoyer + Tout nettoyer diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 5edffe5f5..d6b9a6480 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -371,19 +371,20 @@ 应用版本号 关于我们 - Seafile Android Client -

%s

-
联系我们
-

- Twitter: Seafile
- Email: support@seafile.com
-

- 贡献 -

https://github.com/haiwen/seadroid.git

-
Copyright ©2013-2023 Seafile Ltd.
- ]]> -
+Seafile Android Client +

%s

+
联系我们
+

+Twitter: Seafile
+Email: support@seafile.com
+

+
贡献
+

https://github.com/haiwen/seadroid.git

+
备案编号
+

京ICP备12015883号-10A

+
Copyright ©2013-2023 Seafile Ltd.
+]]>
高级特性 缓存存储 缓存大小 @@ -488,6 +489,7 @@ 未选中任何文件 + 在处理文件之前,请允许应用获取管理外部存储所有文件权限 在处理文件之前,请允许应用获取存储权限 在处理通讯录之前,请允许本应用获取通讯录权限 未获得权限 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a1b8c245e..5a8f80fe5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -514,6 +514,7 @@ Navigation Drawer used to switch between major features of the application + To process files, allows management of all files on external storage To process files, allow storage permission to access them. To process contacts, allow contacts permission to access them. Permission was not granted diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..8fcbb3395 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,15 @@ +## For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx1024m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +# +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +#Sun Sep 10 10:25:30 CST 2023 +android.enableJetifier=true +android.useAndroidX=true \ No newline at end of file