From 8a6988d9cb88b7db7b62b7b85cd07a9e6344bd05 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Tue, 4 Jul 2023 14:01:25 +0200 Subject: [PATCH 1/7] port external storage scanning services to JobIntentService closes acra-crash-reports#584 closes #2645 --- app/src/full/AndroidManifest.xml | 2 ++ .../fdroid/nearby/SDCardScannerService.java | 17 ++++++++--------- .../nearby/TreeUriScannerIntentService.java | 18 +++++++++--------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/app/src/full/AndroidManifest.xml b/app/src/full/AndroidManifest.xml index ae50418b3..cdffee6a7 100644 --- a/app/src/full/AndroidManifest.xml +++ b/app/src/full/AndroidManifest.xml @@ -168,9 +168,11 @@ android:exported="false" /> diff --git a/app/src/full/java/org/fdroid/fdroid/nearby/SDCardScannerService.java b/app/src/full/java/org/fdroid/fdroid/nearby/SDCardScannerService.java index 8fb002189..22ad40498 100644 --- a/app/src/full/java/org/fdroid/fdroid/nearby/SDCardScannerService.java +++ b/app/src/full/java/org/fdroid/fdroid/nearby/SDCardScannerService.java @@ -20,7 +20,6 @@ package org.fdroid.fdroid.nearby; import android.Manifest; -import android.app.IntentService; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -29,6 +28,8 @@ import android.os.Environment; import android.os.Process; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.core.app.JobIntentService; import androidx.core.content.ContextCompat; import org.fdroid.fdroid.Utils; @@ -46,7 +47,7 @@ import java.util.HashSet; import java.util.List; /** - * An {@link IntentService} subclass for scanning removable "external storage" + * An {@link JobIntentService} subclass for scanning removable "external storage" * for F-Droid package repos, e.g. SD Cards. This is intended to support * sharable package repos, so it ignores non-removable storage, like the fake * emulated sdcard from devices with only built-in storage. This method will @@ -63,26 +64,24 @@ import java.util.List; * @see Universal way to write to external SD card on Android * @see The Storage Situation: External Storage */ -public class SDCardScannerService extends IntentService { +public class SDCardScannerService extends JobIntentService { public static final String TAG = "SDCardScannerService"; + private static final int JOB_ID = TAG.hashCode(); private static final String ACTION_SCAN = "org.fdroid.fdroid.nearby.SCAN"; private static final List SKIP_DIRS = Arrays.asList(".android_secure", "LOST.DIR"); - public SDCardScannerService() { - super("SDCardScannerService"); - } public static void scan(Context context) { Intent intent = new Intent(context, SDCardScannerService.class); intent.setAction(ACTION_SCAN); - context.startService(intent); + JobIntentService.enqueueWork(context, SDCardScannerService.class, JOB_ID, intent); } @Override - protected void onHandleIntent(Intent intent) { - if (intent == null || !ACTION_SCAN.equals(intent.getAction())) { + protected void onHandleWork(@NonNull Intent intent) { + if (!ACTION_SCAN.equals(intent.getAction())) { return; } Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST); diff --git a/app/src/full/java/org/fdroid/fdroid/nearby/TreeUriScannerIntentService.java b/app/src/full/java/org/fdroid/fdroid/nearby/TreeUriScannerIntentService.java index 6966e130b..c8443ebf4 100644 --- a/app/src/full/java/org/fdroid/fdroid/nearby/TreeUriScannerIntentService.java +++ b/app/src/full/java/org/fdroid/fdroid/nearby/TreeUriScannerIntentService.java @@ -28,6 +28,8 @@ import android.os.Process; import android.util.Log; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.core.app.JobIntentService; import androidx.documentfile.provider.DocumentFile; import org.apache.commons.io.FileUtils; @@ -54,7 +56,7 @@ import java.util.jar.JarFile; import java.util.jar.JarInputStream; /** - * An {@link IntentService} subclass for handling asynchronous scanning of a + * An {@link JobIntentService} subclass for handling asynchronous scanning of a * removable storage device like an SD Card or USB OTG thumb drive using the * Storage Access Framework. Permission must first be granted by the user * {@link android.content.Intent#ACTION_OPEN_DOCUMENT_TREE} or @@ -73,25 +75,23 @@ import java.util.jar.JarInputStream; * @see Using Scoped Directory Access * @see Open Files using Storage Access Framework */ -public class TreeUriScannerIntentService extends IntentService { +public class TreeUriScannerIntentService extends JobIntentService { public static final String TAG = "TreeUriScannerIntentSer"; + private static final int JOB_ID = TAG.hashCode(); private static final String ACTION_SCAN_TREE_URI = "org.fdroid.fdroid.nearby.action.SCAN_TREE_URI"; + /** * @see DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY * @see ExternalStorageProvider.AUTHORITY */ public static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents"; - public TreeUriScannerIntentService() { - super("TreeUriScannerIntentService"); - } - public static void scan(Context context, Uri data) { Intent intent = new Intent(context, TreeUriScannerIntentService.class); intent.setAction(ACTION_SCAN_TREE_URI); intent.setData(data); - context.startService(intent); + JobIntentService.enqueueWork(context, TreeUriScannerIntentService.class, JOB_ID, intent); } /** @@ -115,8 +115,8 @@ public class TreeUriScannerIntentService extends IntentService { } @Override - protected void onHandleIntent(Intent intent) { - if (intent == null || !ACTION_SCAN_TREE_URI.equals(intent.getAction())) { + protected void onHandleWork(@NonNull Intent intent) { + if (!ACTION_SCAN_TREE_URI.equals(intent.getAction())) { return; } Uri treeUri = intent.getData(); From fd2d94ccd5c915c809fd9244785c1655ce12f8cc Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Tue, 4 Jul 2023 19:55:05 +0200 Subject: [PATCH 2/7] handle Intents that select a specific tab in MainActivity These Intents were being swallowed before they could select the tab. --- .../fdroid/views/main/MainActivity.java | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java b/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java index 719a09dac..3d9ce8e83 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java @@ -140,6 +140,9 @@ public class MainActivity extends AppCompatActivity { AppUpdateStatusManager.getInstance(this).getNumUpdatableApps().observe(this, this::refreshUpdatesBadge); Intent intent = getIntent(); + if (handleMainViewSelectIntent(intent)) { + return; + } handleSearchOrAppViewIntent(intent); } @@ -162,17 +165,6 @@ public class MainActivity extends AppCompatActivity { FDroidApp.checkStartTor(this, Preferences.get()); - if (getIntent().hasExtra(EXTRA_VIEW_UPDATES)) { - getIntent().removeExtra(EXTRA_VIEW_UPDATES); - setSelectedMenuInNav(R.id.updates); - } else if (getIntent().hasExtra(EXTRA_VIEW_NEARBY)) { - getIntent().removeExtra(EXTRA_VIEW_NEARBY); - setSelectedMenuInNav(R.id.nearby); - } else if (getIntent().hasExtra(EXTRA_VIEW_SETTINGS)) { - getIntent().removeExtra(EXTRA_VIEW_SETTINGS); - setSelectedMenuInNav(R.id.settings); - } - // AppDetailsActivity and RepoDetailsActivity set different NFC actions, so reset here NfcHelper.setAndroidBeam(this, getApplication().getPackageName()); checkForAddRepoIntent(getIntent()); @@ -194,6 +186,11 @@ public class MainActivity extends AppCompatActivity { @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); + + if (handleMainViewSelectIntent(intent)) { + return; + } + handleSearchOrAppViewIntent(intent); // This is called here as well as onResume(), because onNewIntent() is not called the first @@ -231,6 +228,23 @@ public class MainActivity extends AppCompatActivity { } } + /** + * Handle an {@link Intent} that shows a specific tab in the main view. + */ + private boolean handleMainViewSelectIntent(Intent intent) { + if (intent.hasExtra(EXTRA_VIEW_NEARBY)) { + setSelectedMenuInNav(R.id.nearby); + return true; + } else if (intent.hasExtra(EXTRA_VIEW_UPDATES)) { + setSelectedMenuInNav(R.id.updates); + return true; + } else if (intent.hasExtra(EXTRA_VIEW_SETTINGS)) { + setSelectedMenuInNav(R.id.settings); + return true; + } + return false; + } + /** * Since any app could send this {@link Intent}, and the search terms are * fed into a SQL query, the data must be strictly sanitized to avoid From 472acf68c384a427e0b58d1cc67f9fda3b031fe4 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Thu, 22 Jun 2023 13:51:01 +0200 Subject: [PATCH 3/7] doc Preferences vs. PreferenceManager.getDefaultSharedPreferences() --- app/src/main/java/org/fdroid/fdroid/Preferences.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/Preferences.java b/app/src/main/java/org/fdroid/fdroid/Preferences.java index 07ae09d15..f3d44445e 100644 --- a/app/src/main/java/org/fdroid/fdroid/Preferences.java +++ b/app/src/main/java/org/fdroid/fdroid/Preferences.java @@ -49,11 +49,13 @@ import java.util.Set; import java.util.concurrent.TimeUnit; /** - * Handles shared preferences for FDroid, looking after the names of - * preferences, default values and caching. Needs to be setup in the FDroidApp - * (using {@link Preferences#setup(android.content.Context)} before it gets - * accessed via the {@link org.fdroid.fdroid.Preferences#get()} - * singleton method. + * Handles the preferences that are shown the Settings UI, looking after the + * names of preferences, default values and caching. Needs to be setup in the + * {@link org.fdroid.fdroid.FDroidApp} (using + * {@link Preferences#setup(android.content.Context)} before it gets accessed + * via the {@link org.fdroid.fdroid.Preferences#get()} singleton method. This + * structure also lets it be used in places in the code where there is no + * {@link Context}. *

