Merge branch 'foreground-background-notification' into 'master'

fix background service crashes and related fixes

Closes #2645 and acra-crash-reports#584

See merge request fdroid/fdroidclient!1256
This commit is contained in:
Hans-Christoph Steiner
2023-07-06 18:51:55 +00:00
6 changed files with 81 additions and 40 deletions

View File

@@ -168,9 +168,11 @@
android:exported="false" />
<service
android:name=".nearby.TreeUriScannerIntentService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false" />
<service
android:name=".nearby.SDCardScannerService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false" />
</application>

View File

@@ -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 <a href="https://stackoverflow.com/a/40201333">Universal way to write to external SD card on Android</a>
* @see <a href="https://commonsware.com/blog/2017/11/14/storage-situation-external-storage.html"> The Storage Situation: External Storage </a>
*/
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<String> 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);
@@ -94,7 +93,14 @@ public class SDCardScannerService extends IntentService {
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) {

View File

@@ -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 <a href="https://developer.android.com/training/articles/scoped-directory-access.html">Using Scoped Directory Access</a>
* @see <a href="https://developer.android.com/guide/topics/providers/document-provider.html">Open Files using Storage Access Framework</a>
*/
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 <a href="https://android.googlesource.com/platform/frameworks/base/+/android-10.0.0_r38/core/java/android/provider/DocumentsContract.java#238">DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY</a>
* @see <a href="https://android.googlesource.com/platform/frameworks/base/+/android-10.0.0_r38/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java#70">ExternalStorageProvider.AUTHORITY</a>
*/
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();

View File

@@ -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}.
* <p>
* 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

View File

@@ -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) {

View File

@@ -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);
}
@@ -140,13 +148,21 @@ public class MainActivity extends AppCompatActivity {
AppUpdateStatusManager.getInstance(this).getNumUpdatableApps().observe(this, this::refreshUpdatesBadge);
Intent intent = getIntent();
if (handleMainViewSelectIntent(intent)) {
return;
}
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() {
@@ -162,17 +178,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 +199,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 +241,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