diff --git a/app/src/androidTest/java/android/test/ProviderTestCase2MockContext.java b/app/src/androidTest/java/android/test/ProviderTestCase2MockContext.java index 659fba2f3..405902649 100644 --- a/app/src/androidTest/java/android/test/ProviderTestCase2MockContext.java +++ b/app/src/androidTest/java/android/test/ProviderTestCase2MockContext.java @@ -98,6 +98,11 @@ public abstract class ProviderTestCase2MockContext ex public Context getApplicationContext() { return this; } + + @Override + public String getPackageName() { + return "org.fdroid.fdroid"; + } } /** diff --git a/app/src/androidTest/java/mock/MockApplicationInfo.java b/app/src/androidTest/java/mock/MockApplicationInfo.java index 4d88d3f9b..1ab4592a4 100644 --- a/app/src/androidTest/java/mock/MockApplicationInfo.java +++ b/app/src/androidTest/java/mock/MockApplicationInfo.java @@ -5,6 +5,9 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import java.io.File; +import java.io.IOException; + @SuppressLint("ParcelCreator") public class MockApplicationInfo extends ApplicationInfo { @@ -12,6 +15,11 @@ public class MockApplicationInfo extends ApplicationInfo { public MockApplicationInfo(PackageInfo info) { this.info = info; + try { + this.publicSourceDir = File.createTempFile(info.packageName, "apk").getAbsolutePath(); + } catch (IOException e) { + this.publicSourceDir = "/data/app/" + info.packageName + "-4.apk"; + } } @Override diff --git a/app/src/androidTest/java/mock/MockInstallablePackageManager.java b/app/src/androidTest/java/mock/MockInstallablePackageManager.java index b7056711f..4b95dcf60 100644 --- a/app/src/androidTest/java/mock/MockInstallablePackageManager.java +++ b/app/src/androidTest/java/mock/MockInstallablePackageManager.java @@ -37,6 +37,7 @@ public class MockInstallablePackageManager extends MockPackageManager { p.packageName = id; p.versionCode = version; p.versionName = versionName; + p.applicationInfo = new MockApplicationInfo(p); info.add(p); } } diff --git a/app/src/androidTest/java/org/fdroid/fdroid/TestUtils.java b/app/src/androidTest/java/org/fdroid/fdroid/TestUtils.java index ab171366e..8b01eb630 100644 --- a/app/src/androidTest/java/org/fdroid/fdroid/TestUtils.java +++ b/app/src/androidTest/java/org/fdroid/fdroid/TestUtils.java @@ -4,7 +4,6 @@ import android.app.Instrumentation; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; -import android.content.Intent; import android.net.Uri; import android.os.Environment; import android.support.annotation.Nullable; @@ -15,9 +14,6 @@ import junit.framework.AssertionFailedError; import org.fdroid.fdroid.data.ApkProvider; import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.data.FDroidProviderTest; -import org.fdroid.fdroid.receiver.PackageAddedReceiver; -import org.fdroid.fdroid.receiver.PackageRemovedReceiver; -import org.fdroid.fdroid.receiver.PackageUpgradedReceiver; import java.io.File; import java.io.FileOutputStream; @@ -28,9 +24,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import mock.MockContextSwappableComponents; -import mock.MockInstallablePackageManager; - public class TestUtils { private static final String TAG = "TestUtils"; @@ -146,54 +139,6 @@ public class TestUtils { return providerTest.getMockContentResolver().insert(uri, values); } - /** - * Will tell {@code pm} that we are installing {@code appId}, and then alert the - * {@link org.fdroid.fdroid.receiver.PackageAddedReceiver}. This will in turn update the - * "installed apps" table in the database. - */ - public static void installAndBroadcast(MockContextSwappableComponents context, - MockInstallablePackageManager pm, String appId, - int versionCode, String versionName) { - - context.setPackageManager(pm); - pm.install(appId, versionCode, versionName); - Intent installIntent = new Intent(Intent.ACTION_PACKAGE_ADDED); - installIntent.setData(Utils.getPackageUri(appId)); - new PackageAddedReceiver().onReceive(context, installIntent); - - } - - /** - * @see org.fdroid.fdroid.TestUtils#installAndBroadcast(mock.MockContextSwappableComponents, mock.MockInstallablePackageManager, String, int, String) - */ - public static void upgradeAndBroadcast(MockContextSwappableComponents context, - MockInstallablePackageManager pm, String appId, - int versionCode, String versionName) { - /* - removeAndBroadcast(context, pm, appId); - installAndBroadcast(context, pm, appId, versionCode, versionName); - */ - context.setPackageManager(pm); - pm.install(appId, versionCode, versionName); - Intent installIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED); - installIntent.setData(Utils.getPackageUri(appId)); - new PackageUpgradedReceiver().onReceive(context, installIntent); - - } - - /** - * @see org.fdroid.fdroid.TestUtils#installAndBroadcast(mock.MockContextSwappableComponents, mock.MockInstallablePackageManager, String, int, String) - */ - public static void removeAndBroadcast(MockContextSwappableComponents context, MockInstallablePackageManager pm, String appId) { - - context.setPackageManager(pm); - pm.remove(appId); - Intent installIntent = new Intent(Intent.ACTION_PACKAGE_REMOVED); - installIntent.setData(Utils.getPackageUri(appId)); - new PackageRemovedReceiver().onReceive(context, installIntent); - - } - @Nullable public static File copyAssetToDir(Context context, String assetName, File directory) { File tempFile; diff --git a/app/src/androidTest/java/org/fdroid/fdroid/data/AppProviderTest.java b/app/src/androidTest/java/org/fdroid/fdroid/data/AppProviderTest.java index b19c1d819..454759c0e 100644 --- a/app/src/androidTest/java/org/fdroid/fdroid/data/AppProviderTest.java +++ b/app/src/androidTest/java/org/fdroid/fdroid/data/AppProviderTest.java @@ -2,6 +2,7 @@ package org.fdroid.fdroid.data; import android.content.ContentResolver; import android.content.ContentValues; +import android.content.pm.PackageInfo; import android.content.res.Resources; import android.database.Cursor; @@ -56,24 +57,27 @@ public class AppProviderTest extends FDroidProviderTest { insertApp("com.example.app1000", "App 1000"); for (int i = 0; i < 50; i++) { - pm.install("com.example.app" + i, 1, "v" + 1); + String packageName = "com.example.app" + i; + pm.install(packageName, 1, "v" + 1); + PackageInfo packageInfo = pm.getPackageInfo(packageName, 0); + InstalledAppProviderService.insertAppIntoDb(getSwappableContext(), packageName, packageInfo); } - InstalledAppCacheUpdater.updateInForeground(getMockContext()); - assertResultCount(1, AppProvider.getInstalledUri()); for (int i = 50; i < 500; i++) { - pm.install("com.example.app" + i, 1, "v" + 1); + String packageName = "com.example.app" + i; + pm.install(packageName, 1, "v" + 1); + PackageInfo packageInfo = pm.getPackageInfo(packageName, 0); + InstalledAppProviderService.insertAppIntoDb(getSwappableContext(), packageName, packageInfo); } - InstalledAppCacheUpdater.updateInForeground(getMockContext()); - assertResultCount(2, AppProvider.getInstalledUri()); for (int i = 500; i < 1100; i++) { - pm.install("com.example.app" + i, 1, "v" + 1); + String packageName = "com.example.app" + i; + pm.install(packageName, 1, "v" + 1); + PackageInfo packageInfo = pm.getPackageInfo(packageName, 0); + InstalledAppProviderService.insertAppIntoDb(getSwappableContext(), packageName, packageInfo); } - InstalledAppCacheUpdater.updateInForeground(getMockContext()); - assertResultCount(3, AppProvider.getInstalledUri()); } @@ -127,7 +131,7 @@ public class AppProviderTest extends FDroidProviderTest { values.put(AppProvider.DataColumns.IGNORE_THISUPDATE, ignoreVercode); insertApp(id, "App: " + id, values); - TestUtils.installAndBroadcast(getSwappableContext(), packageManager, id, installedVercode, "v" + installedVercode); + InstalledAppProviderTest.install(getSwappableContext(), packageManager, id, installedVercode, "v" + installedVercode); } public void testCanUpdate() { @@ -247,7 +251,7 @@ public class AppProviderTest extends FDroidProviderTest { assertResultCount(0, AppProvider.getInstalledUri()); for (int i = 10; i < 20; i++) { - TestUtils.installAndBroadcast(getSwappableContext(), pm, "com.example.test." + i, i, "v1"); + InstalledAppProviderTest.install(getSwappableContext(), pm, "com.example.test." + i, i, "v1"); } assertResultCount(10, AppProvider.getInstalledUri()); diff --git a/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java b/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java index f36fcfed3..e06f7531d 100644 --- a/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java +++ b/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java @@ -1,9 +1,9 @@ package org.fdroid.fdroid.data; import android.content.ContentValues; +import android.content.pm.PackageInfo; -import org.fdroid.fdroid.TestUtils; - +import mock.MockContextSwappableComponents; import mock.MockInstallablePackageManager; @SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics @@ -96,9 +96,8 @@ public class InstalledAppProviderTest extends FDroidProviderTest - + - - - - - - - - - - - - - + @@ -468,6 +456,9 @@ + diff --git a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java index 95e0ea708..41eda63e0 100644 --- a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java +++ b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java @@ -55,7 +55,7 @@ import org.fdroid.fdroid.Preferences.ChangeListener; import org.fdroid.fdroid.Preferences.Theme; import org.fdroid.fdroid.compat.PRNGFixes; import org.fdroid.fdroid.data.AppProvider; -import org.fdroid.fdroid.data.InstalledAppCacheUpdater; +import org.fdroid.fdroid.data.InstalledAppProviderService; import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.net.IconDownloader; import org.fdroid.fdroid.net.WifiStateChangeService; @@ -224,7 +224,7 @@ public class FDroidApp extends Application { curTheme = Preferences.get().getTheme(); Preferences.get().configureProxy(); - InstalledAppCacheUpdater.updateInBackground(getApplicationContext()); + InstalledAppProviderService.compareToPackageManager(this); // If the user changes the preference to do with filtering rooted apps, // it is easier to just notify a change in the app provider, diff --git a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppCacheUpdater.java b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppCacheUpdater.java deleted file mode 100644 index 1e86bc8c9..000000000 --- a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppCacheUpdater.java +++ /dev/null @@ -1,191 +0,0 @@ -package org.fdroid.fdroid.data; - -import android.content.ContentProviderOperation; -import android.content.Context; -import android.content.OperationApplicationException; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.RemoteException; -import android.util.Log; - -import org.fdroid.fdroid.Utils; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * Compares what is in the fdroid_installedApp SQLite database table with the package - * info that we can gleam from the {@link android.content.pm.PackageManager}. If there - * is any updates/removals/insertions which need to take place, we will perform them. - * TODO: The content providers are not thread safe, so it is possible we will be writing - * to the database at the same time we respond to a broadcasted intent. - */ -public final class InstalledAppCacheUpdater { - - private static final String TAG = "InstalledAppCache"; - - private final Context context; - - private final List toInsert = new ArrayList<>(); - private final List toDelete = new ArrayList<>(); - - private InstalledAppCacheUpdater(Context context) { - this.context = context; - } - - /** - * Ensure our database of installed apps is in sync with what the PackageManager tells us is installed. - * Once completed, the relevant ContentProviders will be notified of any changes to installed statuses. - * This method will block until completed, which could be in the order of a few seconds (depending on - * how many apps are installed). - */ - public static void updateInForeground(Context context) { - InstalledAppCacheUpdater updater = new InstalledAppCacheUpdater(context); - if (updater.update()) { - updater.notifyProviders(); - } - } - - /** - * Ensure our database of installed apps is in sync with what the PackageManager tells us is installed. - * The installed app cache hasn't gotten out of sync somehow, e.g. if we crashed/ran out of battery - * half way through responding to a package installed {@link android.content.Intent}. Once completed, - * the relevant {@link android.content.ContentProvider}s will be notified of any changes to installed - * statuses. This method returns immediately, and will continue to work in an AsyncTask. It doesn't - * really matter where we put this in the bootstrap process, because it runs on a different thread, - * which will be delayed by some seconds to avoid an error where the database is locked due to the - * database updater. - */ - public static void updateInBackground(Context context) { - InstalledAppCacheUpdater updater = new InstalledAppCacheUpdater(context); - updater.startBackgroundWorker(); - } - - private boolean update() { - - long startTime = System.currentTimeMillis(); - - compareCacheToPackageManager(); - updateCache(); - - long duration = System.currentTimeMillis() - startTime; - Utils.debugLog(TAG, "Took " + duration + "ms to compare the installed app cache with PackageManager."); - - return hasChanged(); - } - - private void notifyProviders() { - Utils.debugLog(TAG, "Installed app cache has changed, notifying content providers (so they can update the relevant views)."); - context.getContentResolver().notifyChange(AppProvider.getContentUri(), null); - context.getContentResolver().notifyChange(ApkProvider.getContentUri(), null); - } - - private void startBackgroundWorker() { - new PostponedWorker().execute(); - } - - /** - * If any of the cached app details have been removed, updated or inserted, - * then the cache has changed. - */ - private boolean hasChanged() { - return toInsert.size() > 0 || toDelete.size() > 0; - } - - private void updateCache() { - - ArrayList ops = new ArrayList<>(); - ops.addAll(deleteFromCache(toDelete)); - ops.addAll(insertIntoCache(toInsert)); - - if (ops.size() > 0) { - try { - context.getContentResolver().applyBatch(InstalledAppProvider.getAuthority(), ops); - Utils.debugLog(TAG, "Finished executing " + ops.size() + " CRUD operations on installed app cache."); - } catch (RemoteException | OperationApplicationException e) { - Log.e(TAG, "Error updating installed app cache: " + e); - } - } - - } - - private void compareCacheToPackageManager() { - - Map cachedInfo = InstalledAppProvider.Helper.all(context); - - List installedPackages = context.getPackageManager() - .getInstalledPackages(PackageManager.GET_SIGNATURES); - for (PackageInfo appInfo : installedPackages) { - toInsert.add(appInfo); - if (cachedInfo.containsKey(appInfo.packageName)) { - cachedInfo.remove(appInfo.packageName); - } - } - - if (cachedInfo.size() > 0) { - for (Map.Entry entry : cachedInfo.entrySet()) { - toDelete.add(entry.getKey()); - } - } - } - - private List insertIntoCache(List appsToInsert) { - List ops = new ArrayList<>(appsToInsert.size()); - if (appsToInsert.size() > 0) { - Utils.debugLog(TAG, "Preparing to cache installed info for " + appsToInsert.size() + " new apps."); - Uri uri = InstalledAppProvider.getContentUri(); - for (PackageInfo info : appsToInsert) { - ContentProviderOperation op = ContentProviderOperation.newInsert(uri) - .withValue(InstalledAppProvider.DataColumns.PACKAGE_NAME, info.packageName) - .withValue(InstalledAppProvider.DataColumns.VERSION_CODE, info.versionCode) - .withValue(InstalledAppProvider.DataColumns.VERSION_NAME, info.versionName) - .withValue(InstalledAppProvider.DataColumns.APPLICATION_LABEL, - InstalledAppProvider.getApplicationLabel(context, info.packageName)) - .withValue(InstalledAppProvider.DataColumns.SIGNATURE, - InstalledAppProvider.getPackageSig(info)) - .build(); - ops.add(op); - } - } - return ops; - } - - private List deleteFromCache(List packageNames) { - List ops = new ArrayList<>(packageNames.size()); - if (packageNames.size() > 0) { - Utils.debugLog(TAG, "Preparing to remove " + packageNames.size() + " apps from the installed app cache."); - for (final String packageName : packageNames) { - Uri uri = InstalledAppProvider.getAppUri(packageName); - ops.add(ContentProviderOperation.newDelete(uri).build()); - } - } - return ops; - } - - /** - * Waits 5 seconds before beginning to update cache of installed apps. - * This is due to a bug where the database was locked as F-Droid was starting, - * which caused a crash. - */ - private class PostponedWorker extends AsyncTask { - - @Override - protected Boolean doInBackground(Void... params) { - try { - Thread.sleep(10000); - } catch (InterruptedException ignored) { } - return update(); - } - - @Override - protected void onPostExecute(Boolean changed) { - if (changed) { - notifyProviders(); - } - } - } - -} diff --git a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java new file mode 100644 index 000000000..241356eee --- /dev/null +++ b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java @@ -0,0 +1,163 @@ +package org.fdroid.fdroid.data; + +import android.app.IntentService; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Process; + +import org.fdroid.fdroid.Utils; + +import java.io.File; +import java.util.List; +import java.util.Map; + +/** + * Handles all updates to {@link InstalledAppProvider}, whether checking the contents + * versus what Android says is installed, or processing {@link Intent}s that come + * from {@link android.content.BroadcastReceiver}s for {@link Intent#ACTION_PACKAGE_ADDED} + * and {@link Intent#ACTION_PACKAGE_REMOVED} + *

+ * Since {@link android.content.ContentProvider#insert(Uri, ContentValues)} does not check + * for duplicate records, it is entirely the job of this service to ensure that it is not + * inserting duplicate versions of the same installed APK. On that note, + * {@link #insertAppIntoDb(Context, String, PackageInfo)} and + * {@link #deleteAppFromDb(Context, String)} are both static methods to enable easy testing + * of this stuff. + */ +public class InstalledAppProviderService extends IntentService { + private static final String TAG = "InstalledAppProviderSer"; + + private static final String ACTION_INSERT = "org.fdroid.fdroid.data.action.INSERT"; + private static final String ACTION_DELETE = "org.fdroid.fdroid.data.action.DELETE"; + + private static final String EXTRA_PACKAGE_INFO = "org.fdroid.fdroid.data.extra.PACKAGE_INFO"; + + public InstalledAppProviderService() { + super("InstalledAppProviderService"); + } + + /** + * Inserts an app into {@link InstalledAppProvider} based on a {@code package:} {@link Uri}. + * This has no checks for whether it is inserting an exact duplicate, whatever is provided + * will be inserted. + */ + public static void insert(Context context, PackageInfo packageInfo) { + insert(context, Utils.getPackageUri(packageInfo.packageName), packageInfo); + } + + /** + * Inserts an app into {@link InstalledAppProvider} based on a {@code package:} {@link Uri}. + * This has no checks for whether it is inserting an exact duplicate, whatever is provided + * will be inserted. + */ + public static void insert(Context context, Uri uri) { + insert(context, uri, null); + } + + private static void insert(Context context, Uri uri, PackageInfo packageInfo) { + Intent intent = new Intent(context, InstalledAppProviderService.class); + intent.setAction(ACTION_INSERT); + intent.setData(uri); + intent.putExtra(EXTRA_PACKAGE_INFO, packageInfo); + context.startService(intent); + } + + /** + * Deletes an app from {@link InstalledAppProvider} based on a {@code package:} {@link Uri} + */ + public static void delete(Context context, String packageName) { + delete(context, Utils.getPackageUri(packageName)); + } + + /** + * Deletes an app from {@link InstalledAppProvider} based on a {@code package:} {@link Uri} + */ + public static void delete(Context context, Uri uri) { + Intent intent = new Intent(context, InstalledAppProviderService.class); + intent.setAction(ACTION_DELETE); + intent.setData(uri); + context.startService(intent); + } + + /** + * Make sure that {@link InstalledAppProvider}, our database of installed apps, + * is in sync with what the {@link PackageManager} tells us is installed. Once + * completed, the relevant {@link android.content.ContentProvider}s will be + * notified of any changes to installed statuses. + *

+ * The installed app cache could get out of sync, e.g. if F-Droid crashed/ or + * ran out of battery half way through responding to {@link Intent#ACTION_PACKAGE_ADDED}. + * This method returns immediately, and will continue to work in an + * {@link IntentService}. It doesn't really matter where we put this in the + * bootstrap process, because it runs in its own thread, at the lowest priority: + * {@link Process#THREAD_PRIORITY_LOWEST}. + */ + public static void compareToPackageManager(Context context) { + Map cachedInfo = InstalledAppProvider.Helper.all(context); + + List packageInfoList = context.getPackageManager() + .getInstalledPackages(PackageManager.GET_SIGNATURES); + // TODO check packageInfo.lastUpdateTime for freshness + for (PackageInfo packageInfo : packageInfoList) { + insert(context, packageInfo); + if (cachedInfo.containsKey(packageInfo.packageName)) { + cachedInfo.remove(packageInfo.packageName); + } + } + + for (String packageName : cachedInfo.keySet()) { + delete(context, packageName); + } + } + + @Override + protected void onHandleIntent(Intent intent) { + Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST); + if (intent != null) { + String packageName = intent.getData().getSchemeSpecificPart(); + final String action = intent.getAction(); + if (ACTION_INSERT.equals(action)) { + insertAppIntoDb(this, packageName, (PackageInfo) intent.getParcelableExtra(EXTRA_PACKAGE_INFO)); + } else if (ACTION_DELETE.equals(action)) { + deleteAppFromDb(this, packageName); + } + + Utils.debugLog(TAG, "Notifying content providers (so they can update the relevant views)."); + getContentResolver().notifyChange(AppProvider.getContentUri(), null); + getContentResolver().notifyChange(ApkProvider.getContentUri(), null); + } + } + + static void insertAppIntoDb(Context context, String packageName, PackageInfo packageInfo) { + if (packageInfo == null) { + try { + packageInfo = context.getPackageManager().getPackageInfo(packageName, + PackageManager.GET_SIGNATURES); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return; + } + } + + Uri uri = InstalledAppProvider.getContentUri(); + ContentValues contentValues = new ContentValues(); + contentValues.put(InstalledAppProvider.DataColumns.PACKAGE_NAME, packageInfo.packageName); + contentValues.put(InstalledAppProvider.DataColumns.VERSION_CODE, packageInfo.versionCode); + contentValues.put(InstalledAppProvider.DataColumns.VERSION_NAME, packageInfo.versionName); + contentValues.put(InstalledAppProvider.DataColumns.APPLICATION_LABEL, + InstalledAppProvider.getApplicationLabel(context, packageInfo.packageName)); + contentValues.put(InstalledAppProvider.DataColumns.SIGNATURE, + InstalledAppProvider.getPackageSig(packageInfo)); + + context.getContentResolver().insert(uri, contentValues); + } + + static void deleteAppFromDb(Context context, String packageName) { + Uri uri = InstalledAppProvider.getAppUri(packageName); + context.getContentResolver().delete(uri, null, null); + } +} \ No newline at end of file diff --git a/app/src/main/java/org/fdroid/fdroid/receiver/PackageAddedReceiver.java b/app/src/main/java/org/fdroid/fdroid/receiver/PackageAddedReceiver.java deleted file mode 100644 index 849a2c1ed..000000000 --- a/app/src/main/java/org/fdroid/fdroid/receiver/PackageAddedReceiver.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2014 Peter Serwylo, peter@serwylo.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 3 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package org.fdroid.fdroid.receiver; - -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.net.Uri; - -import org.fdroid.fdroid.Utils; -import org.fdroid.fdroid.data.InstalledAppProvider; - -public class PackageAddedReceiver extends PackageReceiver { - - private static final String TAG = "PackageAddedReceiver"; - - @Override - protected boolean toDiscard(Intent intent) { - if (intent.hasExtra(Intent.EXTRA_REPLACING)) { - Utils.debugLog(TAG, "Discarding since this PACKAGE_ADDED is just a PACKAGE_REPLACED"); - return true; - } - return false; - } - - @Override - protected void handle(Context context, String packageName) { - PackageInfo info = getPackageInfo(context, packageName); - if (info == null) { - Utils.debugLog(TAG, "Could not get package info on '" + packageName + "' - skipping."); - return; - } - - Utils.debugLog(TAG, "Inserting installed app info for '" + packageName + "' (v" + info.versionCode + ")"); - - Uri uri = InstalledAppProvider.getContentUri(); - ContentValues values = new ContentValues(4); - values.put(InstalledAppProvider.DataColumns.PACKAGE_NAME, packageName); - values.put(InstalledAppProvider.DataColumns.VERSION_CODE, info.versionCode); - values.put(InstalledAppProvider.DataColumns.VERSION_NAME, info.versionName); - values.put(InstalledAppProvider.DataColumns.APPLICATION_LABEL, - InstalledAppProvider.getApplicationLabel(context, packageName)); - values.put(InstalledAppProvider.DataColumns.SIGNATURE, - InstalledAppProvider.getPackageSig(info)); - context.getContentResolver().insert(uri, values); - } - -} diff --git a/app/src/main/java/org/fdroid/fdroid/receiver/PackageManagerReceiver.java b/app/src/main/java/org/fdroid/fdroid/receiver/PackageManagerReceiver.java new file mode 100644 index 000000000..d9c44b19c --- /dev/null +++ b/app/src/main/java/org/fdroid/fdroid/receiver/PackageManagerReceiver.java @@ -0,0 +1,35 @@ +package org.fdroid.fdroid.receiver; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import org.fdroid.fdroid.Utils; +import org.fdroid.fdroid.data.InstalledAppProviderService; + +/** + * Receive {@link Intent#ACTION_PACKAGE_ADDED} and {@link Intent#ACTION_PACKAGE_REMOVED} + * events from {@link android.content.pm.PackageManager} to keep + * {@link org.fdroid.fdroid.data.InstalledAppProvider} updated. This ignores + * {@link Intent#EXTRA_REPLACING} and instead handles updates by just deleting then + * inserting the app being updated in direct response to the {@code Intent}s from + * the system. This is also necessary because there are no other checks to prevent + * multiple copies of the same app being inserted into {@Link InstalledAppProvider}. + */ +public class PackageManagerReceiver extends BroadcastReceiver { + private static final String TAG = "PackageManagerReceiver"; + + @Override + public void onReceive(Context context, Intent intent) { + if (intent != null) { + String action = intent.getAction(); + if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { + InstalledAppProviderService.insert(context, intent.getData()); + } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { + InstalledAppProviderService.delete(context, intent.getData()); + } else { + Utils.debugLog(TAG, "unsupported action: " + action + " " + intent); + } + } + } +} diff --git a/app/src/main/java/org/fdroid/fdroid/receiver/PackageReceiver.java b/app/src/main/java/org/fdroid/fdroid/receiver/PackageReceiver.java deleted file mode 100644 index b8d368845..000000000 --- a/app/src/main/java/org/fdroid/fdroid/receiver/PackageReceiver.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2014 Ciaran Gultnieks, ciaran@ciarang.com, - * Peter Serwylo, peter@serwylo.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 3 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package org.fdroid.fdroid.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; - -import org.fdroid.fdroid.Utils; -import org.fdroid.fdroid.data.ApkProvider; -import org.fdroid.fdroid.data.AppProvider; - -abstract class PackageReceiver extends BroadcastReceiver { - - private static final String TAG = "PackageReceiver"; - - protected abstract boolean toDiscard(Intent intent); - - protected abstract void handle(Context context, String packageName); - - protected PackageInfo getPackageInfo(Context context, String packageName) { - PackageInfo info = null; - try { - info = context.getPackageManager().getPackageInfo(packageName, PackageManager.GET_SIGNATURES); - } catch (PackageManager.NameNotFoundException e) { - // ignore - } - return info; - } - - @Override - public void onReceive(Context context, Intent intent) { - Utils.debugLog(TAG, "PackageReceiver received [action = '" + intent.getAction() + "', data = '" + intent.getData() + "']"); - if (toDiscard(intent)) { - return; - } - String packageName = intent.getData().getSchemeSpecificPart(); - handle(context, packageName); - context.getContentResolver().notifyChange(AppProvider.getContentUri(packageName), null); - context.getContentResolver().notifyChange(ApkProvider.getAppUri(packageName), null); - } - -} diff --git a/app/src/main/java/org/fdroid/fdroid/receiver/PackageRemovedReceiver.java b/app/src/main/java/org/fdroid/fdroid/receiver/PackageRemovedReceiver.java deleted file mode 100644 index ab0c3827c..000000000 --- a/app/src/main/java/org/fdroid/fdroid/receiver/PackageRemovedReceiver.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2014 Peter Serwylo, peter@serwylo.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 3 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package org.fdroid.fdroid.receiver; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; - -import org.fdroid.fdroid.Utils; -import org.fdroid.fdroid.data.InstalledAppProvider; - -public class PackageRemovedReceiver extends PackageReceiver { - - private static final String TAG = "PackageRemovedReceiver"; - - @Override - protected boolean toDiscard(Intent intent) { - if (intent.hasExtra(Intent.EXTRA_REPLACING)) { - Utils.debugLog(TAG, "Discarding since this PACKAGE_REMOVED is just a PACKAGE_REPLACED"); - return true; - } - return false; - } - - @Override - protected void handle(Context context, String packageName) { - - Utils.debugLog(TAG, "Removing installed app info for '" + packageName + "'"); - - Uri uri = InstalledAppProvider.getAppUri(packageName); - context.getContentResolver().delete(uri, null, null); - } - -} diff --git a/app/src/main/java/org/fdroid/fdroid/receiver/PackageUpgradedReceiver.java b/app/src/main/java/org/fdroid/fdroid/receiver/PackageUpgradedReceiver.java deleted file mode 100644 index dcf914f84..000000000 --- a/app/src/main/java/org/fdroid/fdroid/receiver/PackageUpgradedReceiver.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2014 Peter Serwylo, peter@serwylo.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 3 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package org.fdroid.fdroid.receiver; - -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.net.Uri; - -import org.fdroid.fdroid.Utils; -import org.fdroid.fdroid.data.InstalledAppProvider; - -/** - * For some reason, devices seem to be keen on sending a REMOVED and then an INSTALLED - * intent, rather than an CHANGED intent. Therefore, this is probably not used on many - * devices. Regardless, it is tested in the unit tests and should work on devices that - * opt instead to send the PACKAGE_CHANGED intent. - */ -public class PackageUpgradedReceiver extends PackageReceiver { - - private static final String TAG = "PackageUpgradedReceiver"; - - @Override - protected boolean toDiscard(Intent intent) { - return false; - } - - @Override - protected void handle(Context context, String packageName) { - PackageInfo info = getPackageInfo(context, packageName); - if (info == null) { - Utils.debugLog(TAG, "Could not get package info on '" + packageName + "' - skipping."); - return; - } - - Utils.debugLog(TAG, "Updating installed app info for '" + packageName + "' to v" + info.versionCode + " (" + info.versionName + ")"); - - Uri uri = InstalledAppProvider.getContentUri(); - ContentValues values = new ContentValues(4); - values.put(InstalledAppProvider.DataColumns.PACKAGE_NAME, packageName); - values.put(InstalledAppProvider.DataColumns.VERSION_CODE, info.versionCode); - values.put(InstalledAppProvider.DataColumns.VERSION_NAME, info.versionName); - values.put(InstalledAppProvider.DataColumns.APPLICATION_LABEL, - InstalledAppProvider.getApplicationLabel(context, packageName)); - values.put(InstalledAppProvider.DataColumns.SIGNATURE, - InstalledAppProvider.getPackageSig(info)); - context.getContentResolver().insert(uri, values); - } - -}