* All defaults should be set in {@code res/xml/preferences.xml}. The one * exception is {@link Preferences#PREF_LOCAL_REPO_NAME} since it needs to be From 2bedd289e0efaad7cbf0f0b91b510116b36e0a1d Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Tue, 4 Jul 2023 18:35:59 +0200 Subject: [PATCH 4/7] update official Android/AOSP version names https://en.wikipedia.org/wiki/Android_version_history --- app/src/main/java/org/fdroid/fdroid/Utils.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/Utils.java b/app/src/main/java/org/fdroid/fdroid/Utils.java index 1ae576407..edaa64ec2 100644 --- a/app/src/main/java/org/fdroid/fdroid/Utils.java +++ b/app/src/main/java/org/fdroid/fdroid/Utils.java @@ -300,9 +300,13 @@ public final class Utils { "7.1", // 25 "8.0", // 26 "8.1", // 27 - "9.0", // 28 - "10.0", // 29 - "11.0", // 30 + "9", // 28 + "10", // 29 + "11", // 30 + "12", // 31 + "12.1", // 32 + "13", // 33 + "14", // 34 }; public static String getAndroidVersionName(int sdkLevel) { From 04a5a4b8d93d312f67e3db2c9a2853a29b4ee246 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Thu, 6 Jul 2023 14:28:31 +0200 Subject: [PATCH 5/7] handle Samsung oddness with Environment.isExternalStorageRemovable() closes acra-crash-reports#498 --- .../org/fdroid/fdroid/nearby/SDCardScannerService.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/full/java/org/fdroid/fdroid/nearby/SDCardScannerService.java b/app/src/full/java/org/fdroid/fdroid/nearby/SDCardScannerService.java index 22ad40498..27cd98328 100644 --- a/app/src/full/java/org/fdroid/fdroid/nearby/SDCardScannerService.java +++ b/app/src/full/java/org/fdroid/fdroid/nearby/SDCardScannerService.java @@ -93,7 +93,14 @@ public class SDCardScannerService extends JobIntentService { continue; } Log.i(TAG, "getExternalFilesDirs " + f); - if (Environment.isExternalStorageRemovable(f)) { + boolean isExternalStorageRemovable; + try { + isExternalStorageRemovable = Environment.isExternalStorageRemovable(f); + } catch (IllegalArgumentException e) { + Utils.debugLog(TAG, e.toString()); + continue; + } + if (isExternalStorageRemovable) { String state = Environment.getExternalStorageState(f); if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { From 5cf4f32e448bc592bc07bd2d1d23e20d5e3b0563 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Thu, 6 Jul 2023 14:30:22 +0200 Subject: [PATCH 6/7] fix tab highlight when programmatically showing tab --- .../java/org/fdroid/fdroid/views/main/MainActivity.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java b/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java index 3d9ce8e83..9b05cc66e 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java @@ -146,10 +146,15 @@ public class MainActivity extends AppCompatActivity { handleSearchOrAppViewIntent(intent); } + /** + * {@link android.material.navigation.NavigationBarView} says "Menu items + * can also be used for programmatically selecting which destination is + * currently active. It can be done using {@code MenuItem.setChecked(true)}". + */ private void setSelectedMenuInNav(int menuId) { int position = adapter.adapterPositionFromItemId(menuId); pager.scrollToPosition(position); - bottomNavigation.setSelectedItemId(position); + bottomNavigation.getMenu().getItem(position).setChecked(true); } private void initialRepoUpdateIfRequired() { From fb13bc59f8e84f3eb43d2521e1763db23bfb9bc0 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Thu, 6 Jul 2023 15:29:34 +0200 Subject: [PATCH 7/7] save/restore selected tab when app gets killed Now that setSelectedMenuInNav() works, it makes sense to do this. --- .../org/fdroid/fdroid/views/main/MainActivity.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java b/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java index 9b05cc66e..e0d66e908 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java @@ -26,6 +26,7 @@ import android.Manifest; import android.app.SearchManager; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; @@ -39,6 +40,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; +import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -90,6 +92,8 @@ public class MainActivity extends AppCompatActivity { private static final String ADD_REPO_INTENT_HANDLED = "addRepoIntentHandled"; + private static final String BOTTOM_NAVIGATION_MENU_ID = "bottomNavigationMenuId"; + private static final String ACTION_ADD_REPO = "org.fdroid.fdroid.MainActivity.ACTION_ADD_REPO"; public static final String ACTION_REQUEST_SWAP = "requestSwap"; @@ -122,10 +126,14 @@ public class MainActivity extends AppCompatActivity { pager.setAdapter(adapter); bottomNavigation = findViewById(R.id.bottom_navigation); + final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + setSelectedMenuInNav(sharedPreferences.getInt(BOTTOM_NAVIGATION_MENU_ID, R.id.latest)); bottomNavigation.setOnNavigationItemSelectedListener(item -> { pager.scrollToPosition(item.getOrder()); - if (item.getItemId() == R.id.nearby) { + final int itemId = item.getItemId(); + sharedPreferences.edit().putInt(BOTTOM_NAVIGATION_MENU_ID, itemId).apply(); + if (itemId == R.id.nearby) { NearbyViewBinder.updateUsbOtg(MainActivity.this); }