/* * Copyright (C) 2014-2016 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2016 Blue Jay Wireless * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 3 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ package org.fdroid.fdroid.installer; import android.annotation.SuppressLint; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.util.Log; import org.fdroid.fdroid.R; import org.fdroid.fdroid.data.Apk; /** * A transparent activity as a wrapper around Android's PackageInstaller Intents */ public class DefaultInstallerActivity extends FragmentActivity { private static final String TAG = "DefaultInstallerActivit"; static final String ACTION_INSTALL_PACKAGE = "org.fdroid.fdroid.installer.DefaultInstaller.action.INSTALL_PACKAGE"; static final String ACTION_UNINSTALL_PACKAGE = "org.fdroid.fdroid.installer.DefaultInstaller.action.UNINSTALL_PACKAGE"; private static final int REQUEST_CODE_INSTALL = 0; private static final int REQUEST_CODE_UNINSTALL = 1; private Uri downloadUri; // for the broadcasts private DefaultInstaller installer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); String action = intent.getAction(); Apk apk = intent.getParcelableExtra(Installer.EXTRA_APK); installer = new DefaultInstaller(this, apk); if (ACTION_INSTALL_PACKAGE.equals(action)) { Uri localApkUri = intent.getData(); downloadUri = intent.getParcelableExtra(Installer.EXTRA_DOWNLOAD_URI); installPackage(localApkUri); } else if (ACTION_UNINSTALL_PACKAGE.equals(action)) { uninstallPackage(apk.packageName); } else { throw new IllegalStateException("Intent action not specified!"); } } @SuppressLint("InlinedApi") private void installPackage(Uri uri) { if (uri == null) { throw new RuntimeException("Set the data uri to point to an apk location!"); } // https://code.google.com/p/android/issues/detail?id=205827 if ((Build.VERSION.SDK_INT < 24) && (!uri.getScheme().equals("file"))) { throw new RuntimeException("PackageInstaller < Android N only supports file scheme!"); } if ((Build.VERSION.SDK_INT >= 24) && (!uri.getScheme().equals("content"))) { throw new RuntimeException("PackageInstaller >= Android N only supports content scheme!"); } Intent intent = new Intent(); // Note regarding EXTRA_NOT_UNKNOWN_SOURCE: // works only when being installed as system-app // https://code.google.com/p/android/issues/detail?id=42253 if (Build.VERSION.SDK_INT < 14) { intent.setAction(Intent.ACTION_VIEW); intent.setDataAndType(uri, "application/vnd.android.package-archive"); } else if (Build.VERSION.SDK_INT < 16) { intent.setAction(Intent.ACTION_INSTALL_PACKAGE); intent.setData(uri); intent.putExtra(Intent.EXTRA_RETURN_RESULT, true); intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true); intent.putExtra(Intent.EXTRA_ALLOW_REPLACE, true); } else if (Build.VERSION.SDK_INT < 24) { intent.setAction(Intent.ACTION_INSTALL_PACKAGE); intent.setData(uri); intent.putExtra(Intent.EXTRA_RETURN_RESULT, true); intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true); } else { // Android N intent.setAction(Intent.ACTION_INSTALL_PACKAGE); intent.setData(uri); // grant READ permission for this content Uri intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.putExtra(Intent.EXTRA_RETURN_RESULT, true); intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true); } try { startActivityForResult(intent, REQUEST_CODE_INSTALL); } catch (ActivityNotFoundException e) { Log.e(TAG, "ActivityNotFoundException", e); installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED, "This Android rom does not support ACTION_INSTALL_PACKAGE!"); finish(); } } private void uninstallPackage(String packageName) { // check that the package is installed try { getPackageManager().getPackageInfo(packageName, 0); } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "NameNotFoundException", e); installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_INTERRUPTED, "Package that is scheduled for uninstall is not installed!"); finish(); return; } Uri uri = Uri.fromParts("package", packageName, null); Intent intent = new Intent(); intent.setData(uri); if (Build.VERSION.SDK_INT < 14) { intent.setAction(Intent.ACTION_DELETE); } else { intent.setAction(Intent.ACTION_UNINSTALL_PACKAGE); intent.putExtra(Intent.EXTRA_RETURN_RESULT, true); } try { startActivityForResult(intent, REQUEST_CODE_UNINSTALL); } catch (ActivityNotFoundException e) { Log.e(TAG, "ActivityNotFoundException", e); installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_INTERRUPTED, "This Android rom does not support ACTION_UNINSTALL_PACKAGE!"); finish(); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case REQUEST_CODE_INSTALL: /** * resultCode is always 0 on Android < 4.0. See * com.android.packageinstaller.PackageInstallerActivity: setResult is * never executed on Androids < 4.0 */ if (Build.VERSION.SDK_INT < 14) { installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_COMPLETE); break; } switch (resultCode) { case Activity.RESULT_OK: installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_COMPLETE); break; case Activity.RESULT_CANCELED: installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED); break; case Activity.RESULT_FIRST_USER: default: // AOSP returns Activity.RESULT_FIRST_USER on error installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED, getString(R.string.install_error_unknown)); break; } break; case REQUEST_CODE_UNINSTALL: // resultCode is always 0 on Android < 4.0. if (Build.VERSION.SDK_INT < 14) { installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_COMPLETE); break; } switch (resultCode) { case Activity.RESULT_OK: installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_COMPLETE); break; case Activity.RESULT_CANCELED: installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_INTERRUPTED); break; case Activity.RESULT_FIRST_USER: default: // AOSP UninstallAppProgress returns RESULT_FIRST_USER on error installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_INTERRUPTED, getString(R.string.uninstall_error_unknown)); break; } break; default: throw new RuntimeException("Invalid request code!"); } // after doing the broadcasts, finish this transparent wrapper activity finish(); } }