diff --git a/app/src/main/java/org/fdroid/fdroid/AppDetails.java b/app/src/main/java/org/fdroid/fdroid/AppDetails.java index 379937361..d3a16343f 100644 --- a/app/src/main/java/org/fdroid/fdroid/AppDetails.java +++ b/app/src/main/java/org/fdroid/fdroid/AppDetails.java @@ -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); } diff --git a/app/src/main/java/org/fdroid/fdroid/Utils.java b/app/src/main/java/org/fdroid/fdroid/Utils.java index c93a6e466..31a48692e 100644 --- a/app/src/main/java/org/fdroid/fdroid/Utils.java +++ b/app/src/main/java/org/fdroid/fdroid/Utils.java @@ -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) { diff --git a/app/src/main/java/org/fdroid/fdroid/installer/AndroidInstallerActivity.java b/app/src/main/java/org/fdroid/fdroid/installer/AndroidInstallerActivity.java index 4764941cc..efed31af4 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/AndroidInstallerActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/AndroidInstallerActivity.java @@ -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); - } - } diff --git a/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstaller.java b/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstaller.java index a6fa2b9be..a424460dc 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstaller.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstaller.java @@ -1,100 +1,97 @@ -/* - * Copyright (C) 2014 Dominik Schürmann - * - * 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); } } diff --git a/app/src/main/java/org/fdroid/fdroid/installer/DefaultSdk14Installer.java b/app/src/main/java/org/fdroid/fdroid/installer/DefaultSdk14Installer.java deleted file mode 100644 index 6ca757dc6..000000000 --- a/app/src/main/java/org/fdroid/fdroid/installer/DefaultSdk14Installer.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann - * - * 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; - } - } -} diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallHelper.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallHelper.java deleted file mode 100644 index db1781039..000000000 --- a/app/src/main/java/org/fdroid/fdroid/installer/InstallHelper.java +++ /dev/null @@ -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 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); - } -} diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java index 82eeb125d..2630c6081 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java @@ -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(); diff --git a/app/src/main/java/org/fdroid/fdroid/installer/Installer.java b/app/src/main/java/org/fdroid/fdroid/installer/Installer.java index f9800f74f..458cd1f3a 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/Installer.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/Installer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 Dominik Schürmann + * Copyright (C) 2016 Dominik Schürmann * * 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); } diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallerFactory.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallerFactory.java new file mode 100644 index 000000000..52663b42b --- /dev/null +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallerFactory.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 Dominik Schürmann + * + * 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(); + } + +} diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallerService.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallerService.java index 13fbc7694..323356284 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/InstallerService.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallerService.java @@ -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; - } } diff --git a/app/src/main/java/org/fdroid/fdroid/installer/PrivilegedInstaller.java b/app/src/main/java/org/fdroid/fdroid/installer/PrivilegedInstaller.java index 324ed7767..14eee9c5e 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/PrivilegedInstaller.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/PrivilegedInstaller.java @@ -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; *
  • android.permission.INSTALL_PACKAGES
  • *
  • android.permission.DELETE_PACKAGES
  • * - * + *

    * 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; *

  • On Android < 4.4 also when moved into /system/app/
  • *
  • On Android >= 4.4 also when moved into /system/priv-app/
  • * - * + *

    * 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. - * + *

    * 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 diff --git a/app/src/main/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java b/app/src/main/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java index d789ac7d0..695b6ce27 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java @@ -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!"); + } + } + } + }; + }