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 2c7e2b06b..4a947d545 100644 --- a/app/src/full/java/org/fdroid/fdroid/nearby/TreeUriScannerIntentService.java +++ b/app/src/full/java/org/fdroid/fdroid/nearby/TreeUriScannerIntentService.java @@ -197,7 +197,7 @@ public class TreeUriScannerIntentService extends IntentService { destFile.delete(); Log.i(TAG, "Found a valid, signed index-v1.json"); - for (Repository repo : FDroidApp.repos) { + for (Repository repo : FDroidApp.getRepoManager(context).getRepositories()) { if (fingerprint.equals(repo.getFingerprint())) { Log.i(TAG, repo.getAddress() + " has the SAME fingerprint: " + fingerprint); } else { diff --git a/app/src/main/java/org/fdroid/fdroid/AddRepoIntentService.java b/app/src/main/java/org/fdroid/fdroid/AddRepoIntentService.java index 2652f284d..6cfaae64f 100644 --- a/app/src/main/java/org/fdroid/fdroid/AddRepoIntentService.java +++ b/app/src/main/java/org/fdroid/fdroid/AddRepoIntentService.java @@ -76,7 +76,7 @@ public class AddRepoIntentService extends IntentService { } String fingerprint = uri.getQueryParameter("fingerprint"); - for (Repository repo : FDroidApp.repos) { + for (Repository repo : FDroidApp.getRepoManager(getApplicationContext()).getRepositories()) { if (repo.getEnabled() && TextUtils.equals(fingerprint, repo.getFingerprint())) { if (TextUtils.equals(urlString, repo.getAddress())) { Utils.debugLog(TAG, urlString + " already added as a repo"); diff --git a/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusManager.java b/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusManager.java index 5c4cdde9b..698d385b1 100644 --- a/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusManager.java +++ b/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusManager.java @@ -384,7 +384,7 @@ public final class AppUpdateStatusManager { if (canUpdate.size() > 0) { startBatchUpdates(); for (UpdatableApp app : canUpdate) { - Repository repo = FDroidApp.getRepo(app.getUpdate().getRepoId()); + Repository repo = FDroidApp.getRepoManager(context).getRepository(app.getUpdate().getRepoId()); addApk(new App(app), new Apk(app.getUpdate(), repo), Status.UpdateAvailable, null); } endBatchUpdates(Status.UpdateAvailable); @@ -397,7 +397,7 @@ public final class AppUpdateStatusManager { isBatchUpdating = true; try { for (UpdatableApp app : canUpdate) { - Repository repo = FDroidApp.getRepo(app.getUpdate().getRepoId()); + Repository repo = FDroidApp.getRepoManager(context).getRepository(app.getUpdate().getRepoId()); addApk(new App(app), new Apk(app.getUpdate(), repo), Status.UpdateAvailable, null); } setNumUpdatableApps(canUpdate.size()); diff --git a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java index 41364eaf6..26cff20e7 100644 --- a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java +++ b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java @@ -69,6 +69,7 @@ import org.fdroid.fdroid.panic.HidingManager; import org.fdroid.fdroid.receiver.DeviceStorageReceiver; import org.fdroid.fdroid.work.CleanCacheWorker; import org.fdroid.index.IndexFormatVersion; +import org.fdroid.index.RepoManager; import java.io.IOException; import java.nio.ByteBuffer; @@ -98,6 +99,8 @@ public class FDroidApp extends Application implements androidx.work.Configuratio public static final String SYSTEM_DIR_NAME = Environment.getRootDirectory().getAbsolutePath(); private static FDroidApp instance; + @Nullable + private static RepoManager repoManager; // for the local repo on this device, all static since there is only one public static volatile int port; @@ -108,7 +111,6 @@ public class FDroidApp extends Application implements androidx.work.Configuratio public static volatile String bssid; public static volatile Repository repo; - public static volatile List repos; public static volatile SessionInstallManager sessionInstallManager; public static volatile int networkState = ConnectivityMonitorService.FLAG_NET_UNAVAILABLE; @@ -311,12 +313,6 @@ public class FDroidApp extends Application implements androidx.work.Configuratio } } - // keep a static copy of the repositories around and in-sync - // not how one would normally do this, but it is a common pattern in this codebase - // note: we need this as soon as possible as lots of other code is relying on it - FDroidDatabase db = DBHelper.getDb(this); - db.getRepositoryDao().getLiveRepositories().observeForever(repositories -> repos = repositories); - // register broadcast receivers registerReceiver(new DeviceStorageReceiver(), new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW)); @@ -515,15 +511,6 @@ public class FDroidApp extends Application implements androidx.work.Configuratio } } - @Nullable - public static Repository getRepo(long repoId) { - if (repos == null) return null; - for (Repository r : repos) { - if (r.getRepoId() == repoId) return r; - } - return null; - } - public static Repository createSwapRepo(String address, String certificate) { long now = System.currentTimeMillis(); return new Repository(42L, address, now, IndexFormatVersion.ONE, certificate, 20001L, 42, now); @@ -533,6 +520,11 @@ public class FDroidApp extends Application implements androidx.work.Configuratio return instance; } + public static RepoManager getRepoManager(Context context) { + if (repoManager == null) repoManager = new RepoManager(DBHelper.getDb(context)); + return repoManager; + } + /** * Set up WorkManager on demand to avoid slowing down starts. * diff --git a/app/src/main/java/org/fdroid/fdroid/UpdateService.java b/app/src/main/java/org/fdroid/fdroid/UpdateService.java index 2d5268382..1e0d67c38 100644 --- a/app/src/main/java/org/fdroid/fdroid/UpdateService.java +++ b/app/src/main/java/org/fdroid/fdroid/UpdateService.java @@ -545,7 +545,7 @@ public class UpdateService extends JobIntentService { App updateLastApp = null; Apk updateLastApk = null; for (UpdatableApp app : apps) { - Repository repo = FDroidApp.getRepo(app.getUpdate().getRepoId()); + Repository repo = FDroidApp.getRepoManager(context).getRepository(app.getUpdate().getRepoId()); if (repo == null) continue; // repo could have been removed in the meantime // update our own APK at the end if (TextUtils.equals(ourPackageName, app.getUpdate().getPackageName())) { diff --git a/app/src/main/java/org/fdroid/fdroid/Utils.java b/app/src/main/java/org/fdroid/fdroid/Utils.java index bf422dffe..19f579397 100644 --- a/app/src/main/java/org/fdroid/fdroid/Utils.java +++ b/app/src/main/java/org/fdroid/fdroid/Utils.java @@ -506,7 +506,7 @@ public final class Utils { RequestOptions options = iconRequestOptions.onlyRetrieveFromCache( !Preferences.get().isBackgroundDownloadAllowed()); - Repository repo = FDroidApp.getRepo(repoId); + Repository repo = FDroidApp.getRepoManager(context).getRepository(repoId); if (repo == null) { Glide.with(context).clear(iv); return; diff --git a/app/src/main/java/org/fdroid/fdroid/data/App.java b/app/src/main/java/org/fdroid/fdroid/data/App.java index e8f769bc2..8720df7e5 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/App.java +++ b/app/src/main/java/org/fdroid/fdroid/data/App.java @@ -345,8 +345,8 @@ public class App implements Comparable, Parcelable { * Get the URL with the standard path for displaying in a browser. */ @Nullable - public Uri getShareUri() { - Repository repo = FDroidApp.getRepo(repoId); + public Uri getShareUri(Context context) { + Repository repo = FDroidApp.getRepoManager(context).getRepository(repoId); if (repo == null || repo.getWebBaseUrl() == null) return null; return Uri.parse(repo.getWebBaseUrl()).buildUpon() .path(packageName) @@ -358,12 +358,8 @@ public class App implements Comparable, Parcelable { } public static RequestBuilder loadWithGlide(Context context, long repoId, IndexFile file) { - Repository repo = FDroidApp.getRepo(repoId); + Repository repo = FDroidApp.getRepoManager(context).getRepository(repoId); if (repo == null) { // This is also used for apps that do not have a repo - return Glide.with(context).load((Drawable) null); - } - if (repo == null) { - Log.e(TAG, "Repo not found: " + repoId); return Glide.with(context).load(R.drawable.ic_repo_app_default); } String address = Utils.getRepoAddress(repo); @@ -379,7 +375,7 @@ public class App implements Comparable, Parcelable { public static RequestBuilder loadBitmapWithGlide(Context context, long repoId, IndexFile file) { - Repository repo = FDroidApp.getRepo(repoId); + Repository repo = FDroidApp.getRepoManager(context).getRepository(repoId); if (repo == null) { Log.e(TAG, "Repo not found: " + repoId); return Glide.with(context).asBitmap().load(R.drawable.ic_repo_app_default); diff --git a/app/src/main/java/org/fdroid/fdroid/net/DownloaderService.java b/app/src/main/java/org/fdroid/fdroid/net/DownloaderService.java index 1913d1175..f4358e847 100644 --- a/app/src/main/java/org/fdroid/fdroid/net/DownloaderService.java +++ b/app/src/main/java/org/fdroid/fdroid/net/DownloaderService.java @@ -29,7 +29,6 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.core.app.JobIntentService; -import org.fdroid.database.FDroidDatabase; import org.fdroid.database.Repository; import org.fdroid.download.Downloader; import org.fdroid.download.NotFoundException; @@ -37,7 +36,6 @@ import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.App; -import org.fdroid.fdroid.data.DBHelper; import org.fdroid.fdroid.data.SanitizedFile; import org.fdroid.fdroid.installer.ApkCache; import org.fdroid.fdroid.installer.InstallManagerService; @@ -189,18 +187,14 @@ public class DownloaderService extends JobIntentService { try { activeCanonicalUrl = canonicalUrl.toString(); - Repository repo = FDroidApp.getRepo(repoId); + Context context = getApplicationContext(); + Repository repo = FDroidApp.getRepoManager(context).getRepository(repoId); if (repo == null) { - // right after the app gets re-recreated downloads get re-triggered, so repo can still be null - FDroidDatabase db = DBHelper.getDb(getApplicationContext()); - repo = db.getRepositoryDao().getRepository(repoId); - if (repo == null) { - String canonical = canonicalUrl.toString(); - if (canonical.startsWith("http://1") && canonical.contains(":8888/")) { - String address = canonical.split(":8888/")[0] + ":8888/"; - repo = FDroidApp.createSwapRepo(address, null); // fake repo for swap - } else return; // repo might have been deleted in the meantime - } + String canonical = canonicalUrl.toString(); + if (canonical.startsWith("http://1") && canonical.contains(":8888/")) { + String address = canonical.split(":8888/")[0] + ":8888/"; + repo = FDroidApp.createSwapRepo(address, null); // fake repo for swap + } else return; // repo might have been deleted in the meantime } downloader = DownloaderFactory.INSTANCE.create(repo, downloadUrl, fileV1, localFile); final long[] lastProgressSent = {0}; diff --git a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsActivity.java b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsActivity.java index 21f6147e4..715f8b1da 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsActivity.java @@ -75,6 +75,7 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; +import androidx.core.util.ObjectsCompat; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -289,7 +290,7 @@ public class AppDetailsActivity extends AppCompatActivity app.name, app.summary, app.packageName); Intent uriIntent = new Intent(Intent.ACTION_SEND); - Uri shareUri = app.getShareUri(); + Uri shareUri = app.getShareUri(this); if (shareUri != null) uriIntent.setData(shareUri); uriIntent.putExtra(Intent.EXTRA_TITLE, app.name); @@ -740,8 +741,8 @@ public class AppDetailsActivity extends AppCompatActivity private void onVersionsChanged(List appVersions) { List apks = new ArrayList<>(appVersions.size()); for (AppVersion appVersion : appVersions) { - Repository repo = FDroidApp.getRepo(appVersion.getRepoId()); - Apk apk = new Apk(appVersion, repo); + Repository repo = FDroidApp.getRepoManager(this).getRepository(appVersion.getRepoId()); + Apk apk = new Apk(appVersion, ObjectsCompat.requireNonNull(repo)); // repo shouldn't go missing here apk.setCompatibility(checker); apks.add(apk); } diff --git a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java index 38dcd8b70..50d1c4015 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java +++ b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java @@ -1154,7 +1154,7 @@ public class AppDetailsRecyclerViewAdapter } // Repository name, APK size and required Android version - Repository repo = FDroidApp.getRepo(apk.repoId); + Repository repo = FDroidApp.getRepoManager(context).getRepository(apk.repoId); if (repo != null) { repository.setVisibility(View.VISIBLE); String name = repo.getName(App.getLocales()); diff --git a/app/src/main/java/org/fdroid/fdroid/views/InstallHistoryActivity.java b/app/src/main/java/org/fdroid/fdroid/views/InstallHistoryActivity.java index 08bd7078c..9cb091ab0 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/InstallHistoryActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/InstallHistoryActivity.java @@ -141,7 +141,7 @@ public class InstallHistoryActivity extends AppCompatActivity { if (showingInstallHistory) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("Repos:\n"); - for (Repository repo: FDroidApp.repos) { + for (Repository repo: FDroidApp.getRepoManager(this).getRepositories()) { if (repo.getEnabled()) { stringBuilder.append("* "); stringBuilder.append(repo.getAddress()); diff --git a/app/src/main/java/org/fdroid/fdroid/views/ManageReposActivity.java b/app/src/main/java/org/fdroid/fdroid/views/ManageReposActivity.java index 095056ec1..76a21a838 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/ManageReposActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/ManageReposActivity.java @@ -275,24 +275,7 @@ public class ManageReposActivity extends AppCompatActivity implements RepoAdapte String msg = getDisallowInstallUnknownSourcesErrorMessage(this); Toast.makeText(this, msg, Toast.LENGTH_LONG).show(); } else { - // temporary hack until we clean up this activity - if (FDroidApp.repos == null) { - Utils.runOffUiThread(() -> { - while (FDroidApp.repos == null) { - if (isFinishing() || isDestroyed()) return false; - try { - Thread.sleep(100); - } catch (InterruptedException e) { - Log.e(TAG, "Interrupted while waiting for repos ", e); - } - } - return true; - }, (result) -> { - if (result) new AddRepo(newAddress, newFingerprint, username, password); - }); - } else { - new AddRepo(newAddress, newFingerprint, username, password); - } + new AddRepo(newAddress, newFingerprint, username, password); } } @@ -326,7 +309,7 @@ public class ManageReposActivity extends AppCompatActivity implements RepoAdapte context = ManageReposActivity.this; - for (Repository repo : FDroidApp.repos) { + for (Repository repo : FDroidApp.getRepoManager(ManageReposActivity.this).getRepositories()) { urlRepoMap.put(repo.getAddress(), repo); for (Mirror mirror : repo.getAllMirrors()) { urlRepoMap.put(mirror.getBaseUrl(), repo); diff --git a/app/src/main/java/org/fdroid/fdroid/views/RepoDetailsActivity.java b/app/src/main/java/org/fdroid/fdroid/views/RepoDetailsActivity.java index 6c5ac02c3..bdf5f2580 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/RepoDetailsActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/RepoDetailsActivity.java @@ -136,7 +136,7 @@ public class RepoDetailsActivity extends AppCompatActivity { repoView = findViewById(R.id.repo_view); repoId = getIntent().getLongExtra(ARG_REPO_ID, 0); - repo = FDroidApp.getRepo(repoId); + repo = FDroidApp.getRepoManager(this).getRepository(repoId); TextView inputUrl = findViewById(R.id.input_repo_url); inputUrl.setText(repo.getAddress()); @@ -198,7 +198,7 @@ public class RepoDetailsActivity extends AppCompatActivity { * have been updated. The safest way to deal with this is to reload the * repo object directly from the database. */ - repo = FDroidApp.getRepo(repoId); + repo = FDroidApp.getRepoManager(this).getRepository(repoId); updateRepoView(); LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, diff --git a/app/src/main/java/org/fdroid/fdroid/views/StatusBanner.java b/app/src/main/java/org/fdroid/fdroid/views/StatusBanner.java index f3ea506ba..71eaf702a 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/StatusBanner.java +++ b/app/src/main/java/org/fdroid/fdroid/views/StatusBanner.java @@ -118,7 +118,8 @@ public class StatusBanner extends androidx.appcompat.widget.AppCompatTextView { setVisibility(View.VISIBLE); } else if (overDataState == Preferences.OVER_NETWORK_NEVER && overWiFiState == Preferences.OVER_NETWORK_NEVER) { - List localRepos = UpdateService.getLocalRepos(FDroidApp.repos); + List repos = FDroidApp.getRepoManager(getContext()).getRepositories(); + List localRepos = UpdateService.getLocalRepos(repos); boolean hasLocalNonSystemRepos = true; final List systemPartitions = Arrays.asList("odm", "oem", "product", "system", "vendor"); for (Repository repo : localRepos) { diff --git a/app/src/main/java/org/fdroid/fdroid/views/appdetails/AntiFeaturesListingView.java b/app/src/main/java/org/fdroid/fdroid/views/appdetails/AntiFeaturesListingView.java index 2d0c1681e..bca7962d1 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/appdetails/AntiFeaturesListingView.java +++ b/app/src/main/java/org/fdroid/fdroid/views/appdetails/AntiFeaturesListingView.java @@ -72,7 +72,7 @@ public class AntiFeaturesListingView extends RecyclerView { @Override public void onBindViewHolder(@NonNull AntiFeatureItemViewHolder holder, int position) { final String antiFeatureId = app.antiFeatures[position]; - Repository repo = FDroidApp.getRepo(app.repoId); + Repository repo = FDroidApp.getRepoManager(getContext()).getRepository(app.repoId); if (repo == null) return; LocaleListCompat localeList = LocaleListCompat.getDefault(); AntiFeature antiFeature = repo.getAntiFeatures().get(antiFeatureId); diff --git a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java index 3135f287c..def1c6675 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java +++ b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java @@ -548,7 +548,8 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder { AppVersion version = updateChecker.getSuggestedVersion(app.packageName, app.preferredSigner, releaseChannels); if (version == null) return null; - Repository repo = FDroidApp.getRepo(version.getRepoId()); + Repository repo = FDroidApp.getRepoManager(activity).getRepository(version.getRepoId()); + if (repo == null) return null; return new Apk(version, repo); }, receivedApk -> { if (receivedApk != null) { diff --git a/app/src/main/java/org/fdroid/fdroid/views/main/LatestViewBinder.java b/app/src/main/java/org/fdroid/fdroid/views/main/LatestViewBinder.java index 8c846557c..726a03839 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/main/LatestViewBinder.java +++ b/app/src/main/java/org/fdroid/fdroid/views/main/LatestViewBinder.java @@ -194,7 +194,7 @@ class LatestViewBinder implements Observer>, ChangeListene int repoCount = 0; Long lastUpdate = null; - for (Repository repo : FDroidApp.repos) { + for (Repository repo : FDroidApp.getRepoManager(activity).getRepositories()) { if (repo.getEnabled()) { repoCount++; if (lastUpdate == null && repo.getLastUpdated() != null) lastUpdate = repo.getLastUpdated(); diff --git a/app/src/main/java/org/fdroid/fdroid/views/updates/UpdatesAdapter.java b/app/src/main/java/org/fdroid/fdroid/views/updates/UpdatesAdapter.java index 44e188278..a451c0f04 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/updates/UpdatesAdapter.java +++ b/app/src/main/java/org/fdroid/fdroid/views/updates/UpdatesAdapter.java @@ -135,7 +135,7 @@ public class UpdatesAdapter extends RecyclerView.Adapter> = + MutableStateFlow(emptyList()) + public val repositoriesState: StateFlow> = _repositoriesState.asStateFlow() + + /** + * Used internally as a mechanism to wait until repositories are loaded from the DB. + * This happens quite fast and the load is triggered at construction time. + * However, in some cases like when the app got killed and restarted by the OS, + * code could try to access the [repositoriesState] before they've loaded. + */ + private val repoCountDownLatch = CountDownLatch(1) + + init { + // we need to load the repositories first off the UiThread, so it doesn't deadlock + GlobalScope.launch(coroutineContext) { + _repositoriesState.value = db.getRepositoryDao().getRepositories() + repoCountDownLatch.countDown() + withContext(Dispatchers.Main) { + // keep observing the repos from the DB + // and update internal cache when changes happen + db.getRepositoryDao().getLiveRepositories().observeForever { repositories -> + _repositoriesState.value = repositories + } + } + } + } + + /** + * This method will block the current thread in the rare case + * that the repositories have not been loaded from the DB. + */ + public fun getRepository(repoId: Long): Repository? { + repoCountDownLatch.await() + return repositoriesState.value.firstOrNull { repo -> repo.repoId == repoId } + } + + /** + * This method will block the current thread in the rare case + * that the repositories have not been loaded from the DB. + */ + public fun getRepositories(): List { + repoCountDownLatch.await() + return repositoriesState.value + } + +}