diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 620575958..955db353c 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -3,7 +3,7 @@ package="org.fdroid.fdroid" android:installLocation="auto" android:versionCode="640" - android:versionName="0.64-test" > + android:versionName="0.64-test" xmlns:tools="http://schemas.android.com/tools"> - - - - + + + + - - - diff --git a/src/org/fdroid/fdroid/AppDetails.java b/src/org/fdroid/fdroid/AppDetails.java index 4c90cc83c..ca1d0a707 100644 --- a/src/org/fdroid/fdroid/AppDetails.java +++ b/src/org/fdroid/fdroid/AppDetails.java @@ -21,7 +21,10 @@ package org.fdroid.fdroid; import android.content.*; import android.widget.*; + import org.fdroid.fdroid.data.*; +import org.fdroid.fdroid.installer.Installer; +import org.fdroid.fdroid.installer.Installer.AndroidNotCompatibleException; import org.xml.sax.XMLReader; import android.app.AlertDialog; @@ -253,7 +256,7 @@ public class AppDetails extends ListActivity { private final Context mctx = this; private DisplayImageOptions displayImageOptions; - private InstallManager installManager; + private Installer installer; @Override protected void onCreate(Bundle savedInstanceState) { @@ -307,7 +310,7 @@ public class AppDetails extends ListActivity { } mPm = getPackageManager(); - installManager = new InstallManager(this, mPm, myInstallCallback); + installer = Installer.getActivityInstaller(this, mPm, myInstallCallback); // Get the preferences we're going to use in this Activity... AppDetails old = (AppDetails) getLastNonConfigurationInstance(); @@ -926,16 +929,24 @@ public class AppDetails extends ListActivity { Utils.getApkCacheDir(getBaseContext())); } - private void installApk(File file, String id) { - installManager.installApk(file, id); + private void installApk(File file, String packageName) { + try { + installer.installPackage(file); + } catch (AndroidNotCompatibleException e) { + Log.e(TAG, "Android not compatible with this Installer!", e); + } - notifyAppChanged(id); + notifyAppChanged(packageName); } - private void removeApk(String id) { - installManager.removeApk(id); + private void removeApk(String packageName) { + try { + installer.deletePackage(packageName); + } catch (AndroidNotCompatibleException e) { + Log.e(TAG, "Android not compatible with this Installer!", e); + } - notifyAppChanged(id); + notifyAppChanged(packageName); } /** @@ -946,7 +957,7 @@ public class AppDetails extends ListActivity { getContentResolver().notifyChange(AppProvider.getContentUri(id), null); } - private InstallManager.InstallCallback myInstallCallback = new InstallManager.InstallCallback() { + private Installer.InstallerCallback myInstallCallback = new Installer.InstallerCallback() { @Override public void onPackageInstalled(int returnCode, boolean unattended) { @@ -1156,12 +1167,15 @@ public class AppDetails extends ListActivity { @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // handle cases for install manager first + if (installer.handleOnActivityResult(requestCode, resultCode, data)) { + return; + } + switch (requestCode) { case REQUEST_ENABLE_BLUETOOTH: fdroidApp.sendViaBluetooth(this, resultCode, app.id); break; - default: - installManager.handleOnActivityResult(requestCode, resultCode, data); } } } diff --git a/src/org/fdroid/fdroid/InstallManager.java b/src/org/fdroid/fdroid/InstallManager.java deleted file mode 100644 index 9707af47b..000000000 --- a/src/org/fdroid/fdroid/InstallManager.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2013 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; - -import java.io.File; - -import android.annotation.TargetApi; -import android.app.Activity; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.net.Uri; -import android.os.Build; -import android.util.Log; - -public class InstallManager { - private Activity mActivity; - private PackageManager mPm; - - private InstallCallback mInstallCallback; - - public static final String TAG = "FDroid"; - - private static final int REQUEST_INSTALL = 0; - private static final int REQUEST_UNINSTALL = 1; - - public interface InstallCallback { - - public static final int RETURN_SUCCESS = 1; - public static final int RETURN_CANCEL = 0; - - public void onPackageInstalled(int returnCode, boolean unattended); - - public void onPackageDeleted(int returnCode, boolean unattended); - } - - public InstallManager(Activity activity, PackageManager pm, InstallCallback installCallback) { - super(); - this.mActivity = activity; - this.mPm = pm; - this.mInstallCallback = installCallback; - } - - public void removeApk(String id) { - PackageInfo pkginfo; - try { - pkginfo = mPm.getPackageInfo(id, 0); - } catch (NameNotFoundException e) { - Log.d(TAG, "Couldn't find package " + id + " to uninstall."); - return; - } - - // try unattended delete using internal API. This only works when F-Droid is installed as - // system app - try { - final InstallSystemManager systemInstall = new InstallSystemManager(mActivity, - mySystemCallback); - systemInstall.deletePackage(pkginfo.packageName); - - return; - } catch (Exception e) { - Log.d(TAG, "Unattended delete failed, falling back to normal delete method...", e); - } - - Uri uri = Uri.fromParts("package", pkginfo.packageName, null); - Intent intent = new Intent(Intent.ACTION_DELETE, uri); - mActivity.startActivityForResult(intent, REQUEST_UNINSTALL); - } - - public void installApk(File file, String id) { - // try unattended install using internal API. This only works when F-Droid is installed as - // system app - try { - final InstallSystemManager systemInstall = new InstallSystemManager(mActivity, - mySystemCallback); - systemInstall.installPackage(file); - - return; - } catch (Exception e) { - Log.d(TAG, "Unattended install failed, falling back to normal install method...", e); - } - - Intent intent = new Intent(); - intent.setAction(android.content.Intent.ACTION_VIEW); - intent.setDataAndType(Uri.parse("file://" + file.getPath()), - "application/vnd.android.package-archive"); - extraNotUnknownSource(intent); - mActivity.startActivityForResult(intent, REQUEST_INSTALL); - } - - @TargetApi(14) - private void extraNotUnknownSource(Intent intent) { - if (Build.VERSION.SDK_INT < 14) { - return; - } - intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true); - } - - private InstallSystemManager.InstallSystemCallback mySystemCallback = new InstallSystemManager.InstallSystemCallback() { - - @Override - public void onPackageInstalled(String packageName, int returnCode) { - if (returnCode == InstallSystemManager.INSTALL_SUCCEEDED) { - Log.d(TAG, "Install succeeded"); - mInstallCallback.onPackageInstalled(InstallCallback.RETURN_SUCCESS, - true); - } else { - Log.d(TAG, "Install failed: " + returnCode); - mInstallCallback.onPackageInstalled(InstallCallback.RETURN_CANCEL, - true); - } - - } - - @Override - public void onPackageDeleted(String packageName, int returnCode) { - if (returnCode == InstallSystemManager.DELETE_SUCCEEDED) { - Log.d(TAG, "Delete succeeded"); - mInstallCallback - .onPackageDeleted(InstallCallback.RETURN_SUCCESS, true); - } else { - Log.d(TAG, "Delete failed: " + returnCode); - mInstallCallback.onPackageDeleted(InstallCallback.RETURN_CANCEL, true); - } - - } - - }; - - protected void handleOnActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case REQUEST_INSTALL: - if (resultCode == Activity.RESULT_OK) { - mInstallCallback.onPackageInstalled(InstallCallback.RETURN_SUCCESS, false); - } else { - mInstallCallback.onPackageInstalled(InstallCallback.RETURN_CANCEL, false); - } - break; - case REQUEST_UNINSTALL: - if (resultCode == Activity.RESULT_OK) { - mInstallCallback.onPackageDeleted(InstallCallback.RETURN_SUCCESS, false); - } else { - mInstallCallback.onPackageDeleted(InstallCallback.RETURN_CANCEL, false); - } - break; - } - } - -} diff --git a/src/org/fdroid/fdroid/InstallSystemManager.java b/src/org/fdroid/fdroid/InstallSystemManager.java deleted file mode 100644 index ae9da00eb..000000000 --- a/src/org/fdroid/fdroid/InstallSystemManager.java +++ /dev/null @@ -1,442 +0,0 @@ -/* - * Copyright (C) 2013 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; - -import java.io.File; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -import android.content.Context; -import android.content.pm.IPackageDeleteObserver; -import android.content.pm.IPackageInstallObserver; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.RemoteException; - -/** - * Most parts of this class are based on - * https://github.com/paulononaka/Android-InstallInBackgroundSample and insights while reading the - * Android sourcecode. - * - * The author granted to use it "for whatever you want" - * (http://paulononaka.wordpress.com/2011/07/02/ - * how-to-install-a-application-in-background-on-android/#comment-80) - * - * Return values copied from PackageManger.java from Android sourcecode - */ -public class InstallSystemManager { - - public interface InstallSystemCallback { - - public void onPackageInstalled(String packageName, int returnCode); - - public void onPackageDeleted(String packageName, int returnCode); - } - - public final int INSTALL_REPLACE_EXISTING = 2; - - /** - * Installation return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} on success. - * - * @hide - */ - public static final int INSTALL_SUCCEEDED = 1; - - /** - * Installation return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the package is - * already installed. - * - * @hide - */ - public static final int INSTALL_FAILED_ALREADY_EXISTS = -1; - - /** - * Installation return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the package archive - * file is invalid. - * - * @hide - */ - public static final int INSTALL_FAILED_INVALID_APK = -2; - - /** - * Installation return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the URI passed in - * is invalid. - * - * @hide - */ - public static final int INSTALL_FAILED_INVALID_URI = -3; - - /** - * Installation return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the package manager - * service found that the device didn't have enough storage space to install the app. - * - * @hide - */ - public static final int INSTALL_FAILED_INSUFFICIENT_STORAGE = -4; - - /** - * Installation return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if a package is - * already installed with the same name. - * - * @hide - */ - public static final int INSTALL_FAILED_DUPLICATE_PACKAGE = -5; - - /** - * Installation return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the requested - * shared user does not exist. - * - * @hide - */ - public static final int INSTALL_FAILED_NO_SHARED_USER = -6; - - /** - * Installation return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if a previously - * installed package of the same name has a different signature than the new package (and the - * old package's data was not removed). - * - * @hide - */ - public static final int INSTALL_FAILED_UPDATE_INCOMPATIBLE = -7; - - /** - * Installation return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the new package is - * requested a shared user which is already installed on the device and does not have matching - * signature. - * - * @hide - */ - public static final int INSTALL_FAILED_SHARED_USER_INCOMPATIBLE = -8; - - /** - * Installation return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the new package - * uses a shared library that is not available. - * - * @hide - */ - public static final int INSTALL_FAILED_MISSING_SHARED_LIBRARY = -9; - - /** - * Installation return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the new package - * uses a shared library that is not available. - * - * @hide - */ - public static final int INSTALL_FAILED_REPLACE_COULDNT_DELETE = -10; - - /** - * Installation return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the new package - * failed while optimizing and validating its dex files, either because there was not enough - * storage or the validation failed. - * - * @hide - */ - public static final int INSTALL_FAILED_DEXOPT = -11; - - /** - * Installation return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the new package - * failed because the current SDK version is older than that required by the package. - * - * @hide - */ - public static final int INSTALL_FAILED_OLDER_SDK = -12; - - /** - * Installation return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the new package - * failed because it contains a content provider with the same authority as a provider already - * installed in the system. - * - * @hide - */ - public static final int INSTALL_FAILED_CONFLICTING_PROVIDER = -13; - - /** - * Installation return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the new package - * failed because the current SDK version is newer than that required by the package. - * - * @hide - */ - public static final int INSTALL_FAILED_NEWER_SDK = -14; - - /** - * Installation return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the new package - * failed because it has specified that it is a test-only package and the caller has not - * supplied the {@link #INSTALL_ALLOW_TEST} flag. - * - * @hide - */ - public static final int INSTALL_FAILED_TEST_ONLY = -15; - - /** - * Installation return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the package being - * installed contains native code, but none that is compatible with the the device's CPU_ABI. - * - * @hide - */ - public static final int INSTALL_FAILED_CPU_ABI_INCOMPATIBLE = -16; - - /** - * Installation return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the new package - * uses a feature that is not available. - * - * @hide - */ - public static final int INSTALL_FAILED_MISSING_FEATURE = -17; - - // ------ Errors related to sdcard - /** - * Installation return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if a secure container - * mount point couldn't be accessed on external media. - * - * @hide - */ - public static final int INSTALL_FAILED_CONTAINER_ERROR = -18; - - /** - * Installation return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the new package - * couldn't be installed in the specified install location. - * - * @hide - */ - public static final int INSTALL_FAILED_INVALID_INSTALL_LOCATION = -19; - - /** - * Installation return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the new package - * couldn't be installed in the specified install location because the media is not available. - * - * @hide - */ - public static final int INSTALL_FAILED_MEDIA_UNAVAILABLE = -20; - - /** - * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the parser was - * given a path that is not a file, or does not end with the expected '.apk' extension. - * - * @hide - */ - public static final int INSTALL_PARSE_FAILED_NOT_APK = -100; - - /** - * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the parser was - * unable to retrieve the AndroidManifest.xml file. - * - * @hide - */ - public static final int INSTALL_PARSE_FAILED_BAD_MANIFEST = -101; - - /** - * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the parser - * encountered an unexpected exception. - * - * @hide - */ - public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102; - - /** - * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the parser did not - * find any certificates in the .apk. - * - * @hide - */ - public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103; - - /** - * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the parser found - * inconsistent certificates on the files in the .apk. - * - * @hide - */ - public static final int INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES = -104; - - /** - * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the parser - * encountered a CertificateEncodingException in one of the files in the .apk. - * - * @hide - */ - public static final int INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING = -105; - - /** - * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the parser - * encountered a bad or missing package name in the manifest. - * - * @hide - */ - public static final int INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME = -106; - - /** - * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the parser - * encountered a bad shared user id name in the manifest. - * - * @hide - */ - public static final int INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID = -107; - - /** - * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the parser - * encountered some structural problem in the manifest. - * - * @hide - */ - public static final int INSTALL_PARSE_FAILED_MANIFEST_MALFORMED = -108; - - /** - * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the parser did not - * find any actionable tags (instrumentation or application) in the manifest. - * - * @hide - */ - public static final int INSTALL_PARSE_FAILED_MANIFEST_EMPTY = -109; - - /** - * Installation failed return code: this is passed to the {@link IPackageInstallObserver} by - * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the system failed - * to install the package because of system issues. - * - * @hide - */ - public static final int INSTALL_FAILED_INTERNAL_ERROR = -110; - - /** - * Return code for when package deletion succeeds. This is passed to the - * {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system succeeded in - * deleting the package. - * - * @hide - */ - public static final int DELETE_SUCCEEDED = 1; - - /** - * Deletion failed return code: this is passed to the {@link IPackageDeleteObserver} by - * {@link #deletePackage()} if the system failed to delete the package for an unspecified - * reason. - * - * @hide - */ - public static final int DELETE_FAILED_INTERNAL_ERROR = -1; - - /** - * Deletion failed return code: this is passed to the {@link IPackageDeleteObserver} by - * {@link #deletePackage()} if the system failed to delete the package because it is the active - * DevicePolicy manager. - * - * @hide - */ - public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2; - - /** - * Deletion failed return code: this is passed to the {@link IPackageDeleteObserver} by - * {@link #deletePackage()} if the system failed to delete the package since the user is - * restricted. - * - * @hide - */ - public static final int DELETE_FAILED_USER_RESTRICTED = -3; - - private PackageInstallObserver observer; - private PackageDeleteObserver observerdelete; - private PackageManager pm; - private Method method; - private Method uninstallmethod; - - private InstallSystemCallback installSystemCallback; - - class PackageInstallObserver extends IPackageInstallObserver.Stub { - public void packageInstalled(String packageName, int returnCode) throws RemoteException { - if (installSystemCallback != null) { - installSystemCallback.onPackageInstalled(packageName, returnCode); - } - } - } - - class PackageDeleteObserver extends IPackageDeleteObserver.Stub { - public void packageDeleted(String packageName, int returnCode) throws RemoteException { - if (installSystemCallback != null) { - installSystemCallback.onPackageDeleted(packageName, returnCode); - } - } - } - - public InstallSystemManager(Context context, InstallSystemCallback installCallback) throws SecurityException, NoSuchMethodException { - observer = new PackageInstallObserver(); - observerdelete = new PackageDeleteObserver(); - pm = context.getPackageManager(); - - Class[] types = new Class[] { Uri.class, IPackageInstallObserver.class, int.class, - String.class }; - Class[] uninstalltypes = new Class[] { String.class, IPackageDeleteObserver.class, - int.class }; - - method = pm.getClass().getMethod("installPackage", types); - uninstallmethod = pm.getClass().getMethod("deletePackage", uninstalltypes); - - this.installSystemCallback = installCallback; - } - - public void deletePackage(String packagename) throws IllegalArgumentException, - IllegalAccessException, InvocationTargetException { - uninstallmethod.invoke(pm, new Object[] { packagename, observerdelete, 0 }); - } - - public void installPackage(File apkFile) throws IllegalArgumentException, - IllegalAccessException, InvocationTargetException { - if (!apkFile.exists()) - throw new IllegalArgumentException(); - Uri packageURI = Uri.fromFile(apkFile); - installPackage(packageURI); - } - - public void installPackage(Uri apkFile) throws IllegalArgumentException, - IllegalAccessException, InvocationTargetException { - method.invoke(pm, new Object[] { apkFile, observer, INSTALL_REPLACE_EXISTING, null }); - } - -} diff --git a/src/org/fdroid/fdroid/installer/DefaultInstaller.java b/src/org/fdroid/fdroid/installer/DefaultInstaller.java new file mode 100644 index 000000000..9ce5ba9bd --- /dev/null +++ b/src/org/fdroid/fdroid/installer/DefaultInstaller.java @@ -0,0 +1,120 @@ +/* + * 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 java.io.File; + +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.content.pm.PackageManager.NameNotFoundException; +import android.net.Uri; +import android.os.Build; + +/** + * 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 Activity mActivity; + + public DefaultInstaller(Activity activity, PackageManager pm, InstallerCallback callback) + throws AndroidNotCompatibleException { + super(activity, pm, callback); + this.mActivity = activity; + } + + private static final int REQUEST_CODE_INSTALL = 0; + private static final int REQUEST_CODE_DELETE = 1; + + @Override + public void installPackage(File file) throws AndroidNotCompatibleException { + super.installPackage(file); + + Intent intent = new Intent(); + intent.setAction(android.content.Intent.ACTION_VIEW); + intent.setDataAndType(Uri.parse("file://" + file.getPath()), + "application/vnd.android.package-archive"); + extraNotUnknownSource(intent); + try { + mActivity.startActivityForResult(intent, REQUEST_CODE_INSTALL); + } catch (ActivityNotFoundException e) { + throw new AndroidNotCompatibleException(e); + } + } + + @TargetApi(14) + private void extraNotUnknownSource(Intent intent) { + if (Build.VERSION.SDK_INT < 14) { + return; + } + intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true); + } + + @Override + public void deletePackage(String packageName) throws AndroidNotCompatibleException { + super.deletePackage(packageName); + + PackageInfo pkgInfo = null; + try { + pkgInfo = mPm.getPackageInfo(packageName, 0); + } catch (NameNotFoundException e) { + // already checked in super class + } + + 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 AndroidNotCompatibleException(e); + } + } + + @Override + public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CODE_INSTALL: + if (resultCode == Activity.RESULT_OK) { + mCallback.onPackageInstalled(InstallerCallback.RETURN_SUCCESS, false); + } else { + mCallback.onPackageInstalled(InstallerCallback.RETURN_CANCEL, false); + } + + return true; + case REQUEST_CODE_DELETE: + if (resultCode == Activity.RESULT_OK) { + mCallback.onPackageDeleted(InstallerCallback.RETURN_SUCCESS, false); + } else { + mCallback.onPackageDeleted(InstallerCallback.RETURN_CANCEL, false); + } + + return true; + default: + return false; + } + } + +} diff --git a/src/org/fdroid/fdroid/installer/Installer.java b/src/org/fdroid/fdroid/installer/Installer.java new file mode 100644 index 000000000..2ee8ce1b2 --- /dev/null +++ b/src/org/fdroid/fdroid/installer/Installer.java @@ -0,0 +1,188 @@ +/* + * 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 java.io.File; + +import android.Manifest.permission; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.util.Log; + +abstract public class Installer { + protected Context mContext; + protected PackageManager mPm; + protected InstallerCallback mCallback; + + public static final String TAG = "FDroid"; + + /** + * 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 + */ + public static class AndroidNotCompatibleException extends Exception { + + private static final long serialVersionUID = -8343133906463328027L; + + public AndroidNotCompatibleException() { + } + + public AndroidNotCompatibleException(String message) { + super(message); + } + + public AndroidNotCompatibleException(Throwable cause) { + super(cause); + } + + public AndroidNotCompatibleException(String message, Throwable cause) { + super(message, cause); + } + } + + public interface InstallerCallback { + + public static final int RETURN_SUCCESS = 1; + public static final int RETURN_CANCEL = 0; + + public void onPackageInstalled(int returnCode, boolean unattended); + + public void onPackageDeleted(int returnCode, boolean unattended); + } + + public Installer(Context context, PackageManager pm, InstallerCallback callback) + throws AndroidNotCompatibleException { + this.mContext = context; + this.mPm = pm; + this.mCallback = callback; + } + + /** + * Creates a new Installer for installing/deleting processes starting from + * an Activity + * + * @param context + * @param pm + * @param callback + * @return + * @throws AndroidNotCompatibleException + */ + public static Installer getActivityInstaller(Activity activity, PackageManager pm, + InstallerCallback callback) { + + // system permissions -> SystemPermissionInstaller + if (hasSystemPermissions(activity, pm)) { + Log.d(TAG, "system permissions -> SystemPermissionInstaller"); + + try { + return new SystemPermissionInstaller(activity, pm, callback); + } catch (AndroidNotCompatibleException e) { + Log.e(TAG, "Android not compatible with SystemPermissionInstaller!", e); + } + } + + // try default installer + try { + Log.d(TAG, "try default installer"); + + return new DefaultInstaller(activity, pm, callback); + } catch (AndroidNotCompatibleException e) { + Log.e(TAG, + "Android not compatible with DefaultInstaller! This should really not happen!", + e); + } + + // this should not happen! + return null; + } + + public static Installer getUnattendedInstaller(Context context, PackageManager pm, + InstallerCallback callback) throws AndroidNotCompatibleException { + + if (hasSystemPermissions(context, pm)) { + // we have system permissions! + return new SystemPermissionInstaller(context, pm, callback); + } else { + // nope! + throw new AndroidNotCompatibleException(); + } + } + + private static boolean hasSystemPermissions(Context context, PackageManager pm) { + int checkInstallPermission = + pm.checkPermission(permission.INSTALL_PACKAGES, context.getPackageName()); + int checkDeletePermission = + pm.checkPermission(permission.DELETE_PACKAGES, context.getPackageName()); + boolean permissionsGranted = (checkInstallPermission == PackageManager.PERMISSION_GRANTED + && checkDeletePermission == PackageManager.PERMISSION_GRANTED); + + boolean isSystemApp; + try { + isSystemApp = isSystemApp(pm.getApplicationInfo(context.getPackageName(), 0)); + } catch (NameNotFoundException e) { + isSystemApp = false; + } + + // TODO: is this right??? + // two ways to be able to get system permissions: somehow the + // permissions where actually granted on install or the app has been + // moved later to the system partition -> also access + if (permissionsGranted || isSystemApp) { + return true; + } else { + return false; + } + } + + private static boolean isSystemApp(ApplicationInfo ai) { + int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; + return (ai.flags & mask) != 0; + } + + public void installPackage(File apkFile) throws AndroidNotCompatibleException { + // check if file exists... + if (!apkFile.exists()) { + Log.d(TAG, "Couldn't find file " + apkFile + " to install."); + return; + } + + // extended class now actually installs the package + } + + public void deletePackage(String packageName) throws AndroidNotCompatibleException { + // check if package exists before proceeding... + try { + mPm.getPackageInfo(packageName, 0); + } catch (NameNotFoundException e) { + Log.d(TAG, "Couldn't find package " + packageName + " to delete."); + return; + } + + // extended class now actually deletes the package + } + + public abstract boolean handleOnActivityResult(int requestCode, int resultCode, Intent data); +} diff --git a/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java b/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java new file mode 100644 index 000000000..207cb57b9 --- /dev/null +++ b/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java @@ -0,0 +1,447 @@ +/* + * 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 java.io.File; +import java.lang.reflect.Method; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.IPackageDeleteObserver; +import android.content.pm.IPackageInstallObserver; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.RemoteException; +import android.util.Log; + +/** + * Installer based on using internal hidden APIs of the Android OS, which are + * protected by the permissions + *
    + *
  • android.permission.INSTALL_PACKAGES
  • + *
  • android.permission.DELETE_PACKAGES
  • + *
+ *

+ * Both permissions are only granted on F-Droid's install when F-Droid itself + * has been installed as a system-application. + */ +public class SystemPermissionInstaller extends Installer { + + private PackageInstallObserver mInstallObserver; + private PackageDeleteObserver mDeleteObserver; + private Method mInstallMethod; + private Method mDeleteMethod; + + public SystemPermissionInstaller(Context context, PackageManager pm, + InstallerCallback callback) throws AndroidNotCompatibleException { + super(context, pm, callback); + + // create internal callbacks + mInstallObserver = new PackageInstallObserver(); + mDeleteObserver = new PackageDeleteObserver(); + + try { + Class[] installTypes = new Class[] { + Uri.class, IPackageInstallObserver.class, int.class, + String.class + }; + Class[] deleteTypes = new Class[] { + String.class, IPackageDeleteObserver.class, + int.class + }; + + mInstallMethod = mPm.getClass().getMethod("installPackage", installTypes); + mDeleteMethod = mPm.getClass().getMethod("deletePackage", deleteTypes); + } catch (NoSuchMethodException e) { + throw new AndroidNotCompatibleException(e); + } + } + + /** + * Internal install callback from the system + */ + class PackageInstallObserver extends IPackageInstallObserver.Stub { + public void packageInstalled(String packageName, int returnCode) throws RemoteException { + // TODO: propagate other return codes? + if (returnCode == INSTALL_SUCCEEDED) { + Log.d(TAG, "Install succeeded"); + mCallback.onPackageInstalled(InstallerCallback.RETURN_SUCCESS, true); + } else { + Log.d(TAG, "Install failed: " + returnCode); + mCallback.onPackageInstalled(InstallerCallback.RETURN_CANCEL, true); + } + } + } + + /** + * Internal delete callback from the system + */ + class PackageDeleteObserver extends IPackageDeleteObserver.Stub { + public void packageDeleted(String packageName, int returnCode) throws RemoteException { + // TODO: propagate other return codes? + if (returnCode == DELETE_SUCCEEDED) { + Log.d(TAG, "Delete succeeded"); + mCallback.onPackageDeleted(InstallerCallback.RETURN_SUCCESS, true); + } else { + Log.d(TAG, "Delete failed: " + returnCode); + mCallback.onPackageDeleted(InstallerCallback.RETURN_CANCEL, true); + } + } + } + + @Override + public void installPackage(File apkFile) throws AndroidNotCompatibleException { + super.installPackage(apkFile); + + Uri packageURI = Uri.fromFile(apkFile); + try { + mInstallMethod.invoke(mPm, new Object[] { + packageURI, mInstallObserver, INSTALL_REPLACE_EXISTING, null + }); + } catch (Exception e) { + throw new AndroidNotCompatibleException(e); + } + } + + @Override + public void deletePackage(String packageName) throws AndroidNotCompatibleException { + super.deletePackage(packageName); + + try { + mDeleteMethod.invoke(mPm, new Object[] { + packageName, mDeleteObserver, 0 + }); + } catch (Exception e) { + throw new AndroidNotCompatibleException(e); + } + } + + @Override + public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) { + // no need to handle onActivityResult + return false; + } + + public final int INSTALL_REPLACE_EXISTING = 2; + + /** + * Following return codes are copied from Android 4.3 source code + */ + + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} on + * success. + */ + public static final int INSTALL_SUCCEEDED = 1; + + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the package is already installed. + */ + public static final int INSTALL_FAILED_ALREADY_EXISTS = -1; + + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the package archive file is invalid. + */ + public static final int INSTALL_FAILED_INVALID_APK = -2; + + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the URI passed in is invalid. + */ + public static final int INSTALL_FAILED_INVALID_URI = -3; + + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the package manager service found that the device didn't have enough + * storage space to install the app. + */ + public static final int INSTALL_FAILED_INSUFFICIENT_STORAGE = -4; + + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * a package is already installed with the same name. + */ + public static final int INSTALL_FAILED_DUPLICATE_PACKAGE = -5; + + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the requested shared user does not exist. + */ + public static final int INSTALL_FAILED_NO_SHARED_USER = -6; + + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * a previously installed package of the same name has a different signature + * than the new package (and the old package's data was not removed). + */ + public static final int INSTALL_FAILED_UPDATE_INCOMPATIBLE = -7; + + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the new package is requested a shared user which is already installed on + * the device and does not have matching signature. + */ + public static final int INSTALL_FAILED_SHARED_USER_INCOMPATIBLE = -8; + + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the new package uses a shared library that is not available. + */ + public static final int INSTALL_FAILED_MISSING_SHARED_LIBRARY = -9; + + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the new package uses a shared library that is not available. + */ + public static final int INSTALL_FAILED_REPLACE_COULDNT_DELETE = -10; + + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the new package failed while optimizing and validating its dex files, + * either because there was not enough storage or the validation failed. + */ + public static final int INSTALL_FAILED_DEXOPT = -11; + + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the new package failed because the current SDK version is older than that + * required by the package. + */ + public static final int INSTALL_FAILED_OLDER_SDK = -12; + + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the new package failed because it contains a content provider with the + * same authority as a provider already installed in the system. + */ + public static final int INSTALL_FAILED_CONFLICTING_PROVIDER = -13; + + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the new package failed because the current SDK version is newer than that + * required by the package. + */ + public static final int INSTALL_FAILED_NEWER_SDK = -14; + + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the new package failed because it has specified that it is a test-only + * package and the caller has not supplied the {@link #INSTALL_ALLOW_TEST} + * flag. + */ + public static final int INSTALL_FAILED_TEST_ONLY = -15; + + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the package being installed contains native code, but none that is + * compatible with the the device's CPU_ABI. + */ + public static final int INSTALL_FAILED_CPU_ABI_INCOMPATIBLE = -16; + + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the new package uses a feature that is not available. + */ + public static final int INSTALL_FAILED_MISSING_FEATURE = -17; + + // ------ Errors related to sdcard + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * a secure container mount point couldn't be accessed on external media. + */ + public static final int INSTALL_FAILED_CONTAINER_ERROR = -18; + + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the new package couldn't be installed in the specified install location. + */ + public static final int INSTALL_FAILED_INVALID_INSTALL_LOCATION = -19; + + /** + * Installation return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the new package couldn't be installed in the specified install location + * because the media is not available. + */ + public static final int INSTALL_FAILED_MEDIA_UNAVAILABLE = -20; + + /** + * Installation parse return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the parser was given a path that is not a file, or does not end with the + * expected '.apk' extension. + */ + public static final int INSTALL_PARSE_FAILED_NOT_APK = -100; + + /** + * Installation parse return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the parser was unable to retrieve the AndroidManifest.xml file. + */ + public static final int INSTALL_PARSE_FAILED_BAD_MANIFEST = -101; + + /** + * Installation parse return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the parser encountered an unexpected exception. + */ + public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102; + + /** + * Installation parse return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the parser did not find any certificates in the .apk. + */ + public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103; + + /** + * Installation parse return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the parser found inconsistent certificates on the files in the .apk. + */ + public static final int INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES = -104; + + /** + * Installation parse return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the parser encountered a CertificateEncodingException in one of the files + * in the .apk. + */ + public static final int INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING = -105; + + /** + * Installation parse return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the parser encountered a bad or missing package name in the manifest. + */ + public static final int INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME = -106; + + /** + * Installation parse return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the parser encountered a bad shared user id name in the manifest. + */ + public static final int INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID = -107; + + /** + * Installation parse return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the parser encountered some structural problem in the manifest. + */ + public static final int INSTALL_PARSE_FAILED_MANIFEST_MALFORMED = -108; + + /** + * Installation parse return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the parser did not find any actionable tags (instrumentation or + * application) in the manifest. + */ + public static final int INSTALL_PARSE_FAILED_MANIFEST_EMPTY = -109; + + /** + * Installation failed return code: this is passed to the + * {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the system failed to install the package because of system issues. + */ + public static final int INSTALL_FAILED_INTERNAL_ERROR = -110; + + /** + * Return code for when package deletion succeeds. This is passed to the + * {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system + * succeeded in deleting the package. + */ + public static final int DELETE_SUCCEEDED = 1; + + /** + * Deletion failed return code: this is passed to the + * {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system + * failed to delete the package for an unspecified reason. + */ + public static final int DELETE_FAILED_INTERNAL_ERROR = -1; + + /** + * Deletion failed return code: this is passed to the + * {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system + * failed to delete the package because it is the active DevicePolicy + * manager. + */ + public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2; + + /** + * Deletion failed return code: this is passed to the + * {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system + * failed to delete the package since the user is restricted. + */ + public static final int DELETE_FAILED_USER_RESTRICTED = -3; +}