Merge branch 'InstallManagerService-completion' into 'master'

InstallManagerService completion

This is a collection of fixes to finalize `InstallManagerService` now that !300 is merged.

See merge request !318
This commit is contained in:
Dominik 2016-06-01 22:23:50 +00:00
commit de238f3f5f
10 changed files with 128 additions and 131 deletions

View File

@ -85,14 +85,13 @@ import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.InstalledAppProvider; import org.fdroid.fdroid.data.InstalledAppProvider;
import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.installer.Installer;
import org.fdroid.fdroid.installer.InstallManagerService; import org.fdroid.fdroid.installer.InstallManagerService;
import org.fdroid.fdroid.installer.Installer;
import org.fdroid.fdroid.installer.InstallerFactory; import org.fdroid.fdroid.installer.InstallerFactory;
import org.fdroid.fdroid.installer.InstallerService; import org.fdroid.fdroid.installer.InstallerService;
import org.fdroid.fdroid.net.Downloader; import org.fdroid.fdroid.net.Downloader;
import org.fdroid.fdroid.net.DownloaderService; import org.fdroid.fdroid.net.DownloaderService;
import java.io.File;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -530,11 +529,8 @@ public class AppDetails extends AppCompatActivity {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
cleanUpFinishedDownload(); cleanUpFinishedDownload();
Uri localUri =
Uri.fromFile(new File(intent.getStringExtra(Downloader.EXTRA_DOWNLOAD_PATH)));
localBroadcastManager.registerReceiver(installReceiver, localBroadcastManager.registerReceiver(installReceiver,
Installer.getInstallIntentFilter(localUri)); Installer.getInstallIntentFilter(intent.getData()));
} }
}; };

View File

@ -45,34 +45,34 @@ public class DefaultInstaller extends Installer {
} }
@Override @Override
protected void installPackage(Uri uri, Uri originatingUri, String packageName) { protected void installPackage(Uri localApkUri, Uri downloadUri, String packageName) {
sendBroadcastInstall(uri, originatingUri, Installer.ACTION_INSTALL_STARTED); sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_STARTED);
Utils.debugLog(TAG, "DefaultInstaller uri: " + uri + " file: " + new File(uri.getPath())); Utils.debugLog(TAG, "DefaultInstaller uri: " + localApkUri + " file: " + new File(localApkUri.getPath()));
Uri sanitizedUri; Uri sanitizedUri;
try { try {
sanitizedUri = Installer.prepareApkFile(context, uri, packageName); sanitizedUri = Installer.prepareApkFile(context, localApkUri, packageName);
} catch (Installer.InstallFailedException e) { } catch (Installer.InstallFailedException e) {
Log.e(TAG, "prepareApkFile failed", e); Log.e(TAG, "prepareApkFile failed", e);
sendBroadcastInstall(uri, originatingUri, Installer.ACTION_INSTALL_INTERRUPTED, sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED,
e.getMessage()); e.getMessage());
return; return;
} }
Intent installIntent = new Intent(context, DefaultInstallerActivity.class); Intent installIntent = new Intent(context, DefaultInstallerActivity.class);
installIntent.setAction(DefaultInstallerActivity.ACTION_INSTALL_PACKAGE); installIntent.setAction(DefaultInstallerActivity.ACTION_INSTALL_PACKAGE);
installIntent.putExtra(DefaultInstallerActivity.EXTRA_ORIGINATING_URI, originatingUri); installIntent.putExtra(Installer.EXTRA_DOWNLOAD_URI, downloadUri);
installIntent.setData(sanitizedUri); installIntent.setData(sanitizedUri);
PendingIntent installPendingIntent = PendingIntent.getActivity( PendingIntent installPendingIntent = PendingIntent.getActivity(
context.getApplicationContext(), context.getApplicationContext(),
uri.hashCode(), localApkUri.hashCode(),
installIntent, installIntent,
PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent.FLAG_UPDATE_CURRENT);
sendBroadcastInstall(uri, originatingUri, sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_USER_INTERACTION,
Installer.ACTION_INSTALL_USER_INTERACTION, installPendingIntent); installPendingIntent);
} }
@Override @Override

View File

