From 2ae83e87855bd0b390a94bf4ac0d517e671142eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sat, 19 Oct 2013 19:48:36 +0200 Subject: [PATCH 01/30] Implements unattended install/delete of packages using Android internal APIs. This works if F-Droid is a system app, e.g., by preinstalling it on an Android rom --- AndroidManifest.xml | 4 + lint.xml | 5 + .../content/pm/IPackageDeleteObserver.java | 49 ++ .../content/pm/IPackageInstallObserver.java | 49 ++ src/org/fdroid/fdroid/AppDetails.java | 113 +++-- src/org/fdroid/fdroid/InstallManager.java | 156 +++++++ .../fdroid/fdroid/InstallSystemManager.java | 442 ++++++++++++++++++ 7 files changed, 781 insertions(+), 37 deletions(-) create mode 100644 lint.xml create mode 100644 src/android/content/pm/IPackageDeleteObserver.java create mode 100644 src/android/content/pm/IPackageInstallObserver.java create mode 100644 src/org/fdroid/fdroid/InstallManager.java create mode 100644 src/org/fdroid/fdroid/InstallSystemManager.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 69f65a75e..69b409bc2 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -24,6 +24,10 @@ + + + + + + + + diff --git a/src/android/content/pm/IPackageDeleteObserver.java b/src/android/content/pm/IPackageDeleteObserver.java new file mode 100644 index 000000000..88b83a553 --- /dev/null +++ b/src/android/content/pm/IPackageDeleteObserver.java @@ -0,0 +1,49 @@ +/* + * 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 android.content.pm; + +/** + * Just a non-working implementation of this Stub to satisfy compiler! + */ +public interface IPackageDeleteObserver extends android.os.IInterface { + + public abstract static class Stub extends android.os.Binder implements + android.content.pm.IPackageDeleteObserver { + public Stub() { + throw new RuntimeException("Stub!"); + } + + public static android.content.pm.IPackageDeleteObserver asInterface(android.os.IBinder obj) { + throw new RuntimeException("Stub!"); + } + + public android.os.IBinder asBinder() { + throw new RuntimeException("Stub!"); + } + + public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, + int flags) throws android.os.RemoteException { + throw new RuntimeException("Stub!"); + } + } + + public abstract void packageDeleted(java.lang.String packageName, int returnCode) + throws android.os.RemoteException; +} \ No newline at end of file diff --git a/src/android/content/pm/IPackageInstallObserver.java b/src/android/content/pm/IPackageInstallObserver.java new file mode 100644 index 000000000..f81211404 --- /dev/null +++ b/src/android/content/pm/IPackageInstallObserver.java @@ -0,0 +1,49 @@ +/* + * 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 android.content.pm; + +/** + * Just a non-working implementation of this Stub to satisfy compiler! + */ +public interface IPackageInstallObserver extends android.os.IInterface { + + public abstract static class Stub extends android.os.Binder implements + android.content.pm.IPackageInstallObserver { + public Stub() { + throw new RuntimeException("Stub!"); + } + + public static android.content.pm.IPackageInstallObserver asInterface(android.os.IBinder obj) { + throw new RuntimeException("Stub!"); + } + + public android.os.IBinder asBinder() { + throw new RuntimeException("Stub!"); + } + + public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, + int flags) throws android.os.RemoteException { + throw new RuntimeException("Stub!"); + } + } + + public abstract void packageInstalled(java.lang.String packageName, int returnCode) + throws android.os.RemoteException; +} \ No newline at end of file diff --git a/src/org/fdroid/fdroid/AppDetails.java b/src/org/fdroid/fdroid/AppDetails.java index 485b5fdaf..fe73e0b0f 100644 --- a/src/org/fdroid/fdroid/AppDetails.java +++ b/src/org/fdroid/fdroid/AppDetails.java @@ -62,7 +62,6 @@ import android.view.SubMenu; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; - import android.support.v4.app.NavUtils; import android.support.v4.view.MenuItemCompat; @@ -75,9 +74,6 @@ import com.nostra13.universalimageloader.core.ImageLoader; public class AppDetails extends ListActivity { - private static final int REQUEST_INSTALL = 0; - private static final int REQUEST_UNINSTALL = 1; - private class ApkListAdapter extends BaseAdapter { private List items; @@ -198,6 +194,7 @@ public class AppDetails extends ListActivity { View infoView; private Context mctx = this; + private InstallManager installManager; @Override protected void onCreate(Bundle savedInstanceState) { @@ -240,6 +237,8 @@ public class AppDetails extends ListActivity { setListAdapter(la); mPm = getPackageManager(); + installManager = new InstallManager(this, mPm, myInstallCallback); + // Get the preferences we're going to use in this Activity... AppDetails old = (AppDetails) getLastNonConfigurationInstance(); if (old != null) { @@ -861,30 +860,82 @@ public class AppDetails extends ListActivity { downloadHandler = new DownloadHandler(app.curApk, repoaddress, DB.getDataPath(this)); } + + private void installApk(File file, String id) { + installManager.installApk(file, id); + + ((FDroidApp) getApplication()).invalidateApp(id); + } private void removeApk(String id) { - PackageInfo pkginfo; - try { - pkginfo = mPm.getPackageInfo(id, 0); - } catch (NameNotFoundException e) { - Log.d("FDroid", "Couldn't find package " + id + " to uninstall."); - return; + installManager.removeApk(id); + + ((FDroidApp) getApplication()).invalidateApp(id); + } + + private InstallManager.InstallCallback myInstallCallback = new InstallManager.InstallCallback() { + + @Override + public void onPackageInstalled(int returnCode, boolean unattended) { + // TODO: check return code?! + if (downloadHandler != null) { + downloadHandler = null; + } + + PackageManagerCompat.setInstaller(mPm, app.id); + resetRequired = true; + + // TODO: Somehow the internal API needs time to update the package state. + // This needs to be done nicer! + if (unattended) { + Thread wait = new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(200); + } catch (InterruptedException e) { + } + runOnUiThread(new Runnable() { + @Override + public void run() { + Log.d("FDroid", "resume"); + onResume(); + } + }); + } + }); + wait.start(); + } } - Uri uri = Uri.fromParts("package", pkginfo.packageName, null); - Intent intent = new Intent(Intent.ACTION_DELETE, uri); - startActivityForResult(intent, REQUEST_UNINSTALL); - ((FDroidApp) getApplication()).invalidateApp(id); - } - - private void installApk(File file, String id) { - Intent intent = new Intent(); - intent.setAction(android.content.Intent.ACTION_VIEW); - intent.setDataAndType(Uri.parse("file://" + file.getPath()), - "application/vnd.android.package-archive"); - startActivityForResult(intent, REQUEST_INSTALL); - ((FDroidApp) getApplication()).invalidateApp(id); - } + @Override + public void onPackageDeleted(int returnCode, boolean unattended) { + // TODO: check return code?! + resetRequired = true; + + // TODO: Somehow the internal API needs time to update the package state. + // This needs to be done nicer! + if (unattended) { + Thread wait = new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(200); + } catch (InterruptedException e) { + } + runOnUiThread(new Runnable() { + @Override + public void run() { + Log.d("FDroid", "resume"); + onResume(); + } + }); + } + }); + wait.start(); + } + } + }; private void launchApk(String id) { Intent intent = mPm.getLaunchIntentForPackage(id); @@ -1030,19 +1081,7 @@ public class AppDetails extends ListActivity { @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case REQUEST_INSTALL: - if (downloadHandler != null) { - downloadHandler = null; - } - - PackageManagerCompat.setInstaller(mPm, app.id); - resetRequired = true; - break; - case REQUEST_UNINSTALL: - resetRequired = true; - break; - } + installManager.handleOnActivityResult(requestCode, resultCode, data); } @Override diff --git a/src/org/fdroid/fdroid/InstallManager.java b/src/org/fdroid/fdroid/InstallManager.java new file mode 100644 index 000000000..f1f07bbc1 --- /dev/null +++ b/src/org/fdroid/fdroid/InstallManager.java @@ -0,0 +1,156 @@ +/* + * 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.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.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"); + mActivity.startActivityForResult(intent, REQUEST_INSTALL); + } + + 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 new file mode 100644 index 000000000..ae9da00eb --- /dev/null +++ b/src/org/fdroid/fdroid/InstallSystemManager.java @@ -0,0 +1,442 @@ +/* + * 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 }); + } + +} From 9fd22c92ffc8ad95015663ed595186c0a44810a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Wed, 8 Jan 2014 15:36:20 +0100 Subject: [PATCH 02/30] Add super-fdroid TODO --- TODO.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 000000000..4c2478675 --- /dev/null +++ b/TODO.md @@ -0,0 +1,14 @@ +## TODO + +* Provide a way to make F-Droid a system app on rooted phones + +* Handle error codes returned by InstallSystemManager callback (like the + internal code for PackageInstaller does) + +* Show changed permissions when doing an unattended install + +* Handle ROM updates in some way to avoid being removed from /system + +## Links + +* [Original MR](https://gitorious.org/f-droid/fdroidclient/merge_requests/37) From 7451f00534521c17a096a1baf091893326152c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sat, 26 Apr 2014 02:01:26 +0200 Subject: [PATCH 03/30] Refactoring old code using an abstract class --- AndroidManifest.xml | 10 +- lint.xml | 3 - src/org/fdroid/fdroid/AppDetails.java | 36 +- src/org/fdroid/fdroid/InstallManager.java | 167 ------- .../fdroid/fdroid/InstallSystemManager.java | 442 ----------------- .../fdroid/installer/DefaultInstaller.java | 120 +++++ .../fdroid/fdroid/installer/Installer.java | 188 ++++++++ .../installer/SystemPermissionInstaller.java | 447 ++++++++++++++++++ 8 files changed, 785 insertions(+), 628 deletions(-) delete mode 100644 src/org/fdroid/fdroid/InstallManager.java delete mode 100644 src/org/fdroid/fdroid/InstallSystemManager.java create mode 100644 src/org/fdroid/fdroid/installer/DefaultInstaller.java create mode 100644 src/org/fdroid/fdroid/installer/Installer.java create mode 100644 src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java 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; +} From 732fb87944b24b2c940d8a598b042afb3e118414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sat, 26 Apr 2014 02:40:08 +0200 Subject: [PATCH 04/30] Add libsuperuser as external git submodule and project dependency --- .gitmodules | 4 ++++ ant-prepare.sh | 1 + build.gradle | 1 + extern/libsuperuser | 1 + project.properties | 1 + settings.gradle | 2 +- 6 files changed, 9 insertions(+), 1 deletion(-) create mode 160000 extern/libsuperuser diff --git a/.gitmodules b/.gitmodules index b54d0200a..3b769a41a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,7 @@ path = extern/AndroidPinning url = https://github.com/binaryparadox/AndroidPinning.git ignore = dirty +[submodule "extern/libsuperuser"] + path = extern/libsuperuser + url = https://github.com/dschuermann/libsuperuser.git + ignore = dirty diff --git a/ant-prepare.sh b/ant-prepare.sh index 299d005e9..3752c823e 100755 --- a/ant-prepare.sh +++ b/ant-prepare.sh @@ -3,6 +3,7 @@ android update lib-project --path extern/UniversalImageLoader/library android update lib-project --path extern/AndroidPinning android update lib-project --path extern/MemorizingTrustManager +android update lib-project --path extern/libsuperuser/libsuperuser android update project --path . --name F-Droid # technically optional, needed for the tests diff --git a/build.gradle b/build.gradle index e15c643a0..d70eda598 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,7 @@ dependencies { compile project(':extern:AndroidPinning') compile project(':extern:UniversalImageLoader:library') compile project(':extern:MemorizingTrustManager') + compile project(':extern:libsuperuser:libsuperuser') } project(':extern:UniversalImageLoader:library') { diff --git a/extern/libsuperuser b/extern/libsuperuser new file mode 160000 index 000000000..faffc4112 --- /dev/null +++ b/extern/libsuperuser @@ -0,0 +1 @@ +Subproject commit faffc41121b509b2b1b01d4ecac3f395e4adbee2 diff --git a/project.properties b/project.properties index d6e5f072f..ae8e81427 100644 --- a/project.properties +++ b/project.properties @@ -5,3 +5,4 @@ target=android-19 android.library.reference.1=extern/UniversalImageLoader/library android.library.reference.2=extern/MemorizingTrustManager android.library.reference.3=extern/AndroidPinning +android.library.reference.4=extern/libsuperuser/libsuperuser diff --git a/settings.gradle b/settings.gradle index 66e6ee88a..ed61ad01d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':extern:AndroidPinning', ':extern:UniversalImageLoader:library', ':extern:MemorizingTrustManager' +include ':extern:AndroidPinning', ':extern:UniversalImageLoader:library', ':extern:MemorizingTrustManager', ':extern:libsuperuser:libsuperuser' From 8f0828982241c884206fc64cf532354e75546c3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sat, 26 Apr 2014 13:43:51 +0200 Subject: [PATCH 05/30] AndroidManifest: Add superuser permission, cleanup --- AndroidManifest.xml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 955db353c..c36dca97a 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,9 +1,10 @@ + android:versionName="0.64-test" > - - + + + + + Date: Sun, 27 Apr 2014 18:56:47 +0200 Subject: [PATCH 06/30] Installation/Delete with root shell, add preference for root access, fixes to Installer classes --- res/values/strings.xml | 5 +- res/xml/preferences.xml | 6 +- src/org/fdroid/fdroid/AppDetails.java | 40 +--- src/org/fdroid/fdroid/Preferences.java | 8 +- .../fdroid/fdroid/PreferencesActivity.java | 7 +- .../fdroid/installer/DefaultInstaller.java | 6 +- .../fdroid/fdroid/installer/Installer.java | 51 +++-- .../fdroid/installer/RootInstaller.java | 203 ++++++++++++++++++ .../installer/SystemPermissionInstaller.java | 32 ++- 9 files changed, 298 insertions(+), 60 deletions(-) create mode 100644 src/org/fdroid/fdroid/installer/RootInstaller.java diff --git a/res/values/strings.xml b/res/values/strings.xml index b068d0ae9..5a75d1d49 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -28,7 +28,10 @@ Do not notify of any updates Update history Days to consider apps new or recent: %s - + Root access for app installations + Root access is used to install/delete/update applications + Do not request root access to install/delete/update applications + Search Results App Details No such app found diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 5ae1f7d10..cb331b4d9 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -3,7 +3,8 @@ + RootInstaller + boolean useRootInstaller = Preferences.get().useRootInstaller(); + if (useRootInstaller) { + try { + return new RootInstaller(activity, pm, callback); + } catch (AndroidNotCompatibleException e) { + Log.e(TAG, "Android not compatible with RootInstaller!", e); + } + } + // system permissions -> SystemPermissionInstaller if (hasSystemPermissions(activity, pm)) { Log.d(TAG, "system permissions -> SystemPermissionInstaller"); @@ -104,7 +123,7 @@ abstract public class Installer { } } - // try default installer + // Fallback -> DefaultInstaller try { Log.d(TAG, "try default installer"); @@ -122,6 +141,16 @@ abstract public class Installer { public static Installer getUnattendedInstaller(Context context, PackageManager pm, InstallerCallback callback) throws AndroidNotCompatibleException { + // if root installer has been activated in preferences -> RootInstaller + boolean useRootInstaller = Preferences.get().useRootInstaller(); + if (useRootInstaller) { + try { + return new RootInstaller(context, pm, callback); + } catch (AndroidNotCompatibleException e) { + Log.e(TAG, "Android not compatible with RootInstaller!", e); + } + } + if (hasSystemPermissions(context, pm)) { // we have system permissions! return new SystemPermissionInstaller(context, pm, callback); @@ -139,29 +168,13 @@ abstract public class Installer { 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) { + if (permissionsGranted) { 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()) { diff --git a/src/org/fdroid/fdroid/installer/RootInstaller.java b/src/org/fdroid/fdroid/installer/RootInstaller.java new file mode 100644 index 000000000..d57294140 --- /dev/null +++ b/src/org/fdroid/fdroid/installer/RootInstaller.java @@ -0,0 +1,203 @@ +/* + * 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.util.List; + +import eu.chainfire.libsuperuser.Shell; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; + +/** + * Installer using a root shell and "pm install", "pm uninstall" commands + */ +public class RootInstaller extends Installer { + + Shell.Interactive rootSession; + + public RootInstaller(Context context, PackageManager pm, InstallerCallback callback) + throws AndroidNotCompatibleException { + super(context, pm, callback); + } + + @Override + public void installPackage(final File apkFile) throws AndroidNotCompatibleException { + super.installPackage(apkFile); + + Shell.Builder shellBuilder = new Shell.Builder() + .useSU() + .setWantSTDERR(true) + .setWatchdogTimeout(5) + .setMinimalLogging(true); + + rootSession = shellBuilder.open(new Shell.OnCommandResultListener() { + + // Callback to report whether the shell was successfully + // started up + @Override + public void onCommandResult(int commandCode, int exitCode, List output) { + if (exitCode != Shell.OnCommandResultListener.SHELL_RUNNING) { + // TODO + // wrong uid + // Shell.OnCommandResultListener.SHELL_WRONG_UID + // exec failed + // Shell.OnCommandResultListener.SHELL_EXEC_FAILED + + // reportError("Error opening root shell: exitCode " + + // exitCode); + } else { + // Shell is up: send our first request + sendInstallCommand(apkFile); + } + } + }); + } + + @Override + public void deletePackage(final String packageName) throws AndroidNotCompatibleException { + super.deletePackage(packageName); + + Shell.Builder shellBuilder = new Shell.Builder() + .useSU() + .setWantSTDERR(true) + .setWatchdogTimeout(5) + .setMinimalLogging(true); + + rootSession = shellBuilder.open(new Shell.OnCommandResultListener() { + + // Callback to report whether the shell was successfully + // started up + @Override + public void onCommandResult(int commandCode, int exitCode, List output) { + if (exitCode != Shell.OnCommandResultListener.SHELL_RUNNING) { + // TODO + // wrong uid + // Shell.OnCommandResultListener.SHELL_WRONG_UID + // exec failed + // Shell.OnCommandResultListener.SHELL_EXEC_FAILED + + // reportError("Error opening root shell: exitCode " + + // exitCode); + } else { + // Shell is up: send our first request + sendDeleteCommand(packageName); + } + } + }); + + } + + @Override + public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) { + // no need to handle onActivityResult + return false; + } + + private void sendInstallCommand(File apkFile) { + rootSession.addCommand("pm install -r " + apkFile.getAbsolutePath(), 0, + new Shell.OnCommandResultListener() { + public void onCommandResult(int commandCode, int exitCode, List output) { + // close su shell + rootSession.close(); + + if (exitCode < 0) { + // reportError("Error executing commands: exitCode " + // + exitCode); + mCallback.onPackageInstalled(InstallerCallback.RETURN_CANCEL, true); + } else { + // wait until Android's internal PackageManger has + // received the new package state + Thread wait = new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + } + + mCallback.onPackageInstalled(InstallerCallback.RETURN_SUCCESS, + true); + } + }); + wait.start(); + } + } + }); + } + + private void sendDeleteCommand(String packageName) { + rootSession.addCommand("pm uninstall " + packageName, 0, + new Shell.OnCommandResultListener() { + public void onCommandResult(int commandCode, int exitCode, List output) { + // close su shell + rootSession.close(); + + if (exitCode < 0) { + // reportError("Error executing commands: exitCode " + // + exitCode); + mCallback.onPackageDeleted(InstallerCallback.RETURN_CANCEL, true); + } else { + // wait until Android's internal PackageManger has + // received the new package state + Thread wait = new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + } + + mCallback.onPackageDeleted(InstallerCallback.RETURN_SUCCESS, + true); + } + }); + wait.start(); + } + } + }); + } + + /** + * pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] [-s] [-f] [--algo + * --key --iv ] [--originating-uri + * ] [--referrer ] PATH + *

+ * pm install: installs a package to the system. + *

+ * Options:
+ * -l: install the package with FORWARD_LOCK.
+ * -r: reinstall an exisiting app, keeping its data.
+ * -t: allow test .apks to be installed.
+ * -i: specify the installer package name.
+ * -s: install package on sdcard.
+ * -f: install package on internal flash.
+ * -d: allow version code downgrade.
+ *

+ * pm uninstall [-k] PACKAGE + *

+ * pm uninstall: removes a package from the system. + *

+ * Options:
+ * -k: keep the data and cache directories around after package removal. + */ + +} diff --git a/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java b/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java index 207cb57b9..6cd07e080 100644 --- a/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java +++ b/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java @@ -82,7 +82,21 @@ public class SystemPermissionInstaller extends Installer { // TODO: propagate other return codes? if (returnCode == INSTALL_SUCCEEDED) { Log.d(TAG, "Install succeeded"); - mCallback.onPackageInstalled(InstallerCallback.RETURN_SUCCESS, true); + + // wait until Android's internal PackageManger has + // received the new package state + Thread wait = new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + } + + mCallback.onPackageInstalled(InstallerCallback.RETURN_SUCCESS, true); + } + }); + wait.start(); } else { Log.d(TAG, "Install failed: " + returnCode); mCallback.onPackageInstalled(InstallerCallback.RETURN_CANCEL, true); @@ -98,7 +112,21 @@ public class SystemPermissionInstaller extends Installer { // TODO: propagate other return codes? if (returnCode == DELETE_SUCCEEDED) { Log.d(TAG, "Delete succeeded"); - mCallback.onPackageDeleted(InstallerCallback.RETURN_SUCCESS, true); + + // wait until Android's internal PackageManger has + // received the new package state + Thread wait = new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + } + + mCallback.onPackageDeleted(InstallerCallback.RETURN_SUCCESS, true); + } + }); + wait.start(); } else { Log.d(TAG, "Delete failed: " + returnCode); mCallback.onPackageDeleted(InstallerCallback.RETURN_CANCEL, true); From c4e7e617b2a7d4887da7fa0e20932362c03eee50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 27 Apr 2014 19:59:28 +0200 Subject: [PATCH 07/30] Add root access preference --- res/values/strings.xml | 5 +- .../fdroid/fdroid/PreferencesActivity.java | 58 ++++++++++++++- .../fdroid/installer/CheckRootAsyncTask.java | 74 +++++++++++++++++++ .../fdroid/fdroid/installer/Installer.java | 2 +- 4 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 src/org/fdroid/fdroid/installer/CheckRootAsyncTask.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 5a75d1d49..e003e5f07 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -235,5 +235,8 @@ Security System Wallpaper - + + Root access + Requesting root access… + diff --git a/src/org/fdroid/fdroid/PreferencesActivity.java b/src/org/fdroid/fdroid/PreferencesActivity.java index 9e05c0303..7125dd34a 100644 --- a/src/org/fdroid/fdroid/PreferencesActivity.java +++ b/src/org/fdroid/fdroid/PreferencesActivity.java @@ -20,6 +20,7 @@ package org.fdroid.fdroid; import android.os.Bundle; import android.preference.Preference; +import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceActivity; import android.preference.CheckBoxPreference; import android.preference.EditTextPreference; @@ -27,15 +28,16 @@ import android.preference.ListPreference; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.view.MenuItem; - import android.support.v4.app.NavUtils; import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.compat.ActionBarCompat; +import org.fdroid.fdroid.installer.CheckRootAsyncTask; +import org.fdroid.fdroid.installer.CheckRootAsyncTask.CheckRootCallback; public class PreferencesActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener { - + public static final int RESULT_RESTART = 4; private int result = 0; @@ -155,6 +157,56 @@ public class PreferencesActivity extends PreferenceActivity implements } } + + /** + * Initializes RootInstaller preference. This method ensures that the preference can only be enabled + * when the user grants access to F-Droid. + */ + protected void initRootInstallerPreference() { + CheckBoxPreference pref = (CheckBoxPreference)findPreference(Preferences.PREF_ROOT_INSTALLER); + + // we are handling persistence ourself! + pref.setPersistent(false); + + pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { + + @Override + public boolean onPreferenceClick(Preference preference) { + final CheckBoxPreference pref = (CheckBoxPreference) preference; + + if (pref.isChecked()) { + CheckRootAsyncTask checkTask = new CheckRootAsyncTask(PreferencesActivity.this, new CheckRootCallback() { + + @Override + public void onRootCheck(boolean rootGranted) { + if (rootGranted) { + // root access granted + SharedPreferences.Editor editor = pref.getSharedPreferences().edit(); + editor.putBoolean(Preferences.PREF_ROOT_INSTALLER, true); + editor.commit(); + pref.setChecked(true); + } else { + // root access disallowed + SharedPreferences.Editor editor = pref.getSharedPreferences().edit(); + editor.putBoolean(Preferences.PREF_ROOT_INSTALLER, false); + editor.commit(); + pref.setChecked(false); + } + } + }); + checkTask.execute(); + } else { + SharedPreferences.Editor editor = pref.getSharedPreferences().edit(); + editor.putBoolean(Preferences.PREF_ROOT_INSTALLER, false); + editor.commit(); + pref.setChecked(false); + } + + return true; + } + }); + + } @Override protected void onResume() { @@ -166,6 +218,8 @@ public class PreferencesActivity extends PreferenceActivity implements for (String key : summariesToUpdate) { updateSummary(key, false); } + + initRootInstallerPreference(); } @Override diff --git a/src/org/fdroid/fdroid/installer/CheckRootAsyncTask.java b/src/org/fdroid/fdroid/installer/CheckRootAsyncTask.java new file mode 100644 index 000000000..c0254905f --- /dev/null +++ b/src/org/fdroid/fdroid/installer/CheckRootAsyncTask.java @@ -0,0 +1,74 @@ +/* + * 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 org.fdroid.fdroid.R; + +import eu.chainfire.libsuperuser.Shell; +import android.app.ProgressDialog; +import android.content.Context; +import android.os.AsyncTask; + +public class CheckRootAsyncTask extends AsyncTask { + + ProgressDialog mDialog; + Context mContext; + CheckRootCallback mCallback; + + public interface CheckRootCallback { + public void onRootCheck(boolean rootGranted); + } + + public CheckRootAsyncTask(Context context, CheckRootCallback callback) { + super(); + this.mContext = context; + this.mCallback = callback; + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + + mDialog = new ProgressDialog(mContext); + mDialog.setTitle(R.string.requesting_root_access_title); + mDialog.setMessage(mContext.getString(R.string.requesting_root_access_body)); + mDialog.setIndeterminate(true); + mDialog.setCancelable(false); + mDialog.show(); + } + + @Override + protected Boolean doInBackground(Void... params) { + + boolean suAvailable = Shell.SU.available(); + + return suAvailable; + } + + @Override + protected void onPostExecute(Boolean result) { + super.onPostExecute(result); + + mDialog.dismiss(); + + mCallback.onRootCheck(result); + } + +} diff --git a/src/org/fdroid/fdroid/installer/Installer.java b/src/org/fdroid/fdroid/installer/Installer.java index 54d0bd171..f27857f81 100644 --- a/src/org/fdroid/fdroid/installer/Installer.java +++ b/src/org/fdroid/fdroid/installer/Installer.java @@ -93,7 +93,7 @@ abstract public class Installer { * Creates a new Installer for installing/deleting processes starting from * an Activity * - * @param context + * @param activity * @param pm * @param callback * @return From 36b269cd19f41a12723d67ab114bcc48eb84f303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 27 Apr 2014 20:02:03 +0200 Subject: [PATCH 08/30] Clarify comment in prefs --- src/org/fdroid/fdroid/PreferencesActivity.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/fdroid/fdroid/PreferencesActivity.java b/src/org/fdroid/fdroid/PreferencesActivity.java index 7125dd34a..61fd2cf15 100644 --- a/src/org/fdroid/fdroid/PreferencesActivity.java +++ b/src/org/fdroid/fdroid/PreferencesActivity.java @@ -159,8 +159,8 @@ public class PreferencesActivity extends PreferenceActivity implements } /** - * Initializes RootInstaller preference. This method ensures that the preference can only be enabled - * when the user grants access to F-Droid. + * Initializes RootInstaller preference. This method ensures that the preference can only be checked and persisted + * when the user grants root access for F-Droid. */ protected void initRootInstallerPreference() { CheckBoxPreference pref = (CheckBoxPreference)findPreference(Preferences.PREF_ROOT_INSTALLER); From fbc47c1428ea099b8ec2077fd5d85778f04c45b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 27 Apr 2014 20:11:36 +0200 Subject: [PATCH 09/30] Show dialog if root access was denied --- res/values/strings.xml | 2 ++ src/org/fdroid/fdroid/PreferencesActivity.java | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index e003e5f07..560c9b42e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -238,5 +238,7 @@ Root access Requesting root access… + Root access denied + Either your Android device is not rooted or you have denied root access for F-Droid. diff --git a/src/org/fdroid/fdroid/PreferencesActivity.java b/src/org/fdroid/fdroid/PreferencesActivity.java index 61fd2cf15..7847f7bf1 100644 --- a/src/org/fdroid/fdroid/PreferencesActivity.java +++ b/src/org/fdroid/fdroid/PreferencesActivity.java @@ -25,6 +25,8 @@ import android.preference.PreferenceActivity; import android.preference.CheckBoxPreference; import android.preference.EditTextPreference; import android.preference.ListPreference; +import android.app.AlertDialog; +import android.app.ProgressDialog; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.view.MenuItem; @@ -186,11 +188,17 @@ public class PreferencesActivity extends PreferenceActivity implements editor.commit(); pref.setChecked(true); } else { - // root access disallowed + // root access denied SharedPreferences.Editor editor = pref.getSharedPreferences().edit(); editor.putBoolean(Preferences.PREF_ROOT_INSTALLER, false); editor.commit(); pref.setChecked(false); + + AlertDialog.Builder alertBuilder = new AlertDialog.Builder(PreferencesActivity.this); + alertBuilder.setTitle(R.string.root_access_denied_title); + alertBuilder.setMessage(PreferencesActivity.this.getString(R.string.root_access_denied_body)); + alertBuilder.setNeutralButton(android.R.string.ok, null); + alertBuilder.create().show(); } } }); From 2399f954ed1fb71dba7932dd159bdea15e0ff6ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 27 Apr 2014 20:42:45 +0200 Subject: [PATCH 10/30] Installer: Starting with better error handling, new callback design --- src/org/fdroid/fdroid/AppDetails.java | 69 +++++++++++-------- .../fdroid/fdroid/PreferencesActivity.java | 1 - .../fdroid/installer/DefaultInstaller.java | 8 +-- .../fdroid/fdroid/installer/Installer.java | 11 +-- .../fdroid/installer/RootInstaller.java | 28 ++++---- .../installer/SystemPermissionInstaller.java | 8 +-- 6 files changed, 69 insertions(+), 56 deletions(-) diff --git a/src/org/fdroid/fdroid/AppDetails.java b/src/org/fdroid/fdroid/AppDetails.java index 5a96ffd65..14806aacb 100644 --- a/src/org/fdroid/fdroid/AppDetails.java +++ b/src/org/fdroid/fdroid/AppDetails.java @@ -57,6 +57,7 @@ import android.view.MenuItem; import android.view.SubMenu; import android.view.View; import android.view.ViewGroup; +import android.view.Window; import android.graphics.Bitmap; import com.nostra13.universalimageloader.core.DisplayImageOptions; @@ -260,7 +261,8 @@ public class AppDetails extends ListActivity { @Override protected void onCreate(Bundle savedInstanceState) { - + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + fdroidApp = ((FDroidApp) getApplication()); fdroidApp.applyTheme(this); @@ -353,6 +355,7 @@ public class AppDetails extends ListActivity { @Override protected void onResume() { + Log.d(TAG, "onresume"); super.onResume(); if (resetRequired) { if (!reset()) { @@ -930,6 +933,8 @@ public class AppDetails extends ListActivity { } private void installApk(File file, String packageName) { + setProgressBarIndeterminateVisibility(true); + try { installer.installPackage(file); } catch (AndroidNotCompatibleException e) { @@ -940,6 +945,8 @@ public class AppDetails extends ListActivity { } private void removeApk(String packageName) { + setProgressBarIndeterminateVisibility(true); + try { installer.deletePackage(packageName); } catch (AndroidNotCompatibleException e) { @@ -960,40 +967,46 @@ public class AppDetails extends ListActivity { private Installer.InstallerCallback myInstallerCallback = new Installer.InstallerCallback() { @Override - public void onPackageInstalled(int returnCode, boolean unattended) { - // TODO: check return code?! - if (downloadHandler != null) { - downloadHandler = null; - } - - PackageManagerCompat.setInstaller(mPm, app.id); + public void onSuccess(int operation, boolean unattended) { resetRequired = true; - // if unattended, onResume is not execute automatically - if (unattended) { - runOnUiThread(new Runnable() { - @Override - public void run() { - onResume(); - } - }); + if (operation == Installer.InstallerCallback.OPERATION_INSTALL) { + if (downloadHandler != null) { + downloadHandler = null; + } + + PackageManagerCompat.setInstaller(mPm, app.id); } + + // if unattended, onResume is not execute automatically +// if (unattended) { + runOnUiThread(new Runnable() { + @Override + public void run() { + onResume(); + setProgressBarIndeterminateVisibility(false); + } + }); +// } + } @Override - public void onPackageDeleted(int returnCode, boolean unattended) { - // TODO: check return code?! - resetRequired = true; + public void onError(int operation, boolean unattended, final String reason) { + runOnUiThread(new Runnable() { + @Override + public void run() { + setProgressBarIndeterminateVisibility(false); + + // TODO + AlertDialog.Builder alertBuilder = new AlertDialog.Builder(AppDetails.this); + alertBuilder.setTitle("Error"); + alertBuilder.setMessage(reason); + alertBuilder.setNeutralButton(android.R.string.ok, null); + alertBuilder.create().show(); + } + }); - // if unattended, onResume is not execute automatically - if (unattended) { - runOnUiThread(new Runnable() { - @Override - public void run() { - onResume(); - } - }); - } } }; diff --git a/src/org/fdroid/fdroid/PreferencesActivity.java b/src/org/fdroid/fdroid/PreferencesActivity.java index 7847f7bf1..7f74c145e 100644 --- a/src/org/fdroid/fdroid/PreferencesActivity.java +++ b/src/org/fdroid/fdroid/PreferencesActivity.java @@ -26,7 +26,6 @@ import android.preference.CheckBoxPreference; import android.preference.EditTextPreference; import android.preference.ListPreference; import android.app.AlertDialog; -import android.app.ProgressDialog; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.view.MenuItem; diff --git a/src/org/fdroid/fdroid/installer/DefaultInstaller.java b/src/org/fdroid/fdroid/installer/DefaultInstaller.java index 5f6c558fe..ddb38caf7 100644 --- a/src/org/fdroid/fdroid/installer/DefaultInstaller.java +++ b/src/org/fdroid/fdroid/installer/DefaultInstaller.java @@ -98,17 +98,17 @@ public class DefaultInstaller extends Installer { switch (requestCode) { case REQUEST_CODE_INSTALL: if (resultCode == Activity.RESULT_OK) { - mCallback.onPackageInstalled(InstallerCallback.RETURN_SUCCESS, false); + mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL, false); } else { - mCallback.onPackageInstalled(InstallerCallback.RETURN_CANCEL, false); + mCallback.onError(InstallerCallback.OPERATION_INSTALL, false, "todo"); } return true; case REQUEST_CODE_DELETE: if (resultCode == Activity.RESULT_OK) { - mCallback.onPackageDeleted(InstallerCallback.RETURN_SUCCESS, false); + mCallback.onSuccess(InstallerCallback.OPERATION_DELETE, false); } else { - mCallback.onPackageDeleted(InstallerCallback.RETURN_CANCEL, false); + mCallback.onError(InstallerCallback.OPERATION_DELETE, false, "todo"); } return true; diff --git a/src/org/fdroid/fdroid/installer/Installer.java b/src/org/fdroid/fdroid/installer/Installer.java index f27857f81..ec3a97e33 100644 --- a/src/org/fdroid/fdroid/installer/Installer.java +++ b/src/org/fdroid/fdroid/installer/Installer.java @@ -74,12 +74,13 @@ abstract public class Installer { */ public interface InstallerCallback { - public static final int RETURN_SUCCESS = 1; - public static final int RETURN_CANCEL = 0; + public static final int OPERATION_GENERIC_ERROR = 0; + public static final int OPERATION_INSTALL = 1; + public static final int OPERATION_DELETE = 2; - public void onPackageInstalled(int returnCode, boolean unattended); - - public void onPackageDeleted(int returnCode, boolean unattended); + public void onSuccess(int operation, boolean unattended); + + public void onError(int operation, boolean unattended, String reason); } public Installer(Context context, PackageManager pm, InstallerCallback callback) diff --git a/src/org/fdroid/fdroid/installer/RootInstaller.java b/src/org/fdroid/fdroid/installer/RootInstaller.java index d57294140..2d66a3335 100644 --- a/src/org/fdroid/fdroid/installer/RootInstaller.java +++ b/src/org/fdroid/fdroid/installer/RootInstaller.java @@ -62,8 +62,9 @@ public class RootInstaller extends Installer { // exec failed // Shell.OnCommandResultListener.SHELL_EXEC_FAILED - // reportError("Error opening root shell: exitCode " + - // exitCode); + // TODO + mCallback.onError(InstallerCallback.OPERATION_GENERIC_ERROR, true, + "Error opening root shell with exitCode " + exitCode); } else { // Shell is up: send our first request sendInstallCommand(apkFile); @@ -95,8 +96,9 @@ public class RootInstaller extends Installer { // exec failed // Shell.OnCommandResultListener.SHELL_EXEC_FAILED - // reportError("Error opening root shell: exitCode " + - // exitCode); + // TODO + mCallback.onError(InstallerCallback.OPERATION_GENERIC_ERROR, true, + "Error opening root shell with exitCode " + exitCode); } else { // Shell is up: send our first request sendDeleteCommand(packageName); @@ -120,9 +122,9 @@ public class RootInstaller extends Installer { rootSession.close(); if (exitCode < 0) { - // reportError("Error executing commands: exitCode " - // + exitCode); - mCallback.onPackageInstalled(InstallerCallback.RETURN_CANCEL, true); + // TODO + mCallback.onError(InstallerCallback.OPERATION_INSTALL, true, + "Install failed with exit code " + exitCode); } else { // wait until Android's internal PackageManger has // received the new package state @@ -134,8 +136,7 @@ public class RootInstaller extends Installer { } catch (InterruptedException e) { } - mCallback.onPackageInstalled(InstallerCallback.RETURN_SUCCESS, - true); + mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL, true); } }); wait.start(); @@ -152,9 +153,9 @@ public class RootInstaller extends Installer { rootSession.close(); if (exitCode < 0) { - // reportError("Error executing commands: exitCode " - // + exitCode); - mCallback.onPackageDeleted(InstallerCallback.RETURN_CANCEL, true); + // TODO + mCallback.onError(InstallerCallback.OPERATION_DELETE, true, + "Delete failed with exit code " + exitCode); } else { // wait until Android's internal PackageManger has // received the new package state @@ -166,8 +167,7 @@ public class RootInstaller extends Installer { } catch (InterruptedException e) { } - mCallback.onPackageDeleted(InstallerCallback.RETURN_SUCCESS, - true); + mCallback.onSuccess(InstallerCallback.OPERATION_DELETE, true); } }); wait.start(); diff --git a/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java b/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java index 6cd07e080..51da59fa5 100644 --- a/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java +++ b/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java @@ -93,13 +93,13 @@ public class SystemPermissionInstaller extends Installer { } catch (InterruptedException e) { } - mCallback.onPackageInstalled(InstallerCallback.RETURN_SUCCESS, true); + mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL, true); } }); wait.start(); } else { Log.d(TAG, "Install failed: " + returnCode); - mCallback.onPackageInstalled(InstallerCallback.RETURN_CANCEL, true); + mCallback.onError(InstallerCallback.OPERATION_INSTALL, true, "todo"); } } } @@ -123,13 +123,13 @@ public class SystemPermissionInstaller extends Installer { } catch (InterruptedException e) { } - mCallback.onPackageDeleted(InstallerCallback.RETURN_SUCCESS, true); + mCallback.onSuccess(InstallerCallback.OPERATION_DELETE, true); } }); wait.start(); } else { Log.d(TAG, "Delete failed: " + returnCode); - mCallback.onPackageDeleted(InstallerCallback.RETURN_CANCEL, true); + mCallback.onError(InstallerCallback.OPERATION_DELETE, true, "todo"); } } } From b3ca9154594a628393cf3587e9874d63d913a3fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 27 Apr 2014 21:40:22 +0200 Subject: [PATCH 11/30] RootInstaller: multiple apks --- .../fdroid/fdroid/installer/Installer.java | 16 ++- .../fdroid/installer/RootInstaller.java | 105 ++++++++++++++---- 2 files changed, 99 insertions(+), 22 deletions(-) diff --git a/src/org/fdroid/fdroid/installer/Installer.java b/src/org/fdroid/fdroid/installer/Installer.java index ec3a97e33..bc0cc549c 100644 --- a/src/org/fdroid/fdroid/installer/Installer.java +++ b/src/org/fdroid/fdroid/installer/Installer.java @@ -20,6 +20,7 @@ package org.fdroid.fdroid.installer; import java.io.File; +import java.util.List; import org.fdroid.fdroid.Preferences; @@ -74,12 +75,11 @@ abstract public class Installer { */ public interface InstallerCallback { - public static final int OPERATION_GENERIC_ERROR = 0; public static final int OPERATION_INSTALL = 1; public static final int OPERATION_DELETE = 2; public void onSuccess(int operation, boolean unattended); - + public void onError(int operation, boolean unattended, String reason); } @@ -186,6 +186,18 @@ abstract public class Installer { // extended class now actually installs the package } + public void installPackage(List apkFiles) throws AndroidNotCompatibleException { + // check if files exist... + for (File apkFile : apkFiles) { + 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 { diff --git a/src/org/fdroid/fdroid/installer/RootInstaller.java b/src/org/fdroid/fdroid/installer/RootInstaller.java index 2d66a3335..14697d76d 100644 --- a/src/org/fdroid/fdroid/installer/RootInstaller.java +++ b/src/org/fdroid/fdroid/installer/RootInstaller.java @@ -20,6 +20,7 @@ package org.fdroid.fdroid.installer; import java.io.File; +import java.util.ArrayList; import java.util.List; import eu.chainfire.libsuperuser.Shell; @@ -39,17 +40,21 @@ public class RootInstaller extends Installer { super(context, pm, callback); } - @Override - public void installPackage(final File apkFile) throws AndroidNotCompatibleException { - super.installPackage(apkFile); - + private Shell.Builder createShellBuilder() { Shell.Builder shellBuilder = new Shell.Builder() .useSU() .setWantSTDERR(true) .setWatchdogTimeout(5) .setMinimalLogging(true); - rootSession = shellBuilder.open(new Shell.OnCommandResultListener() { + return shellBuilder; + } + + @Override + public void installPackage(final File apkFile) throws AndroidNotCompatibleException { + super.installPackage(apkFile); + + rootSession = createShellBuilder().open(new Shell.OnCommandResultListener() { // Callback to report whether the shell was successfully // started up @@ -63,11 +68,37 @@ public class RootInstaller extends Installer { // Shell.OnCommandResultListener.SHELL_EXEC_FAILED // TODO - mCallback.onError(InstallerCallback.OPERATION_GENERIC_ERROR, true, + mCallback.onError(InstallerCallback.OPERATION_INSTALL, true, "Error opening root shell with exitCode " + exitCode); } else { - // Shell is up: send our first request - sendInstallCommand(apkFile); + addInstallCommand(apkFile); + } + } + }); + } + + @Override + public void installPackage(final List apkFiles) throws AndroidNotCompatibleException { + super.installPackage(apkFiles); + + rootSession = createShellBuilder().open(new Shell.OnCommandResultListener() { + + // Callback to report whether the shell was successfully + // started up + @Override + public void onCommandResult(int commandCode, int exitCode, List output) { + if (exitCode != Shell.OnCommandResultListener.SHELL_RUNNING) { + // TODO + // wrong uid + // Shell.OnCommandResultListener.SHELL_WRONG_UID + // exec failed + // Shell.OnCommandResultListener.SHELL_EXEC_FAILED + + // TODO + mCallback.onError(InstallerCallback.OPERATION_INSTALL, true, + "Error opening root shell with exitCode " + exitCode); + } else { + addInstallCommand(apkFiles); } } }); @@ -77,13 +108,7 @@ public class RootInstaller extends Installer { public void deletePackage(final String packageName) throws AndroidNotCompatibleException { super.deletePackage(packageName); - Shell.Builder shellBuilder = new Shell.Builder() - .useSU() - .setWantSTDERR(true) - .setWatchdogTimeout(5) - .setMinimalLogging(true); - - rootSession = shellBuilder.open(new Shell.OnCommandResultListener() { + rootSession = createShellBuilder().open(new Shell.OnCommandResultListener() { // Callback to report whether the shell was successfully // started up @@ -97,11 +122,10 @@ public class RootInstaller extends Installer { // Shell.OnCommandResultListener.SHELL_EXEC_FAILED // TODO - mCallback.onError(InstallerCallback.OPERATION_GENERIC_ERROR, true, + mCallback.onError(InstallerCallback.OPERATION_DELETE, true, "Error opening root shell with exitCode " + exitCode); } else { - // Shell is up: send our first request - sendDeleteCommand(packageName); + addDeleteCommand(packageName); } } }); @@ -114,7 +138,48 @@ public class RootInstaller extends Installer { return false; } - private void sendInstallCommand(File apkFile) { + private void addInstallCommand(List apkFiles) { + ArrayList commands = new ArrayList(); + String pm = "pm install -r "; + for (File apkFile : apkFiles) { + commands.add(pm + apkFile.getAbsolutePath()); + } + + rootSession.addCommand(commands, 0, + new Shell.OnCommandResultListener() { + public void onCommandResult(int commandCode, int exitCode, + List output) { + // close su shell + rootSession.close(); + + if (exitCode < 0) { + // TODO + mCallback.onError(InstallerCallback.OPERATION_INSTALL, true, + "Install failed with exit code " + exitCode); + } else { + // wait until Android's internal PackageManger + // has received the new package state + Thread wait = new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + } + + mCallback.onSuccess( + InstallerCallback.OPERATION_INSTALL, + true); + } + }); + wait.start(); + } + } + }); + + } + + private void addInstallCommand(File apkFile) { rootSession.addCommand("pm install -r " + apkFile.getAbsolutePath(), 0, new Shell.OnCommandResultListener() { public void onCommandResult(int commandCode, int exitCode, List output) { @@ -145,7 +210,7 @@ public class RootInstaller extends Installer { }); } - private void sendDeleteCommand(String packageName) { + private void addDeleteCommand(String packageName) { rootSession.addCommand("pm uninstall " + packageName, 0, new Shell.OnCommandResultListener() { public void onCommandResult(int commandCode, int exitCode, List output) { From d7203535e775ed662a30206256325973da703c35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 27 Apr 2014 22:22:21 +0200 Subject: [PATCH 12/30] Add update all button (no functionality yet) --- res/values/strings.xml | 1 + .../fdroid/installer/DefaultInstaller.java | 5 + .../fdroid/fdroid/installer/Installer.java | 2 + .../fdroid/installer/RootInstaller.java | 5 + .../installer/SystemPermissionInstaller.java | 5 + .../fragments/CanUpdateAppsFragment.java | 136 ++++++++++++++++++ 6 files changed, 154 insertions(+) diff --git a/res/values/strings.xml b/res/values/strings.xml index 560c9b42e..143888446 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -240,5 +240,6 @@ Requesting root access… Root access denied Either your Android device is not rooted or you have denied root access for F-Droid. + Update all diff --git a/src/org/fdroid/fdroid/installer/DefaultInstaller.java b/src/org/fdroid/fdroid/installer/DefaultInstaller.java index ddb38caf7..9c1a9af91 100644 --- a/src/org/fdroid/fdroid/installer/DefaultInstaller.java +++ b/src/org/fdroid/fdroid/installer/DefaultInstaller.java @@ -117,4 +117,9 @@ public class DefaultInstaller extends Installer { } } + @Override + public boolean supportsUnattendedOperations() { + return false; + } + } diff --git a/src/org/fdroid/fdroid/installer/Installer.java b/src/org/fdroid/fdroid/installer/Installer.java index bc0cc549c..1a4917386 100644 --- a/src/org/fdroid/fdroid/installer/Installer.java +++ b/src/org/fdroid/fdroid/installer/Installer.java @@ -211,4 +211,6 @@ abstract public class Installer { } public abstract boolean handleOnActivityResult(int requestCode, int resultCode, Intent data); + + public abstract boolean supportsUnattendedOperations(); } diff --git a/src/org/fdroid/fdroid/installer/RootInstaller.java b/src/org/fdroid/fdroid/installer/RootInstaller.java index 14697d76d..3c62d71a1 100644 --- a/src/org/fdroid/fdroid/installer/RootInstaller.java +++ b/src/org/fdroid/fdroid/installer/RootInstaller.java @@ -241,6 +241,11 @@ public class RootInstaller extends Installer { }); } + @Override + public boolean supportsUnattendedOperations() { + return true; + } + /** * pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] [-s] [-f] [--algo * --key --iv ] [--originating-uri diff --git a/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java b/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java index 51da59fa5..6ae0bb377 100644 --- a/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java +++ b/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java @@ -167,6 +167,11 @@ public class SystemPermissionInstaller extends Installer { return false; } + @Override + public boolean supportsUnattendedOperations() { + return false; + } + public final int INSTALL_REPLACE_EXISTING = 2; /** diff --git a/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java b/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java index d42cbd6c2..9a7cb1c65 100644 --- a/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java +++ b/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java @@ -1,13 +1,39 @@ + package org.fdroid.fdroid.views.fragments; +import android.content.Context; import android.net.Uri; +import android.os.Bundle; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.ProgressBar; +import android.widget.TextView; + import org.fdroid.fdroid.R; import org.fdroid.fdroid.data.AppProvider; +import org.fdroid.fdroid.installer.Installer; import org.fdroid.fdroid.views.AppListAdapter; import org.fdroid.fdroid.views.CanUpdateAppListAdapter; public class CanUpdateAppsFragment extends AppListFragment { + // copied from ListFragment + static final int INTERNAL_EMPTY_ID = 0x00ff0001; + static final int INTERNAL_PROGRESS_CONTAINER_ID = 0x00ff0002; + static final int INTERNAL_LIST_CONTAINER_ID = 0x00ff0003; + // added for update button + static final int UPDATE_ALL_BUTTON_ID = 0x00ff0004; + + private Button mUpdateAllButton; + private Installer mInstaller; + @Override protected AppListAdapter getAppListAdapter() { return new CanUpdateAppListAdapter(getActivity(), null); @@ -23,4 +49,114 @@ public class CanUpdateAppsFragment extends AppListFragment { return AppProvider.getCanUpdateUri(); } + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mInstaller = Installer.getActivityInstaller(getActivity(), getActivity() + .getPackageManager(), null); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mUpdateAllButton.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + // TODO + + } + }); + } + + // TODO: not really called again after coming back from preference + @Override + public void onResume() { + super.onResume(); + + if (mInstaller.supportsUnattendedOperations()) { + mUpdateAllButton.setVisibility(View.VISIBLE); + } else { + mUpdateAllButton.setVisibility(View.GONE); + } + } + + /** + * Copied from ListFragment and added EditText for search on top of list. We + * do not use a custom layout here, because this breaks the progress bar + * functionality of ListFragment. + * + * @param inflater + * @param container + * @param savedInstanceState + * @return + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final Context context = getActivity(); + + FrameLayout root = new FrameLayout(context); + + // ------------------------------------------------------------------ + + LinearLayout pframe = new LinearLayout(context); + pframe.setId(INTERNAL_PROGRESS_CONTAINER_ID); + pframe.setOrientation(LinearLayout.VERTICAL); + pframe.setVisibility(View.GONE); + pframe.setGravity(Gravity.CENTER); + + ProgressBar progress = new ProgressBar(context, null, + android.R.attr.progressBarStyleLarge); + pframe.addView(progress, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + root.addView(pframe, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); + + // ------------------------------------------------------------------ + + FrameLayout lframe = new FrameLayout(context); + lframe.setId(INTERNAL_LIST_CONTAINER_ID); + + TextView tv = new TextView(getActivity()); + tv.setId(INTERNAL_EMPTY_ID); + tv.setGravity(Gravity.CENTER); + lframe.addView(tv, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); + + // Added update all button + LinearLayout linearLayout = new LinearLayout(context); + linearLayout.setOrientation(LinearLayout.VERTICAL); + + mUpdateAllButton = new Button(context); + mUpdateAllButton.setId(UPDATE_ALL_BUTTON_ID); + mUpdateAllButton.setText(R.string.update_all); + mUpdateAllButton.setCompoundDrawablesWithIntrinsicBounds( + getResources().getDrawable(R.drawable.ic_menu_refresh), null, null, null); + + linearLayout.addView(mUpdateAllButton, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + ListView lv = new ListView(getActivity()); + lv.setId(android.R.id.list); + lv.setDrawSelectorOnTop(false); + linearLayout.addView(lv, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); + + lframe.addView(linearLayout, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); + + root.addView(lframe, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); + + // ------------------------------------------------------------------ + + root.setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); + + return root; + } + } From 53d9a15d1134840017a0cce39ec20516abeac233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 27 Apr 2014 22:30:49 +0200 Subject: [PATCH 13/30] Fix comment in CanUpdateAppsFragment --- .../fdroid/views/fragments/CanUpdateAppsFragment.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java b/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java index 9a7cb1c65..1b17e9534 100644 --- a/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java +++ b/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java @@ -84,9 +84,9 @@ public class CanUpdateAppsFragment extends AppListFragment { } /** - * Copied from ListFragment and added EditText for search on top of list. We - * do not use a custom layout here, because this breaks the progress bar - * functionality of ListFragment. + * Copied from ListFragment and added Button on top of list. We do not use a + * custom layout here, because this breaks the progress bar functionality of + * ListFragment. * * @param inflater * @param container From 7d3723fe696b2759039d832d79c9b8ad3ef82fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Mon, 28 Apr 2014 17:43:33 +0200 Subject: [PATCH 14/30] Remove EXTRA_NOT_UNKNOWN_SOURCE from DefaultInstaller Doesn't work because: * In this installer, we are not a system app * Our intent action is of type ACTION_VIEW, not ACTION_INSTALL_PACKAGE The equivalent of "we are not an unknown source" would be the other installers, such as the Root and System ones. --- src/org/fdroid/fdroid/installer/DefaultInstaller.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/org/fdroid/fdroid/installer/DefaultInstaller.java b/src/org/fdroid/fdroid/installer/DefaultInstaller.java index 9c1a9af91..25d84e209 100644 --- a/src/org/fdroid/fdroid/installer/DefaultInstaller.java +++ b/src/org/fdroid/fdroid/installer/DefaultInstaller.java @@ -57,7 +57,6 @@ public class DefaultInstaller extends Installer { intent.setAction(android.content.Intent.ACTION_VIEW); intent.setDataAndType(Uri.parse("file://" + apkFile.getPath()), "application/vnd.android.package-archive"); - extraNotUnknownSource(intent); try { mActivity.startActivityForResult(intent, REQUEST_CODE_INSTALL); } catch (ActivityNotFoundException e) { @@ -65,14 +64,6 @@ public class DefaultInstaller extends Installer { } } - @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); From 033db01b9a5a1e78f395868b37654dd48087026a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 28 Apr 2014 18:45:04 +0200 Subject: [PATCH 15/30] Information about system permissions --- .../installer/SystemPermissionInstaller.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java b/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java index 6ae0bb377..754924c9a 100644 --- a/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java +++ b/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java @@ -39,8 +39,21 @@ import android.util.Log; *

  • 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. + * Both permissions are protected by systemOrSignature (in newer versions: + * system|signature) and only granted on F-Droid's install in the following + * cases: + *

      + *
    • On all Android versions if F-Droid is pre-deployed as a + * system-application with the Rom
    • + *
    • 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 + * https://android.googlesource.com/platform + * /frameworks/base/+/ccbf84f44c9e6a5ed3c08673614826bb237afc54 */ public class SystemPermissionInstaller extends Installer { From 0887da72ed042c5f62cdf6c835cbd86174bef20b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sat, 10 May 2014 19:46:46 +0200 Subject: [PATCH 16/30] Add default installer for Android >= 4.0 --- src/org/fdroid/fdroid/AppDetails.java | 2 - .../fdroid/installer/CheckRootAsyncTask.java | 6 +- .../fdroid/installer/DefaultInstaller.java | 44 +++--- .../installer/DefaultInstallerSdk14.java | 139 ++++++++++++++++++ .../fdroid/fdroid/installer/Installer.java | 15 +- 5 files changed, 180 insertions(+), 26 deletions(-) create mode 100644 src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java diff --git a/src/org/fdroid/fdroid/AppDetails.java b/src/org/fdroid/fdroid/AppDetails.java index 14806aacb..619d2c577 100644 --- a/src/org/fdroid/fdroid/AppDetails.java +++ b/src/org/fdroid/fdroid/AppDetails.java @@ -77,8 +77,6 @@ import java.util.List; public class AppDetails extends ListActivity { private static final String TAG = "AppDetails"; - private static final int REQUEST_INSTALL = 0; - private static final int REQUEST_UNINSTALL = 1; public static final int REQUEST_ENABLE_BLUETOOTH = 2; public static final String EXTRA_APPID = "appid"; diff --git a/src/org/fdroid/fdroid/installer/CheckRootAsyncTask.java b/src/org/fdroid/fdroid/installer/CheckRootAsyncTask.java index c0254905f..3146e636f 100644 --- a/src/org/fdroid/fdroid/installer/CheckRootAsyncTask.java +++ b/src/org/fdroid/fdroid/installer/CheckRootAsyncTask.java @@ -27,7 +27,6 @@ import android.content.Context; import android.os.AsyncTask; public class CheckRootAsyncTask extends AsyncTask { - ProgressDialog mDialog; Context mContext; CheckRootCallback mCallback; @@ -56,10 +55,7 @@ public class CheckRootAsyncTask extends AsyncTask { @Override protected Boolean doInBackground(Void... params) { - - boolean suAvailable = Shell.SU.available(); - - return suAvailable; + return Shell.SU.available(); } @Override diff --git a/src/org/fdroid/fdroid/installer/DefaultInstaller.java b/src/org/fdroid/fdroid/installer/DefaultInstaller.java index 25d84e209..1de5e3417 100644 --- a/src/org/fdroid/fdroid/installer/DefaultInstaller.java +++ b/src/org/fdroid/fdroid/installer/DefaultInstaller.java @@ -21,7 +21,6 @@ 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; @@ -29,13 +28,13 @@ 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. + * 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 Activity mActivity; @@ -49,13 +48,25 @@ public class DefaultInstaller extends Installer { private static final int REQUEST_CODE_INSTALL = 0; private static final int REQUEST_CODE_DELETE = 1; + // TODO: check before installation? + // int result = Settings.Secure.getInt(getContentResolver(), + // Settings.Secure.INSTALL_NON_MARKET_APPS, 0); + // if (result == 0) { + // // show some dialog here + // // ... + // // and may be show application settings dialog manually + // Intent intent = new Intent(); + // intent.setAction(Settings.ACTION_APPLICATION_SETTINGS); + // startActivity(intent); + // } + @Override public void installPackage(File apkFile) throws AndroidNotCompatibleException { super.installPackage(apkFile); Intent intent = new Intent(); - intent.setAction(android.content.Intent.ACTION_VIEW); - intent.setDataAndType(Uri.parse("file://" + apkFile.getPath()), + intent.setAction(Intent.ACTION_VIEW); + intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive"); try { mActivity.startActivityForResult(intent, REQUEST_CODE_INSTALL); @@ -86,21 +97,18 @@ public class DefaultInstaller extends Installer { @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: - if (resultCode == Activity.RESULT_OK) { - mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL, false); - } else { - mCallback.onError(InstallerCallback.OPERATION_INSTALL, false, "todo"); - } + mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL, false); return true; case REQUEST_CODE_DELETE: - if (resultCode == Activity.RESULT_OK) { - mCallback.onSuccess(InstallerCallback.OPERATION_DELETE, false); - } else { - mCallback.onError(InstallerCallback.OPERATION_DELETE, false, "todo"); - } + mCallback.onSuccess(InstallerCallback.OPERATION_DELETE, false); return true; default: diff --git a/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java b/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java new file mode 100644 index 000000000..8fbb97e7a --- /dev/null +++ b/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java @@ -0,0 +1,139 @@ +/* + * 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; + +/** + * 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 DefaultInstallerSdk14 extends Installer { + private Activity mActivity; + + public DefaultInstallerSdk14(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; + + @SuppressWarnings("deprecation") + @Override + public void installPackage(File apkFile) throws AndroidNotCompatibleException { + super.installPackage(apkFile); + + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_INSTALL_PACKAGE); + intent.setData(Uri.fromFile(apkFile)); + 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 (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) { + // deprecated in Android 4.1 + intent.putExtra(Intent.EXTRA_ALLOW_REPLACE, true); + } + try { + mActivity.startActivityForResult(intent, REQUEST_CODE_INSTALL); + } catch (ActivityNotFoundException e) { + throw new AndroidNotCompatibleException(e); + } + } + + @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_UNINSTALL_PACKAGE, uri); + intent.putExtra(Intent.EXTRA_RETURN_RESULT, true); + try { + mActivity.startActivityForResult(intent, REQUEST_CODE_DELETE); + } catch (ActivityNotFoundException e) { + throw new AndroidNotCompatibleException(e); + } + } + + @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! + */ + switch (requestCode) { + case REQUEST_CODE_INSTALL: + if (resultCode == Activity.RESULT_OK) { + mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL, false); + } else if (resultCode == Activity.RESULT_CANCELED) { + // TODO + mCallback.onError(InstallerCallback.OPERATION_INSTALL, false, "canceled"); + } else { + mCallback.onError(InstallerCallback.OPERATION_INSTALL, false, "todo"); + } + + return true; + case REQUEST_CODE_DELETE: + if (resultCode == Activity.RESULT_OK) { + mCallback.onSuccess(InstallerCallback.OPERATION_DELETE, false); + } else if (resultCode == Activity.RESULT_CANCELED) { + // TODO + mCallback.onError(InstallerCallback.OPERATION_DELETE, false, "canceled"); + } else { + mCallback.onError(InstallerCallback.OPERATION_DELETE, false, "todo"); + } + + return true; + default: + return false; + } + } + + @Override + public boolean supportsUnattendedOperations() { + return false; + } + +} diff --git a/src/org/fdroid/fdroid/installer/Installer.java b/src/org/fdroid/fdroid/installer/Installer.java index 1a4917386..425b4c579 100644 --- a/src/org/fdroid/fdroid/installer/Installer.java +++ b/src/org/fdroid/fdroid/installer/Installer.java @@ -124,6 +124,19 @@ abstract public class Installer { } } + // Android >= 4.0 + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + try { + Log.d(TAG, "try default installer for Android 4"); + + return new DefaultInstallerSdk14(activity, pm, callback); + } catch (AndroidNotCompatibleException e) { + Log.e(TAG, + "Android not compatible with DefaultInstallerSdk14! This should really not happen!", + e); + } + } + // Fallback -> DefaultInstaller try { Log.d(TAG, "try default installer"); @@ -211,6 +224,6 @@ abstract public class Installer { } public abstract boolean handleOnActivityResult(int requestCode, int resultCode, Intent data); - + public abstract boolean supportsUnattendedOperations(); } From c48dcdc785dea063f67766ae6867dff4427987c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 11 May 2014 00:03:26 +0200 Subject: [PATCH 17/30] Change callback --- src/org/fdroid/fdroid/AppDetails.java | 86 ++++++++--------- .../fdroid/compat/PackageManagerCompat.java | 10 +- .../fdroid/installer/DefaultInstaller.java | 6 +- .../installer/DefaultInstallerSdk14.java | 94 ++++++++++++++----- .../fdroid/fdroid/installer/Installer.java | 7 +- .../fdroid/installer/RootInstaller.java | 45 +++++---- .../installer/SystemPermissionInstaller.java | 14 +-- 7 files changed, 156 insertions(+), 106 deletions(-) diff --git a/src/org/fdroid/fdroid/AppDetails.java b/src/org/fdroid/fdroid/AppDetails.java index 619d2c577..6d8d4361a 100644 --- a/src/org/fdroid/fdroid/AppDetails.java +++ b/src/org/fdroid/fdroid/AppDetails.java @@ -25,6 +25,7 @@ import android.widget.*; import org.fdroid.fdroid.data.*; import org.fdroid.fdroid.installer.Installer; import org.fdroid.fdroid.installer.Installer.AndroidNotCompatibleException; +import org.fdroid.fdroid.installer.Installer.InstallerCallback; import org.xml.sax.XMLReader; import android.app.AlertDialog; @@ -310,7 +311,8 @@ public class AppDetails extends ListActivity { } mPm = getPackageManager(); - installer = Installer.getActivityInstaller(this, mPm, myInstallerCallback); + installer = Installer.getActivityInstaller(this, mPm, + myInstallerCallback); // Get the preferences we're going to use in this Activity... AppDetails old = (AppDetails) getLastNonConfigurationInstance(); @@ -929,82 +931,80 @@ public class AppDetails extends ListActivity { downloadHandler = new DownloadHandler(apk, repoaddress, Utils.getApkCacheDir(getBaseContext())); } - private void installApk(File file, String packageName) { setProgressBarIndeterminateVisibility(true); - + try { installer.installPackage(file); } catch (AndroidNotCompatibleException e) { Log.e(TAG, "Android not compatible with this Installer!", e); } - - notifyAppChanged(packageName); } private void removeApk(String packageName) { setProgressBarIndeterminateVisibility(true); - + try { installer.deletePackage(packageName); } catch (AndroidNotCompatibleException e) { Log.e(TAG, "Android not compatible with this Installer!", e); } - - notifyAppChanged(packageName); - } + } /** - * We could probably drop this, and let the PackageReceiver take care of notifications - * for us, but I don't think the package receiver notifications are very instantaneous. + * We could probably drop this, and let the PackageReceiver take care of + * notifications for us, but I don't think the package receiver + * notifications are very instantaneous. */ private void notifyAppChanged(String id) { getContentResolver().notifyChange(AppProvider.getContentUri(id), null); } - - private Installer.InstallerCallback myInstallerCallback = new Installer.InstallerCallback() { + + Installer.InstallerCallback myInstallerCallback = new Installer.InstallerCallback() { @Override - public void onSuccess(int operation, boolean unattended) { - resetRequired = true; - - if (operation == Installer.InstallerCallback.OPERATION_INSTALL) { - if (downloadHandler != null) { - downloadHandler = null; - } - - PackageManagerCompat.setInstaller(mPm, app.id); - } - - // if unattended, onResume is not execute automatically -// if (unattended) { + public void onSuccess(final int operation) { runOnUiThread(new Runnable() { @Override public void run() { + Log.d(TAG, "handling installer onSuccess"); + + notifyAppChanged(app.id); + + resetRequired = true; + + if (operation == Installer.InstallerCallback.OPERATION_INSTALL) { + if (downloadHandler != null) { + downloadHandler = null; + } + + PackageManagerCompat.setInstaller(mPm, app.id); + } + + // TODO: whole onResume? onResume(); setProgressBarIndeterminateVisibility(false); } }); -// } - } @Override - public void onError(int operation, boolean unattended, final String reason) { - runOnUiThread(new Runnable() { - @Override - public void run() { - setProgressBarIndeterminateVisibility(false); - - // TODO - AlertDialog.Builder alertBuilder = new AlertDialog.Builder(AppDetails.this); - alertBuilder.setTitle("Error"); - alertBuilder.setMessage(reason); - alertBuilder.setNeutralButton(android.R.string.ok, null); - alertBuilder.create().show(); - } - }); - + public void onError(int operation, final int errorCode) { + if (errorCode != InstallerCallback.ERROR_CODE_CANCELED) { + runOnUiThread(new Runnable() { + @Override + public void run() { + setProgressBarIndeterminateVisibility(false); + + // TODO + AlertDialog.Builder alertBuilder = new AlertDialog.Builder(AppDetails.this); + alertBuilder.setTitle("Error"); + alertBuilder.setMessage("errorCode: " + errorCode); + alertBuilder.setNeutralButton(android.R.string.ok, null); + alertBuilder.create().show(); + } + }); + } } }; diff --git a/src/org/fdroid/fdroid/compat/PackageManagerCompat.java b/src/org/fdroid/fdroid/compat/PackageManagerCompat.java index 714c5b144..5aa752c1f 100644 --- a/src/org/fdroid/fdroid/compat/PackageManagerCompat.java +++ b/src/org/fdroid/fdroid/compat/PackageManagerCompat.java @@ -9,12 +9,12 @@ import android.util.Log; public class PackageManagerCompat extends Compatibility { @TargetApi(11) - public static void setInstaller(PackageManager mPm, String app_id) { + public static void setInstaller(PackageManager mPm, String packageName) { if (!hasApi(11)) return; try { - mPm.setInstallerPackageName(app_id, "org.fdroid.fdroid"); + mPm.setInstallerPackageName(packageName, "org.fdroid.fdroid"); Log.d("FDroid", "Installer package name for " + - app_id + " set successfully"); + packageName + " set successfully"); } catch (Exception e) { // Many problems can occur: // * App wasn't installed due to incompatibility @@ -22,8 +22,8 @@ public class PackageManagerCompat extends Compatibility { // * Another app interfered in the process // * Another app already set the target's installer package // * ... - Log.d("FDroid", "Could not set installer package name for " + - app_id + ": " + e.getMessage()); + Log.e("FDroid", "Could not set installer package name for " + + packageName, e); } } diff --git a/src/org/fdroid/fdroid/installer/DefaultInstaller.java b/src/org/fdroid/fdroid/installer/DefaultInstaller.java index 1de5e3417..e9ae33ca0 100644 --- a/src/org/fdroid/fdroid/installer/DefaultInstaller.java +++ b/src/org/fdroid/fdroid/installer/DefaultInstaller.java @@ -59,7 +59,7 @@ public class DefaultInstaller extends Installer { // intent.setAction(Settings.ACTION_APPLICATION_SETTINGS); // startActivity(intent); // } - + @Override public void installPackage(File apkFile) throws AndroidNotCompatibleException { super.installPackage(apkFile); @@ -104,11 +104,11 @@ public class DefaultInstaller extends Installer { */ switch (requestCode) { case REQUEST_CODE_INSTALL: - mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL, false); + mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL); return true; case REQUEST_CODE_DELETE: - mCallback.onSuccess(InstallerCallback.OPERATION_DELETE, false); + mCallback.onSuccess(InstallerCallback.OPERATION_DELETE); return true; default: diff --git a/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java b/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java index 8fbb97e7a..218a3242b 100644 --- a/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java +++ b/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java @@ -21,6 +21,8 @@ package org.fdroid.fdroid.installer; import java.io.File; +import org.fdroid.fdroid.installer.Installer.InstallerCallback; + import android.annotation.TargetApi; import android.app.Activity; import android.content.ActivityNotFoundException; @@ -97,38 +99,82 @@ public class DefaultInstallerSdk14 extends Installer { } @Override - public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) { + public boolean handleOnActivityResult(final int requestCode, final int resultCode, Intent data) { /** * resultCode is always 0 on Android < 4.0. See * com.android.packageinstaller.PackageInstallerActivity: setResult is * never executed! */ - switch (requestCode) { - case REQUEST_CODE_INSTALL: - if (resultCode == Activity.RESULT_OK) { - mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL, false); - } else if (resultCode == Activity.RESULT_CANCELED) { - // TODO - mCallback.onError(InstallerCallback.OPERATION_INSTALL, false, "canceled"); - } else { - mCallback.onError(InstallerCallback.OPERATION_INSTALL, false, "todo"); + // wait until Android's internal PackageManger has + // received the new package state + Thread wait = new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { } - return true; - case REQUEST_CODE_DELETE: - if (resultCode == Activity.RESULT_OK) { - mCallback.onSuccess(InstallerCallback.OPERATION_DELETE, false); - } else if (resultCode == Activity.RESULT_CANCELED) { - // TODO - mCallback.onError(InstallerCallback.OPERATION_DELETE, false, "canceled"); - } else { - mCallback.onError(InstallerCallback.OPERATION_DELETE, false, "todo"); - } + 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); + } - return true; - default: - return false; - } + // 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 { + mCallback.onError(InstallerCallback.OPERATION_DELETE, + InstallerCallback.ERROR_CODE_OTHER); + } + + // return true; + default: + // return false; + } + } + }); + wait.start(); + return true; + + // 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); + // } + // + // 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 { + // mCallback.onError(InstallerCallback.OPERATION_DELETE, + // InstallerCallback.ERROR_CODE_OTHER); + // } + // + // return true; + // default: + // return false; + // } } @Override diff --git a/src/org/fdroid/fdroid/installer/Installer.java b/src/org/fdroid/fdroid/installer/Installer.java index 425b4c579..f2e185376 100644 --- a/src/org/fdroid/fdroid/installer/Installer.java +++ b/src/org/fdroid/fdroid/installer/Installer.java @@ -77,10 +77,13 @@ abstract public class Installer { public static final int OPERATION_INSTALL = 1; public static final int OPERATION_DELETE = 2; + + public static final int ERROR_CODE_CANCELED = 1; + public static final int ERROR_CODE_OTHER = 2; - public void onSuccess(int operation, boolean unattended); + public void onSuccess(int operation); - public void onError(int operation, boolean unattended, String reason); + public void onError(int operation, int errorCode); } public Installer(Context context, PackageManager pm, InstallerCallback callback) diff --git a/src/org/fdroid/fdroid/installer/RootInstaller.java b/src/org/fdroid/fdroid/installer/RootInstaller.java index 3c62d71a1..04380428f 100644 --- a/src/org/fdroid/fdroid/installer/RootInstaller.java +++ b/src/org/fdroid/fdroid/installer/RootInstaller.java @@ -27,6 +27,7 @@ import eu.chainfire.libsuperuser.Shell; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.util.Log; /** * Installer using a root shell and "pm install", "pm uninstall" commands @@ -67,9 +68,9 @@ public class RootInstaller extends Installer { // exec failed // Shell.OnCommandResultListener.SHELL_EXEC_FAILED - // TODO - mCallback.onError(InstallerCallback.OPERATION_INSTALL, true, - "Error opening root shell with exitCode " + exitCode); + Log.e(TAG, "Error opening root shell with exitCode " + exitCode); + mCallback.onError(InstallerCallback.OPERATION_INSTALL, + InstallerCallback.ERROR_CODE_OTHER); } else { addInstallCommand(apkFile); } @@ -94,9 +95,9 @@ public class RootInstaller extends Installer { // exec failed // Shell.OnCommandResultListener.SHELL_EXEC_FAILED - // TODO - mCallback.onError(InstallerCallback.OPERATION_INSTALL, true, - "Error opening root shell with exitCode " + exitCode); + Log.e(TAG, "Error opening root shell with exitCode " + exitCode); + mCallback.onError(InstallerCallback.OPERATION_INSTALL, + InstallerCallback.ERROR_CODE_OTHER); } else { addInstallCommand(apkFiles); } @@ -121,9 +122,9 @@ public class RootInstaller extends Installer { // exec failed // Shell.OnCommandResultListener.SHELL_EXEC_FAILED - // TODO - mCallback.onError(InstallerCallback.OPERATION_DELETE, true, - "Error opening root shell with exitCode " + exitCode); + Log.e(TAG, "Error opening root shell with exitCode " + exitCode); + mCallback.onError(InstallerCallback.OPERATION_DELETE, + InstallerCallback.ERROR_CODE_OTHER); } else { addDeleteCommand(packageName); } @@ -153,9 +154,9 @@ public class RootInstaller extends Installer { rootSession.close(); if (exitCode < 0) { - // TODO - mCallback.onError(InstallerCallback.OPERATION_INSTALL, true, - "Install failed with exit code " + exitCode); + Log.e(TAG, "Install failed with exit code " + exitCode); + mCallback.onError(InstallerCallback.OPERATION_INSTALL, + InstallerCallback.ERROR_CODE_OTHER); } else { // wait until Android's internal PackageManger // has received the new package state @@ -167,9 +168,7 @@ public class RootInstaller extends Installer { } catch (InterruptedException e) { } - mCallback.onSuccess( - InstallerCallback.OPERATION_INSTALL, - true); + mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL); } }); wait.start(); @@ -187,9 +186,9 @@ public class RootInstaller extends Installer { rootSession.close(); if (exitCode < 0) { - // TODO - mCallback.onError(InstallerCallback.OPERATION_INSTALL, true, - "Install failed with exit code " + exitCode); + Log.e(TAG, "Install failed with exit code " + exitCode); + mCallback.onError(InstallerCallback.OPERATION_INSTALL, + InstallerCallback.ERROR_CODE_OTHER); } else { // wait until Android's internal PackageManger has // received the new package state @@ -201,7 +200,7 @@ public class RootInstaller extends Installer { } catch (InterruptedException e) { } - mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL, true); + mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL); } }); wait.start(); @@ -218,9 +217,9 @@ public class RootInstaller extends Installer { rootSession.close(); if (exitCode < 0) { - // TODO - mCallback.onError(InstallerCallback.OPERATION_DELETE, true, - "Delete failed with exit code " + exitCode); + Log.e(TAG, "Delete failed with exit code " + exitCode); + mCallback.onError(InstallerCallback.OPERATION_DELETE, + InstallerCallback.ERROR_CODE_OTHER); } else { // wait until Android's internal PackageManger has // received the new package state @@ -232,7 +231,7 @@ public class RootInstaller extends Installer { } catch (InterruptedException e) { } - mCallback.onSuccess(InstallerCallback.OPERATION_DELETE, true); + mCallback.onSuccess(InstallerCallback.OPERATION_DELETE); } }); wait.start(); diff --git a/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java b/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java index 754924c9a..a3fdb905e 100644 --- a/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java +++ b/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java @@ -106,13 +106,14 @@ public class SystemPermissionInstaller extends Installer { } catch (InterruptedException e) { } - mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL, true); + mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL); } }); wait.start(); } else { - Log.d(TAG, "Install failed: " + returnCode); - mCallback.onError(InstallerCallback.OPERATION_INSTALL, true, "todo"); + Log.e(TAG, "Install failed with returnCode " + returnCode); + mCallback.onError(InstallerCallback.OPERATION_INSTALL, + InstallerCallback.ERROR_CODE_OTHER); } } } @@ -136,13 +137,14 @@ public class SystemPermissionInstaller extends Installer { } catch (InterruptedException e) { } - mCallback.onSuccess(InstallerCallback.OPERATION_DELETE, true); + mCallback.onSuccess(InstallerCallback.OPERATION_DELETE); } }); wait.start(); } else { - Log.d(TAG, "Delete failed: " + returnCode); - mCallback.onError(InstallerCallback.OPERATION_DELETE, true, "todo"); + Log.e(TAG, "Delete failed with returnCode " + returnCode); + mCallback.onError(InstallerCallback.OPERATION_DELETE, + InstallerCallback.ERROR_CODE_OTHER); } } } From e1d9e0f3b1b5754f33fb334c973a3122847ab57c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 11 May 2014 00:26:31 +0200 Subject: [PATCH 18/30] Document installer callback hack --- src/org/fdroid/fdroid/AppDetails.java | 50 ++++++---- .../installer/DefaultInstallerSdk14.java | 94 +++++-------------- .../fdroid/installer/RootInstaller.java | 45 +-------- .../installer/SystemPermissionInstaller.java | 30 +----- 4 files changed, 64 insertions(+), 155 deletions(-) diff --git a/src/org/fdroid/fdroid/AppDetails.java b/src/org/fdroid/fdroid/AppDetails.java index 6d8d4361a..adcfd6707 100644 --- a/src/org/fdroid/fdroid/AppDetails.java +++ b/src/org/fdroid/fdroid/AppDetails.java @@ -964,28 +964,46 @@ public class AppDetails extends ListActivity { @Override public void onSuccess(final int operation) { - runOnUiThread(new Runnable() { + // TODO: this is a hack!!! + // Currently the views are not automatically updated when the receivers are notified + // if an app is installed/removed + // We are currently waiting that the receivers change the database and then reload the view + // + // Better approach: + // Implement Android Loader that restarts automatically on db change! + Thread wait = new Thread(new Runnable() { @Override public void run() { - Log.d(TAG, "handling installer onSuccess"); - - notifyAppChanged(app.id); - - resetRequired = true; - - if (operation == Installer.InstallerCallback.OPERATION_INSTALL) { - if (downloadHandler != null) { - downloadHandler = null; - } - - PackageManagerCompat.setInstaller(mPm, app.id); + try { + Thread.sleep(2000); + } catch (InterruptedException e) { } - // TODO: whole onResume? - onResume(); - setProgressBarIndeterminateVisibility(false); + runOnUiThread(new Runnable() { + @Override + public void run() { + Log.d(TAG, "handling installer onSuccess"); + + notifyAppChanged(app.id); + + resetRequired = true; + + if (operation == Installer.InstallerCallback.OPERATION_INSTALL) { + if (downloadHandler != null) { + downloadHandler = null; + } + + PackageManagerCompat.setInstaller(mPm, app.id); + } + + // TODO: whole onResume? + onResume(); + setProgressBarIndeterminateVisibility(false); + } + }); } }); + wait.start(); } @Override diff --git a/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java b/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java index 218a3242b..e08157037 100644 --- a/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java +++ b/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java @@ -21,8 +21,6 @@ package org.fdroid.fdroid.installer; import java.io.File; -import org.fdroid.fdroid.installer.Installer.InstallerCallback; - import android.annotation.TargetApi; import android.app.Activity; import android.content.ActivityNotFoundException; @@ -99,82 +97,40 @@ public class DefaultInstallerSdk14 extends Installer { } @Override - public boolean handleOnActivityResult(final int requestCode, final int resultCode, Intent data) { + 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! */ - // wait until Android's internal PackageManger has - // received the new package state - Thread wait = new Thread(new Runnable() { - @Override - public void run() { - try { - Thread.sleep(2000); - } catch (InterruptedException e) { + 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); } - 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); - } - - // 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 { - mCallback.onError(InstallerCallback.OPERATION_DELETE, - InstallerCallback.ERROR_CODE_OTHER); - } - - // return true; - default: - // return false; + 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 { + mCallback.onError(InstallerCallback.OPERATION_DELETE, + InstallerCallback.ERROR_CODE_OTHER); } - } - }); - wait.start(); - return true; - // 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); - // } - // - // 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 { - // mCallback.onError(InstallerCallback.OPERATION_DELETE, - // InstallerCallback.ERROR_CODE_OTHER); - // } - // - // return true; - // default: - // return false; - // } + return true; + default: + return false; + } } @Override diff --git a/src/org/fdroid/fdroid/installer/RootInstaller.java b/src/org/fdroid/fdroid/installer/RootInstaller.java index 04380428f..67e10aa0a 100644 --- a/src/org/fdroid/fdroid/installer/RootInstaller.java +++ b/src/org/fdroid/fdroid/installer/RootInstaller.java @@ -158,20 +158,7 @@ public class RootInstaller extends Installer { mCallback.onError(InstallerCallback.OPERATION_INSTALL, InstallerCallback.ERROR_CODE_OTHER); } else { - // wait until Android's internal PackageManger - // has received the new package state - Thread wait = new Thread(new Runnable() { - @Override - public void run() { - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - } - - mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL); - } - }); - wait.start(); + mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL); } } }); @@ -190,20 +177,7 @@ public class RootInstaller extends Installer { mCallback.onError(InstallerCallback.OPERATION_INSTALL, InstallerCallback.ERROR_CODE_OTHER); } else { - // wait until Android's internal PackageManger has - // received the new package state - Thread wait = new Thread(new Runnable() { - @Override - public void run() { - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - } - - mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL); - } - }); - wait.start(); + mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL); } } }); @@ -221,20 +195,7 @@ public class RootInstaller extends Installer { mCallback.onError(InstallerCallback.OPERATION_DELETE, InstallerCallback.ERROR_CODE_OTHER); } else { - // wait until Android's internal PackageManger has - // received the new package state - Thread wait = new Thread(new Runnable() { - @Override - public void run() { - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - } - - mCallback.onSuccess(InstallerCallback.OPERATION_DELETE); - } - }); - wait.start(); + mCallback.onSuccess(InstallerCallback.OPERATION_DELETE); } } }); diff --git a/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java b/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java index a3fdb905e..708a6caaa 100644 --- a/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java +++ b/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java @@ -96,20 +96,7 @@ public class SystemPermissionInstaller extends Installer { if (returnCode == INSTALL_SUCCEEDED) { Log.d(TAG, "Install succeeded"); - // wait until Android's internal PackageManger has - // received the new package state - Thread wait = new Thread(new Runnable() { - @Override - public void run() { - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - } - - mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL); - } - }); - wait.start(); + mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL); } else { Log.e(TAG, "Install failed with returnCode " + returnCode); mCallback.onError(InstallerCallback.OPERATION_INSTALL, @@ -127,20 +114,7 @@ public class SystemPermissionInstaller extends Installer { if (returnCode == DELETE_SUCCEEDED) { Log.d(TAG, "Delete succeeded"); - // wait until Android's internal PackageManger has - // received the new package state - Thread wait = new Thread(new Runnable() { - @Override - public void run() { - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - } - - mCallback.onSuccess(InstallerCallback.OPERATION_DELETE); - } - }); - wait.start(); + mCallback.onSuccess(InstallerCallback.OPERATION_DELETE); } else { Log.e(TAG, "Delete failed with returnCode " + returnCode); mCallback.onError(InstallerCallback.OPERATION_DELETE, From d250ac727bd2c24bf9fa9ba8cd6a3c6b9cb5cef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 11 May 2014 00:36:54 +0200 Subject: [PATCH 19/30] Cleanup installer code --- .../fdroid/fdroid/installer/DefaultInstaller.java | 12 ------------ src/org/fdroid/fdroid/installer/RootInstaller.java | 12 +++--------- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/src/org/fdroid/fdroid/installer/DefaultInstaller.java b/src/org/fdroid/fdroid/installer/DefaultInstaller.java index e9ae33ca0..c43d15cae 100644 --- a/src/org/fdroid/fdroid/installer/DefaultInstaller.java +++ b/src/org/fdroid/fdroid/installer/DefaultInstaller.java @@ -48,18 +48,6 @@ public class DefaultInstaller extends Installer { private static final int REQUEST_CODE_INSTALL = 0; private static final int REQUEST_CODE_DELETE = 1; - // TODO: check before installation? - // int result = Settings.Secure.getInt(getContentResolver(), - // Settings.Secure.INSTALL_NON_MARKET_APPS, 0); - // if (result == 0) { - // // show some dialog here - // // ... - // // and may be show application settings dialog manually - // Intent intent = new Intent(); - // intent.setAction(Settings.ACTION_APPLICATION_SETTINGS); - // startActivity(intent); - // } - @Override public void installPackage(File apkFile) throws AndroidNotCompatibleException { super.installPackage(apkFile); diff --git a/src/org/fdroid/fdroid/installer/RootInstaller.java b/src/org/fdroid/fdroid/installer/RootInstaller.java index 67e10aa0a..cea4858b3 100644 --- a/src/org/fdroid/fdroid/installer/RootInstaller.java +++ b/src/org/fdroid/fdroid/installer/RootInstaller.java @@ -62,10 +62,8 @@ public class RootInstaller extends Installer { @Override public void onCommandResult(int commandCode, int exitCode, List output) { if (exitCode != Shell.OnCommandResultListener.SHELL_RUNNING) { - // TODO - // wrong uid + // NOTE: Additional exit codes: // Shell.OnCommandResultListener.SHELL_WRONG_UID - // exec failed // Shell.OnCommandResultListener.SHELL_EXEC_FAILED Log.e(TAG, "Error opening root shell with exitCode " + exitCode); @@ -89,10 +87,8 @@ public class RootInstaller extends Installer { @Override public void onCommandResult(int commandCode, int exitCode, List output) { if (exitCode != Shell.OnCommandResultListener.SHELL_RUNNING) { - // TODO - // wrong uid + // NOTE: Additional exit codes: // Shell.OnCommandResultListener.SHELL_WRONG_UID - // exec failed // Shell.OnCommandResultListener.SHELL_EXEC_FAILED Log.e(TAG, "Error opening root shell with exitCode " + exitCode); @@ -116,10 +112,8 @@ public class RootInstaller extends Installer { @Override public void onCommandResult(int commandCode, int exitCode, List output) { if (exitCode != Shell.OnCommandResultListener.SHELL_RUNNING) { - // TODO - // wrong uid + // NOTE: Additional exit codes: // Shell.OnCommandResultListener.SHELL_WRONG_UID - // exec failed // Shell.OnCommandResultListener.SHELL_EXEC_FAILED Log.e(TAG, "Error opening root shell with exitCode " + exitCode); From 04577d213c3d28241d9655dde743e6b4417ad346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 11 May 2014 01:38:17 +0200 Subject: [PATCH 20/30] Use ContentObserver to observe install status --- src/org/fdroid/fdroid/AppDetails.java | 105 +++++++++++++++----------- 1 file changed, 60 insertions(+), 45 deletions(-) diff --git a/src/org/fdroid/fdroid/AppDetails.java b/src/org/fdroid/fdroid/AppDetails.java index adcfd6707..029ae805b 100644 --- a/src/org/fdroid/fdroid/AppDetails.java +++ b/src/org/fdroid/fdroid/AppDetails.java @@ -33,18 +33,17 @@ import android.app.ListActivity; import android.app.ProgressDialog; import android.bluetooth.BluetoothAdapter; import android.net.Uri; -import android.nfc.NfcAdapter; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.preference.PreferenceManager; import android.support.v4.app.NavUtils; import android.support.v4.view.MenuItemCompat; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageInfo; import android.content.pm.Signature; import android.content.pm.PackageManager.NameNotFoundException; +import android.database.ContentObserver; import android.text.Editable; import android.text.Html; import android.text.Html.TagHandler; @@ -96,6 +95,31 @@ public class AppDetails extends ListActivity { TextView added; TextView nativecode; } + + // observer to update view when package has been installed/removed + AppObserver myAppObserver; + class AppObserver extends ContentObserver { + public AppObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + this.onChange(selfChange, null); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (!reset()) { + AppDetails.this.finish(); + return; + } + updateViews(); + + MenuManager.create(AppDetails.this).invalidateOptionsMenu(); + } + } + private class ApkListAdapter extends ArrayAdapter { @@ -323,7 +347,6 @@ public class AppDetails extends ListActivity { finish(); return; } - resetRequired = false; } SharedPreferences prefs = PreferenceManager @@ -347,7 +370,6 @@ public class AppDetails extends ListActivity { private boolean pref_expert; private boolean pref_permissions; private boolean pref_incompatibleVersions; - private boolean resetRequired; // The signature of the installed version. private Signature mInstalledSignature; @@ -357,12 +379,17 @@ public class AppDetails extends ListActivity { protected void onResume() { Log.d(TAG, "onresume"); super.onResume(); - if (resetRequired) { - if (!reset()) { - finish(); - return; - } - resetRequired = false; + + // register observer to know when install status changes + myAppObserver = new AppObserver(new Handler()); + getContentResolver().registerContentObserver( + AppProvider.getContentUri(app.id), + true, + myAppObserver); + + if (!reset()) { + finish(); + return; } updateViews(); @@ -375,6 +402,9 @@ public class AppDetails extends ListActivity { @Override protected void onPause() { + if (myAppObserver != null) { + getContentResolver().unregisterContentObserver(myAppObserver); + } if (downloadHandler != null) { downloadHandler.stopUpdates(); } @@ -964,51 +994,36 @@ public class AppDetails extends ListActivity { @Override public void onSuccess(final int operation) { - // TODO: this is a hack!!! - // Currently the views are not automatically updated when the receivers are notified - // if an app is installed/removed - // We are currently waiting that the receivers change the database and then reload the view - // - // Better approach: - // Implement Android Loader that restarts automatically on db change! - Thread wait = new Thread(new Runnable() { + runOnUiThread(new Runnable() { @Override public void run() { - try { - Thread.sleep(2000); - } catch (InterruptedException e) { + Log.d(TAG, "handling installer onSuccess"); + + notifyAppChanged(app.id); + + if (operation == Installer.InstallerCallback.OPERATION_INSTALL) { + if (downloadHandler != null) { + downloadHandler = null; + } + + PackageManagerCompat.setInstaller(mPm, app.id); } - runOnUiThread(new Runnable() { - @Override - public void run() { - Log.d(TAG, "handling installer onSuccess"); - - notifyAppChanged(app.id); - - resetRequired = true; - - if (operation == Installer.InstallerCallback.OPERATION_INSTALL) { - if (downloadHandler != null) { - downloadHandler = null; - } - - PackageManagerCompat.setInstaller(mPm, app.id); - } - - // TODO: whole onResume? - onResume(); - setProgressBarIndeterminateVisibility(false); - } - }); + setProgressBarIndeterminateVisibility(false); } }); - wait.start(); } @Override public void onError(int operation, final int errorCode) { - if (errorCode != InstallerCallback.ERROR_CODE_CANCELED) { + if (errorCode == InstallerCallback.ERROR_CODE_CANCELED) { + runOnUiThread(new Runnable() { + @Override + public void run() { + setProgressBarIndeterminateVisibility(false); + } + }); + } else { runOnUiThread(new Runnable() { @Override public void run() { From df696ed81e87f1a8ab6643e82e8d44c6aff89f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 11 May 2014 02:13:27 +0200 Subject: [PATCH 21/30] Installer: define all methods as abstract that need to be implemented in subclasses --- .../fdroid/installer/DefaultInstaller.java | 15 +++++++++------ .../fdroid/installer/DefaultInstallerSdk14.java | 15 +++++++++------ src/org/fdroid/fdroid/installer/Installer.java | 17 +++++++++++++---- .../fdroid/fdroid/installer/RootInstaller.java | 12 +++--------- .../installer/SystemPermissionInstaller.java | 15 +++++++++------ .../views/fragments/CanUpdateAppsFragment.java | 2 +- 6 files changed, 44 insertions(+), 32 deletions(-) diff --git a/src/org/fdroid/fdroid/installer/DefaultInstaller.java b/src/org/fdroid/fdroid/installer/DefaultInstaller.java index c43d15cae..31d288f64 100644 --- a/src/org/fdroid/fdroid/installer/DefaultInstaller.java +++ b/src/org/fdroid/fdroid/installer/DefaultInstaller.java @@ -20,6 +20,7 @@ package org.fdroid.fdroid.installer; import java.io.File; +import java.util.List; import android.app.Activity; import android.content.ActivityNotFoundException; @@ -49,9 +50,7 @@ public class DefaultInstaller extends Installer { private static final int REQUEST_CODE_DELETE = 1; @Override - public void installPackage(File apkFile) throws AndroidNotCompatibleException { - super.installPackage(apkFile); - + public void installPackageInternal(File apkFile) throws AndroidNotCompatibleException { Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(apkFile), @@ -64,9 +63,7 @@ public class DefaultInstaller extends Installer { } @Override - public void deletePackage(String packageName) throws AndroidNotCompatibleException { - super.deletePackage(packageName); - + public void deletePackageInternal(String packageName) throws AndroidNotCompatibleException { PackageInfo pkgInfo = null; try { pkgInfo = mPm.getPackageInfo(packageName, 0); @@ -109,4 +106,10 @@ public class DefaultInstaller extends Installer { return false; } + @Override + protected void installPackageInternal(List apkFiles) throws AndroidNotCompatibleException { + // TODO Auto-generated method stub + + } + } diff --git a/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java b/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java index e08157037..d5761725b 100644 --- a/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java +++ b/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java @@ -20,6 +20,7 @@ package org.fdroid.fdroid.installer; import java.io.File; +import java.util.List; import android.annotation.TargetApi; import android.app.Activity; @@ -53,9 +54,7 @@ public class DefaultInstallerSdk14 extends Installer { @SuppressWarnings("deprecation") @Override - public void installPackage(File apkFile) throws AndroidNotCompatibleException { - super.installPackage(apkFile); - + public void installPackageInternal(File apkFile) throws AndroidNotCompatibleException { Intent intent = new Intent(); intent.setAction(Intent.ACTION_INSTALL_PACKAGE); intent.setData(Uri.fromFile(apkFile)); @@ -76,9 +75,7 @@ public class DefaultInstallerSdk14 extends Installer { } @Override - public void deletePackage(String packageName) throws AndroidNotCompatibleException { - super.deletePackage(packageName); - + public void deletePackageInternal(String packageName) throws AndroidNotCompatibleException { PackageInfo pkgInfo = null; try { pkgInfo = mPm.getPackageInfo(packageName, 0); @@ -138,4 +135,10 @@ public class DefaultInstallerSdk14 extends Installer { return false; } + @Override + protected void installPackageInternal(List apkFiles) throws AndroidNotCompatibleException { + // TODO Auto-generated method stub + + } + } diff --git a/src/org/fdroid/fdroid/installer/Installer.java b/src/org/fdroid/fdroid/installer/Installer.java index f2e185376..d09b88380 100644 --- a/src/org/fdroid/fdroid/installer/Installer.java +++ b/src/org/fdroid/fdroid/installer/Installer.java @@ -77,7 +77,7 @@ abstract public class Installer { public static final int OPERATION_INSTALL = 1; public static final int OPERATION_DELETE = 2; - + public static final int ERROR_CODE_CANCELED = 1; public static final int ERROR_CODE_OTHER = 2; @@ -199,7 +199,7 @@ abstract public class Installer { return; } - // extended class now actually installs the package + installPackageInternal(apkFile); } public void installPackage(List apkFiles) throws AndroidNotCompatibleException { @@ -211,7 +211,7 @@ abstract public class Installer { } } - // extended class now actually installs the package + installPackageInternal(apkFiles); } public void deletePackage(String packageName) throws AndroidNotCompatibleException { @@ -223,9 +223,18 @@ abstract public class Installer { return; } - // extended class now actually deletes the package + deletePackageInternal(packageName); } + protected abstract void installPackageInternal(File apkFile) + throws AndroidNotCompatibleException; + + protected abstract void installPackageInternal(List apkFiles) + throws AndroidNotCompatibleException; + + protected abstract void deletePackageInternal(String packageName) + throws AndroidNotCompatibleException; + public abstract boolean handleOnActivityResult(int requestCode, int resultCode, Intent data); public abstract boolean supportsUnattendedOperations(); diff --git a/src/org/fdroid/fdroid/installer/RootInstaller.java b/src/org/fdroid/fdroid/installer/RootInstaller.java index cea4858b3..0d7a6bfdc 100644 --- a/src/org/fdroid/fdroid/installer/RootInstaller.java +++ b/src/org/fdroid/fdroid/installer/RootInstaller.java @@ -52,9 +52,7 @@ public class RootInstaller extends Installer { } @Override - public void installPackage(final File apkFile) throws AndroidNotCompatibleException { - super.installPackage(apkFile); - + public void installPackageInternal(final File apkFile) throws AndroidNotCompatibleException { rootSession = createShellBuilder().open(new Shell.OnCommandResultListener() { // Callback to report whether the shell was successfully @@ -77,9 +75,7 @@ public class RootInstaller extends Installer { } @Override - public void installPackage(final List apkFiles) throws AndroidNotCompatibleException { - super.installPackage(apkFiles); - + public void installPackageInternal(final List apkFiles) throws AndroidNotCompatibleException { rootSession = createShellBuilder().open(new Shell.OnCommandResultListener() { // Callback to report whether the shell was successfully @@ -102,9 +98,7 @@ public class RootInstaller extends Installer { } @Override - public void deletePackage(final String packageName) throws AndroidNotCompatibleException { - super.deletePackage(packageName); - + public void deletePackageInternal(final String packageName) throws AndroidNotCompatibleException { rootSession = createShellBuilder().open(new Shell.OnCommandResultListener() { // Callback to report whether the shell was successfully diff --git a/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java b/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java index 708a6caaa..6dad5a78e 100644 --- a/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java +++ b/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java @@ -21,6 +21,7 @@ package org.fdroid.fdroid.installer; import java.io.File; import java.lang.reflect.Method; +import java.util.List; import android.content.Context; import android.content.Intent; @@ -124,9 +125,7 @@ public class SystemPermissionInstaller extends Installer { } @Override - public void installPackage(File apkFile) throws AndroidNotCompatibleException { - super.installPackage(apkFile); - + public void installPackageInternal(File apkFile) throws AndroidNotCompatibleException { Uri packageURI = Uri.fromFile(apkFile); try { mInstallMethod.invoke(mPm, new Object[] { @@ -138,9 +137,7 @@ public class SystemPermissionInstaller extends Installer { } @Override - public void deletePackage(String packageName) throws AndroidNotCompatibleException { - super.deletePackage(packageName); - + public void deletePackageInternal(String packageName) throws AndroidNotCompatibleException { try { mDeleteMethod.invoke(mPm, new Object[] { packageName, mDeleteObserver, 0 @@ -466,4 +463,10 @@ public class SystemPermissionInstaller extends Installer { * failed to delete the package since the user is restricted. */ public static final int DELETE_FAILED_USER_RESTRICTED = -3; + + @Override + protected void installPackageInternal(List apkFiles) throws AndroidNotCompatibleException { + // TODO Auto-generated method stub + + } } diff --git a/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java b/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java index 1b17e9534..0e28f3010 100644 --- a/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java +++ b/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java @@ -60,7 +60,7 @@ public class CanUpdateAppsFragment extends AppListFragment { @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - + mUpdateAllButton.setOnClickListener(new OnClickListener() { @Override From 6419915d5ada26b643324c1ffef219c5a4d4c600 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 11 May 2014 02:25:50 +0200 Subject: [PATCH 22/30] Strings for error dialog, cleanup --- res/values/strings.xml | 4 +++- src/org/fdroid/fdroid/AppDetails.java | 24 ++++++------------------ 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 6bbbcf099..f342e1faf 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -270,5 +270,7 @@ Root access denied Either your Android device is not rooted or you have denied root access for F-Droid. Update all - + (De-)Installation Error + The (de-)installation failed. If you are using root access, try disabling this setting! + diff --git a/src/org/fdroid/fdroid/AppDetails.java b/src/org/fdroid/fdroid/AppDetails.java index 029ae805b..ab0d812e2 100644 --- a/src/org/fdroid/fdroid/AppDetails.java +++ b/src/org/fdroid/fdroid/AppDetails.java @@ -981,26 +981,13 @@ public class AppDetails extends ListActivity { } } - /** - * We could probably drop this, and let the PackageReceiver take care of - * notifications for us, but I don't think the package receiver - * notifications are very instantaneous. - */ - private void notifyAppChanged(String id) { - getContentResolver().notifyChange(AppProvider.getContentUri(id), null); - } - Installer.InstallerCallback myInstallerCallback = new Installer.InstallerCallback() { @Override public void onSuccess(final int operation) { runOnUiThread(new Runnable() { @Override - public void run() { - Log.d(TAG, "handling installer onSuccess"); - - notifyAppChanged(app.id); - + public void run() { if (operation == Installer.InstallerCallback.OPERATION_INSTALL) { if (downloadHandler != null) { downloadHandler = null; @@ -1028,11 +1015,12 @@ public class AppDetails extends ListActivity { @Override public void run() { setProgressBarIndeterminateVisibility(false); - - // TODO + + Log.e(TAG, "Installer aborted with errorCode: " + errorCode); + AlertDialog.Builder alertBuilder = new AlertDialog.Builder(AppDetails.this); - alertBuilder.setTitle("Error"); - alertBuilder.setMessage("errorCode: " + errorCode); + alertBuilder.setTitle(R.string.installer_error_title); + alertBuilder.setMessage(R.string.installer_error_title); alertBuilder.setNeutralButton(android.R.string.ok, null); alertBuilder.create().show(); } From 6710e9a4b97393d2a2f1f0427490ade5db2f4d13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 11 May 2014 02:31:53 +0200 Subject: [PATCH 23/30] dont show update all button for now... --- .../fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java b/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java index 0e28f3010..aeb59e683 100644 --- a/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java +++ b/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java @@ -77,7 +77,8 @@ public class CanUpdateAppsFragment extends AppListFragment { super.onResume(); if (mInstaller.supportsUnattendedOperations()) { - mUpdateAllButton.setVisibility(View.VISIBLE); +// mUpdateAllButton.setVisibility(View.VISIBLE); + mUpdateAllButton.setVisibility(View.GONE); } else { mUpdateAllButton.setVisibility(View.GONE); } From aeae0bcec3a2838ea987816588016311ea21c36d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 11 May 2014 11:13:16 +0200 Subject: [PATCH 24/30] Code/doc cleanup --- src/org/fdroid/fdroid/AppDetails.java | 2 +- .../fdroid/installer/DefaultInstaller.java | 2 +- .../installer/DefaultInstallerSdk14.java | 7 +--- .../fdroid/installer/RootInstaller.java | 42 ++++++++++--------- 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/src/org/fdroid/fdroid/AppDetails.java b/src/org/fdroid/fdroid/AppDetails.java index ab0d812e2..96e82b6bf 100644 --- a/src/org/fdroid/fdroid/AppDetails.java +++ b/src/org/fdroid/fdroid/AppDetails.java @@ -96,7 +96,7 @@ public class AppDetails extends ListActivity { TextView nativecode; } - // observer to update view when package has been installed/removed + // observer to update view when package has been installed/deleted AppObserver myAppObserver; class AppObserver extends ContentObserver { public AppObserver(Handler handler) { diff --git a/src/org/fdroid/fdroid/installer/DefaultInstaller.java b/src/org/fdroid/fdroid/installer/DefaultInstaller.java index 31d288f64..3f7447341 100644 --- a/src/org/fdroid/fdroid/installer/DefaultInstaller.java +++ b/src/org/fdroid/fdroid/installer/DefaultInstaller.java @@ -31,7 +31,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.net.Uri; /** - * For Android < 4 Default Installer using the public PackageManager API of + * 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 diff --git a/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java b/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java index d5761725b..deeb97125 100644 --- a/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java +++ b/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java @@ -33,7 +33,7 @@ import android.net.Uri; import android.os.Build; /** - * For Android >= 4.0 Default Installer using the public PackageManager API of + * 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 @@ -95,11 +95,6 @@ public class DefaultInstallerSdk14 extends Installer { @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! - */ switch (requestCode) { case REQUEST_CODE_INSTALL: if (resultCode == Activity.RESULT_OK) { diff --git a/src/org/fdroid/fdroid/installer/RootInstaller.java b/src/org/fdroid/fdroid/installer/RootInstaller.java index 0d7a6bfdc..a00a50d59 100644 --- a/src/org/fdroid/fdroid/installer/RootInstaller.java +++ b/src/org/fdroid/fdroid/installer/RootInstaller.java @@ -75,7 +75,8 @@ public class RootInstaller extends Installer { } @Override - public void installPackageInternal(final List apkFiles) throws AndroidNotCompatibleException { + public void installPackageInternal(final List apkFiles) + throws AndroidNotCompatibleException { rootSession = createShellBuilder().open(new Shell.OnCommandResultListener() { // Callback to report whether the shell was successfully @@ -98,7 +99,8 @@ public class RootInstaller extends Installer { } @Override - public void deletePackageInternal(final String packageName) throws AndroidNotCompatibleException { + public void deletePackageInternal(final String packageName) + throws AndroidNotCompatibleException { rootSession = createShellBuilder().open(new Shell.OnCommandResultListener() { // Callback to report whether the shell was successfully @@ -127,6 +129,24 @@ public class RootInstaller extends Installer { return false; } + private void addInstallCommand(File apkFile) { + rootSession.addCommand("pm install -r " + apkFile.getAbsolutePath(), 0, + new Shell.OnCommandResultListener() { + public void onCommandResult(int commandCode, int exitCode, List output) { + // close su shell + rootSession.close(); + + if (exitCode < 0) { + Log.e(TAG, "Install failed with exit code " + exitCode); + mCallback.onError(InstallerCallback.OPERATION_INSTALL, + InstallerCallback.ERROR_CODE_OTHER); + } else { + mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL); + } + } + }); + } + private void addInstallCommand(List apkFiles) { ArrayList commands = new ArrayList(); String pm = "pm install -r "; @@ -153,24 +173,6 @@ public class RootInstaller extends Installer { } - private void addInstallCommand(File apkFile) { - rootSession.addCommand("pm install -r " + apkFile.getAbsolutePath(), 0, - new Shell.OnCommandResultListener() { - public void onCommandResult(int commandCode, int exitCode, List output) { - // close su shell - rootSession.close(); - - if (exitCode < 0) { - Log.e(TAG, "Install failed with exit code " + exitCode); - mCallback.onError(InstallerCallback.OPERATION_INSTALL, - InstallerCallback.ERROR_CODE_OTHER); - } else { - mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL); - } - } - }); - } - private void addDeleteCommand(String packageName) { rootSession.addCommand("pm uninstall " + packageName, 0, new Shell.OnCommandResultListener() { From 91ddc838a7df9fe011245db3282b8257a8013d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 11 May 2014 11:16:26 +0200 Subject: [PATCH 25/30] More cleanup in installer classes --- .../fdroid/installer/DefaultInstaller.java | 16 ++++++++-------- .../fdroid/installer/DefaultInstallerSdk14.java | 16 ++++++++-------- .../fdroid/fdroid/installer/RootInstaller.java | 6 +++--- .../installer/SystemPermissionInstaller.java | 15 ++++++++------- 4 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/org/fdroid/fdroid/installer/DefaultInstaller.java b/src/org/fdroid/fdroid/installer/DefaultInstaller.java index 3f7447341..48619f877 100644 --- a/src/org/fdroid/fdroid/installer/DefaultInstaller.java +++ b/src/org/fdroid/fdroid/installer/DefaultInstaller.java @@ -50,7 +50,7 @@ public class DefaultInstaller extends Installer { private static final int REQUEST_CODE_DELETE = 1; @Override - public void installPackageInternal(File apkFile) throws AndroidNotCompatibleException { + protected void installPackageInternal(File apkFile) throws AndroidNotCompatibleException { Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(apkFile), @@ -63,7 +63,13 @@ public class DefaultInstaller extends Installer { } @Override - public void deletePackageInternal(String packageName) throws AndroidNotCompatibleException { + protected void installPackageInternal(List apkFiles) throws AndroidNotCompatibleException { + // TODO Auto-generated method stub + + } + + @Override + protected void deletePackageInternal(String packageName) throws AndroidNotCompatibleException { PackageInfo pkgInfo = null; try { pkgInfo = mPm.getPackageInfo(packageName, 0); @@ -106,10 +112,4 @@ public class DefaultInstaller extends Installer { return false; } - @Override - protected void installPackageInternal(List apkFiles) throws AndroidNotCompatibleException { - // TODO Auto-generated method stub - - } - } diff --git a/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java b/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java index deeb97125..4a33716ed 100644 --- a/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java +++ b/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java @@ -54,7 +54,7 @@ public class DefaultInstallerSdk14 extends Installer { @SuppressWarnings("deprecation") @Override - public void installPackageInternal(File apkFile) throws AndroidNotCompatibleException { + protected void installPackageInternal(File apkFile) throws AndroidNotCompatibleException { Intent intent = new Intent(); intent.setAction(Intent.ACTION_INSTALL_PACKAGE); intent.setData(Uri.fromFile(apkFile)); @@ -75,7 +75,13 @@ public class DefaultInstallerSdk14 extends Installer { } @Override - public void deletePackageInternal(String packageName) throws AndroidNotCompatibleException { + protected void installPackageInternal(List apkFiles) throws AndroidNotCompatibleException { + // TODO Auto-generated method stub + + } + + @Override + protected void deletePackageInternal(String packageName) throws AndroidNotCompatibleException { PackageInfo pkgInfo = null; try { pkgInfo = mPm.getPackageInfo(packageName, 0); @@ -130,10 +136,4 @@ public class DefaultInstallerSdk14 extends Installer { return false; } - @Override - protected void installPackageInternal(List apkFiles) throws AndroidNotCompatibleException { - // TODO Auto-generated method stub - - } - } diff --git a/src/org/fdroid/fdroid/installer/RootInstaller.java b/src/org/fdroid/fdroid/installer/RootInstaller.java index a00a50d59..d6d4079d7 100644 --- a/src/org/fdroid/fdroid/installer/RootInstaller.java +++ b/src/org/fdroid/fdroid/installer/RootInstaller.java @@ -52,7 +52,7 @@ public class RootInstaller extends Installer { } @Override - public void installPackageInternal(final File apkFile) throws AndroidNotCompatibleException { + protected void installPackageInternal(final File apkFile) throws AndroidNotCompatibleException { rootSession = createShellBuilder().open(new Shell.OnCommandResultListener() { // Callback to report whether the shell was successfully @@ -75,7 +75,7 @@ public class RootInstaller extends Installer { } @Override - public void installPackageInternal(final List apkFiles) + protected void installPackageInternal(final List apkFiles) throws AndroidNotCompatibleException { rootSession = createShellBuilder().open(new Shell.OnCommandResultListener() { @@ -99,7 +99,7 @@ public class RootInstaller extends Installer { } @Override - public void deletePackageInternal(final String packageName) + protected void deletePackageInternal(final String packageName) throws AndroidNotCompatibleException { rootSession = createShellBuilder().open(new Shell.OnCommandResultListener() { diff --git a/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java b/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java index 6dad5a78e..f97cd6858 100644 --- a/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java +++ b/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java @@ -125,7 +125,7 @@ public class SystemPermissionInstaller extends Installer { } @Override - public void installPackageInternal(File apkFile) throws AndroidNotCompatibleException { + protected void installPackageInternal(File apkFile) throws AndroidNotCompatibleException { Uri packageURI = Uri.fromFile(apkFile); try { mInstallMethod.invoke(mPm, new Object[] { @@ -137,7 +137,13 @@ public class SystemPermissionInstaller extends Installer { } @Override - public void deletePackageInternal(String packageName) throws AndroidNotCompatibleException { + protected void installPackageInternal(List apkFiles) throws AndroidNotCompatibleException { + // TODO Auto-generated method stub + + } + + @Override + protected void deletePackageInternal(String packageName) throws AndroidNotCompatibleException { try { mDeleteMethod.invoke(mPm, new Object[] { packageName, mDeleteObserver, 0 @@ -464,9 +470,4 @@ public class SystemPermissionInstaller extends Installer { */ public static final int DELETE_FAILED_USER_RESTRICTED = -3; - @Override - protected void installPackageInternal(List apkFiles) throws AndroidNotCompatibleException { - // TODO Auto-generated method stub - - } } From 54ce7a07a6aecdcc174b0e32c561527f7d5e95a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 12 May 2014 11:31:25 +0200 Subject: [PATCH 26/30] Cleanup for getActivityInstaller --- .../fdroid/fdroid/installer/Installer.java | 32 +++++++++---------- .../fdroid/installer/RootInstaller.java | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/org/fdroid/fdroid/installer/Installer.java b/src/org/fdroid/fdroid/installer/Installer.java index d09b88380..76ec90003 100644 --- a/src/org/fdroid/fdroid/installer/Installer.java +++ b/src/org/fdroid/fdroid/installer/Installer.java @@ -109,6 +109,8 @@ abstract public class Installer { // if root installer has been activated in preferences -> RootInstaller boolean useRootInstaller = Preferences.get().useRootInstaller(); if (useRootInstaller) { + Log.d(TAG, "root installer preference enabled -> RootInstaller"); + try { return new RootInstaller(activity, pm, callback); } catch (AndroidNotCompatibleException e) { @@ -127,28 +129,25 @@ abstract public class Installer { } } - // Android >= 4.0 + // Fallback -> DefaultInstaller if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + // Default installer on Android >= 4.0 try { - Log.d(TAG, "try default installer for Android 4"); + Log.d(TAG, "try default installer for Android >= 4"); return new DefaultInstallerSdk14(activity, pm, callback); } catch (AndroidNotCompatibleException e) { - Log.e(TAG, - "Android not compatible with DefaultInstallerSdk14! This should really not happen!", - e); + Log.e(TAG, "Android not compatible with DefaultInstallerSdk14!", e); } - } + } else { + // Default installer on Android < 4.0 + try { + Log.d(TAG, "try default installer for Android < 4"); - // Fallback -> DefaultInstaller - 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); + return new DefaultInstaller(activity, pm, callback); + } catch (AndroidNotCompatibleException e) { + Log.e(TAG, "Android not compatible with DefaultInstaller!", e); + } } // this should not happen! @@ -182,7 +181,8 @@ abstract public class Installer { pm.checkPermission(permission.INSTALL_PACKAGES, context.getPackageName()); int checkDeletePermission = pm.checkPermission(permission.DELETE_PACKAGES, context.getPackageName()); - boolean permissionsGranted = (checkInstallPermission == PackageManager.PERMISSION_GRANTED + boolean permissionsGranted = + (checkInstallPermission == PackageManager.PERMISSION_GRANTED && checkDeletePermission == PackageManager.PERMISSION_GRANTED); if (permissionsGranted) { diff --git a/src/org/fdroid/fdroid/installer/RootInstaller.java b/src/org/fdroid/fdroid/installer/RootInstaller.java index d6d4079d7..ca6fb683b 100644 --- a/src/org/fdroid/fdroid/installer/RootInstaller.java +++ b/src/org/fdroid/fdroid/installer/RootInstaller.java @@ -205,7 +205,7 @@ public class RootInstaller extends Installer { *

    * Options:
    * -l: install the package with FORWARD_LOCK.
    - * -r: reinstall an exisiting app, keeping its data.
    + * -r: reinstall an existing app, keeping its data.
    * -t: allow test .apks to be installed.
    * -i: specify the installer package name.
    * -s: install package on sdcard.
    From 63b0bbd12061631d7626a1116590fc9859039bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 12 May 2014 11:40:25 +0200 Subject: [PATCH 27/30] Move root access preference down into other category --- res/xml/preferences.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index cb331b4d9..0e477cb97 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -18,9 +18,6 @@ android:maxLength="2" android:numeric="integer" android:title="@string/update_history" /> - + From c65a25524c91c6e94a5eadc8c7c9d5cf5ebe8cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 12 May 2014 11:45:26 +0200 Subject: [PATCH 28/30] Log errors --- src/org/fdroid/fdroid/installer/Installer.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/org/fdroid/fdroid/installer/Installer.java b/src/org/fdroid/fdroid/installer/Installer.java index 76ec90003..f87c51604 100644 --- a/src/org/fdroid/fdroid/installer/Installer.java +++ b/src/org/fdroid/fdroid/installer/Installer.java @@ -195,7 +195,7 @@ abstract public class Installer { public void installPackage(File apkFile) throws AndroidNotCompatibleException { // check if file exists... if (!apkFile.exists()) { - Log.d(TAG, "Couldn't find file " + apkFile + " to install."); + Log.e(TAG, "Couldn't find file " + apkFile + " to install."); return; } @@ -206,7 +206,7 @@ abstract public class Installer { // check if files exist... for (File apkFile : apkFiles) { if (!apkFile.exists()) { - Log.d(TAG, "Couldn't find file " + apkFile + " to install."); + Log.e(TAG, "Couldn't find file " + apkFile + " to install."); return; } } @@ -219,7 +219,7 @@ abstract public class Installer { try { mPm.getPackageInfo(packageName, 0); } catch (NameNotFoundException e) { - Log.d(TAG, "Couldn't find package " + packageName + " to delete."); + Log.e(TAG, "Couldn't find package " + packageName + " to delete."); return; } From 2bd686dfe8b8cd326a2cb10c9dd0b354cce71fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 12 May 2014 12:55:17 +0200 Subject: [PATCH 29/30] Add preference to opt-out of SystemPermissionInstaller --- res/values/strings.xml | 3 +++ res/xml/preferences.xml | 3 +++ src/org/fdroid/fdroid/Preferences.java | 8 +++++++- src/org/fdroid/fdroid/PreferencesActivity.java | 9 +++++++-- src/org/fdroid/fdroid/installer/Installer.java | 11 ++++++----- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index f342e1faf..e2d07dfc8 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -31,6 +31,9 @@ Root access for app installations Root access is used to install/delete/update applications Do not request root access to install/delete/update applications + Use system permissions for app installations + F-Droid tries to use system permissions to install/delete/update applications (only possible when installed as system-app) + F-Droid does not try to use system permissions to install/delete/update applications (only possible when installed as system-app) Search Results App Details diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 0e477cb97..b9571a814 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -53,5 +53,8 @@ + diff --git a/src/org/fdroid/fdroid/Preferences.java b/src/org/fdroid/fdroid/Preferences.java index b625f31fe..29c2c0d39 100644 --- a/src/org/fdroid/fdroid/Preferences.java +++ b/src/org/fdroid/fdroid/Preferences.java @@ -37,11 +37,13 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi public static final String PREF_EXPERT = "expert"; public static final String PREF_UPD_LAST = "lastUpdateCheck"; public static final String PREF_ROOT_INSTALLER = "rootInstaller"; + public static final String PREF_SYSTEM_INSTALLER = "systemInstaller"; private static final boolean DEFAULT_COMPACT_LAYOUT = false; private static final boolean DEFAULT_ROOTED = true; private static final int DEFAULT_UPD_HISTORY = 14; private static final boolean DEFAULT_ROOT_INSTALLER = false; + private static final boolean DEFAULT_SYSTEM_INSTALLER = true; private boolean compactLayout = DEFAULT_COMPACT_LAYOUT; private boolean filterAppsRequiringRoot = DEFAULT_ROOTED; @@ -64,9 +66,13 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi initialized.put(key, false); } - public boolean useRootInstaller() { + public boolean isRootInstallerEnabled() { return preferences.getBoolean(PREF_ROOT_INSTALLER, DEFAULT_ROOT_INSTALLER); } + + public boolean isSystemInstallerEnabled() { + return preferences.getBoolean(PREF_SYSTEM_INSTALLER, DEFAULT_SYSTEM_INSTALLER); + } public boolean hasCompactLayout() { if (!isInitialized(PREF_COMPACT_LAYOUT)) { diff --git a/src/org/fdroid/fdroid/PreferencesActivity.java b/src/org/fdroid/fdroid/PreferencesActivity.java index 7f74c145e..a8013e4ca 100644 --- a/src/org/fdroid/fdroid/PreferencesActivity.java +++ b/src/org/fdroid/fdroid/PreferencesActivity.java @@ -55,7 +55,8 @@ public class PreferencesActivity extends PreferenceActivity implements Preferences.PREF_IGN_TOUCH, Preferences.PREF_CACHE_APK, Preferences.PREF_EXPERT, - Preferences.PREF_ROOT_INSTALLER + Preferences.PREF_ROOT_INSTALLER, + Preferences.PREF_SYSTEM_INSTALLER }; @Override @@ -156,6 +157,10 @@ public class PreferencesActivity extends PreferenceActivity implements onoffSummary(key, R.string.root_installer_on, R.string.root_installer_off); + } else if (key.equals(Preferences.PREF_SYSTEM_INSTALLER)) { + onoffSummary(key, R.string.system_installer_on, + R.string.system_installer_off); + } } @@ -164,7 +169,7 @@ public class PreferencesActivity extends PreferenceActivity implements * when the user grants root access for F-Droid. */ protected void initRootInstallerPreference() { - CheckBoxPreference pref = (CheckBoxPreference)findPreference(Preferences.PREF_ROOT_INSTALLER); + CheckBoxPreference pref = (CheckBoxPreference) findPreference(Preferences.PREF_ROOT_INSTALLER); // we are handling persistence ourself! pref.setPersistent(false); diff --git a/src/org/fdroid/fdroid/installer/Installer.java b/src/org/fdroid/fdroid/installer/Installer.java index f87c51604..e2ca27f96 100644 --- a/src/org/fdroid/fdroid/installer/Installer.java +++ b/src/org/fdroid/fdroid/installer/Installer.java @@ -107,8 +107,8 @@ abstract public class Installer { InstallerCallback callback) { // if root installer has been activated in preferences -> RootInstaller - boolean useRootInstaller = Preferences.get().useRootInstaller(); - if (useRootInstaller) { + boolean isRootInstallerEnabled = Preferences.get().isRootInstallerEnabled(); + if (isRootInstallerEnabled) { Log.d(TAG, "root installer preference enabled -> RootInstaller"); try { @@ -118,8 +118,9 @@ abstract public class Installer { } } - // system permissions -> SystemPermissionInstaller - if (hasSystemPermissions(activity, pm)) { + // system permissions and pref enabled -> SystemPermissionInstaller + boolean isSystemInstallerEnabled = Preferences.get().isSystemInstallerEnabled(); + if (isSystemInstallerEnabled && hasSystemPermissions(activity, pm)) { Log.d(TAG, "system permissions -> SystemPermissionInstaller"); try { @@ -158,7 +159,7 @@ abstract public class Installer { InstallerCallback callback) throws AndroidNotCompatibleException { // if root installer has been activated in preferences -> RootInstaller - boolean useRootInstaller = Preferences.get().useRootInstaller(); + boolean useRootInstaller = Preferences.get().isRootInstallerEnabled(); if (useRootInstaller) { try { return new RootInstaller(context, pm, callback); From acd3137cbc9cab48dabf919365cbeb1e0e0f032c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 12 May 2014 18:59:03 +0200 Subject: [PATCH 30/30] Make prefs coherent, rename them, and rename SystemPermissionInstaller to SystemInstaller --- res/values/strings.xml | 14 ++--- res/xml/preferences.xml | 2 +- src/org/fdroid/fdroid/Preferences.java | 2 +- .../fdroid/fdroid/PreferencesActivity.java | 51 ++++++++++++++++++- .../installer/DefaultInstallerSdk14.java | 2 + .../fdroid/fdroid/installer/Installer.java | 22 ++++---- ...ionInstaller.java => SystemInstaller.java} | 4 +- 7 files changed, 77 insertions(+), 20 deletions(-) rename src/org/fdroid/fdroid/installer/{SystemPermissionInstaller.java => SystemInstaller.java} (99%) diff --git a/res/values/strings.xml b/res/values/strings.xml index e2d07dfc8..de6073c47 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -28,12 +28,12 @@ Do not notify of any updates Update history Days to consider apps new or recent: %s - Root access for app installations - Root access is used to install/delete/update applications - Do not request root access to install/delete/update applications - Use system permissions for app installations - F-Droid tries to use system permissions to install/delete/update applications (only possible when installed as system-app) - F-Droid does not try to use system permissions to install/delete/update applications (only possible when installed as system-app) + Install using root access + Request root access to install, update, and remove packages + Do not request root access to install, update, and remove packages + Install using system-permissions + Use system permissions to install, update, and remove packages + Do not use system permissions to install, update, and remove packages Search Results App Details @@ -275,5 +275,7 @@ Update all (De-)Installation Error The (de-)installation failed. If you are using root access, try disabling this setting! + System permissions denied + This option is only available when F-Droid is installed as a system-app. diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index b9571a814..fa534900f 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -54,7 +54,7 @@ android:defaultValue="false" android:key="rootInstaller" /> diff --git a/src/org/fdroid/fdroid/Preferences.java b/src/org/fdroid/fdroid/Preferences.java index 29c2c0d39..44d1c113c 100644 --- a/src/org/fdroid/fdroid/Preferences.java +++ b/src/org/fdroid/fdroid/Preferences.java @@ -43,7 +43,7 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi private static final boolean DEFAULT_ROOTED = true; private static final int DEFAULT_UPD_HISTORY = 14; private static final boolean DEFAULT_ROOT_INSTALLER = false; - private static final boolean DEFAULT_SYSTEM_INSTALLER = true; + private static final boolean DEFAULT_SYSTEM_INSTALLER = false; private boolean compactLayout = DEFAULT_COMPACT_LAYOUT; private boolean filterAppsRequiringRoot = DEFAULT_ROOTED; diff --git a/src/org/fdroid/fdroid/PreferencesActivity.java b/src/org/fdroid/fdroid/PreferencesActivity.java index a8013e4ca..55c36ec3e 100644 --- a/src/org/fdroid/fdroid/PreferencesActivity.java +++ b/src/org/fdroid/fdroid/PreferencesActivity.java @@ -35,6 +35,7 @@ import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.compat.ActionBarCompat; import org.fdroid.fdroid.installer.CheckRootAsyncTask; import org.fdroid.fdroid.installer.CheckRootAsyncTask.CheckRootCallback; +import org.fdroid.fdroid.installer.Installer; public class PreferencesActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener { @@ -217,13 +218,59 @@ public class PreferencesActivity extends PreferenceActivity implements return true; } }); + } + + /** + * Initializes SystemInstaller preference, which can only be enabled when F-Droid is installed as a system-app + */ + protected void initSystemInstallerPreference() { + CheckBoxPreference pref = (CheckBoxPreference) findPreference(Preferences.PREF_SYSTEM_INSTALLER); + // we are handling persistence ourself! + pref.setPersistent(false); + + pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { + + @Override + public boolean onPreferenceClick(Preference preference) { + final CheckBoxPreference pref = (CheckBoxPreference) preference; + + if (pref.isChecked()) { + if (Installer.hasSystemPermissions(PreferencesActivity.this, PreferencesActivity.this.getPackageManager())) { + // system-permission are granted, i.e. F-Droid is a system-app + SharedPreferences.Editor editor = pref.getSharedPreferences().edit(); + editor.putBoolean(Preferences.PREF_SYSTEM_INSTALLER, true); + editor.commit(); + pref.setChecked(true); + } else { + // system-permission not available + SharedPreferences.Editor editor = pref.getSharedPreferences().edit(); + editor.putBoolean(Preferences.PREF_SYSTEM_INSTALLER, false); + editor.commit(); + pref.setChecked(false); + + AlertDialog.Builder alertBuilder = new AlertDialog.Builder(PreferencesActivity.this); + alertBuilder.setTitle(R.string.system_permission_denied_title); + alertBuilder.setMessage(PreferencesActivity.this.getString(R.string.system_permission_denied_body)); + alertBuilder.setNeutralButton(android.R.string.ok, null); + alertBuilder.create().show(); + } + } else { + SharedPreferences.Editor editor = pref.getSharedPreferences().edit(); + editor.putBoolean(Preferences.PREF_SYSTEM_INSTALLER, false); + editor.commit(); + pref.setChecked(false); + } + + return true; + } + }); } @Override protected void onResume() { - super.onResume(); + getPreferenceScreen().getSharedPreferences() .registerOnSharedPreferenceChangeListener(this); @@ -232,11 +279,13 @@ public class PreferencesActivity extends PreferenceActivity implements } initRootInstallerPreference(); + initSystemInstallerPreference(); } @Override protected void onPause() { super.onPause(); + getPreferenceScreen().getSharedPreferences() .unregisterOnSharedPreferenceChangeListener(this); } diff --git a/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java b/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java index 4a33716ed..b45241b80 100644 --- a/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java +++ b/src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java @@ -121,6 +121,8 @@ public class DefaultInstallerSdk14 extends Installer { 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); } diff --git a/src/org/fdroid/fdroid/installer/Installer.java b/src/org/fdroid/fdroid/installer/Installer.java index e2ca27f96..dfcae1b82 100644 --- a/src/org/fdroid/fdroid/installer/Installer.java +++ b/src/org/fdroid/fdroid/installer/Installer.java @@ -118,16 +118,20 @@ abstract public class Installer { } } - // system permissions and pref enabled -> SystemPermissionInstaller + // system permissions and pref enabled -> SystemInstaller boolean isSystemInstallerEnabled = Preferences.get().isSystemInstallerEnabled(); - if (isSystemInstallerEnabled && hasSystemPermissions(activity, pm)) { - Log.d(TAG, "system permissions -> SystemPermissionInstaller"); + if (isSystemInstallerEnabled) { + if (hasSystemPermissions(activity, pm)) { + Log.d(TAG, "system permissions -> SystemInstaller"); - try { - return new SystemPermissionInstaller(activity, pm, callback); - } catch (AndroidNotCompatibleException e) { - Log.e(TAG, "Android not compatible with SystemPermissionInstaller!", e); + try { + return new SystemInstaller(activity, pm, callback); + } catch (AndroidNotCompatibleException 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!"); } // Fallback -> DefaultInstaller @@ -170,14 +174,14 @@ abstract public class Installer { if (hasSystemPermissions(context, pm)) { // we have system permissions! - return new SystemPermissionInstaller(context, pm, callback); + return new SystemInstaller(context, pm, callback); } else { // nope! throw new AndroidNotCompatibleException(); } } - private static boolean hasSystemPermissions(Context context, PackageManager pm) { + public static boolean hasSystemPermissions(Context context, PackageManager pm) { int checkInstallPermission = pm.checkPermission(permission.INSTALL_PACKAGES, context.getPackageName()); int checkDeletePermission = diff --git a/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java b/src/org/fdroid/fdroid/installer/SystemInstaller.java similarity index 99% rename from src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java rename to src/org/fdroid/fdroid/installer/SystemInstaller.java index f97cd6858..c3af08249 100644 --- a/src/org/fdroid/fdroid/installer/SystemPermissionInstaller.java +++ b/src/org/fdroid/fdroid/installer/SystemInstaller.java @@ -56,14 +56,14 @@ import android.util.Log; * https://android.googlesource.com/platform * /frameworks/base/+/ccbf84f44c9e6a5ed3c08673614826bb237afc54 */ -public class SystemPermissionInstaller extends Installer { +public class SystemInstaller extends Installer { private PackageInstallObserver mInstallObserver; private PackageDeleteObserver mDeleteObserver; private Method mInstallMethod; private Method mDeleteMethod; - public SystemPermissionInstaller(Context context, PackageManager pm, + public SystemInstaller(Context context, PackageManager pm, InstallerCallback callback) throws AndroidNotCompatibleException { super(context, pm, callback);