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(); }