@ -38,18 +38,15 @@ import org.fdroid.fdroid.R;
public class DefaultInstallerActivity extends FragmentActivity { public class DefaultInstallerActivity extends FragmentActivity {
public static final String TAG = "AndroidInstallerAct"; public static final String TAG = "AndroidInstallerAct";
public static final String ACTION_INSTALL_PACKAGE = "org.fdroid.fdroid.INSTALL_PACKAGE"; static final String ACTION_INSTALL_PACKAGE = "org.fdroid.fdroid.installer.DefaultInstaller.action.INSTALL_PACKAGE";
public static final String ACTION_UNINSTALL_PACKAGE = "org.fdroid.fdroid.UNINSTALL_PACKAGE"; static final String ACTION_UNINSTALL_PACKAGE = "org.fdroid.fdroid.installer.DefaultInstaller.action.UNINSTALL_PACKAGE";
public static final String EXTRA_UNINSTALL_PACKAGE_NAME = "uninstallPackageName"; static final String EXTRA_UNINSTALL_PACKAGE_NAME = "org.fdroid.fdroid.installer.DefaultInstaller.extra.UNINSTALL_PACKAGE_NAME";
public static final String EXTRA_ORIGINATING_URI = "originatingUri";
private static final int REQUEST_CODE_INSTALL = 0; private static final int REQUEST_CODE_INSTALL = 0;
private static final int REQUEST_CODE_UNINSTALL = 1; private static final int REQUEST_CODE_UNINSTALL = 1;
private Uri installOriginatingUri; private Uri downloadUri;
private Uri installUri;
private String uninstallPackageName; private String uninstallPackageName;
// for the broadcasts // for the broadcasts
@ -64,10 +61,9 @@ public class DefaultInstallerActivity extends FragmentActivity {
Intent intent = getIntent(); Intent intent = getIntent();
String action = intent.getAction(); String action = intent.getAction();
if (ACTION_INSTALL_PACKAGE.equals(action)) { if (ACTION_INSTALL_PACKAGE.equals(action)) {
installUri = intent.getData(); Uri localApkUri = intent.getData();
installOriginatingUri = intent.getParcelableExtra(EXTRA_ORIGINATING_URI); downloadUri = intent.getParcelableExtra(Installer.EXTRA_DOWNLOAD_URI);
installPackage(localApkUri);
installPackage(installUri, installOriginatingUri);
} else if (ACTION_UNINSTALL_PACKAGE.equals(action)) { } else if (ACTION_UNINSTALL_PACKAGE.equals(action)) {
uninstallPackageName = intent.getStringExtra(EXTRA_UNINSTALL_PACKAGE_NAME); uninstallPackageName = intent.getStringExtra(EXTRA_UNINSTALL_PACKAGE_NAME);
@ -78,7 +74,7 @@ public class DefaultInstallerActivity extends FragmentActivity {
} }
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
private void installPackage(Uri uri, Uri originatingUri) { private void installPackage(Uri uri) {
if (uri == null) { if (uri == null) {
throw new RuntimeException("Set the data uri to point to an apk location!"); throw new RuntimeException("Set the data uri to point to an apk location!");
} }
@ -121,12 +117,11 @@ public class DefaultInstallerActivity extends FragmentActivity {
startActivityForResult(intent, REQUEST_CODE_INSTALL); startActivityForResult(intent, REQUEST_CODE_INSTALL);
} catch (ActivityNotFoundException e) { } catch (ActivityNotFoundException e) {
Log.e(TAG, "ActivityNotFoundException", e); Log.e(TAG, "ActivityNotFoundException", e);
installer.sendBroadcastInstall(uri, originatingUri, Installer.ACTION_INSTALL_INTERRUPTED, installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED,
"This Android rom does not support ACTION_INSTALL_PACKAGE!"); "This Android rom does not support ACTION_INSTALL_PACKAGE!");
finish(); finish();
} }
installer.sendBroadcastInstall(installUri, installOriginatingUri, installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_STARTED);
Installer.ACTION_INSTALL_STARTED);
} }
protected void uninstallPackage(String packageName) { protected void uninstallPackage(String packageName) {
@ -172,31 +167,29 @@ public class DefaultInstallerActivity extends FragmentActivity {
* never executed on Androids < 4.0 * never executed on Androids < 4.0
*/ */
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
installer.sendBroadcastInstall(installUri, installOriginatingUri, installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_COMPLETE);
Installer.ACTION_INSTALL_COMPLETE);
break; break;
} }
// Fallback on N for https://gitlab.com/fdroid/fdroidclient/issues/631 // Fallback on N for https://gitlab.com/fdroid/fdroidclient/issues/631
if ("N".equals(Build.VERSION.CODENAME)) { if ("N".equals(Build.VERSION.CODENAME)) {
installer.sendBroadcastInstall(installUri, installOriginatingUri, installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_COMPLETE);
Installer.ACTION_INSTALL_COMPLETE);
break; break;
} }
switch (resultCode) { switch (resultCode) {
case Activity.RESULT_OK: case Activity.RESULT_OK:
installer.sendBroadcastInstall(installUri, installOriginatingUri, installer.sendBroadcastInstall(downloadUri,
Installer.ACTION_INSTALL_COMPLETE); Installer.ACTION_INSTALL_COMPLETE);
break; break;
case Activity.RESULT_CANCELED: case Activity.RESULT_CANCELED:
installer.sendBroadcastInstall(installUri, installOriginatingUri, installer.sendBroadcastInstall(downloadUri,
Installer.ACTION_INSTALL_INTERRUPTED); Installer.ACTION_INSTALL_INTERRUPTED);
break; break;
case Activity.RESULT_FIRST_USER: case Activity.RESULT_FIRST_USER:
default: default:
// AOSP returns Activity.RESULT_FIRST_USER on error // AOSP returns Activity.RESULT_FIRST_USER on error
installer.sendBroadcastInstall(installUri, installOriginatingUri, installer.sendBroadcastInstall(downloadUri,
Installer.ACTION_INSTALL_INTERRUPTED, Installer.ACTION_INSTALL_INTERRUPTED,
getString(R.string.install_error_unknown)); getString(R.string.install_error_unknown));
break; break;

View File

@ -46,14 +46,13 @@ public class ExtensionInstaller extends Installer {
} }
@Override @Override
protected void installPackage(Uri uri, Uri originatingUri, String packageName) { protected void installPackage(Uri localApkUri, Uri downloadUri, String packageName) {
Uri sanitizedUri; Uri sanitizedUri;
try { try {
sanitizedUri = Installer.prepareApkFile(context, uri, packageName); sanitizedUri = Installer.prepareApkFile(context, localApkUri, packageName);
} catch (InstallFailedException e) { } catch (InstallFailedException e) {
Log.e(TAG, "prepareApkFile failed", e); Log.e(TAG, "prepareApkFile failed", e);
sendBroadcastInstall(uri, originatingUri, Installer.ACTION_INSTALL_INTERRUPTED, sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED, e.getMessage());
e.getMessage());
return; return;
} }
@ -61,7 +60,7 @@ public class ExtensionInstaller extends Installer {
// NOTE: Disabled for debug builds to be able to use official extension from repo // NOTE: Disabled for debug builds to be able to use official extension from repo
ApkSignatureVerifier signatureVerifier = new ApkSignatureVerifier(context); ApkSignatureVerifier signatureVerifier = new ApkSignatureVerifier(context);
if (!BuildConfig.DEBUG && !signatureVerifier.hasFDroidSignature(new File(sanitizedUri.getPath()))) { if (!BuildConfig.DEBUG && !signatureVerifier.hasFDroidSignature(new File(sanitizedUri.getPath()))) {
sendBroadcastInstall(uri, originatingUri, Installer.ACTION_INSTALL_INTERRUPTED, sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED,
"APK signature of extension not correct!"); "APK signature of extension not correct!");
} }
Intent installIntent = new Intent(context, InstallExtensionDialogActivity.class); Intent installIntent = new Intent(context, InstallExtensionDialogActivity.class);
@ -70,15 +69,15 @@ public class ExtensionInstaller extends Installer {
PendingIntent installPendingIntent = PendingIntent.getActivity( PendingIntent installPendingIntent = PendingIntent.getActivity(
context.getApplicationContext(), context.getApplicationContext(),
uri.hashCode(), localApkUri.hashCode(),
installIntent, installIntent,
PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent.FLAG_UPDATE_CURRENT);
sendBroadcastInstall(uri, originatingUri, sendBroadcastInstall(downloadUri,
Installer.ACTION_INSTALL_USER_INTERACTION, installPendingIntent); Installer.ACTION_INSTALL_USER_INTERACTION, installPendingIntent);
// don't use broadcasts for the rest of this special installer // don't use broadcasts for the rest of this special installer
sendBroadcastInstall(uri, originatingUri, Installer.ACTION_INSTALL_COMPLETE); sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_COMPLETE);
} }
@Override @Override

View File

@ -55,8 +55,10 @@ import java.util.Set;
* <li>for a {@link Uri} ID, use {@code Uri}, {@link Intent#getData()} * <li>for a {@link Uri} ID, use {@code Uri}, {@link Intent#getData()}
* <li>for a {@code String} ID, use {@code urlString}, {@link Uri#toString()}, or * <li>for a {@code String} ID, use {@code urlString}, {@link Uri#toString()}, or
* {@link Intent#getDataString()} * {@link Intent#getDataString()}
* <li>for an {@code int} ID, use {@link String#hashCode()} * <li>for an {@code int} ID, use {@link String#hashCode()} or {@link Uri#hashCode()}
* </ul></p> * </ul></p>
* The implementations of {@link Uri#toString()} and {@link Intent#getDataString()} both
* include caching of the generated {@code String}, so it should be plenty fast.
*/ */
public class InstallManagerService extends Service { public class InstallManagerService extends Service {
public static final String TAG = "InstallManagerService"; public static final String TAG = "InstallManagerService";
@ -154,7 +156,7 @@ public class InstallManagerService extends Service {
Apk apk = new Apk(intent.getParcelableExtra(EXTRA_APK)); Apk apk = new Apk(intent.getParcelableExtra(EXTRA_APK));
addToActive(urlString, app, apk); addToActive(urlString, app, apk);
NotificationCompat.Builder builder = createNotificationBuilder(intent.getDataString(), apk); NotificationCompat.Builder builder = createNotificationBuilder(urlString, apk);
notificationManager.notify(urlString.hashCode(), builder.build()); notificationManager.notify(urlString.hashCode(), builder.build());
registerDownloaderReceivers(urlString, builder); registerDownloaderReceivers(urlString, builder);
@ -224,18 +226,18 @@ public class InstallManagerService extends Service {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
// elsewhere called urlString // elsewhere called urlString
Uri originatingUri = intent.getData(); Uri downloadUri = intent.getData();
String urlString = downloadUri.toString();
File localFile = new File(intent.getStringExtra(Downloader.EXTRA_DOWNLOAD_PATH)); File localFile = new File(intent.getStringExtra(Downloader.EXTRA_DOWNLOAD_PATH));
Uri localUri = Uri.fromFile(localFile); Uri localApkUri = Uri.fromFile(localFile);
Utils.debugLog(TAG, "download completed of " + originatingUri Utils.debugLog(TAG, "download completed of " + urlString + " to " + localApkUri);
+ " to " + localUri);
unregisterDownloaderReceivers(intent.getDataString()); unregisterDownloaderReceivers(urlString);
registerInstallerReceivers(downloadUri);
registerInstallerReceivers(localUri); Apk apk = ACTIVE_APKS.get(urlString);
Apk apk = ACTIVE_APKS.get(originatingUri.toString()); InstallerService.install(context, localApkUri, downloadUri, apk.packageName);
InstallerService.install(context, localUri, originatingUri, apk.packageName);
} }
}; };
BroadcastReceiver interruptedReceiver = new BroadcastReceiver() { BroadcastReceiver interruptedReceiver = new BroadcastReceiver() {
@ -262,19 +264,18 @@ public class InstallManagerService extends Service {
} }
private void registerInstallerReceivers(Uri uri) { private void registerInstallerReceivers(Uri downloadUri) {
BroadcastReceiver installReceiver = new BroadcastReceiver() { BroadcastReceiver installReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
Uri originatingUri = intent.getParcelableExtra(Installer.EXTRA_ORIGINATING_URI); String downloadUrl = intent.getDataString();
switch (intent.getAction()) { switch (intent.getAction()) {
case Installer.ACTION_INSTALL_STARTED: case Installer.ACTION_INSTALL_STARTED:
// nothing to do // nothing to do
break; break;
case Installer.ACTION_INSTALL_COMPLETE: case Installer.ACTION_INSTALL_COMPLETE:
Apk apkComplete = removeFromActive(originatingUri.toString()); Apk apkComplete = removeFromActive(downloadUrl);
PackageManagerCompat.setInstaller(getPackageManager(), apkComplete.packageName); PackageManagerCompat.setInstaller(getPackageManager(), apkComplete.packageName);
@ -286,31 +287,31 @@ public class InstallManagerService extends Service {
// show notification if app details is not visible // show notification if app details is not visible
if (!TextUtils.isEmpty(errorMessage)) { if (!TextUtils.isEmpty(errorMessage)) {
App app = getAppFromActive(originatingUri.toString()); App app = getAppFromActive(downloadUrl);
String title = String.format(
getString(R.string.install_error_notify_title),
app.name);
// show notification if app details is not visible // show notification if app details is not visible
if (AppDetails.isAppVisible(app.packageName)) { if (AppDetails.isAppVisible(app.packageName)) {
cancelNotification(originatingUri.toString()); cancelNotification(downloadUrl);
} else { } else {
notifyError(originatingUri.toString(), title, errorMessage); String title = String.format(
getString(R.string.install_error_notify_title),
app.name);
notifyError(downloadUrl, title, errorMessage);
} }
} }
removeFromActive(downloadUrl);
localBroadcastManager.unregisterReceiver(this); localBroadcastManager.unregisterReceiver(this);
break; break;
case Installer.ACTION_INSTALL_USER_INTERACTION: case Installer.ACTION_INSTALL_USER_INTERACTION:
PendingIntent installPendingIntent = PendingIntent installPendingIntent =
intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI); intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI);
Apk apkUserInteraction = getApkFromActive(originatingUri.toString()); Apk apkUserInteraction = getApkFromActive(downloadUrl);
// show notification if app details is not visible // show notification if app details is not visible
if (AppDetails.isAppVisible(apkUserInteraction.packageName)) { if (AppDetails.isAppVisible(apkUserInteraction.packageName)) {
cancelNotification(originatingUri.toString()); cancelNotification(downloadUrl);
} else { } else {
notifyDownloadComplete(apkUserInteraction, originatingUri.toString(), installPendingIntent); notifyDownloadComplete(apkUserInteraction, downloadUrl, installPendingIntent);
} }
break; break;
@ -321,7 +322,7 @@ public class InstallManagerService extends Service {
}; };
localBroadcastManager.registerReceiver(installReceiver, localBroadcastManager.registerReceiver(installReceiver,
Installer.getInstallIntentFilter(uri)); Installer.getInstallIntentFilter(downloadUri));
} }
private NotificationCompat.Builder createNotificationBuilder(String urlString, Apk apk) { private NotificationCompat.Builder createNotificationBuilder(String urlString, Apk apk) {

View File

@ -46,7 +46,7 @@ import java.security.NoSuchAlgorithmException;
import java.util.Map; import java.util.Map;
/** /**
* * Handles the actual install process. Subclasses implement the details.
*/ */
public abstract class Installer { public abstract class Installer {
final Context context; final Context context;
@ -64,10 +64,14 @@ public abstract class Installer {
public static final String ACTION_UNINSTALL_USER_INTERACTION = "org.fdroid.fdroid.installer.Installer.action.UNINSTALL_USER_INTERACTION"; public static final String ACTION_UNINSTALL_USER_INTERACTION = "org.fdroid.fdroid.installer.Installer.action.UNINSTALL_USER_INTERACTION";
/** /**
* Same as http://developer.android.com/reference/android/content/Intent.html#EXTRA_ORIGINATING_URI * The URI where the APK was originally downloaded from. This is also used
* In InstallManagerService often called urlString * as the unique ID representing this in the whole install process in
* {@link InstallManagerService}, there is is generally known as the
* "download URL" since it is the URL used to download the APK.
*
* @see Intent#EXTRA_ORIGINATING_URI
*/ */
public static final String EXTRA_ORIGINATING_URI = "org.fdroid.fdroid.installer.Installer.extra.ORIGINATING_URI"; static final String EXTRA_DOWNLOAD_URI = "org.fdroid.fdroid.installer.Installer.extra.DOWNLOAD_URI";
public static final String EXTRA_PACKAGE_NAME = "org.fdroid.fdroid.installer.Installer.extra.PACKAGE_NAME"; 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_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"; public static final String EXTRA_ERROR_MESSAGE = "org.fdroid.fdroid.net.installer.Installer.extra.ERROR_MESSAGE";
@ -91,7 +95,7 @@ public abstract class Installer {
localBroadcastManager = LocalBroadcastManager.getInstance(context); localBroadcastManager = LocalBroadcastManager.getInstance(context);
} }
public static Uri prepareApkFile(Context context, Uri uri, String packageName) static Uri prepareApkFile(Context context, Uri uri, String packageName)
throws InstallFailedException { throws InstallFailedException {
File apkFile = new File(uri.getPath()); File apkFile = new File(uri.getPath());
@ -229,7 +233,7 @@ public abstract class Installer {
/** /**
* Checks the APK file against the provided hash, returning whether it is a match. * Checks the APK file against the provided hash, returning whether it is a match.
*/ */
public static boolean verifyApkFile(File apkFile, String hash, String hashType) static boolean verifyApkFile(File apkFile, String hash, String hashType)
throws NoSuchAlgorithmException { throws NoSuchAlgorithmException {
if (!apkFile.exists()) { if (!apkFile.exists()) {
return false; return false;
@ -238,24 +242,23 @@ public abstract class Installer {
return hasher.match(hash); return hasher.match(hash);
} }
public void sendBroadcastInstall(Uri uri, Uri originatingUri, String action, void sendBroadcastInstall(Uri downloadUri, String action,
PendingIntent pendingIntent) { PendingIntent pendingIntent) {
sendBroadcastInstall(uri, originatingUri, action, pendingIntent, null); sendBroadcastInstall(downloadUri, action, pendingIntent, null);
} }
public void sendBroadcastInstall(Uri uri, Uri originatingUri, String action) { void sendBroadcastInstall(Uri downloadUri, String action) {
sendBroadcastInstall(uri, originatingUri, action, null, null); sendBroadcastInstall(downloadUri, action, null, null);
} }
public void sendBroadcastInstall(Uri uri, Uri originatingUri, String action, String errorMessage) { void sendBroadcastInstall(Uri downloadUri, String action, String errorMessage) {
sendBroadcastInstall(uri, originatingUri, action, null, errorMessage); sendBroadcastInstall(downloadUri, action, null, errorMessage);
} }
public void sendBroadcastInstall(Uri uri, Uri originatingUri, String action, void sendBroadcastInstall(Uri downloadUri, String action,
PendingIntent pendingIntent, String errorMessage) { PendingIntent pendingIntent, String errorMessage) {
Intent intent = new Intent(action); Intent intent = new Intent(action);
intent.setData(uri); intent.setData(downloadUri);
intent.putExtra(Installer.EXTRA_ORIGINATING_URI, originatingUri);
intent.putExtra(Installer.EXTRA_USER_INTERACTION_PI, pendingIntent); intent.putExtra(Installer.EXTRA_USER_INTERACTION_PI, pendingIntent);
if (!TextUtils.isEmpty(errorMessage)) { if (!TextUtils.isEmpty(errorMessage)) {
intent.putExtra(Installer.EXTRA_ERROR_MESSAGE, errorMessage); intent.putExtra(Installer.EXTRA_ERROR_MESSAGE, errorMessage);
@ -263,20 +266,20 @@ public abstract class Installer {
localBroadcastManager.sendBroadcast(intent); localBroadcastManager.sendBroadcast(intent);
} }
public void sendBroadcastUninstall(String packageName, String action, String errorMessage) { void sendBroadcastUninstall(String packageName, String action, String errorMessage) {
sendBroadcastUninstall(packageName, action, null, errorMessage); sendBroadcastUninstall(packageName, action, null, errorMessage);
} }
public void sendBroadcastUninstall(String packageName, String action) { void sendBroadcastUninstall(String packageName, String action) {
sendBroadcastUninstall(packageName, action, null, null); sendBroadcastUninstall(packageName, action, null, null);
} }
public void sendBroadcastUninstall(String packageName, String action, void sendBroadcastUninstall(String packageName, String action,
PendingIntent pendingIntent) { PendingIntent pendingIntent) {
sendBroadcastUninstall(packageName, action, pendingIntent, null); sendBroadcastUninstall(packageName, action, pendingIntent, null);
} }
public void sendBroadcastUninstall(String packageName, String action, void sendBroadcastUninstall(String packageName, String action,
PendingIntent pendingIntent, String errorMessage) { PendingIntent pendingIntent, String errorMessage) {
Uri uri = Uri.fromParts("package", packageName, null); Uri uri = Uri.fromParts("package", packageName, null);
@ -290,6 +293,10 @@ public abstract class Installer {
localBroadcastManager.sendBroadcast(intent); localBroadcastManager.sendBroadcast(intent);
} }
/**
* Gets an {@link IntentFilter} for matching events from the install
* process based on the original download URL as a {@link Uri}.
*/
public static IntentFilter getInstallIntentFilter(Uri uri) { public static IntentFilter getInstallIntentFilter(Uri uri) {
IntentFilter intentFilter = new IntentFilter(); IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Installer.ACTION_INSTALL_STARTED); intentFilter.addAction(Installer.ACTION_INSTALL_STARTED);
@ -297,6 +304,7 @@ public abstract class Installer {
intentFilter.addAction(Installer.ACTION_INSTALL_INTERRUPTED); intentFilter.addAction(Installer.ACTION_INSTALL_INTERRUPTED);
intentFilter.addAction(Installer.ACTION_INSTALL_USER_INTERACTION); intentFilter.addAction(Installer.ACTION_INSTALL_USER_INTERACTION);
intentFilter.addDataScheme(uri.getScheme()); intentFilter.addDataScheme(uri.getScheme());
intentFilter.addDataAuthority(uri.getHost(), String.valueOf(uri.getPort()));
intentFilter.addDataPath(uri.getPath(), PatternMatcher.PATTERN_LITERAL); intentFilter.addDataPath(uri.getPath(), PatternMatcher.PATTERN_LITERAL);
return intentFilter; return intentFilter;
} }
@ -312,10 +320,20 @@ public abstract class Installer {
return intentFilter; return intentFilter;
} }
protected abstract void installPackage(Uri uri, Uri originatingUri, String packageName); /**
* @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 packageName package name of the app that should be installed
*/
protected abstract void installPackage(Uri localApkUri, Uri downloadUri, String packageName);
protected abstract void uninstallPackage(String packageName); protected abstract void uninstallPackage(String packageName);
/**
* This {@link Installer} instance is capable of "unattended" install and
* uninstall activities, without the system enforcing a user prompt.
*/
protected abstract boolean isUnattended(); protected abstract boolean isUnattended();
} }

View File

@ -33,6 +33,10 @@ import android.net.Uri;
* i.e., runs sequentially * i.e., runs sequentially
* - no cancel operation is needed. Cancelling an installation * - no cancel operation is needed. Cancelling an installation
* would be the same as starting uninstall afterwards * would be the same as starting uninstall afterwards
* <p/>
* The download URL is only used as the unique ID that represents this
* particular apk throughout the whole install process in
* {@link InstallManagerService}.
*/ */
public class InstallerService extends IntentService { public class InstallerService extends IntentService {
@ -50,9 +54,8 @@ public class InstallerService extends IntentService {
if (ACTION_INSTALL.equals(intent.getAction())) { if (ACTION_INSTALL.equals(intent.getAction())) {
Uri uri = intent.getData(); Uri uri = intent.getData();
Uri originatingUri = intent.getParcelableExtra(Installer.EXTRA_ORIGINATING_URI); Uri downloadUri = intent.getParcelableExtra(Installer.EXTRA_DOWNLOAD_URI);
installer.installPackage(uri, downloadUri, packageName);
installer.installPackage(uri, originatingUri, packageName);
} else if (ACTION_UNINSTALL.equals(intent.getAction())) { } else if (ACTION_UNINSTALL.equals(intent.getAction())) {
installer.uninstallPackage(packageName); installer.uninstallPackage(packageName);
} }
@ -61,16 +64,16 @@ public class InstallerService extends IntentService {
/** /**
* Install an apk from {@link Uri} * Install an apk from {@link Uri}
* *
* @param context this app's {@link Context} * @param context this app's {@link Context}
* @param uri {@link Uri} pointing to (downloaded) local apk file * @param localApkUri {@link Uri} pointing to (downloaded) local apk file
* @param originatingUri {@link Uri} where the apk has been downloaded from * @param downloadUri {@link Uri} where the apk has been downloaded from
* @param packageName package name of the app that should be installed * @param packageName package name of the app that should be installed
*/ */
public static void install(Context context, Uri uri, Uri originatingUri, String packageName) { public static void install(Context context, Uri localApkUri, Uri downloadUri, String packageName) {
Intent intent = new Intent(context, InstallerService.class); Intent intent = new Intent(context, InstallerService.class);
intent.setAction(ACTION_INSTALL); intent.setAction(ACTION_INSTALL);
intent.setData(uri); intent.setData(localApkUri);
intent.putExtra(Installer.EXTRA_ORIGINATING_URI, originatingUri); intent.putExtra(Installer.EXTRA_DOWNLOAD_URI, downloadUri);
intent.putExtra(Installer.EXTRA_PACKAGE_NAME, packageName); intent.putExtra(Installer.EXTRA_PACKAGE_NAME, packageName);
context.startService(intent); context.startService(intent);
} }

View File

@ -297,15 +297,15 @@ public class PrivilegedInstaller extends Installer {
} }
@Override @Override
protected void installPackage(final Uri uri, final Uri originatingUri, String packageName) { protected void installPackage(final Uri localApkUri, final Uri downloadUri, String packageName) {
sendBroadcastInstall(uri, originatingUri, Installer.ACTION_INSTALL_STARTED); sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_STARTED);
final Uri sanitizedUri; final Uri sanitizedUri;
try { try {
sanitizedUri = Installer.prepareApkFile(context, uri, packageName); sanitizedUri = Installer.prepareApkFile(context, localApkUri, packageName);
} catch (Installer.InstallFailedException e) { } catch (Installer.InstallFailedException e) {
Log.e(TAG, "prepareApkFile failed", e); Log.e(TAG, "prepareApkFile failed", e);
sendBroadcastInstall(uri, originatingUri, Installer.ACTION_INSTALL_INTERRUPTED, sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED,
e.getMessage()); e.getMessage());
return; return;
} }
@ -318,9 +318,9 @@ public class PrivilegedInstaller extends Installer {
@Override @Override
public void handleResult(String packageName, int returnCode) throws RemoteException { public void handleResult(String packageName, int returnCode) throws RemoteException {
if (returnCode == INSTALL_SUCCEEDED) { if (returnCode == INSTALL_SUCCEEDED) {
sendBroadcastInstall(uri, originatingUri, ACTION_INSTALL_COMPLETE); sendBroadcastInstall(downloadUri, ACTION_INSTALL_COMPLETE);
} else { } else {
sendBroadcastInstall(uri, originatingUri, ACTION_INSTALL_INTERRUPTED, sendBroadcastInstall(downloadUri, ACTION_INSTALL_INTERRUPTED,
"Error " + returnCode + ": " "Error " + returnCode + ": "
+ INSTALL_RETURN_CODES.get(returnCode)); + INSTALL_RETURN_CODES.get(returnCode));
} }
@ -330,7 +330,7 @@ public class PrivilegedInstaller extends Installer {
try { try {
boolean hasPermissions = privService.hasPrivilegedPermissions(); boolean hasPermissions = privService.hasPrivilegedPermissions();
if (!hasPermissions) { if (!hasPermissions) {
sendBroadcastInstall(uri, originatingUri, ACTION_INSTALL_INTERRUPTED, sendBroadcastInstall(downloadUri, ACTION_INSTALL_INTERRUPTED,
context.getString(R.string.system_install_denied_permissions)); context.getString(R.string.system_install_denied_permissions));
return; return;
} }
@ -339,7 +339,7 @@ public class PrivilegedInstaller extends Installer {
null, callback); null, callback);
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "RemoteException", e); Log.e(TAG, "RemoteException", e);
sendBroadcastInstall(uri, originatingUri, ACTION_INSTALL_INTERRUPTED, sendBroadcastInstall(downloadUri, ACTION_INSTALL_INTERRUPTED,
"connecting to privileged service failed"); "connecting to privileged service failed");
} }
} }

View File

@ -34,6 +34,13 @@ import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.R; import org.fdroid.fdroid.R;
import org.fdroid.fdroid.installer.Installer; import org.fdroid.fdroid.installer.Installer;
/**
* This class provides the confirmation prompt for when the user chooses to
* uninstall an app. This has to be implemented here for the privileged
* extension, it is only shown for {@link Installer} instances that can do
* installs and uninstalls without user prompts, which is detected via
* {@link Installer#isUnattended()}.
*/
public class UninstallDialogActivity extends FragmentActivity { public class UninstallDialogActivity extends FragmentActivity {
@Override @Override

View File

@ -44,14 +44,10 @@ import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.NewRepoConfig; import org.fdroid.fdroid.data.NewRepoConfig;
import org.fdroid.fdroid.installer.InstallManagerService; import org.fdroid.fdroid.installer.InstallManagerService;
import org.fdroid.fdroid.installer.Installer; import org.fdroid.fdroid.installer.Installer;
import org.fdroid.fdroid.installer.InstallerService;
import org.fdroid.fdroid.localrepo.LocalRepoManager; import org.fdroid.fdroid.localrepo.LocalRepoManager;
import org.fdroid.fdroid.localrepo.SwapService; import org.fdroid.fdroid.localrepo.SwapService;
import org.fdroid.fdroid.localrepo.peers.Peer; import org.fdroid.fdroid.localrepo.peers.Peer;
import org.fdroid.fdroid.net.Downloader;
import org.fdroid.fdroid.net.DownloaderService;
import java.io.File;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
@ -773,26 +769,10 @@ public class SwapWorkflowActivity extends AppCompatActivity {
public void install(@NonNull final App app) { public void install(@NonNull final App app) {
final Apk apk = ApkProvider.Helper.find(this, app.packageName, app.suggestedVersionCode); final Apk apk = ApkProvider.Helper.find(this, app.packageName, app.suggestedVersionCode);
String urlString = apk.getUrl(); Uri downloadUri = Uri.parse(apk.getUrl());
BroadcastReceiver downloadCompleteReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String path = intent.getStringExtra(Downloader.EXTRA_DOWNLOAD_PATH);
handleDownloadComplete(new File(path), app.packageName, intent.getDataString());
}
};
localBroadcastManager.registerReceiver(downloadCompleteReceiver,
DownloaderService.getIntentFilter(urlString, Downloader.ACTION_COMPLETE));
InstallManagerService.queue(this, app, apk);
}
private void handleDownloadComplete(File apkFile, String packageName, String urlString) {
Uri originatingUri = Uri.parse(urlString);
Uri localUri = Uri.fromFile(apkFile);
localBroadcastManager.registerReceiver(installReceiver, localBroadcastManager.registerReceiver(installReceiver,
Installer.getInstallIntentFilter(Uri.fromFile(apkFile))); Installer.getInstallIntentFilter(downloadUri));
InstallerService.install(this, localUri, originatingUri, packageName); InstallManagerService.queue(this, app, apk);
} }
private final BroadcastReceiver installReceiver = new BroadcastReceiver() { private final BroadcastReceiver installReceiver = new BroadcastReceiver() {