Refactor installer package

This commit is contained in:
Dominik Schürmann 2016-05-23 20:48:45 +03:00
parent 6d2f2d20a8
commit a2356b05d6
12 changed files with 473 additions and 827 deletions

View File

@ -86,7 +86,7 @@ import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.InstalledAppProvider;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.installer.InstallHelper;
import org.fdroid.fdroid.installer.Installer;
import org.fdroid.fdroid.installer.InstallManagerService;
import org.fdroid.fdroid.installer.InstallerService;
import org.fdroid.fdroid.net.Downloader;
@ -532,7 +532,7 @@ public class AppDetails extends AppCompatActivity {
Uri localUri =
Uri.fromFile(new File(intent.getStringExtra(Downloader.EXTRA_DOWNLOAD_PATH)));
localBroadcastManager.registerReceiver(installReceiver,
InstallerService.getInstallIntentFilter(localUri));
Installer.getInstallIntentFilter(localUri));
}
};
@ -555,12 +555,12 @@ public class AppDetails extends AppCompatActivity {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
case InstallHelper.ACTION_INSTALL_STARTED: {
case Installer.ACTION_INSTALL_STARTED: {
headerFragment.startProgress();
headerFragment.showIndeterminateProgress(getString(R.string.installing));
break;
}
case InstallHelper.ACTION_INSTALL_COMPLETE: {
case Installer.ACTION_INSTALL_COMPLETE: {
headerFragment.removeProgress();
localBroadcastManager.unregisterReceiver(this);
@ -569,7 +569,7 @@ public class AppDetails extends AppCompatActivity {
onAppChanged();
break;
}
case InstallHelper.ACTION_INSTALL_INTERRUPTED: {
case Installer.ACTION_INSTALL_INTERRUPTED: {
headerFragment.removeProgress();
localBroadcastManager.unregisterReceiver(this);
@ -613,9 +613,9 @@ public class AppDetails extends AppCompatActivity {
// });
break;
}
case InstallHelper.ACTION_INSTALL_USER_INTERACTION: {
case Installer.ACTION_INSTALL_USER_INTERACTION: {
PendingIntent installPendingIntent =
intent.getParcelableExtra(InstallHelper.EXTRA_USER_INTERACTION_PI);
intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI);
try {
installPendingIntent.send();
@ -636,19 +636,19 @@ public class AppDetails extends AppCompatActivity {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
case InstallHelper.ACTION_UNINSTALL_STARTED: {
case Installer.ACTION_UNINSTALL_STARTED: {
headerFragment.startProgress();
headerFragment.showIndeterminateProgress(getString(R.string.uninstalling));
break;
}
case InstallHelper.ACTION_UNINSTALL_COMPLETE: {
case Installer.ACTION_UNINSTALL_COMPLETE: {
headerFragment.removeProgress();
localBroadcastManager.unregisterReceiver(this);
onAppChanged();
break;
}
case InstallHelper.ACTION_UNINSTALL_INTERRUPTED: {
case Installer.ACTION_UNINSTALL_INTERRUPTED: {
headerFragment.removeProgress();
localBroadcastManager.unregisterReceiver(this);
@ -691,9 +691,9 @@ public class AppDetails extends AppCompatActivity {
// });
break;
}
case InstallHelper.ACTION_UNINSTALL_USER_INTERACTION: {
case Installer.ACTION_UNINSTALL_USER_INTERACTION: {
PendingIntent uninstallPendingIntent =
intent.getParcelableExtra(InstallHelper.EXTRA_USER_INTERACTION_PI);
intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI);
try {
uninstallPendingIntent.send();
@ -1038,7 +1038,7 @@ public class AppDetails extends AppCompatActivity {
private void uninstallApk(String packageName) {
localBroadcastManager.registerReceiver(uninstallReceiver,
InstallerService.getUninstallIntentFilter(packageName));
Installer.getUninstallIntentFilter(packageName));
InstallerService.uninstall(context, packageName);
}

View File

@ -270,7 +270,7 @@ public final class Utils {
* This location is only for caching, do not install directly from this location
* because if the file is on the External Storage, any other app could swap out
* the APK while the install was in process, allowing malware to install things.
* Using {@link org.fdroid.fdroid.installer.Installer#installPackage(File, String, String)}
* Using {@link Installer#installPackage(File, String, String)}
* is fine since that does the right thing.
*/
public static File getApkCacheDir(Context context) {

View File

@ -28,8 +28,6 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
import org.fdroid.fdroid.Utils;
@ -49,18 +47,19 @@ public class AndroidInstallerActivity extends FragmentActivity {
private static final int REQUEST_CODE_INSTALL = 0;
private static final int REQUEST_CODE_UNINSTALL = 1;
private LocalBroadcastManager localBroadcastManager;
private Uri mInstallOriginatingUri;
private Uri mInstallUri;
private String mUninstallPackageName;
// for the broadcasts
private DefaultInstaller installer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
localBroadcastManager = LocalBroadcastManager.getInstance(this);
installer = new DefaultInstaller(this);
Intent intent = getIntent();
String action = intent.getAction();
@ -131,12 +130,12 @@ public class AndroidInstallerActivity extends FragmentActivity {
startActivityForResult(intent, REQUEST_CODE_INSTALL);
} catch (ActivityNotFoundException e) {
Log.e(TAG, "ActivityNotFoundException", e);
sendBroadcastInstall(uri, originatingUri, InstallHelper.ACTION_INSTALL_INTERRUPTED,
installer.sendBroadcastInstall(uri, originatingUri, Installer.ACTION_INSTALL_INTERRUPTED,
"This Android rom does not support ACTION_INSTALL_PACKAGE!");
finish();
}
sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
InstallHelper.ACTION_INSTALL_STARTED);
installer.sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
Installer.ACTION_INSTALL_STARTED);
}
protected void uninstallPackage(String packageName) {
@ -147,7 +146,7 @@ public class AndroidInstallerActivity extends FragmentActivity {
getPackageManager().getPackageInfo(packageName, 0);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "NameNotFoundException", e);
sendBroadcastUninstall(packageName, InstallHelper.ACTION_UNINSTALL_INTERRUPTED,
installer.sendBroadcastUninstall(packageName, Installer.ACTION_UNINSTALL_INTERRUPTED,
"Package that is scheduled for uninstall is not installed!");
finish();
return;
@ -167,7 +166,7 @@ public class AndroidInstallerActivity extends FragmentActivity {
startActivityForResult(intent, REQUEST_CODE_UNINSTALL);
} catch (ActivityNotFoundException e) {
Log.e(TAG, "ActivityNotFoundException", e);
sendBroadcastUninstall(packageName, InstallHelper.ACTION_UNINSTALL_INTERRUPTED,
installer.sendBroadcastUninstall(packageName, Installer.ACTION_UNINSTALL_INTERRUPTED,
"This Android rom does not support ACTION_UNINSTALL_PACKAGE!");
finish();
}
@ -183,34 +182,34 @@ public class AndroidInstallerActivity extends FragmentActivity {
* never executed on Androids < 4.0
*/
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
InstallHelper.ACTION_INSTALL_COMPLETE);
installer.sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
Installer.ACTION_INSTALL_COMPLETE);
break;
}
// Fallback on N for https://gitlab.com/fdroid/fdroidclient/issues/631
if ("N".equals(Build.VERSION.CODENAME)) {
sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
InstallHelper.ACTION_INSTALL_COMPLETE);
installer.sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
Installer.ACTION_INSTALL_COMPLETE);
break;
}
switch (resultCode) {
case Activity.RESULT_OK: {
sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
InstallHelper.ACTION_INSTALL_COMPLETE);
installer.sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
Installer.ACTION_INSTALL_COMPLETE);
break;
}
case Activity.RESULT_CANCELED: {
sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
InstallHelper.ACTION_INSTALL_INTERRUPTED);
installer.sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
Installer.ACTION_INSTALL_INTERRUPTED);
break;
}
default:
case Activity.RESULT_FIRST_USER: {
// AOSP actually returns Activity.RESULT_FIRST_USER if something breaks
sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
InstallHelper.ACTION_INSTALL_INTERRUPTED, "error");
installer.sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
Installer.ACTION_INSTALL_INTERRUPTED, "error");
break;
}
}
@ -220,28 +219,28 @@ public class AndroidInstallerActivity extends FragmentActivity {
case REQUEST_CODE_UNINSTALL: {
// resultCode is always 0 on Android < 4.0.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
sendBroadcastUninstall(mUninstallPackageName,
InstallHelper.ACTION_UNINSTALL_COMPLETE);
installer.sendBroadcastUninstall(mUninstallPackageName,
Installer.ACTION_UNINSTALL_COMPLETE);
break;
}
switch (resultCode) {
case Activity.RESULT_OK: {
sendBroadcastUninstall(mUninstallPackageName,
InstallHelper.ACTION_UNINSTALL_COMPLETE);
installer.sendBroadcastUninstall(mUninstallPackageName,
Installer.ACTION_UNINSTALL_COMPLETE);
break;
}
case Activity.RESULT_CANCELED: {
sendBroadcastUninstall(mUninstallPackageName,
InstallHelper.ACTION_UNINSTALL_INTERRUPTED);
installer.sendBroadcastUninstall(mUninstallPackageName,
Installer.ACTION_UNINSTALL_INTERRUPTED);
break;
}
default:
case Activity.RESULT_FIRST_USER: {
// AOSP UninstallAppProgress actually returns
// Activity.RESULT_FIRST_USER if something breaks
sendBroadcastUninstall(mUninstallPackageName,
InstallHelper.ACTION_UNINSTALL_INTERRUPTED,
installer.sendBroadcastUninstall(mUninstallPackageName,
Installer.ACTION_UNINSTALL_INTERRUPTED,
"error");
break;
}
@ -258,34 +257,4 @@ public class AndroidInstallerActivity extends FragmentActivity {
finish();
}
private void sendBroadcastInstall(Uri uri, Uri originatingUri, String action) {
sendBroadcastInstall(uri, originatingUri, action, null);
}
private void sendBroadcastInstall(Uri uri, Uri originatingUri, String action, String errorMessage) {
Intent intent = new Intent(action);
intent.setData(uri);
intent.putExtra(InstallHelper.EXTRA_ORIGINATING_URI, originatingUri);
if (!TextUtils.isEmpty(errorMessage)) {
intent.putExtra(InstallHelper.EXTRA_ERROR_MESSAGE, errorMessage);
}
localBroadcastManager.sendBroadcast(intent);
}
private void sendBroadcastUninstall(String packageName, String action) {
sendBroadcastUninstall(packageName, action, null);
}
private void sendBroadcastUninstall(String packageName, String action, String errorMessage) {
Uri uri = Uri.fromParts("package", packageName, null);
Intent intent = new Intent(action);
intent.setData(uri); // for broadcast filter
intent.putExtra(InstallHelper.EXTRA_UNINSTALL_PACKAGE_NAME, packageName);
if (!TextUtils.isEmpty(errorMessage)) {
intent.putExtra(InstallHelper.EXTRA_ERROR_MESSAGE, errorMessage);
}
localBroadcastManager.sendBroadcast(intent);
}
}

View File

@ -1,100 +1,97 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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.Activity;
import android.content.ActivityNotFoundException;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.privileged.install.InstallExtensionDialogActivity;
import java.io.File;
/**
* For Android < 4: Default Installer using the public PackageManager API of
* Android to install/delete packages. This starts a Activity from the Android
* OS showing all permissions/changed permissions. The the user needs to
* manually press an install button, this Installer cannot be used for
* unattended installations.
*/
public class DefaultInstaller extends Installer {
private final Activity mActivity;
public DefaultInstaller(Activity activity, PackageManager pm, InstallerCallback callback)
throws InstallFailedException {
super(activity, pm, callback);
this.mActivity = activity;
private static final String TAG = "DefaultInstaller";
DefaultInstaller(Context context) {
super(context);
}
private static final int REQUEST_CODE_INSTALL = 0;
private static final int REQUEST_CODE_DELETE = 1;
@Override
protected void installPackageInternal(File apkFile) throws InstallFailedException {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(apkFile),
"application/vnd.android.package-archive");
protected void installPackage(Uri uri, Uri originatingUri, String packageName) {
sendBroadcastInstall(uri, originatingUri, Installer.ACTION_INSTALL_STARTED);
Utils.debugLog(TAG, "ACTION_INSTALL uri: " + uri + " file: " + new File(uri.getPath()));
// TODO: rework for uri
File sanitizedFile = null;
try {
mActivity.startActivityForResult(intent, REQUEST_CODE_INSTALL);
} catch (ActivityNotFoundException e) {
throw new InstallFailedException(e);
sanitizedFile = Installer.prepareApkFile(mContext, new File(uri.getPath()), packageName);
} catch (Installer.InstallFailedException e) {
e.printStackTrace();
}
}
Uri sanitizedUri = Uri.fromFile(sanitizedFile);
@Override
protected void deletePackageInternal(String packageName) throws InstallFailedException {
try {
PackageInfo pkgInfo = mPm.getPackageInfo(packageName, 0);
Utils.debugLog(TAG, "ACTION_INSTALL sanitizedUri: " + sanitizedUri);
Uri uri = Uri.fromParts("package", pkgInfo.packageName, null);
Intent intent = new Intent(Intent.ACTION_DELETE, uri);
try {
mActivity.startActivityForResult(intent, REQUEST_CODE_DELETE);
} catch (ActivityNotFoundException e) {
throw new InstallFailedException(e);
Intent installIntent;
// special case: F-Droid Privileged Extension
if (packageName != null && packageName.equals(PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME)) {
// extension must be signed with the same public key as main F-Droid
// NOTE: Disabled for debug builds to be able to use official extension from repo
ApkSignatureVerifier signatureVerifier = new ApkSignatureVerifier(mContext);
if (!BuildConfig.DEBUG && !signatureVerifier.hasFDroidSignature(sanitizedFile)) {
throw new RuntimeException("APK signature of extension not correct!");
}
} catch (PackageManager.NameNotFoundException e) {
// already checked in super class
installIntent = new Intent(mContext, InstallExtensionDialogActivity.class);
installIntent.setAction(InstallExtensionDialogActivity.ACTION_INSTALL);
installIntent.putExtra(InstallExtensionDialogActivity.EXTRA_INSTALL_APK,
sanitizedFile.getAbsolutePath());
} else {
installIntent = new Intent(mContext, AndroidInstallerActivity.class);
installIntent.setAction(AndroidInstallerActivity.ACTION_INSTALL_PACKAGE);
installIntent.putExtra(AndroidInstallerActivity.EXTRA_ORIGINATING_URI, originatingUri);
installIntent.setData(sanitizedUri);
}
PendingIntent installPendingIntent = PendingIntent.getActivity(
mContext.getApplicationContext(),
uri.hashCode(),
installIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
sendBroadcastInstall(uri, originatingUri,
Installer.ACTION_INSTALL_USER_INTERACTION, installPendingIntent);
}
@Override
public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) {
/**
* resultCode is always 0 on Android < 4.0. See
* com.android.packageinstaller.PackageInstallerActivity: setResult is
* never executed on Androids before 4.0
*/
switch (requestCode) {
case REQUEST_CODE_INSTALL:
mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL);
protected void uninstallPackage(String packageName) {
sendBroadcastUninstall(packageName, Installer.ACTION_UNINSTALL_STARTED);
return true;
case REQUEST_CODE_DELETE:
mCallback.onSuccess(InstallerCallback.OPERATION_DELETE);
return true;
default:
return false;
Intent uninstallIntent;
// special case: F-Droid Privileged Extension
if (packageName != null && packageName.equals(PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME)) {
uninstallIntent = new Intent(mContext, InstallExtensionDialogActivity.class);
uninstallIntent.setAction(InstallExtensionDialogActivity.ACTION_UNINSTALL);
} else {
uninstallIntent = new Intent(mContext, AndroidInstallerActivity.class);
uninstallIntent.setAction(AndroidInstallerActivity.ACTION_UNINSTALL_PACKAGE);
uninstallIntent.putExtra(
AndroidInstallerActivity.EXTRA_UNINSTALL_PACKAGE_NAME, packageName);
}
PendingIntent uninstallPendingIntent = PendingIntent.getActivity(
mContext.getApplicationContext(),
packageName.hashCode(),
uninstallIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
sendBroadcastUninstall(packageName,
Installer.ACTION_UNINSTALL_USER_INTERACTION, uninstallPendingIntent);
}
}

View File

@ -1,134 +0,0 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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.annotation.TargetApi;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import java.io.File;
/**
* For Android >= 4.0: Default Installer using the public PackageManager API of
* Android to install/delete packages. This starts a Activity from the Android
* OS showing all permissions/changed permissions. The the user needs to
* manually press an install button, this Installer cannot be used for
* unattended installations.
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public class DefaultSdk14Installer extends Installer {
private final Activity mActivity;
public DefaultSdk14Installer(Activity activity, PackageManager pm, InstallerCallback callback)
throws InstallFailedException {
super(activity, pm, callback);
this.mActivity = activity;
}
private static final int REQUEST_CODE_INSTALL = 0;
private static final int REQUEST_CODE_DELETE = 1;
@SuppressWarnings("deprecation")
@Override
protected void installPackageInternal(File apkFile) throws InstallFailedException {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_INSTALL_PACKAGE);
intent.setData(Uri.fromFile(apkFile));
// EXTRA_RETURN_RESULT throws a RuntimeException on N
// https://gitlab.com/fdroid/fdroidclient/issues/631
if (!"N".equals(Build.VERSION.CODENAME)) {
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
}
// following extras only work when being installed as system-app
// https://code.google.com/p/android/issues/detail?id=42253
intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
if (Build.VERSION.SDK_INT < 16) {
// deprecated in Android 4.1
intent.putExtra(Intent.EXTRA_ALLOW_REPLACE, true);
}
try {
mActivity.startActivityForResult(intent, REQUEST_CODE_INSTALL);
} catch (ActivityNotFoundException e) {
throw new InstallFailedException(e);
}
}
@Override
protected void deletePackageInternal(String packageName) throws InstallFailedException {
try {
PackageInfo pkgInfo = mPm.getPackageInfo(packageName, 0);
Uri uri = Uri.fromParts("package", pkgInfo.packageName, null);
Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, uri);
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
try {
mActivity.startActivityForResult(intent, REQUEST_CODE_DELETE);
} catch (ActivityNotFoundException e) {
throw new InstallFailedException(e);
}
} catch (PackageManager.NameNotFoundException e) {
// already checked in super class
}
}
@Override
public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CODE_INSTALL:
if (resultCode == Activity.RESULT_OK) {
mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL);
} else if (resultCode == Activity.RESULT_CANCELED) {
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
InstallerCallback.ERROR_CODE_CANCELED);
} else {
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
InstallerCallback.ERROR_CODE_OTHER);
}
// Fallback on N for https://gitlab.com/fdroid/fdroidclient/issues/631
if ("N".equals(Build.VERSION.CODENAME)) {
mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL);
}
return true;
case REQUEST_CODE_DELETE:
if (resultCode == Activity.RESULT_OK) {
mCallback.onSuccess(InstallerCallback.OPERATION_DELETE);
} else if (resultCode == Activity.RESULT_CANCELED) {
mCallback.onError(InstallerCallback.OPERATION_DELETE,
InstallerCallback.ERROR_CODE_CANCELED);
} else {
// UninstallAppProgress actually returns
// Activity.RESULT_FIRST_USER if something breaks
mCallback.onError(InstallerCallback.OPERATION_DELETE,
InstallerCallback.ERROR_CODE_OTHER);
}
return true;
default:
return false;
}
}
}

View File

@ -1,128 +0,0 @@
package org.fdroid.fdroid.installer;
import android.app.Activity;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import org.apache.commons.io.FileUtils;
import org.fdroid.fdroid.AndroidXMLDecompress;
import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.Hasher;
import org.fdroid.fdroid.compat.FileCompat;
import org.fdroid.fdroid.data.SanitizedFile;
import org.fdroid.fdroid.privileged.install.InstallExtensionDialogActivity;
import java.io.File;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
public class InstallHelper {
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";
public static final String ACTION_INSTALL_USER_INTERACTION = "org.fdroid.fdroid.installer.Installer.action.INSTALL_USER_INTERACTION";
public static final String ACTION_UNINSTALL_STARTED = "org.fdroid.fdroid.installer.Installer.action.UNINSTALL_STARTED";
public static final String ACTION_UNINSTALL_COMPLETE = "org.fdroid.fdroid.installer.Installer.action.UNINSTALL_COMPLETE";
public static final String ACTION_UNINSTALL_INTERRUPTED = "org.fdroid.fdroid.installer.Installer.action.UNINSTALL_INTERRUPTED";
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
* In InstallManagerService often called urlString
*/
public static final String EXTRA_ORIGINATING_URI = "org.fdroid.fdroid.installer.InstallerService.extra.ORIGINATING_URI";
public static final String EXTRA_UNINSTALL_PACKAGE_NAME = "org.fdroid.fdroid.installer.InstallerService.extra.UNINSTALL_PACKAGE_NAME";
public static final String EXTRA_USER_INTERACTION_PI = "org.fdroid.fdroid.installer.InstallerService.extra.USER_INTERACTION_PI";
public static final String EXTRA_ERROR_MESSAGE = "org.fdroid.fdroid.net.Downloader.extra.ERROR_MESSAGE";
public static SanitizedFile preparePackage(Context context, File apkFile, String packageName, String urlString)
throws Installer.InstallFailedException {
SanitizedFile apkToInstall;
try {
Map<String, Object> attributes = AndroidXMLDecompress.getManifestHeaderAttributes(apkFile.getAbsolutePath());
/* This isn't really needed, but might as well since we have the data already */
// if (attributes.containsKey("packageName") && !TextUtils.equals(packageName, (String) attributes.get("packageName"))) {
// throw new Installer.InstallFailedException(apkFile + " has packageName that clashes with " + packageName);
// }
if (!attributes.containsKey("versionCode")) {
throw new Installer.InstallFailedException(apkFile + " is missing versionCode!");
}
int versionCode = (Integer) attributes.get("versionCode");
// Apk apk = ApkProvider.Helper.find(context, packageName, versionCode, new String[]{
// ApkProvider.DataColumns.HASH,
// ApkProvider.DataColumns.HASH_TYPE,
// });
/* Always copy the APK to the safe location inside of the protected area
* of the app to prevent attacks based on other apps swapping the file
* out during the install process. Most likely, apkFile was just downloaded,
* so it should still be in the RAM disk cache */
apkToInstall = SanitizedFile.knownSanitized(File.createTempFile("install-", ".apk", context.getFilesDir()));
FileUtils.copyFile(apkFile, apkToInstall);
// if (!verifyApkFile(apkToInstall, apk.hash, apk.hashType)) {
// FileUtils.deleteQuietly(apkFile);
// throw new Installer.InstallFailedException(apkFile + " failed to verify!");
// }
apkFile = null; // ensure this is not used now that its copied to apkToInstall
// special case: F-Droid Privileged Extension
if (packageName != null && packageName.equals(PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME)) {
// extension must be signed with the same public key as main F-Droid
// NOTE: Disabled for debug builds to be able to use official extension from repo
ApkSignatureVerifier signatureVerifier = new ApkSignatureVerifier(context);
if (!BuildConfig.DEBUG && !signatureVerifier.hasFDroidSignature(apkToInstall)) {
throw new Installer.InstallFailedException("APK signature of extension not correct!");
}
Activity activity = (Activity) context;
Intent installIntent = new Intent(activity, InstallExtensionDialogActivity.class);
installIntent.setAction(InstallExtensionDialogActivity.ACTION_INSTALL);
installIntent.putExtra(InstallExtensionDialogActivity.EXTRA_INSTALL_APK, apkToInstall.getAbsolutePath());
activity.startActivity(installIntent);
return null;
}
// Need the apk to be world readable, so that the installer is able to read it.
// Note that saving it into external storage for the purpose of letting the installer
// have access is insecure, because apps with permission to write to the external
// storage can overwrite the app between F-Droid asking for it to be installed and
// the installer actually installing it.
FileCompat.setReadable(apkToInstall, true);
NotificationManager nm = (NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE);
nm.cancel(urlString.hashCode());
} catch (NumberFormatException | IOException e) {
throw new Installer.InstallFailedException(e);
} catch (ClassCastException e) {
throw new Installer.InstallFailedException("F-Droid Privileged can only be updated using an activity!");
}
return apkToInstall;
}
/**
* Checks the APK file against the provided hash, returning whether it is a match.
*/
public static boolean verifyApkFile(File apkFile, String hash, String hashType)
throws NoSuchAlgorithmException {
if (!apkFile.exists()) {
return false;
}
Hasher hasher = new Hasher(hashType, apkFile);
return hasher.match(hash);
}
}

View File

@ -168,7 +168,7 @@ public class InstallManagerService extends Service {
sendBroadcast(intent.getData(), Downloader.ACTION_STARTED, apkFilePath);
sendBroadcast(intent.getData(), Downloader.ACTION_COMPLETE, apkFilePath);
} else {
Utils.debugLog(TAG, " delete and download again " + urlString + " " + apkFilePath);
Utils.debugLog(TAG, "delete and download again " + urlString + " " + apkFilePath);
apkFilePath.delete();
DownloaderService.queue(this, urlString);
}
@ -233,14 +233,15 @@ public class InstallManagerService extends Service {
unregisterDownloaderReceivers(intent.getDataString());
registerInstallerReceivers(localUri);
InstallerService.install(context, localUri, originatingUri);
Apk apk = ACTIVE_APKS.get(originatingUri.toString());
InstallerService.install(context, localUri, originatingUri, apk.packageName);
}
};
BroadcastReceiver interruptedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String urlString = intent.getDataString();
Apk apk = removeFromActive(urlString);
removeFromActive(urlString);
unregisterDownloaderReceivers(urlString);
cancelNotification(urlString);
}
@ -266,16 +267,12 @@ public class InstallManagerService extends Service {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
case InstallHelper.ACTION_INSTALL_STARTED: {
Utils.debugLog(TAG, "ACTION_INSTALL_STARTED");
case Installer.ACTION_INSTALL_STARTED: {
break;
}
case InstallHelper.ACTION_INSTALL_COMPLETE: {
Utils.debugLog(TAG, "ACTION_INSTALL_COMPLETE");
case Installer.ACTION_INSTALL_COMPLETE: {
Uri originatingUri =
intent.getParcelableExtra(InstallHelper.EXTRA_ORIGINATING_URI);
intent.getParcelableExtra(Installer.EXTRA_ORIGINATING_URI);
String urlString = originatingUri.toString();
removeFromActive(urlString);
@ -283,29 +280,24 @@ public class InstallManagerService extends Service {
break;
}
case InstallHelper.ACTION_INSTALL_INTERRUPTED: {
Utils.debugLog(TAG, "ACTION_INSTALL_INTERRUPTED");
case Installer.ACTION_INSTALL_INTERRUPTED: {
localBroadcastManager.unregisterReceiver(this);
break;
}
case InstallHelper.ACTION_INSTALL_USER_INTERACTION: {
Utils.debugLog(TAG, "ACTION_INSTALL_USER_INTERACTION");
case Installer.ACTION_INSTALL_USER_INTERACTION: {
Uri originatingUri =
intent.getParcelableExtra(InstallHelper.EXTRA_ORIGINATING_URI);
intent.getParcelableExtra(Installer.EXTRA_ORIGINATING_URI);
PendingIntent installPendingIntent =
intent.getParcelableExtra(InstallHelper.EXTRA_USER_INTERACTION_PI);
// TODO
String urlString = originatingUri.toString();
Apk apk = getFromActive(urlString);
Utils.debugLog(TAG, "urlString: " + urlString);
intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI);
Utils.debugLog(TAG, "originatingUri: " + originatingUri);
Apk apk = getFromActive(originatingUri.toString());
// show notification if app details is not visible
if (AppDetails.isAppVisible(apk.packageName)) {
cancelNotification(urlString);
cancelNotification(originatingUri.toString());
} else {
notifyDownloadComplete(apk, urlString, installPendingIntent);
notifyDownloadComplete(apk, originatingUri.toString(), installPendingIntent);
}
break;
@ -318,7 +310,7 @@ public class InstallManagerService extends Service {
};
localBroadcastManager.registerReceiver(installReceiver,
InstallerService.getInstallIntentFilter(uri));
Installer.getInstallIntentFilter(uri));
}
private NotificationCompat.Builder createNotificationBuilder(String urlString, Apk apk) {
@ -327,7 +319,7 @@ public class InstallManagerService extends Service {
.setAutoCancel(false)
.setOngoing(true)
.setContentIntent(getAppDetailsIntent(downloadUrlId, apk))
.setContentTitle(getString(R.string.downloading_apk, getAppName(urlString, apk)))
.setContentTitle(getString(R.string.downloading_apk, getAppName(apk)))
.addAction(R.drawable.ic_cancel_black_24dp, getString(R.string.cancel),
DownloaderService.getCancelPendingIntent(this, urlString))
.setSmallIcon(android.R.drawable.stat_sys_download)
@ -335,7 +327,7 @@ public class InstallManagerService extends Service {
.setProgress(100, 0, true);
}
private String getAppName(String urlString, Apk apk) {
private String getAppName(Apk apk) {
App app = ACTIVE_APPS.get(apk.packageName);
return app.name;
}
@ -371,7 +363,7 @@ 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) {
title = String.format(getString(R.string.tap_to_install_format), getAppName(urlString, apk));
title = String.format(getString(R.string.tap_to_install_format), getAppName(apk));
}
int downloadUrlId = urlString.hashCode();

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2016 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -19,47 +19,55 @@
package org.fdroid.fdroid.installer;
import android.app.Activity;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.PatternMatcher;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
import org.apache.commons.io.FileUtils;
import org.fdroid.fdroid.AndroidXMLDecompress;
import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.Hasher;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.SanitizedFile;
import org.fdroid.fdroid.privileged.install.InstallExtensionDialogActivity;
import org.fdroid.fdroid.privileged.views.AppDiff;
import org.fdroid.fdroid.privileged.views.AppSecurityPermissions;
import org.fdroid.fdroid.privileged.views.InstallConfirmActivity;
import java.io.File;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
/**
* Abstract Installer class. Also provides static methods to automatically
* instantiate a working Installer based on F-Droids granted permissions.
*/
public abstract class Installer {
final Context mContext;
final PackageManager mPm;
final InstallerCallback mCallback;
private LocalBroadcastManager localBroadcastManager;
private static final String TAG = "Installer";
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";
public static final String ACTION_INSTALL_USER_INTERACTION = "org.fdroid.fdroid.installer.Installer.action.INSTALL_USER_INTERACTION";
public static final String ACTION_UNINSTALL_STARTED = "org.fdroid.fdroid.installer.Installer.action.UNINSTALL_STARTED";
public static final String ACTION_UNINSTALL_COMPLETE = "org.fdroid.fdroid.installer.Installer.action.UNINSTALL_COMPLETE";
public static final String ACTION_UNINSTALL_INTERRUPTED = "org.fdroid.fdroid.installer.Installer.action.UNINSTALL_INTERRUPTED";
public static final String ACTION_UNINSTALL_USER_INTERACTION = "org.fdroid.fdroid.installer.Installer.action.UNINSTALL_USER_INTERACTION";
/**
* This is thrown when an Installer is not compatible with the Android OS it
* is running on. This could be due to a broken superuser in case of
* RootInstaller or due to an incompatible Android version in case of
* SystemPermissionInstaller
* Same as http://developer.android.com/reference/android/content/Intent.html#EXTRA_ORIGINATING_URI
* In InstallManagerService often called urlString
*/
public static final String EXTRA_ORIGINATING_URI = "org.fdroid.fdroid.installer.InstallerService.extra.ORIGINATING_URI";
public static final String EXTRA_PACKAGE_NAME = "org.fdroid.fdroid.installer.InstallerService.extra.PACKAGE_NAME";
public static final String EXTRA_USER_INTERACTION_PI = "org.fdroid.fdroid.installer.InstallerService.extra.USER_INTERACTION_PI";
public static final String EXTRA_ERROR_MESSAGE = "org.fdroid.fdroid.net.Downloader.extra.ERROR_MESSAGE";
public static class InstallFailedException extends Exception {
private static final long serialVersionUID = -8343133906463328027L;
@ -77,97 +85,28 @@ public abstract class Installer {
* Callback from Installer. NOTE: This callback can be in a different thread
* than the UI thread
*/
public interface InstallerCallback {
// public interface InstallerCallback {
//
// int OPERATION_INSTALL = 1;
// int OPERATION_DELETE = 2;
//
// // Avoid using [-1,1] as they may conflict with Activity.RESULT_*
// int ERROR_CODE_CANCELED = 2;
// int ERROR_CODE_OTHER = 3;
// int ERROR_CODE_CANNOT_PARSE = 4;
//
// void onSuccess(int operation);
//
// void onError(int operation, int errorCode);
// }
int OPERATION_INSTALL = 1;
int OPERATION_DELETE = 2;
// Avoid using [-1,1] as they may conflict with Activity.RESULT_*
int ERROR_CODE_CANCELED = 2;
int ERROR_CODE_OTHER = 3;
int ERROR_CODE_CANNOT_PARSE = 4;
void onSuccess(int operation);
void onError(int operation, int errorCode);
}
Installer(Context context, PackageManager pm, InstallerCallback callback)
throws InstallFailedException {
Installer(Context context) {
this.mContext = context;
this.mPm = pm;
this.mCallback = callback;
this.mPm = context.getPackageManager();
localBroadcastManager = LocalBroadcastManager.getInstance(context);
}
public static Installer getActivityInstaller(Activity activity, InstallerCallback callback) {
return getActivityInstaller(activity, activity.getPackageManager(), callback);
}
/**
* Creates a new Installer for installing/deleting processes starting from
* an Activity
*/
public static Installer getActivityInstaller(Activity activity, PackageManager pm,
InstallerCallback callback) {
// system permissions and pref enabled -> SystemInstaller
boolean isSystemInstallerEnabled = Preferences.get().isPrivilegedInstallerEnabled();
if (isSystemInstallerEnabled) {
if (PrivilegedInstaller.isExtensionInstalledCorrectly(activity)
== PrivilegedInstaller.IS_EXTENSION_INSTALLED_YES) {
Utils.debugLog(TAG, "system permissions -> SystemInstaller");
try {
return new PrivilegedInstaller(activity, pm, callback);
} catch (InstallFailedException e) {
Log.e(TAG, "Android not compatible with SystemInstaller!", e);
}
} else {
Log.e(TAG, "SystemInstaller is enabled in prefs, but system-perms are not granted!");
}
}
// else -> DefaultInstaller
if (android.os.Build.VERSION.SDK_INT >= 14) {
// Default installer on Android >= 4.0
try {
Utils.debugLog(TAG, "try default installer for android >= 14");
return new DefaultSdk14Installer(activity, pm, callback);
} catch (InstallFailedException e) {
Log.e(TAG, "Android not compatible with DefaultInstallerSdk14!", e);
}
} else {
// Default installer on Android < 4.0 (android-14)
try {
Utils.debugLog(TAG, "try default installer for android < 14");
return new DefaultInstaller(activity, pm, callback);
} catch (InstallFailedException e) {
Log.e(TAG, "Android not compatible with DefaultInstaller!", e);
}
}
// this should not happen!
return null;
}
/**
* Checks the APK file against the provided hash, returning whether it is a match.
*/
public static boolean verifyApkFile(File apkFile, String hash, String hashType)
throws NoSuchAlgorithmException {
if (!apkFile.exists()) {
return false;
}
Hasher hasher = new Hasher(hashType, apkFile);
return hasher.match(hash);
}
/**
* This is the safe, single point of entry for submitting an APK file to be installed.
*/
public void installPackage(File apkFile, String packageName, String urlString)
public static SanitizedFile prepareApkFile(Context context, File apkFile, String packageName)
throws InstallFailedException {
SanitizedFile apkToInstall = null;
try {
@ -182,7 +121,7 @@ public abstract class Installer {
throw new InstallFailedException(apkFile + " is missing versionCode!");
}
int versionCode = (Integer) attributes.get("versionCode");
Apk apk = ApkProvider.Helper.find(mContext, packageName, versionCode, new String[]{
Apk apk = ApkProvider.Helper.find(context, packageName, versionCode, new String[]{
ApkProvider.DataColumns.HASH,
ApkProvider.DataColumns.HASH_TYPE,
});
@ -190,7 +129,8 @@ public abstract class Installer {
* of the app to prevent attacks based on other apps swapping the file
* out during the install process. Most likely, apkFile was just downloaded,
* so it should still be in the RAM disk cache */
apkToInstall = SanitizedFile.knownSanitized(File.createTempFile("install-", ".apk", mContext.getFilesDir()));
apkToInstall = SanitizedFile.knownSanitized(File.createTempFile("install-", ".apk",
context.getFilesDir()));
FileUtils.copyFile(apkFile, apkToInstall);
if (!verifyApkFile(apkToInstall, apk.hash, apk.hashType)) {
FileUtils.deleteQuietly(apkFile);
@ -198,36 +138,14 @@ public abstract class Installer {
}
apkFile = null; // ensure this is not used now that its copied to apkToInstall
// special case: F-Droid Privileged Extension
if (packageName != null && packageName.equals(PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME)) {
// extension must be signed with the same public key as main F-Droid
// NOTE: Disabled for debug builds to be able to use official extension from repo
ApkSignatureVerifier signatureVerifier = new ApkSignatureVerifier(mContext);
if (!BuildConfig.DEBUG && !signatureVerifier.hasFDroidSignature(apkToInstall)) {
throw new InstallFailedException("APK signature of extension not correct!");
}
Activity activity = (Activity) mContext;
Intent installIntent = new Intent(activity, InstallExtensionDialogActivity.class);
installIntent.setAction(InstallExtensionDialogActivity.ACTION_INSTALL);
installIntent.putExtra(InstallExtensionDialogActivity.EXTRA_INSTALL_APK, apkToInstall.getAbsolutePath());
activity.startActivity(installIntent);
return;
}
// Need the apk to be world readable, so that the installer is able to read it.
// Note that saving it into external storage for the purpose of letting the installer
// have access is insecure, because apps with permission to write to the external
// storage can overwrite the app between F-Droid asking for it to be installed and
// the installer actually installing it.
apkToInstall.setReadable(true, false);
installPackageInternal(apkToInstall);
NotificationManager nm = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
nm.cancel(urlString.hashCode());
} catch (NumberFormatException | NoSuchAlgorithmException | IOException e) {
} catch (NumberFormatException | IOException | NoSuchAlgorithmException e) {
throw new InstallFailedException(e);
} catch (ClassCastException e) {
throw new InstallFailedException("F-Droid Privileged can only be updated using an activity!");
@ -248,41 +166,139 @@ public abstract class Installer {
}
}.start();
}
return apkToInstall;
}
public void deletePackage(String packageName) throws InstallFailedException {
// check if package exists before proceeding...
try {
mPm.getPackageInfo(packageName, 0);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Couldn't find package " + packageName + " to delete.");
return;
}
// special case: F-Droid Privileged Extension
if (packageName != null && packageName.equals(PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME)) {
Activity activity;
try {
activity = (Activity) mContext;
} catch (ClassCastException e) {
Utils.debugLog(TAG, "F-Droid Privileged can only be uninstalled using an activity!");
return;
}
Intent uninstallIntent = new Intent(activity, InstallExtensionDialogActivity.class);
uninstallIntent.setAction(InstallExtensionDialogActivity.ACTION_UNINSTALL);
activity.startActivity(uninstallIntent);
return;
}
deletePackageInternal(packageName);
public PendingIntent getPermissionScreen(Apk apk) {
// old code:
// Uri packageUri = Uri.fromFile(apkFile);
// int count = newPermissionCount(packageUri);
// if (count < 0) {
// mCallback.onError(InstallerCallback.OPERATION_INSTALL,
// InstallerCallback.ERROR_CODE_CANNOT_PARSE);
// return;
// }
// if (count > 0) {
// Intent intent = new Intent(mContext, InstallConfirmActivity.class);
// intent.setData(packageUri);
// mActivity.startActivityForResult(intent, REQUEST_CONFIRM_PERMS);
// } else {
// try {
// doInstallPackageInternal(packageUri);
// } catch (InstallFailedException e) {
// mCallback.onError(InstallerCallback.OPERATION_INSTALL,
// InstallerCallback.ERROR_CODE_OTHER);
// }
// }
return null;
}
protected abstract void installPackageInternal(File apkFile)
throws InstallFailedException;
protected abstract void deletePackageInternal(String packageName)
throws InstallFailedException;
private int newPermissionCount(Uri packageUri) {
AppDiff appDiff = new AppDiff(mContext.getPackageManager(), packageUri);
if (appDiff.mPkgInfo == null) {
// could not get diff because we couldn't parse the package
return -1;
}
AppSecurityPermissions perms = new AppSecurityPermissions(mContext, appDiff.mPkgInfo);
if (appDiff.mInstalledAppInfo != null) {
// update to an existing app
return perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW);
}
// default: even if there aren't any permissions, we want to make the
// user always confirm installing new apps
return 1;
}
/**
* Checks the APK file against the provided hash, returning whether it is a match.
*/
public static boolean verifyApkFile(File apkFile, String hash, String hashType)
throws NoSuchAlgorithmException {
if (!apkFile.exists()) {
return false;
}
Hasher hasher = new Hasher(hashType, apkFile);
return hasher.match(hash);
}
public void sendBroadcastInstall(Uri uri, Uri originatingUri, String action,
PendingIntent pendingIntent) {
sendBroadcastInstall(uri, originatingUri, action, pendingIntent, null);
}
public void sendBroadcastInstall(Uri uri, Uri originatingUri, String action) {
sendBroadcastInstall(uri, originatingUri, action, null, null);
}
public void sendBroadcastInstall(Uri uri, Uri originatingUri, String action, String errorMessage) {
sendBroadcastInstall(uri, originatingUri, action, null, errorMessage);
}
public void sendBroadcastInstall(Uri uri, Uri originatingUri, String action,
PendingIntent pendingIntent, String errorMessage) {
Intent intent = new Intent(action);
intent.setData(uri);
intent.putExtra(Installer.EXTRA_ORIGINATING_URI, originatingUri);
intent.putExtra(Installer.EXTRA_USER_INTERACTION_PI, pendingIntent);
if (!TextUtils.isEmpty(errorMessage)) {
intent.putExtra(Installer.EXTRA_ERROR_MESSAGE, errorMessage);
}
localBroadcastManager.sendBroadcast(intent);
}
public void sendBroadcastUninstall(String packageName, String action, String errorMessage) {
sendBroadcastUninstall(packageName, action, null, errorMessage);
}
public void sendBroadcastUninstall(String packageName, String action) {
sendBroadcastUninstall(packageName, action, null, null);
}
public void sendBroadcastUninstall(String packageName, String action,
PendingIntent pendingIntent) {
sendBroadcastUninstall(packageName, action, pendingIntent, null);
}
public void sendBroadcastUninstall(String packageName, String action,
PendingIntent pendingIntent, String errorMessage) {
Uri uri = Uri.fromParts("package", packageName, null);
Intent intent = new Intent(action);
intent.setData(uri); // for broadcast filtering
intent.putExtra(Installer.EXTRA_PACKAGE_NAME, packageName);
intent.putExtra(Installer.EXTRA_USER_INTERACTION_PI, pendingIntent);
if (!TextUtils.isEmpty(errorMessage)) {
intent.putExtra(Installer.EXTRA_ERROR_MESSAGE, errorMessage);
}
localBroadcastManager.sendBroadcast(intent);
}
public static IntentFilter getInstallIntentFilter(Uri uri) {
IntentFilter intentFilter = new IntentFilter();
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.addDataScheme(uri.getScheme());
intentFilter.addDataPath(uri.getPath(), PatternMatcher.PATTERN_LITERAL);
return intentFilter;
}
public static IntentFilter getUninstallIntentFilter(String packageName) {
IntentFilter intentFilter = new IntentFilter();
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);
intentFilter.addDataScheme("package");
intentFilter.addDataPath(packageName, PatternMatcher.PATTERN_LITERAL);
return intentFilter;
}
protected abstract void installPackage(Uri uri, Uri originatingUri, String packageName);
protected abstract void uninstallPackage(String packageName);
public abstract boolean handleOnActivityResult(int requestCode, int resultCode, Intent data);
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2016 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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.content.Context;
import android.util.Log;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.Utils;
public class InstallerFactory {
private static final String TAG = "InstallerFactory";
public static Installer create(Context context) {
Installer installer;
if (isPrivilegedInstallerEnabled()) {
if (PrivilegedInstaller.isExtensionInstalledCorrectly(context)
== PrivilegedInstaller.IS_EXTENSION_INSTALLED_YES) {
Utils.debugLog(TAG, "privileged extension correctly installed -> PrivilegedInstaller");
installer = new PrivilegedInstaller(context);
} else {
Log.e(TAG, "PrivilegedInstaller is enabled in prefs, but permissions are not granted!");
// fallback to default installer
installer = new DefaultInstaller(context);
}
} else {
installer = new DefaultInstaller(context);
}
return installer;
}
/**
* Extension has privileged permissions and preference is enabled?
*/
private static boolean isPrivilegedInstallerEnabled() {
return Preferences.get().isPrivilegedInstallerEnabled();
}
}

View File

@ -18,27 +18,21 @@
package org.fdroid.fdroid.installer;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PatternMatcher;
import android.os.Process;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
import org.fdroid.fdroid.Utils;
import java.io.File;
/**
* InstallerService based on DownloaderService
*/
@ -51,6 +45,7 @@ public class InstallerService extends Service {
private volatile Looper serviceLooper;
private static volatile ServiceHandler serviceHandler;
private LocalBroadcastManager localBroadcastManager;
private Installer installer;
private final class ServiceHandler extends Handler {
ServiceHandler(Looper looper) {
@ -76,6 +71,7 @@ public class InstallerService extends Service {
serviceLooper = thread.getLooper();
serviceHandler = new ServiceHandler(serviceLooper);
localBroadcastManager = LocalBroadcastManager.getInstance(this);
installer = InstallerFactory.create(this);
}
@Override
@ -92,7 +88,7 @@ public class InstallerService extends Service {
serviceHandler.sendMessage(msg);
Utils.debugLog(TAG, "Start install of " + uri.toString());
} else if (ACTION_UNINSTALL.equals(intent.getAction())) {
String packageName = intent.getStringExtra(InstallHelper.EXTRA_UNINSTALL_PACKAGE_NAME);
String packageName = intent.getStringExtra(Installer.EXTRA_PACKAGE_NAME);
Message msg = serviceHandler.obtainMessage();
msg.arg1 = startId;
@ -125,140 +121,37 @@ public class InstallerService extends Service {
switch (intent.getAction()) {
case ACTION_INSTALL: {
Uri uri = intent.getData();
Uri originatingUri = intent.getParcelableExtra(InstallHelper.EXTRA_ORIGINATING_URI);
sendBroadcastInstall(uri, originatingUri, InstallHelper.ACTION_INSTALL_STARTED);
Utils.debugLog(TAG, "ACTION_INSTALL uri: " + uri + " file: " + new File(uri.getPath()));
// TODO: rework for uri
Uri sanitizedUri = null;
try {
File file = InstallHelper.preparePackage(this, new File(uri.getPath()), null,
originatingUri.toString());
sanitizedUri = Uri.fromFile(file);
} catch (Installer.InstallFailedException e) {
e.printStackTrace();
}
Utils.debugLog(TAG, "ACTION_INSTALL sanitizedUri: " + sanitizedUri);
Intent installIntent = new Intent(this, AndroidInstallerActivity.class);
installIntent.setAction(AndroidInstallerActivity.ACTION_INSTALL_PACKAGE);
installIntent.putExtra(AndroidInstallerActivity.EXTRA_ORIGINATING_URI, originatingUri);
installIntent.setData(sanitizedUri);
PendingIntent installPendingIntent = PendingIntent.getActivity(this.getApplicationContext(),
uri.hashCode(),
installIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
sendBroadcastInstall(uri, originatingUri, InstallHelper.ACTION_INSTALL_USER_INTERACTION,
installPendingIntent);
Uri originatingUri = intent.getParcelableExtra(Installer.EXTRA_ORIGINATING_URI);
String packageName = intent.getStringExtra(Installer.EXTRA_PACKAGE_NAME);
installer.installPackage(uri, originatingUri, packageName);
break;
}
case ACTION_UNINSTALL: {
String packageName =
intent.getStringExtra(InstallHelper.EXTRA_UNINSTALL_PACKAGE_NAME);
sendBroadcastUninstall(packageName, InstallHelper.ACTION_UNINSTALL_STARTED);
Intent installIntent = new Intent(this, AndroidInstallerActivity.class);
installIntent.setAction(AndroidInstallerActivity.ACTION_UNINSTALL_PACKAGE);
installIntent.putExtra(
AndroidInstallerActivity.EXTRA_UNINSTALL_PACKAGE_NAME, packageName);
PendingIntent uninstallPendingIntent = PendingIntent.getActivity(this.getApplicationContext(),
packageName.hashCode(),
installIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
sendBroadcastUninstall(packageName, InstallHelper.ACTION_UNINSTALL_USER_INTERACTION,
uninstallPendingIntent);
intent.getStringExtra(Installer.EXTRA_PACKAGE_NAME);
installer.uninstallPackage(packageName);
break;
}
}
}
private void sendBroadcastInstall(Uri uri, Uri originatingUri, String action,
PendingIntent pendingIntent) {
sendBroadcastInstall(uri, originatingUri, action, pendingIntent, null);
}
private void sendBroadcastInstall(Uri uri, Uri originatingUri, String action) {
sendBroadcastInstall(uri, originatingUri, action, null, null);
}
private void sendBroadcastInstall(Uri uri, Uri originatingUri, String action,
PendingIntent pendingIntent, String errorMessage) {
Intent intent = new Intent(action);
intent.setData(uri);
intent.putExtra(InstallHelper.EXTRA_ORIGINATING_URI, originatingUri);
intent.putExtra(InstallHelper.EXTRA_USER_INTERACTION_PI, pendingIntent);
if (!TextUtils.isEmpty(errorMessage)) {
intent.putExtra(InstallHelper.EXTRA_ERROR_MESSAGE, errorMessage);
}
localBroadcastManager.sendBroadcast(intent);
}
private void sendBroadcastUninstall(String packageName, String action) {
sendBroadcastUninstall(packageName, action, null, null);
}
private void sendBroadcastUninstall(String packageName, String action,
PendingIntent pendingIntent) {
sendBroadcastUninstall(packageName, action, pendingIntent, null);
}
private void sendBroadcastUninstall(String packageName, String action,
PendingIntent pendingIntent, String errorMessage) {
Uri uri = Uri.fromParts("package", packageName, null);
Intent intent = new Intent(action);
intent.setData(uri); // for broadcast filtering
intent.putExtra(InstallHelper.EXTRA_UNINSTALL_PACKAGE_NAME, packageName);
intent.putExtra(InstallHelper.EXTRA_USER_INTERACTION_PI, pendingIntent);
if (!TextUtils.isEmpty(errorMessage)) {
intent.putExtra(InstallHelper.EXTRA_ERROR_MESSAGE, errorMessage);
}
localBroadcastManager.sendBroadcast(intent);
}
public static void install(Context context, Uri uri, Uri originatingUri) {
public static void install(Context context, Uri uri, Uri originatingUri, String packageName) {
Intent intent = new Intent(context, InstallerService.class);
intent.setAction(ACTION_INSTALL);
intent.setData(uri);
intent.putExtra(InstallHelper.EXTRA_ORIGINATING_URI, originatingUri);
intent.putExtra(Installer.EXTRA_ORIGINATING_URI, originatingUri);
intent.putExtra(Installer.EXTRA_PACKAGE_NAME, packageName);
context.startService(intent);
}
public static void uninstall(Context context, String packageName) {
Intent intent = new Intent(context, InstallerService.class);
intent.setAction(ACTION_UNINSTALL);
intent.putExtra(InstallHelper.EXTRA_UNINSTALL_PACKAGE_NAME, packageName);
intent.putExtra(Installer.EXTRA_PACKAGE_NAME, packageName);
context.startService(intent);
}
public static IntentFilter getInstallIntentFilter(Uri uri) {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(InstallHelper.ACTION_INSTALL_STARTED);
intentFilter.addAction(InstallHelper.ACTION_INSTALL_COMPLETE);
intentFilter.addAction(InstallHelper.ACTION_INSTALL_INTERRUPTED);
intentFilter.addAction(InstallHelper.ACTION_INSTALL_USER_INTERACTION);
intentFilter.addDataScheme(uri.getScheme());
intentFilter.addDataPath(uri.getPath(), PatternMatcher.PATTERN_LITERAL);
return intentFilter;
}
public static IntentFilter getUninstallIntentFilter(String packageName) {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(InstallHelper.ACTION_UNINSTALL_STARTED);
intentFilter.addAction(InstallHelper.ACTION_UNINSTALL_COMPLETE);
intentFilter.addAction(InstallHelper.ACTION_UNINSTALL_INTERRUPTED);
intentFilter.addAction(InstallHelper.ACTION_UNINSTALL_USER_INTERACTION);
intentFilter.addDataScheme("package");
intentFilter.addDataPath(packageName, PatternMatcher.PATTERN_LITERAL);
return intentFilter;
}
}

View File

@ -20,7 +20,6 @@
package org.fdroid.fdroid.installer;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
@ -39,9 +38,6 @@ import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.privileged.IPrivilegedCallback;
import org.fdroid.fdroid.privileged.IPrivilegedService;
import org.fdroid.fdroid.privileged.views.AppDiff;
import org.fdroid.fdroid.privileged.views.AppSecurityPermissions;
import org.fdroid.fdroid.privileged.views.InstallConfirmActivity;
import java.io.File;
@ -52,7 +48,7 @@ import java.io.File;
* <li>android.permission.INSTALL_PACKAGES</li>
* <li>android.permission.DELETE_PACKAGES</li>
* </ul>
*
* <p/>
* Both permissions are protected by systemOrSignature (in newer versions:
* system|signature) and only granted on F-Droid's install in the following
* cases:
@ -62,7 +58,7 @@ import java.io.File;
* <li>On Android < 4.4 also when moved into /system/app/</li>
* <li>On Android >= 4.4 also when moved into /system/priv-app/</li>
* </ul>
*
* <p/>
* Sources for Android 4.4 change:
* https://groups.google.com/forum/#!msg/android-
* security-discuss/r7uL_OEMU5c/LijNHvxeV80J
@ -76,19 +72,13 @@ public class PrivilegedInstaller extends Installer {
private static final String PRIVILEGED_EXTENSION_SERVICE_INTENT = "org.fdroid.fdroid.privileged.IPrivilegedService";
public static final String PRIVILEGED_EXTENSION_PACKAGE_NAME = "org.fdroid.fdroid.privileged";
private final Activity mActivity;
private static final int REQUEST_CONFIRM_PERMS = 0;
public static final int IS_EXTENSION_INSTALLED_NO = 0;
public static final int IS_EXTENSION_INSTALLED_YES = 1;
public static final int IS_EXTENSION_INSTALLED_SIGNATURE_PROBLEM = 2;
public static final int IS_EXTENSION_INSTALLED_PERMISSIONS_PROBLEM = 3;
public PrivilegedInstaller(Activity activity, PackageManager pm,
InstallerCallback callback) throws InstallFailedException {
super(activity, pm, callback);
this.mActivity = activity;
public PrivilegedInstaller(Context context) {
super(context);
}
public static boolean isExtensionInstalled(Context context) {
@ -155,46 +145,10 @@ public class PrivilegedInstaller extends Installer {
return IS_EXTENSION_INSTALLED_YES;
}
@Override
protected void installPackageInternal(File apkFile) throws InstallFailedException {
Uri packageUri = Uri.fromFile(apkFile);
int count = newPermissionCount(packageUri);
if (count < 0) {
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
InstallerCallback.ERROR_CODE_CANNOT_PARSE);
return;
}
if (count > 0) {
Intent intent = new Intent(mContext, InstallConfirmActivity.class);
intent.setData(packageUri);
mActivity.startActivityForResult(intent, REQUEST_CONFIRM_PERMS);
} else {
try {
doInstallPackageInternal(packageUri);
} catch (InstallFailedException e) {
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
InstallerCallback.ERROR_CODE_OTHER);
}
}
}
protected void installPackage(final Uri uri, Uri originatingUri, String packageName) {
private int newPermissionCount(Uri packageUri) {
AppDiff appDiff = new AppDiff(mContext.getPackageManager(), packageUri);
if (appDiff.mPkgInfo == null) {
// could not get diff because we couldn't parse the package
return -1;
}
AppSecurityPermissions perms = new AppSecurityPermissions(mContext, appDiff.mPkgInfo);
if (appDiff.mInstalledAppInfo != null) {
// update to an existing app
return perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW);
}
// default: even if there aren't any permissions, we want to make the
// user always confirm installing new apps
return 1;
}
private void doInstallPackageInternal(final Uri packageURI) throws InstallFailedException {
ServiceConnection mServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder service) {
IPrivilegedService privService = IPrivilegedService.Stub.asInterface(service);
@ -205,17 +159,17 @@ public class PrivilegedInstaller extends Installer {
// TODO: propagate other return codes?
if (returnCode == INSTALL_SUCCEEDED) {
Utils.debugLog(TAG, "Install succeeded");
mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL);
// mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL);
} else {
Log.e(TAG, "Install failed with returnCode " + returnCode);
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
InstallerCallback.ERROR_CODE_OTHER);
// mCallback.onError(InstallerCallback.OPERATION_INSTALL,
// InstallerCallback.ERROR_CODE_OTHER);
}
}
};
try {
privService.installPackage(packageURI, INSTALL_REPLACE_EXISTING, null, callback);
privService.installPackage(uri, INSTALL_REPLACE_EXISTING, null, callback);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException", e);
}
@ -232,8 +186,7 @@ public class PrivilegedInstaller extends Installer {
}
@Override
protected void deletePackageInternal(final String packageName)
throws InstallFailedException {
protected void uninstallPackage(final String packageName) {
ApplicationInfo appInfo;
try {
//noinspection WrongConstant (lint is actually wrong here!)
@ -248,8 +201,8 @@ public class PrivilegedInstaller extends Installer {
if (isSystem && !isUpdate) {
// Cannot remove system apps unless we're uninstalling updates
mCallback.onError(InstallerCallback.OPERATION_DELETE,
InstallerCallback.ERROR_CODE_OTHER);
// mCallback.onError(InstallerCallback.OPERATION_DELETE,
// InstallerCallback.ERROR_CODE_OTHER);
return;
}
@ -270,8 +223,8 @@ public class PrivilegedInstaller extends Installer {
try {
doDeletePackageInternal(packageName);
} catch (InstallFailedException e) {
mCallback.onError(InstallerCallback.OPERATION_DELETE,
InstallerCallback.ERROR_CODE_OTHER);
// mCallback.onError(InstallerCallback.OPERATION_DELETE,
// InstallerCallback.ERROR_CODE_OTHER);
}
}
});
@ -280,8 +233,8 @@ public class PrivilegedInstaller extends Installer {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
mCallback.onError(InstallerCallback.OPERATION_DELETE,
InstallerCallback.ERROR_CODE_CANCELED);
// mCallback.onError(InstallerCallback.OPERATION_DELETE,
// InstallerCallback.ERROR_CODE_CANCELED);
}
});
builder.setMessage(messageId);
@ -301,11 +254,11 @@ public class PrivilegedInstaller extends Installer {
if (returnCode == DELETE_SUCCEEDED) {
Utils.debugLog(TAG, "Delete succeeded");
mCallback.onSuccess(InstallerCallback.OPERATION_DELETE);
// mCallback.onSuccess(InstallerCallback.OPERATION_DELETE);
} else {
Log.e(TAG, "Delete failed with returnCode " + returnCode);
mCallback.onError(InstallerCallback.OPERATION_DELETE,
InstallerCallback.ERROR_CODE_OTHER);
// mCallback.onError(InstallerCallback.OPERATION_DELETE,
// InstallerCallback.ERROR_CODE_OTHER);
}
}
};
@ -327,30 +280,30 @@ public class PrivilegedInstaller extends Installer {
Context.BIND_AUTO_CREATE);
}
@Override
public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CONFIRM_PERMS:
if (resultCode == Activity.RESULT_OK) {
final Uri packageUri = data.getData();
try {
doInstallPackageInternal(packageUri);
} catch (InstallFailedException e) {
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
InstallerCallback.ERROR_CODE_OTHER);
}
} else if (resultCode == InstallConfirmActivity.RESULT_CANNOT_PARSE) {
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
InstallerCallback.ERROR_CODE_CANNOT_PARSE);
} else { // Activity.RESULT_CANCELED
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
InstallerCallback.ERROR_CODE_CANCELED);
}
return true;
default:
return false;
}
}
// @Override
// public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) {
// switch (requestCode) {
// case REQUEST_CONFIRM_PERMS:
// if (resultCode == Activity.RESULT_OK) {
// final Uri packageUri = data.getData();
// try {
// doInstallPackageInternal(packageUri);
// } catch (InstallFailedException e) {
// mCallback.onError(InstallerCallback.OPERATION_INSTALL,
// InstallerCallback.ERROR_CODE_OTHER);
// }
// } else if (resultCode == InstallConfirmActivity.RESULT_CANNOT_PARSE) {
// mCallback.onError(InstallerCallback.OPERATION_INSTALL,
// InstallerCallback.ERROR_CODE_CANNOT_PARSE);
// } else { // Activity.RESULT_CANCELED
// mCallback.onError(InstallerCallback.OPERATION_INSTALL,
// InstallerCallback.ERROR_CODE_CANCELED);
// }
// return true;
// default:
// return false;
// }
// }
public static final int INSTALL_REPLACE_EXISTING = 2;
@ -644,7 +597,7 @@ public class PrivilegedInstaller extends Installer {
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
* if the system failed to install the package because it is attempting to define a
* permission that is already defined by some existing package.
*
* <p/>
* The package name of the app which has already defined the permission is passed to
* a {@link PackageInstallObserver}, if any, as the {@link #EXTRA_EXISTING_PACKAGE}
* string extra; and the name of the permission being redefined is passed in the

View File

@ -1,6 +1,7 @@
package org.fdroid.fdroid.views.swap;
import android.app.Activity;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@ -37,12 +38,14 @@ import org.fdroid.fdroid.NfcHelper;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.compat.PackageManagerCompat;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.NewRepoConfig;
import org.fdroid.fdroid.installer.InstallManagerService;
import org.fdroid.fdroid.installer.Installer;
import org.fdroid.fdroid.installer.InstallerService;
import org.fdroid.fdroid.localrepo.LocalRepoManager;
import org.fdroid.fdroid.localrepo.SwapService;
import org.fdroid.fdroid.localrepo.peers.Peer;
@ -786,25 +789,49 @@ public class SwapWorkflowActivity extends AppCompatActivity {
}
private void handleDownloadComplete(File apkFile, String packageName, String urlString) {
Uri originatingUri = Uri.parse(urlString);
Uri localUri = Uri.fromFile(apkFile);
try {
Installer.getActivityInstaller(this, new Installer.InstallerCallback() {
@Override
public void onSuccess(int operation) {
// TODO: Don't reload the view weely-neely, but rather get the view to listen
// for broadcasts that say the install was complete.
showRelevantView(true);
}
@Override
public void onError(int operation, int errorCode) {
// TODO: Boo!
}
}).installPackage(apkFile, packageName, urlString);
localBroadcastManager.unregisterReceiver(downloadCompleteReceiver);
} catch (Installer.InstallFailedException e) {
// TODO: Handle exception properly
}
localBroadcastManager.registerReceiver(installReceiver,
Installer.getInstallIntentFilter(Uri.fromFile(apkFile)));
InstallerService.install(this, localUri, originatingUri, packageName);
}
private final BroadcastReceiver installReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
case Installer.ACTION_INSTALL_STARTED: {
break;
}
case Installer.ACTION_INSTALL_COMPLETE: {
localBroadcastManager.unregisterReceiver(this);
showRelevantView(true);
break;
}
case Installer.ACTION_INSTALL_INTERRUPTED: {
localBroadcastManager.unregisterReceiver(this);
// TODO: handle errors!
break;
}
case Installer.ACTION_INSTALL_USER_INTERACTION: {
PendingIntent installPendingIntent =
intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI);
try {
installPendingIntent.send();
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "PI canceled", e);
}
break;
}
default: {
throw new RuntimeException("intent action not handled!");
}
}
}
};
}