From e69a6d5a8f24e7745516001f58bee49e05f2ea9e Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Thu, 1 Sep 2016 14:59:26 +0200 Subject: [PATCH 1/8] store Apk instance in Installer instance for fetching state If F-Droid or InstallManagerService get killed while an install is in progress, that install will ultimately broadcast back to InstallManagerService to manage the notifications. The state is gone since things have been killed, so include the Apk instance in the Intent that is included in the broadcasts so that InstallManagerService can fetch all required info from the database. closes #698 --- .../java/org/fdroid/fdroid/AppDetails.java | 22 ++++---- .../java/org/fdroid/fdroid/RepoUpdater.java | 4 +- .../fdroid/installer/DefaultInstaller.java | 17 +++---- .../installer/DefaultInstallerActivity.java | 28 ++++------ .../fdroid/installer/ExtensionInstaller.java | 16 +++--- .../installer/InstallManagerService.java | 8 +-- .../fdroid/fdroid/installer/Installer.java | 51 ++++++++++--------- .../fdroid/installer/InstallerFactory.java | 19 ++++--- .../fdroid/installer/InstallerService.java | 12 ++--- .../fdroid/installer/PrivilegedInstaller.java | 19 +++---- .../views/UninstallDialogActivity.java | 8 +-- 11 files changed, 105 insertions(+), 99 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/AppDetails.java b/app/src/main/java/org/fdroid/fdroid/AppDetails.java index f433ae184..a4fbbcd24 100644 --- a/app/src/main/java/org/fdroid/fdroid/AppDetails.java +++ b/app/src/main/java/org/fdroid/fdroid/AppDetails.java @@ -862,7 +862,7 @@ public class AppDetails extends AppCompatActivity { return true; case UNINSTALL: - uninstallApk(app.packageName); + uninstallApk(); return true; case IGNOREALL: @@ -959,9 +959,13 @@ public class AppDetails extends AppCompatActivity { InstallManagerService.queue(this, app, apk); } - private void uninstallApk(String packageName) { - Installer installer = InstallerFactory.create(this, null); - Intent intent = installer.getUninstallScreen(packageName); + /** + * Queue for uninstall based on the instance variable {@link #app} + */ + private void uninstallApk() { + Apk apk = app.installedApk; + Installer installer = InstallerFactory.create(this, apk); + Intent intent = installer.getUninstallScreen(apk); if (intent != null) { // uninstall screen required Utils.debugLog(TAG, "screen screen required"); @@ -975,7 +979,7 @@ public class AppDetails extends AppCompatActivity { private void startUninstall() { localBroadcastManager.registerReceiver(uninstallReceiver, Installer.getUninstallIntentFilter(app.packageName)); - InstallerService.uninstall(context, app.packageName); + InstallerService.uninstall(context, app.installedApk); } private void launchApk(String packageName) { @@ -1630,7 +1634,7 @@ public class AppDetails extends AppCompatActivity { // If "launchable", launch activity.launchApk(app.packageName); } else { - activity.uninstallApk(app.packageName); + activity.uninstallApk(); } } else if (app.suggestedVersionCode > 0) { // If not installed, install @@ -1658,10 +1662,6 @@ public class AppDetails extends AppCompatActivity { appDetails = (AppDetails) activity; } - void remove() { - appDetails.uninstallApk(appDetails.getApp().packageName); - } - @Override public void onViewCreated(View view, Bundle savedInstanceState) { // A bit of a hack, but we can't add the header view in setupSummaryHeader(), @@ -1689,7 +1689,7 @@ public class AppDetails extends AppCompatActivity { App app = appDetails.getApp(); final Apk apk = appDetails.getApks().getItem(position - l.getHeaderViewsCount()); if (app.installedVersionCode == apk.versionCode) { - remove(); + appDetails.uninstallApk(); } else if (app.installedVersionCode > apk.versionCode) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setMessage(R.string.installDowngrade); diff --git a/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java b/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java index 9d30651e1..c4acc9a76 100644 --- a/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java +++ b/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java @@ -483,7 +483,9 @@ public class RepoUpdater { } if (repoPushRequest.versionCode == null || repoPushRequest.versionCode == packageInfo.versionCode) { - InstallerService.uninstall(context, packageName); + Apk apk = ApkProvider.Helper.find(context, repoPushRequest.packageName, + packageInfo.versionCode); + InstallerService.uninstall(context, apk); } else { Utils.debugLog(TAG, "ignoring request based on versionCode:" + repoPushRequest); } diff --git a/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstaller.java b/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstaller.java index cf440ae96..fa202dda5 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstaller.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstaller.java @@ -41,8 +41,8 @@ public class DefaultInstaller extends Installer { private static final String TAG = "DefaultInstaller"; - DefaultInstaller(Context context) { - super(context); + DefaultInstaller(Context context, Apk apk) { + super(context, apk); } @Override @@ -54,6 +54,7 @@ public class DefaultInstaller extends Installer { Intent installIntent = new Intent(context, DefaultInstallerActivity.class); installIntent.setAction(DefaultInstallerActivity.ACTION_INSTALL_PACKAGE); installIntent.putExtra(Installer.EXTRA_DOWNLOAD_URI, downloadUri); + installIntent.putExtra(Installer.EXTRA_APK, apk); installIntent.setData(localApkUri); PendingIntent installPendingIntent = PendingIntent.getActivity( @@ -67,21 +68,19 @@ public class DefaultInstaller extends Installer { } @Override - protected void uninstallPackage(String packageName) { - sendBroadcastUninstall(packageName, Installer.ACTION_UNINSTALL_STARTED); + protected void uninstallPackage() { + sendBroadcastUninstall(Installer.ACTION_UNINSTALL_STARTED); Intent uninstallIntent = new Intent(context, DefaultInstallerActivity.class); uninstallIntent.setAction(DefaultInstallerActivity.ACTION_UNINSTALL_PACKAGE); - uninstallIntent.putExtra( - DefaultInstallerActivity.EXTRA_UNINSTALL_PACKAGE_NAME, packageName); + uninstallIntent.putExtra(Installer.EXTRA_APK, apk); PendingIntent uninstallPendingIntent = PendingIntent.getActivity( context.getApplicationContext(), - packageName.hashCode(), + apk.packageName.hashCode(), uninstallIntent, PendingIntent.FLAG_UPDATE_CURRENT); - sendBroadcastUninstall(packageName, - Installer.ACTION_UNINSTALL_USER_INTERACTION, uninstallPendingIntent); + sendBroadcastUninstall(Installer.ACTION_UNINSTALL_USER_INTERACTION, uninstallPendingIntent); } @Override diff --git a/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstallerActivity.java b/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstallerActivity.java index 8c7769f41..04efb2087 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstallerActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstallerActivity.java @@ -31,6 +31,7 @@ import android.support.v4.app.FragmentActivity; import android.util.Log; import org.fdroid.fdroid.R; +import org.fdroid.fdroid.data.Apk; /** * A transparent activity as a wrapper around Android's PackageInstaller Intents @@ -41,13 +42,10 @@ public class DefaultInstallerActivity extends FragmentActivity { static final String ACTION_INSTALL_PACKAGE = "org.fdroid.fdroid.installer.DefaultInstaller.action.INSTALL_PACKAGE"; static final String ACTION_UNINSTALL_PACKAGE = "org.fdroid.fdroid.installer.DefaultInstaller.action.UNINSTALL_PACKAGE"; - static final String EXTRA_UNINSTALL_PACKAGE_NAME = "org.fdroid.fdroid.installer.DefaultInstaller.extra.UNINSTALL_PACKAGE_NAME"; - private static final int REQUEST_CODE_INSTALL = 0; private static final int REQUEST_CODE_UNINSTALL = 1; private Uri downloadUri; - private String uninstallPackageName; // for the broadcasts private DefaultInstaller installer; @@ -56,18 +54,16 @@ public class DefaultInstallerActivity extends FragmentActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - installer = new DefaultInstaller(this); - Intent intent = getIntent(); String action = intent.getAction(); + Apk apk = intent.getParcelableExtra(Installer.EXTRA_APK); + installer = new DefaultInstaller(this, apk); if (ACTION_INSTALL_PACKAGE.equals(action)) { Uri localApkUri = intent.getData(); downloadUri = intent.getParcelableExtra(Installer.EXTRA_DOWNLOAD_URI); installPackage(localApkUri); } else if (ACTION_UNINSTALL_PACKAGE.equals(action)) { - uninstallPackageName = intent.getStringExtra(EXTRA_UNINSTALL_PACKAGE_NAME); - - uninstallPackage(uninstallPackageName); + uninstallPackage(apk.packageName); } else { throw new IllegalStateException("Intent action not specified!"); } @@ -134,7 +130,7 @@ public class DefaultInstallerActivity extends FragmentActivity { getPackageManager().getPackageInfo(packageName, 0); } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "NameNotFoundException", e); - installer.sendBroadcastUninstall(packageName, Installer.ACTION_UNINSTALL_INTERRUPTED, + installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_INTERRUPTED, "Package that is scheduled for uninstall is not installed!"); finish(); return; @@ -155,7 +151,7 @@ public class DefaultInstallerActivity extends FragmentActivity { startActivityForResult(intent, REQUEST_CODE_UNINSTALL); } catch (ActivityNotFoundException e) { Log.e(TAG, "ActivityNotFoundException", e); - installer.sendBroadcastUninstall(packageName, Installer.ACTION_UNINSTALL_INTERRUPTED, + installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_INTERRUPTED, "This Android rom does not support ACTION_UNINSTALL_PACKAGE!"); finish(); } @@ -197,25 +193,21 @@ public class DefaultInstallerActivity extends FragmentActivity { case REQUEST_CODE_UNINSTALL: // resultCode is always 0 on Android < 4.0. if (Build.VERSION.SDK_INT < 14) { - installer.sendBroadcastUninstall(uninstallPackageName, - Installer.ACTION_UNINSTALL_COMPLETE); + installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_COMPLETE); break; } switch (resultCode) { case Activity.RESULT_OK: - installer.sendBroadcastUninstall(uninstallPackageName, - Installer.ACTION_UNINSTALL_COMPLETE); + installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_COMPLETE); break; case Activity.RESULT_CANCELED: - installer.sendBroadcastUninstall(uninstallPackageName, - Installer.ACTION_UNINSTALL_INTERRUPTED); + installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_INTERRUPTED); break; case Activity.RESULT_FIRST_USER: default: // AOSP UninstallAppProgress returns RESULT_FIRST_USER on error - installer.sendBroadcastUninstall(uninstallPackageName, - Installer.ACTION_UNINSTALL_INTERRUPTED, + installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_INTERRUPTED, getString(R.string.uninstall_error_unknown)); break; } diff --git a/app/src/main/java/org/fdroid/fdroid/installer/ExtensionInstaller.java b/app/src/main/java/org/fdroid/fdroid/installer/ExtensionInstaller.java index d40137c2d..ed3f00f66 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/ExtensionInstaller.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/ExtensionInstaller.java @@ -1,4 +1,5 @@ /* + * Copyright (C) 2016 Blue Jay Wireless * Copyright (C) 2016 Dominik Schürmann * * This program is free software; you can redistribute it and/or @@ -39,8 +40,8 @@ import java.io.File; */ public class ExtensionInstaller extends Installer { - ExtensionInstaller(Context context) { - super(context); + ExtensionInstaller(Context context, Apk apk) { + super(context, apk); } @Override @@ -71,23 +72,22 @@ public class ExtensionInstaller extends Installer { } @Override - protected void uninstallPackage(String packageName) { - sendBroadcastUninstall(packageName, Installer.ACTION_UNINSTALL_STARTED); + protected void uninstallPackage() { + sendBroadcastUninstall(Installer.ACTION_UNINSTALL_STARTED); Intent uninstallIntent = new Intent(context, InstallExtensionDialogActivity.class); uninstallIntent.setAction(InstallExtensionDialogActivity.ACTION_UNINSTALL); PendingIntent uninstallPendingIntent = PendingIntent.getActivity( context.getApplicationContext(), - packageName.hashCode(), + apk.packageName.hashCode(), uninstallIntent, PendingIntent.FLAG_UPDATE_CURRENT); - sendBroadcastUninstall(packageName, - Installer.ACTION_UNINSTALL_USER_INTERACTION, uninstallPendingIntent); + sendBroadcastUninstall(Installer.ACTION_UNINSTALL_USER_INTERACTION, uninstallPendingIntent); // don't use broadcasts for the rest of this special installer - sendBroadcastUninstall(packageName, Installer.ACTION_UNINSTALL_COMPLETE); + sendBroadcastUninstall(Installer.ACTION_UNINSTALL_COMPLETE); } @Override diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java index 1a21bd9db..7fedf8256 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java @@ -235,6 +235,7 @@ public class InstallManagerService extends Service { @Override public void onReceive(Context context, Intent intent) { String downloadUrl = intent.getDataString(); + Apk apk; switch (intent.getAction()) { case Installer.ACTION_INSTALL_STARTED: // nothing to do @@ -247,6 +248,7 @@ public class InstallManagerService extends Service { localBroadcastManager.unregisterReceiver(this); break; case Installer.ACTION_INSTALL_INTERRUPTED: + apk = intent.getParcelableExtra(Installer.EXTRA_APK); String errorMessage = intent.getStringExtra(Installer.EXTRA_ERROR_MESSAGE); @@ -264,15 +266,15 @@ public class InstallManagerService extends Service { localBroadcastManager.unregisterReceiver(this); break; case Installer.ACTION_INSTALL_USER_INTERACTION: + apk = intent.getParcelableExtra(Installer.EXTRA_APK); PendingIntent installPendingIntent = intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI); - Apk apkUserInteraction = getApkFromActive(downloadUrl); // show notification if app details is not visible - if (AppDetails.isAppVisible(apkUserInteraction.packageName)) { + if (AppDetails.isAppVisible(apk.packageName)) { cancelNotification(downloadUrl); } else { - notifyDownloadComplete(apkUserInteraction, downloadUrl, installPendingIntent); + notifyDownloadComplete(apk, downloadUrl, installPendingIntent); } break; diff --git a/app/src/main/java/org/fdroid/fdroid/installer/Installer.java b/app/src/main/java/org/fdroid/fdroid/installer/Installer.java index 8df65cd4c..bdcf2a271 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/Installer.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/Installer.java @@ -1,4 +1,5 @@ /* + * Copyright (C) 2016 Blue Jay Wireless * Copyright (C) 2016 Dominik Schürmann * * This program is free software; you can redistribute it and/or @@ -42,11 +43,12 @@ import java.io.IOException; * Handles the actual install process. Subclasses implement the details. */ public abstract class Installer { - final Context context; - private final LocalBroadcastManager localBroadcastManager; - private static final String TAG = "Installer"; + final Context context; + final Apk apk; + private final LocalBroadcastManager localBroadcastManager; + public static final String ACTION_INSTALL_STARTED = "org.fdroid.fdroid.installer.Installer.action.INSTALL_STARTED"; public static final String ACTION_INSTALL_COMPLETE = "org.fdroid.fdroid.installer.Installer.action.INSTALL_COMPLETE"; public static final String ACTION_INSTALL_INTERRUPTED = "org.fdroid.fdroid.installer.Installer.action.INSTALL_INTERRUPTED"; @@ -67,12 +69,16 @@ public abstract class Installer { */ static final String EXTRA_DOWNLOAD_URI = "org.fdroid.fdroid.installer.Installer.extra.DOWNLOAD_URI"; public static final String EXTRA_APK = "org.fdroid.fdroid.installer.Installer.extra.APK"; - public static final String EXTRA_PACKAGE_NAME = "org.fdroid.fdroid.installer.Installer.extra.PACKAGE_NAME"; public static final String EXTRA_USER_INTERACTION_PI = "org.fdroid.fdroid.installer.Installer.extra.USER_INTERACTION_PI"; public static final String EXTRA_ERROR_MESSAGE = "org.fdroid.fdroid.net.installer.Installer.extra.ERROR_MESSAGE"; - Installer(Context context) { + /** + * @param apk must be included so that all the phases of the install process + * can get all the data about the app, even after F-Droid was killed + */ + Installer(Context context, Apk apk) { this.context = context; + this.apk = apk; localBroadcastManager = LocalBroadcastManager.getInstance(context); } @@ -125,18 +131,18 @@ public abstract class Installer { * Returns an Intent to start a dialog wrapped in an activity * for uninstall confirmation. * - * @param packageName packageName of app to uninstall + * @param apk {@link Apk} instance of app to uninstall * @return Intent with activity for uninstall confirmation * Returns null if Installer handles that on itself, e.g., * with DefaultInstaller. */ - public Intent getUninstallScreen(String packageName) { + public Intent getUninstallScreen(Apk apk) { if (!isUnattended()) { return null; } Intent intent = new Intent(context, UninstallDialogActivity.class); - intent.putExtra(Installer.EXTRA_PACKAGE_NAME, packageName); + intent.putExtra(Installer.EXTRA_APK, apk); return intent; } @@ -158,31 +164,31 @@ public abstract class Installer { Intent intent = new Intent(action); intent.setData(downloadUri); intent.putExtra(Installer.EXTRA_USER_INTERACTION_PI, pendingIntent); + intent.putExtra(Installer.EXTRA_APK, apk); if (!TextUtils.isEmpty(errorMessage)) { intent.putExtra(Installer.EXTRA_ERROR_MESSAGE, errorMessage); } localBroadcastManager.sendBroadcast(intent); } - void sendBroadcastUninstall(String packageName, String action, String errorMessage) { - sendBroadcastUninstall(packageName, action, null, errorMessage); + void sendBroadcastUninstall(String action, String errorMessage) { + sendBroadcastUninstall(action, null, errorMessage); } - void sendBroadcastUninstall(String packageName, String action) { - sendBroadcastUninstall(packageName, action, null, null); + void sendBroadcastUninstall(String action) { + sendBroadcastUninstall(action, null, null); } - void sendBroadcastUninstall(String packageName, String action, PendingIntent pendingIntent) { - sendBroadcastUninstall(packageName, action, pendingIntent, null); + void sendBroadcastUninstall(String action, PendingIntent pendingIntent) { + sendBroadcastUninstall(action, pendingIntent, null); } - void sendBroadcastUninstall(String packageName, String action, - PendingIntent pendingIntent, String errorMessage) { - Uri uri = Uri.fromParts("package", packageName, null); + void sendBroadcastUninstall(String action, PendingIntent pendingIntent, String errorMessage) { + Uri uri = Uri.fromParts("package", apk.packageName, null); Intent intent = new Intent(action); intent.setData(uri); // for broadcast filtering - intent.putExtra(Installer.EXTRA_PACKAGE_NAME, packageName); + intent.putExtra(Installer.EXTRA_APK, apk); intent.putExtra(Installer.EXTRA_USER_INTERACTION_PI, pendingIntent); if (!TextUtils.isEmpty(errorMessage)) { intent.putExtra(Installer.EXTRA_ERROR_MESSAGE, errorMessage); @@ -242,7 +248,7 @@ public abstract class Installer { if (isUnattended()) { Log.e(TAG, e.getMessage(), e); Log.e(TAG, "Falling back to AOSP DefaultInstaller!"); - DefaultInstaller defaultInstaller = new DefaultInstaller(context); + DefaultInstaller defaultInstaller = new DefaultInstaller(context, apk); defaultInstaller.installPackageInternal(localApkUri, downloadUri, apk); return; } @@ -266,11 +272,10 @@ public abstract class Installer { protected abstract void installPackageInternal(Uri localApkUri, Uri downloadUri, Apk apk); /** - * Uninstall app - * - * @param packageName package name of the app that should be uninstalled + * Uninstall app as defined by {@link Installer#apk} in + * {@link Installer#Installer(Context, Apk)} */ - protected abstract void uninstallPackage(String packageName); + protected abstract void uninstallPackage(); /** * This {@link Installer} instance is capable of "unattended" install and diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallerFactory.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallerFactory.java index 3c19ae42c..47c9bc74c 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/InstallerFactory.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallerFactory.java @@ -1,4 +1,5 @@ /* + * Copyright (C) 2016 Blue Jay Wireless * Copyright (C) 2016 Dominik Schürmann * * This program is free software; you can redistribute it and/or @@ -20,6 +21,7 @@ package org.fdroid.fdroid.installer; import android.content.Context; +import android.text.TextUtils; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.Apk; @@ -34,22 +36,23 @@ public class InstallerFactory { * case to install the "F-Droid Privileged Extension" ExtensionInstaller. * * @param context current {@link Context} - * @param apk apk to be installed. Required to select the ExtensionInstaller. - * If this is null, the ExtensionInstaller will never be returned. + * @param apk to be installed, always required. * @return instance of an Installer */ public static Installer create(Context context, Apk apk) { - Installer installer; + if (apk == null || TextUtils.isEmpty(apk.packageName)) { + throw new IllegalArgumentException("packageName must not be empty!"); + } - if (apk != null - && apk.packageName.equals(PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME)) { + Installer installer; + if (apk.packageName.equals(PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME)) { // special case for "F-Droid Privileged Extension" - installer = new ExtensionInstaller(context); + installer = new ExtensionInstaller(context, apk); } else if (PrivilegedInstaller.isDefault(context)) { Utils.debugLog(TAG, "privileged extension correctly installed -> PrivilegedInstaller"); - installer = new PrivilegedInstaller(context); + installer = new PrivilegedInstaller(context, apk); } else { - installer = new DefaultInstaller(context); + installer = new DefaultInstaller(context, apk); } return installer; diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallerService.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallerService.java index 4e3b3ad9f..e911ebf2e 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/InstallerService.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallerService.java @@ -1,4 +1,5 @@ /* + * Copyright (C) 2016 Blue Jay Wireless * Copyright (C) 2016 Dominik Schürmann * * This program is free software; you can redistribute it and/or @@ -65,8 +66,7 @@ public class InstallerService extends IntentService { Uri downloadUri = intent.getParcelableExtra(Installer.EXTRA_DOWNLOAD_URI); installer.installPackage(uri, downloadUri, apk); } else if (ACTION_UNINSTALL.equals(intent.getAction())) { - String packageName = intent.getStringExtra(Installer.EXTRA_PACKAGE_NAME); - installer.uninstallPackage(packageName); + installer.uninstallPackage(); } } @@ -90,13 +90,13 @@ public class InstallerService extends IntentService { /** * Uninstall an app * - * @param context this app's {@link Context} - * @param packageName package name of the app that will be uninstalled + * @param context this app's {@link Context} + * @param apk {@link Apk} instance of the app that will be uninstalled */ - public static void uninstall(Context context, String packageName) { + public static void uninstall(Context context, Apk apk) { Intent intent = new Intent(context, InstallerService.class); intent.setAction(ACTION_UNINSTALL); - intent.putExtra(Installer.EXTRA_PACKAGE_NAME, packageName); + intent.putExtra(Installer.EXTRA_APK, apk); context.startService(intent); } diff --git a/app/src/main/java/org/fdroid/fdroid/installer/PrivilegedInstaller.java b/app/src/main/java/org/fdroid/fdroid/installer/PrivilegedInstaller.java index 8649f729b..c1f7ac64a 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/PrivilegedInstaller.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/PrivilegedInstaller.java @@ -1,4 +1,5 @@ /* + * Copyright (C) 2016 Blue Jay Wireless * Copyright (C) 2014-2016 Dominik Schürmann * Copyright (C) 2015 Daniel Martí * @@ -255,8 +256,8 @@ public class PrivilegedInstaller extends Installer { "device owner has marked the package as uninstallable."); } - public PrivilegedInstaller(Context context) { - super(context); + public PrivilegedInstaller(Context context, Apk apk) { + super(context, apk); } public static boolean isExtensionInstalled(Context context) { @@ -354,8 +355,8 @@ public class PrivilegedInstaller extends Installer { } @Override - protected void uninstallPackage(final String packageName) { - sendBroadcastUninstall(packageName, Installer.ACTION_UNINSTALL_STARTED); + protected void uninstallPackage() { + sendBroadcastUninstall(Installer.ACTION_UNINSTALL_STARTED); ServiceConnection mServiceConnection = new ServiceConnection() { public void onServiceConnected(ComponentName name, IBinder service) { @@ -365,9 +366,9 @@ public class PrivilegedInstaller extends Installer { @Override public void handleResult(String packageName, int returnCode) throws RemoteException { if (returnCode == DELETE_SUCCEEDED) { - sendBroadcastUninstall(packageName, ACTION_UNINSTALL_COMPLETE); + sendBroadcastUninstall(ACTION_UNINSTALL_COMPLETE); } else { - sendBroadcastUninstall(packageName, ACTION_UNINSTALL_INTERRUPTED, + sendBroadcastUninstall(ACTION_UNINSTALL_INTERRUPTED, "Error " + returnCode + ": " + UNINSTALL_RETURN_CODES.get(returnCode)); } @@ -377,15 +378,15 @@ public class PrivilegedInstaller extends Installer { try { boolean hasPermissions = privService.hasPrivilegedPermissions(); if (!hasPermissions) { - sendBroadcastUninstall(packageName, ACTION_UNINSTALL_INTERRUPTED, + sendBroadcastUninstall(ACTION_UNINSTALL_INTERRUPTED, context.getString(R.string.system_install_denied_permissions)); return; } - privService.deletePackage(packageName, 0, callback); + privService.deletePackage(apk.packageName, 0, callback); } catch (RemoteException e) { Log.e(TAG, "RemoteException", e); - sendBroadcastUninstall(packageName, ACTION_UNINSTALL_INTERRUPTED, + sendBroadcastUninstall(ACTION_UNINSTALL_INTERRUPTED, "connecting to privileged service failed"); } } diff --git a/app/src/main/java/org/fdroid/fdroid/privileged/views/UninstallDialogActivity.java b/app/src/main/java/org/fdroid/fdroid/privileged/views/UninstallDialogActivity.java index fa2380719..2538cefb8 100644 --- a/app/src/main/java/org/fdroid/fdroid/privileged/views/UninstallDialogActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/privileged/views/UninstallDialogActivity.java @@ -32,6 +32,7 @@ import android.view.ContextThemeWrapper; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.R; +import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.installer.Installer; /** @@ -48,14 +49,15 @@ public class UninstallDialogActivity extends FragmentActivity { super.onCreate(savedInstanceState); final Intent intent = getIntent(); - final String packageName = intent.getStringExtra(Installer.EXTRA_PACKAGE_NAME); + final Apk apk = intent.getParcelableExtra(Installer.EXTRA_APK); PackageManager pm = getPackageManager(); ApplicationInfo appInfo; try { //noinspection WrongConstant (lint is actually wrong here!) - appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES); + appInfo = pm.getApplicationInfo(apk.packageName, + PackageManager.GET_UNINSTALLED_PACKAGES); } catch (PackageManager.NameNotFoundException e) { throw new RuntimeException("Failed to get ApplicationInfo for uninstalling"); } @@ -86,7 +88,7 @@ public class UninstallDialogActivity extends FragmentActivity { @Override public void onClick(DialogInterface dialog, int which) { Intent data = new Intent(); - data.putExtra(Installer.EXTRA_PACKAGE_NAME, packageName); + data.putExtra(Installer.EXTRA_APK, apk); setResult(Activity.RESULT_OK, intent); finish(); } From cb4edbed44c72cbd8315ee9f665d4403fa0b1e88 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Thu, 1 Sep 2016 15:58:09 +0200 Subject: [PATCH 2/8] if updating notification, also try getting app name from DB Now that the packageName is included in the Installer broadcast Intents, it can be used to fetch the app name from the database, if all other ways fail. --- .../fdroid/installer/InstallManagerService.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java index 7fedf8256..62b1b0d88 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java @@ -5,6 +5,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -22,6 +23,7 @@ import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.compat.PackageManagerCompat; import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.App; +import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.net.Downloader; import org.fdroid.fdroid.net.DownloaderService; @@ -255,6 +257,10 @@ public class InstallManagerService extends Service { // show notification if app details is not visible if (!TextUtils.isEmpty(errorMessage)) { App app = getAppFromActive(downloadUrl); + if (app == null) { + ContentResolver resolver = context.getContentResolver(); + app = AppProvider.Helper.findByPackageName(resolver, apk.packageName); + } // show notification if app details is not visible if (app != null && AppDetails.isAppVisible(app.packageName)) { cancelNotification(downloadUrl); @@ -337,10 +343,14 @@ public class InstallManagerService extends Service { title = String.format(getString(R.string.tap_to_update_format), pm.getApplicationLabel(pm.getApplicationInfo(apk.packageName, 0))); } catch (PackageManager.NameNotFoundException e) { - // TODO use packageName to fetch App instance from database if not cached String name = getAppName(apk); if (TextUtils.isEmpty(name) || name.equals(new App().name)) { - return; // do not have a name to display, so leave notification as is + ContentResolver resolver = getContentResolver(); + App app = AppProvider.Helper.findByPackageName(resolver, apk.packageName); + if (app == null || TextUtils.isEmpty(app.name)) { + return; // do not have a name to display, so leave notification as is + } + name = app.name; } title = String.format(getString(R.string.tap_to_install_format), name); } From c02125db01d5e24a92fa437f7165d678b09a1c80 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Thu, 8 Sep 2016 13:58:54 +0200 Subject: [PATCH 3/8] store install/uninstall history for later use The install and uninstall history has lots of uses, including displaying to the user in the app itself, reporting to the Device Administrator to enable tracking of installs/uninstalls from the admin's app repo, etc. It can also be used as part of a "popularity contest" #396 --- app/src/main/AndroidManifest.xml | 3 + .../java/org/fdroid/fdroid/FDroidApp.java | 4 + .../installer/InstallHistoryService.java | 124 ++++++++++++++++++ app/src/main/res/values/donottranslate.xml | 4 +- 4 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/org/fdroid/fdroid/installer/InstallHistoryService.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index eae2723ad..d4bfaeb67 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -468,6 +468,9 @@ + diff --git a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java index 610779db5..81b96cbed 100644 --- a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java +++ b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java @@ -56,6 +56,7 @@ import org.fdroid.fdroid.compat.PRNGFixes; import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.data.InstalledAppProviderService; import org.fdroid.fdroid.data.Repo; +import org.fdroid.fdroid.installer.InstallHistoryService; import org.fdroid.fdroid.net.IconDownloader; import org.fdroid.fdroid.net.WifiStateChangeService; @@ -299,6 +300,9 @@ public class FDroidApp extends Application { }); configureTor(Preferences.get().isTorEnabled()); + + // TODO enable this based on a preference + InstallHistoryService.register(this); } @TargetApi(18) diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallHistoryService.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallHistoryService.java new file mode 100644 index 000000000..a6789538b --- /dev/null +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallHistoryService.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2016 Blue Jay Wireless + * + * 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.installer; + +import android.app.IntentService; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Process; +import android.support.v4.content.LocalBroadcastManager; +import android.text.TextUtils; + +import org.fdroid.fdroid.R; +import org.fdroid.fdroid.Utils; +import org.fdroid.fdroid.data.Apk; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * Saves all activity of installs and uninstalls to the database for later use, like + * displaying in some kind of history viewer or reporting to a "popularity contest" + * app tracker. + */ +public class InstallHistoryService extends IntentService { + public static final String TAG = "InstallHistoryService"; + + private static BroadcastReceiver broadcastReceiver; + + public static void register(Context context) { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addDataScheme("http"); + intentFilter.addDataScheme("https"); + intentFilter.addDataScheme("package"); + intentFilter.addAction(Installer.ACTION_INSTALL_STARTED); + intentFilter.addAction(Installer.ACTION_INSTALL_COMPLETE); + intentFilter.addAction(Installer.ACTION_INSTALL_INTERRUPTED); + intentFilter.addAction(Installer.ACTION_INSTALL_USER_INTERACTION); + intentFilter.addAction(Installer.ACTION_UNINSTALL_STARTED); + intentFilter.addAction(Installer.ACTION_UNINSTALL_COMPLETE); + intentFilter.addAction(Installer.ACTION_UNINSTALL_INTERRUPTED); + intentFilter.addAction(Installer.ACTION_UNINSTALL_USER_INTERACTION); + + broadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + queue(context, intent); + } + }; + LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context); + localBroadcastManager.registerReceiver(broadcastReceiver, intentFilter); + } + + public static void unregister(Context context) { + LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context); + localBroadcastManager.unregisterReceiver(broadcastReceiver); + } + + public static void queue(Context context, Intent intent) { + Utils.debugLog(TAG, "queue " + intent); + intent.setClass(context, InstallHistoryService.class); + context.startService(intent); + } + + public InstallHistoryService() { + super("InstallHistoryService"); + } + + @Override + protected void onHandleIntent(Intent intent) { + Utils.debugLog(TAG, "onHandleIntent " + intent); + if (intent == null) { + return; + } + + Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST); + long timestamp = System.currentTimeMillis(); + Apk apk = intent.getParcelableExtra(Installer.EXTRA_APK); + String packageName = apk.packageName; + int versionCode = apk.versionCode; + + List values = new ArrayList<>(4); + values.add(String.valueOf(timestamp)); + values.add(packageName); + values.add(String.valueOf(versionCode)); + values.add(intent.getAction()); + + File logFile = new File(getFilesDir(), getString(R.string.install_history_log_file)); + FileWriter fw = null; + PrintWriter out = null; + try { + fw = new FileWriter(logFile, true); + out = new PrintWriter(fw); + out.println(TextUtils.join(",", values)); + } catch (IOException e) { + Utils.debugLog(TAG, e.getMessage()); + } finally { + Utils.closeQuietly(out); + Utils.closeQuietly(fw); + } + } +} diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 00e6b1e2d..33ee1d38a 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -7,7 +7,7 @@ https://gitlab.com/fdroid/fdroidclient team@f-droid.org GNU General Public License version\u00A03 or later - + transition_app_item_icon https:// @@ -15,6 +15,8 @@ %1$s on F-Droid + all + 0 1 From f102ccff60d4a9b0f7480aeabf5362d4b71bb223 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Fri, 9 Sep 2016 12:50:23 +0200 Subject: [PATCH 4/8] allow install history to be read from an ContentProvider This allows a designated app to read the install history from F-Droid via a ContentProvider. The app is designated by the packageName defined in the string install_history_reader_packageName. --- app/src/main/AndroidManifest.xml | 9 +++++++++ app/src/main/java/org/fdroid/fdroid/FDroidApp.java | 11 +++++++++++ .../fdroid/installer/InstallHistoryService.java | 8 ++++++-- app/src/main/res/values/donottranslate.xml | 5 ++++- .../main/res/xml/install_history_file_provider.xml | 6 ++++++ 5 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 app/src/main/res/xml/install_history_file_provider.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d4bfaeb67..70cecf9cb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -109,6 +109,15 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/apk_file_provider" /> + + + = 19) { + modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION; + } + grantUriPermission(packageName, InstallHistoryService.LOG_URI, modeFlags); + } } @TargetApi(18) diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallHistoryService.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallHistoryService.java index a6789538b..e111b15df 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/InstallHistoryService.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallHistoryService.java @@ -24,11 +24,11 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.net.Uri; import android.os.Process; import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; -import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.Apk; @@ -47,6 +47,8 @@ import java.util.List; public class InstallHistoryService extends IntentService { public static final String TAG = "InstallHistoryService"; + public static final Uri LOG_URI = Uri.parse("content://org.fdroid.fdroid.installer/install_history/all"); + private static BroadcastReceiver broadcastReceiver; public static void register(Context context) { @@ -107,7 +109,9 @@ public class InstallHistoryService extends IntentService { values.add(String.valueOf(versionCode)); values.add(intent.getAction()); - File logFile = new File(getFilesDir(), getString(R.string.install_history_log_file)); + File installHistoryDir = new File(getCacheDir(), "install_history"); + installHistoryDir.mkdir(); + File logFile = new File(installHistoryDir, "all"); FileWriter fw = null; PrintWriter out = null; try { diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 33ee1d38a..cb5f3c688 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -15,7 +15,10 @@ %1$s on F-Droid - all + + 1-THIS MEANS NO APP IS GRANTED ACCESS! + @string/install_history_reader_packageName_UNSET 0 diff --git a/app/src/main/res/xml/install_history_file_provider.xml b/app/src/main/res/xml/install_history_file_provider.xml new file mode 100644 index 000000000..06e42c762 --- /dev/null +++ b/app/src/main/res/xml/install_history_file_provider.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file From a08a32020a8625375a8e38ef69edfdee2e0a9469 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Fri, 9 Sep 2016 15:34:47 +0200 Subject: [PATCH 5/8] add preference to control storing install history For now, this is disabled by default and hidden in the expert preferences since it doesn't do anything yet inside of F-Droid. It is useful now for whitelabel builds to fetch the install history from another app. #396 --- app/src/main/java/org/fdroid/fdroid/FDroidApp.java | 5 +++-- app/src/main/java/org/fdroid/fdroid/Preferences.java | 6 ++++++ .../fdroid/fdroid/installer/InstallHistoryService.java | 4 ++++ .../fdroid/views/fragments/PreferencesFragment.java | 9 +++++++++ app/src/main/res/values/strings.xml | 2 ++ app/src/main/res/xml/preferences.xml | 6 ++++++ 6 files changed, 30 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java index 3998e883f..c4a9369c2 100644 --- a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java +++ b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java @@ -301,8 +301,9 @@ public class FDroidApp extends Application { configureTor(Preferences.get().isTorEnabled()); - // TODO enable this based on a preference - InstallHistoryService.register(this); + if (Preferences.get().isKeepingInstallHistory()) { + InstallHistoryService.register(this); + } String packageName = getString(R.string.install_history_reader_packageName); String unset = getString(R.string.install_history_reader_packageName_UNSET); diff --git a/app/src/main/java/org/fdroid/fdroid/Preferences.java b/app/src/main/java/org/fdroid/fdroid/Preferences.java index 1973d4dd0..7a5b91bed 100644 --- a/app/src/main/java/org/fdroid/fdroid/Preferences.java +++ b/app/src/main/java/org/fdroid/fdroid/Preferences.java @@ -55,6 +55,7 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh public static final String PREF_IGN_TOUCH = "ignoreTouchscreen"; public static final String PREF_KEEP_CACHE_TIME = "keepCacheFor"; public static final String PREF_UNSTABLE_UPDATES = "unstableUpdates"; + public static final String PREF_KEEP_INSTALL_HISTORY = "keepInstallHistory"; public static final String PREF_EXPERT = "expert"; public static final String PREF_PRIVILEGED_INSTALLER = "privilegedInstaller"; public static final String PREF_UNINSTALL_PRIVILEGED_APP = "uninstallPrivilegedApp"; @@ -75,6 +76,7 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh //private static final boolean DEFAULT_LOCAL_REPO_BONJOUR = true; private static final long DEFAULT_KEEP_CACHE_TIME = TimeUnit.DAYS.toMillis(1); private static final boolean DEFAULT_UNSTABLE_UPDATES = false; + private static final boolean DEFAULT_KEEP_INSTALL_HISTORY = false; //private static final boolean DEFAULT_LOCAL_REPO_HTTPS = false; private static final boolean DEFAULT_INCOMP_VER = false; private static final boolean DEFAULT_EXPERT = false; @@ -184,6 +186,10 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh return preferences.getBoolean(PREF_UNSTABLE_UPDATES, DEFAULT_UNSTABLE_UPDATES); } + public boolean isKeepingInstallHistory() { + return preferences.getBoolean(PREF_KEEP_INSTALL_HISTORY, DEFAULT_KEEP_INSTALL_HISTORY); + } + public boolean showIncompatibleVersions() { return preferences.getBoolean(PREF_INCOMP_VER, DEFAULT_INCOMP_VER); } diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallHistoryService.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallHistoryService.java index e111b15df..6d930c850 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/InstallHistoryService.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallHistoryService.java @@ -52,6 +52,9 @@ public class InstallHistoryService extends IntentService { private static BroadcastReceiver broadcastReceiver; public static void register(Context context) { + if (broadcastReceiver != null) { + return; // already registered + } IntentFilter intentFilter = new IntentFilter(); intentFilter.addDataScheme("http"); intentFilter.addDataScheme("https"); @@ -78,6 +81,7 @@ public class InstallHistoryService extends IntentService { public static void unregister(Context context) { LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context); localBroadcastManager.unregisterReceiver(broadcastReceiver); + broadcastReceiver = null; } public static void queue(Context context, Intent intent) { diff --git a/app/src/main/java/org/fdroid/fdroid/views/fragments/PreferencesFragment.java b/app/src/main/java/org/fdroid/fdroid/views/fragments/PreferencesFragment.java index 4304b87c6..c1355b5ad 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/fragments/PreferencesFragment.java +++ b/app/src/main/java/org/fdroid/fdroid/views/fragments/PreferencesFragment.java @@ -19,6 +19,7 @@ import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.PreferencesActivity; import org.fdroid.fdroid.R; +import org.fdroid.fdroid.installer.InstallHistoryService; import org.fdroid.fdroid.installer.PrivilegedInstaller; import info.guardianproject.netcipher.NetCipher; @@ -193,6 +194,14 @@ public class PreferencesFragment extends PreferenceFragment } break; + case Preferences.PREF_KEEP_INSTALL_HISTORY: + CheckBoxPreference p = (CheckBoxPreference) findPreference(key); + if (p.isChecked()) { + InstallHistoryService.register(getContext()); + } else { + InstallHistoryService.unregister(getContext()); + } + break; } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a3a30b88f..d23295a97 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,6 +14,8 @@ Updates Unstable updates Suggest updates to unstable versions + Keep install history + Store a log of all installs and uninstalls inside F-Droid Other Automatic update interval diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index aa80594bd..96ae03aaa 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -91,6 +91,12 @@ android:summary="@string/unstable_updates_summary" android:defaultValue="false" android:dependency="expert" /> + Date: Tue, 20 Sep 2016 11:47:49 +0200 Subject: [PATCH 6/8] send Installer.ACTION_INSTALL_STARTED consistently ACTION_INSTALL_STARTED was being sent twice per transaction with the default installer. Also, it should be sent as the first step of the install process. --- .../fdroid/installer/DefaultInstaller.java | 9 ++------- .../installer/DefaultInstallerActivity.java | 4 ++-- .../fdroid/installer/InstallManagerService.java | 5 ++++- .../org/fdroid/fdroid/installer/Installer.java | 17 ++++++++--------- .../fdroid/installer/PrivilegedInstaller.java | 2 -- 5 files changed, 16 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstaller.java b/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstaller.java index fa202dda5..6fba6ec6a 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstaller.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstaller.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Dominik Schürmann + * Copyright (C) 2016 Blue Jay Wireless * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -25,11 +26,8 @@ import android.content.Intent; import android.net.Uri; import android.os.Build; -import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.Apk; -import java.io.File; - /** * The default installer of F-Droid. It uses the normal Intents APIs of Android * to install apks. Its main inner workings are encapsulated in DefaultInstallerActivity. @@ -39,7 +37,7 @@ import java.io.File; */ public class DefaultInstaller extends Installer { - private static final String TAG = "DefaultInstaller"; + public static final String TAG = "DefaultInstaller"; DefaultInstaller(Context context, Apk apk) { super(context, apk); @@ -47,9 +45,6 @@ public class DefaultInstaller extends Installer { @Override protected void installPackageInternal(Uri localApkUri, Uri downloadUri, Apk apk) { - sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_STARTED); - - Utils.debugLog(TAG, "DefaultInstaller uri: " + localApkUri + " file: " + new File(localApkUri.getPath())); Intent installIntent = new Intent(context, DefaultInstallerActivity.class); installIntent.setAction(DefaultInstallerActivity.ACTION_INSTALL_PACKAGE); diff --git a/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstallerActivity.java b/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstallerActivity.java index 04efb2087..9c53800f2 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstallerActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstallerActivity.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014-2016 Dominik Schürmann + * Copyright (C) 2016 Blue Jay Wireless * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -37,7 +38,7 @@ import org.fdroid.fdroid.data.Apk; * A transparent activity as a wrapper around Android's PackageInstaller Intents */ public class DefaultInstallerActivity extends FragmentActivity { - private static final String TAG = "AndroidInstallerAct"; + private static final String TAG = "DefaultInstallerActivit"; static final String ACTION_INSTALL_PACKAGE = "org.fdroid.fdroid.installer.DefaultInstaller.action.INSTALL_PACKAGE"; static final String ACTION_UNINSTALL_PACKAGE = "org.fdroid.fdroid.installer.DefaultInstaller.action.UNINSTALL_PACKAGE"; @@ -121,7 +122,6 @@ public class DefaultInstallerActivity extends FragmentActivity { "This Android rom does not support ACTION_INSTALL_PACKAGE!"); finish(); } - installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_STARTED); } private void uninstallPackage(String packageName) { diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java index 62b1b0d88..2e3564507 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java @@ -462,10 +462,13 @@ public class InstallManagerService extends Service { */ public static void queue(Context context, App app, Apk apk) { String urlString = apk.getUrl(); + Uri downloadUri = Uri.parse(urlString); + Installer.sendBroadcastInstall(context, downloadUri, Installer.ACTION_INSTALL_STARTED, apk, + null, null); Utils.debugLog(TAG, "queue " + app.packageName + " " + apk.versionCode + " from " + urlString); Intent intent = new Intent(context, InstallManagerService.class); intent.setAction(ACTION_INSTALL); - intent.setData(Uri.parse(urlString)); + intent.setData(downloadUri); intent.putExtra(EXTRA_APP, app); intent.putExtra(EXTRA_APK, apk); context.startService(intent); diff --git a/app/src/main/java/org/fdroid/fdroid/installer/Installer.java b/app/src/main/java/org/fdroid/fdroid/installer/Installer.java index bdcf2a271..ffa50fe41 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/Installer.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/Installer.java @@ -47,7 +47,6 @@ public abstract class Installer { final Context context; final Apk apk; - private final LocalBroadcastManager localBroadcastManager; public static final String ACTION_INSTALL_STARTED = "org.fdroid.fdroid.installer.Installer.action.INSTALL_STARTED"; public static final String ACTION_INSTALL_COMPLETE = "org.fdroid.fdroid.installer.Installer.action.INSTALL_COMPLETE"; @@ -79,7 +78,6 @@ public abstract class Installer { Installer(Context context, Apk apk) { this.context = context; this.apk = apk; - localBroadcastManager = LocalBroadcastManager.getInstance(context); } /** @@ -148,19 +146,20 @@ public abstract class Installer { } void sendBroadcastInstall(Uri downloadUri, String action, PendingIntent pendingIntent) { - sendBroadcastInstall(downloadUri, action, pendingIntent, null); + sendBroadcastInstall(context, downloadUri, action, apk, pendingIntent, null); } void sendBroadcastInstall(Uri downloadUri, String action) { - sendBroadcastInstall(downloadUri, action, null, null); + sendBroadcastInstall(context, downloadUri, action, apk, null, null); } void sendBroadcastInstall(Uri downloadUri, String action, String errorMessage) { - sendBroadcastInstall(downloadUri, action, null, errorMessage); + sendBroadcastInstall(context, downloadUri, action, apk, null, errorMessage); } - void sendBroadcastInstall(Uri downloadUri, String action, - PendingIntent pendingIntent, String errorMessage) { + static void sendBroadcastInstall(Context context, + Uri downloadUri, String action, Apk apk, + PendingIntent pendingIntent, String errorMessage) { Intent intent = new Intent(action); intent.setData(downloadUri); intent.putExtra(Installer.EXTRA_USER_INTERACTION_PI, pendingIntent); @@ -168,7 +167,7 @@ public abstract class Installer { if (!TextUtils.isEmpty(errorMessage)) { intent.putExtra(Installer.EXTRA_ERROR_MESSAGE, errorMessage); } - localBroadcastManager.sendBroadcast(intent); + LocalBroadcastManager.getInstance(context).sendBroadcast(intent); } void sendBroadcastUninstall(String action, String errorMessage) { @@ -193,7 +192,7 @@ public abstract class Installer { if (!TextUtils.isEmpty(errorMessage)) { intent.putExtra(Installer.EXTRA_ERROR_MESSAGE, errorMessage); } - localBroadcastManager.sendBroadcast(intent); + LocalBroadcastManager.getInstance(context).sendBroadcast(intent); } /** diff --git a/app/src/main/java/org/fdroid/fdroid/installer/PrivilegedInstaller.java b/app/src/main/java/org/fdroid/fdroid/installer/PrivilegedInstaller.java index c1f7ac64a..11c6f984e 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/PrivilegedInstaller.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/PrivilegedInstaller.java @@ -308,8 +308,6 @@ public class PrivilegedInstaller extends Installer { @Override protected void installPackageInternal(final Uri localApkUri, final Uri downloadUri, Apk apk) { - sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_STARTED); - ServiceConnection mServiceConnection = new ServiceConnection() { public void onServiceConnected(ComponentName name, IBinder service) { IPrivilegedService privService = IPrivilegedService.Stub.asInterface(service); From 79df0a1f9b66043e583736042be23bfd985e6d7e Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Wed, 21 Sep 2016 12:17:55 +0200 Subject: [PATCH 7/8] show complete link command in debug logging Its helpful when debugging if the complete command is printed, without omitting the last element. --- app/src/main/java/org/fdroid/fdroid/compat/FileCompat.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/fdroid/fdroid/compat/FileCompat.java b/app/src/main/java/org/fdroid/fdroid/compat/FileCompat.java index 5612d6d05..6ad56b4c3 100644 --- a/app/src/main/java/org/fdroid/fdroid/compat/FileCompat.java +++ b/app/src/main/java/org/fdroid/fdroid/compat/FileCompat.java @@ -66,7 +66,8 @@ public class FileCompat { dest.getAbsolutePath(), }; try { - Utils.debugLog(TAG, "Executing command: " + commands[0] + " " + commands[1] + " " + commands[2]); + Utils.debugLog(TAG, "Executing command: " + commands[0] + " " + commands[1] + + " " + commands[2] + " " + commands[3]); Process proc = Runtime.getRuntime().exec(commands); Utils.consumeStream(proc.getInputStream()); Utils.consumeStream(proc.getErrorStream()); From 372b28a71ad245ee712a7de002f38ef98ef1ddfd Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Thu, 29 Sep 2016 13:57:28 +0200 Subject: [PATCH 8/8] Installer methods no longer need to accept Apk as an argument Since e69a6d5a8f24e7745516001f58bee49e05f2ea9e, the Apk instance is provided in the constructor and is available as a final instance variable. No need to pass it around. Thanks to @pserwylo for spotting this. --- .../java/org/fdroid/fdroid/AppDetails.java | 4 ++-- .../fdroid/installer/DefaultInstaller.java | 2 +- .../fdroid/installer/ExtensionInstaller.java | 2 +- .../fdroid/fdroid/installer/Installer.java | 19 ++++++++----------- .../fdroid/installer/InstallerService.java | 2 +- .../fdroid/installer/PrivilegedInstaller.java | 2 +- 6 files changed, 14 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/AppDetails.java b/app/src/main/java/org/fdroid/fdroid/AppDetails.java index a4fbbcd24..fd7030dff 100644 --- a/app/src/main/java/org/fdroid/fdroid/AppDetails.java +++ b/app/src/main/java/org/fdroid/fdroid/AppDetails.java @@ -942,7 +942,7 @@ public class AppDetails extends AppCompatActivity { private void initiateInstall(Apk apk) { Installer installer = InstallerFactory.create(this, apk); - Intent intent = installer.getPermissionScreen(apk); + Intent intent = installer.getPermissionScreen(); if (intent != null) { // permission screen required Utils.debugLog(TAG, "permission screen required"); @@ -965,7 +965,7 @@ public class AppDetails extends AppCompatActivity { private void uninstallApk() { Apk apk = app.installedApk; Installer installer = InstallerFactory.create(this, apk); - Intent intent = installer.getUninstallScreen(apk); + Intent intent = installer.getUninstallScreen(); if (intent != null) { // uninstall screen required Utils.debugLog(TAG, "screen screen required"); diff --git a/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstaller.java b/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstaller.java index 6fba6ec6a..e35ad48ae 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstaller.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstaller.java @@ -44,7 +44,7 @@ public class DefaultInstaller extends Installer { } @Override - protected void installPackageInternal(Uri localApkUri, Uri downloadUri, Apk apk) { + protected void installPackageInternal(Uri localApkUri, Uri downloadUri) { Intent installIntent = new Intent(context, DefaultInstallerActivity.class); installIntent.setAction(DefaultInstallerActivity.ACTION_INSTALL_PACKAGE); diff --git a/app/src/main/java/org/fdroid/fdroid/installer/ExtensionInstaller.java b/app/src/main/java/org/fdroid/fdroid/installer/ExtensionInstaller.java index ed3f00f66..3d0b11623 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/ExtensionInstaller.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/ExtensionInstaller.java @@ -45,7 +45,7 @@ public class ExtensionInstaller extends Installer { } @Override - protected void installPackageInternal(Uri localApkUri, Uri downloadUri, Apk apk) { + protected void installPackageInternal(Uri localApkUri, Uri downloadUri) { // extension must be signed with the same public key as main F-Droid // NOTE: Disabled for debug builds to be able to test official extension from repo ApkSignatureVerifier signatureVerifier = new ApkSignatureVerifier(context); diff --git a/app/src/main/java/org/fdroid/fdroid/installer/Installer.java b/app/src/main/java/org/fdroid/fdroid/installer/Installer.java index ffa50fe41..ce1216a0b 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/Installer.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/Installer.java @@ -83,17 +83,16 @@ public abstract class Installer { /** * Returns permission screen for given apk. * - * @param apk instance of Apk * @return Intent with Activity to show required permissions. * Returns null if Installer handles that on itself, e.g., with DefaultInstaller, * or if no new permissions have been introduced during an update */ - public Intent getPermissionScreen(Apk apk) { + public Intent getPermissionScreen() { if (!isUnattended()) { return null; } - int count = newPermissionCount(apk); + int count = newPermissionCount(); if (count == 0) { // no permission screen needed! return null; @@ -105,7 +104,7 @@ public abstract class Installer { return intent; } - private int newPermissionCount(Apk apk) { + private int newPermissionCount() { boolean supportsRuntimePermissions = apk.targetSdkVersion >= 23; if (supportsRuntimePermissions) { return 0; @@ -129,12 +128,11 @@ public abstract class Installer { * Returns an Intent to start a dialog wrapped in an activity * for uninstall confirmation. * - * @param apk {@link Apk} instance of app to uninstall * @return Intent with activity for uninstall confirmation * Returns null if Installer handles that on itself, e.g., * with DefaultInstaller. */ - public Intent getUninstallScreen(Apk apk) { + public Intent getUninstallScreen() { if (!isUnattended()) { return null; } @@ -228,9 +226,8 @@ public abstract class Installer { * @param localApkUri points to the local copy of the APK to be installed * @param downloadUri serves as the unique ID for all actions related to the * installation of that specific APK - * @param apk apk object of the app that should be installed */ - public void installPackage(Uri localApkUri, Uri downloadUri, Apk apk) { + public void installPackage(Uri localApkUri, Uri downloadUri) { try { // verify that permissions of the apk file match the ones from the apk object ApkVerifier apkVerifier = new ApkVerifier(context, localApkUri, apk); @@ -248,7 +245,7 @@ public abstract class Installer { Log.e(TAG, e.getMessage(), e); Log.e(TAG, "Falling back to AOSP DefaultInstaller!"); DefaultInstaller defaultInstaller = new DefaultInstaller(context, apk); - defaultInstaller.installPackageInternal(localApkUri, downloadUri, apk); + defaultInstaller.installPackageInternal(localApkUri, downloadUri); return; } } @@ -265,10 +262,10 @@ public abstract class Installer { return; } - installPackageInternal(sanitizedUri, downloadUri, apk); + installPackageInternal(sanitizedUri, downloadUri); } - protected abstract void installPackageInternal(Uri localApkUri, Uri downloadUri, Apk apk); + protected abstract void installPackageInternal(Uri localApkUri, Uri downloadUri); /** * Uninstall app as defined by {@link Installer#apk} in diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallerService.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallerService.java index e911ebf2e..3784c788c 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/InstallerService.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallerService.java @@ -64,7 +64,7 @@ public class InstallerService extends IntentService { if (ACTION_INSTALL.equals(intent.getAction())) { Uri uri = intent.getData(); Uri downloadUri = intent.getParcelableExtra(Installer.EXTRA_DOWNLOAD_URI); - installer.installPackage(uri, downloadUri, apk); + installer.installPackage(uri, downloadUri); } else if (ACTION_UNINSTALL.equals(intent.getAction())) { installer.uninstallPackage(); } diff --git a/app/src/main/java/org/fdroid/fdroid/installer/PrivilegedInstaller.java b/app/src/main/java/org/fdroid/fdroid/installer/PrivilegedInstaller.java index 11c6f984e..1ecd62789 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/PrivilegedInstaller.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/PrivilegedInstaller.java @@ -307,7 +307,7 @@ public class PrivilegedInstaller extends Installer { } @Override - protected void installPackageInternal(final Uri localApkUri, final Uri downloadUri, Apk apk) { + protected void installPackageInternal(final Uri localApkUri, final Uri downloadUri) { ServiceConnection mServiceConnection = new ServiceConnection() { public void onServiceConnected(ComponentName name, IBinder service) { IPrivilegedService privService = IPrivilegedService.Stub.asInterface(service);