Refactor installer package
This commit is contained in:
parent
6d2f2d20a8
commit
a2356b05d6
@ -86,7 +86,7 @@ import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
import org.fdroid.fdroid.data.InstalledAppProvider;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
import org.fdroid.fdroid.installer.InstallHelper;
|
||||
import org.fdroid.fdroid.installer.Installer;
|
||||
import org.fdroid.fdroid.installer.InstallManagerService;
|
||||
import org.fdroid.fdroid.installer.InstallerService;
|
||||
import org.fdroid.fdroid.net.Downloader;
|
||||
@ -532,7 +532,7 @@ public class AppDetails extends AppCompatActivity {
|
||||
Uri localUri =
|
||||
Uri.fromFile(new File(intent.getStringExtra(Downloader.EXTRA_DOWNLOAD_PATH)));
|
||||
localBroadcastManager.registerReceiver(installReceiver,
|
||||
InstallerService.getInstallIntentFilter(localUri));
|
||||
Installer.getInstallIntentFilter(localUri));
|
||||
}
|
||||
};
|
||||
|
||||
@ -555,12 +555,12 @@ public class AppDetails extends AppCompatActivity {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()) {
|
||||
case InstallHelper.ACTION_INSTALL_STARTED: {
|
||||
case Installer.ACTION_INSTALL_STARTED: {
|
||||
headerFragment.startProgress();
|
||||
headerFragment.showIndeterminateProgress(getString(R.string.installing));
|
||||
break;
|
||||
}
|
||||
case InstallHelper.ACTION_INSTALL_COMPLETE: {
|
||||
case Installer.ACTION_INSTALL_COMPLETE: {
|
||||
headerFragment.removeProgress();
|
||||
localBroadcastManager.unregisterReceiver(this);
|
||||
|
||||
@ -569,7 +569,7 @@ public class AppDetails extends AppCompatActivity {
|
||||
onAppChanged();
|
||||
break;
|
||||
}
|
||||
case InstallHelper.ACTION_INSTALL_INTERRUPTED: {
|
||||
case Installer.ACTION_INSTALL_INTERRUPTED: {
|
||||
headerFragment.removeProgress();
|
||||
localBroadcastManager.unregisterReceiver(this);
|
||||
|
||||
@ -613,9 +613,9 @@ public class AppDetails extends AppCompatActivity {
|
||||
// });
|
||||
break;
|
||||
}
|
||||
case InstallHelper.ACTION_INSTALL_USER_INTERACTION: {
|
||||
case Installer.ACTION_INSTALL_USER_INTERACTION: {
|
||||
PendingIntent installPendingIntent =
|
||||
intent.getParcelableExtra(InstallHelper.EXTRA_USER_INTERACTION_PI);
|
||||
intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI);
|
||||
|
||||
try {
|
||||
installPendingIntent.send();
|
||||
@ -636,19 +636,19 @@ public class AppDetails extends AppCompatActivity {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()) {
|
||||
case InstallHelper.ACTION_UNINSTALL_STARTED: {
|
||||
case Installer.ACTION_UNINSTALL_STARTED: {
|
||||
headerFragment.startProgress();
|
||||
headerFragment.showIndeterminateProgress(getString(R.string.uninstalling));
|
||||
break;
|
||||
}
|
||||
case InstallHelper.ACTION_UNINSTALL_COMPLETE: {
|
||||
case Installer.ACTION_UNINSTALL_COMPLETE: {
|
||||
headerFragment.removeProgress();
|
||||
localBroadcastManager.unregisterReceiver(this);
|
||||
|
||||
onAppChanged();
|
||||
break;
|
||||
}
|
||||
case InstallHelper.ACTION_UNINSTALL_INTERRUPTED: {
|
||||
case Installer.ACTION_UNINSTALL_INTERRUPTED: {
|
||||
headerFragment.removeProgress();
|
||||
localBroadcastManager.unregisterReceiver(this);
|
||||
|
||||
@ -691,9 +691,9 @@ public class AppDetails extends AppCompatActivity {
|
||||
// });
|
||||
break;
|
||||
}
|
||||
case InstallHelper.ACTION_UNINSTALL_USER_INTERACTION: {
|
||||
case Installer.ACTION_UNINSTALL_USER_INTERACTION: {
|
||||
PendingIntent uninstallPendingIntent =
|
||||
intent.getParcelableExtra(InstallHelper.EXTRA_USER_INTERACTION_PI);
|
||||
intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI);
|
||||
|
||||
try {
|
||||
uninstallPendingIntent.send();
|
||||
@ -1038,7 +1038,7 @@ public class AppDetails extends AppCompatActivity {
|
||||
|
||||
private void uninstallApk(String packageName) {
|
||||
localBroadcastManager.registerReceiver(uninstallReceiver,
|
||||
InstallerService.getUninstallIntentFilter(packageName));
|
||||
Installer.getUninstallIntentFilter(packageName));
|
||||
InstallerService.uninstall(context, packageName);
|
||||
}
|
||||
|
||||
|
@ -270,7 +270,7 @@ public final class Utils {
|
||||
* This location is only for caching, do not install directly from this location
|
||||
* because if the file is on the External Storage, any other app could swap out
|
||||
* the APK while the install was in process, allowing malware to install things.
|
||||
* Using {@link org.fdroid.fdroid.installer.Installer#installPackage(File, String, String)}
|
||||
* Using {@link Installer#installPackage(File, String, String)}
|
||||
* is fine since that does the right thing.
|
||||
*/
|
||||
public static File getApkCacheDir(Context context) {
|
||||
|
@ -28,8 +28,6 @@ import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.Utils;
|
||||
@ -49,18 +47,19 @@ public class AndroidInstallerActivity extends FragmentActivity {
|
||||
private static final int REQUEST_CODE_INSTALL = 0;
|
||||
private static final int REQUEST_CODE_UNINSTALL = 1;
|
||||
|
||||
private LocalBroadcastManager localBroadcastManager;
|
||||
|
||||
private Uri mInstallOriginatingUri;
|
||||
private Uri mInstallUri;
|
||||
|
||||
private String mUninstallPackageName;
|
||||
|
||||
// for the broadcasts
|
||||
private DefaultInstaller installer;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
localBroadcastManager = LocalBroadcastManager.getInstance(this);
|
||||
installer = new DefaultInstaller(this);
|
||||
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getAction();
|
||||
@ -131,12 +130,12 @@ public class AndroidInstallerActivity extends FragmentActivity {
|
||||
startActivityForResult(intent, REQUEST_CODE_INSTALL);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.e(TAG, "ActivityNotFoundException", e);
|
||||
sendBroadcastInstall(uri, originatingUri, InstallHelper.ACTION_INSTALL_INTERRUPTED,
|
||||
installer.sendBroadcastInstall(uri, originatingUri, Installer.ACTION_INSTALL_INTERRUPTED,
|
||||
"This Android rom does not support ACTION_INSTALL_PACKAGE!");
|
||||
finish();
|
||||
}
|
||||
sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
|
||||
InstallHelper.ACTION_INSTALL_STARTED);
|
||||
installer.sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
|
||||
Installer.ACTION_INSTALL_STARTED);
|
||||
}
|
||||
|
||||
protected void uninstallPackage(String packageName) {
|
||||
@ -147,7 +146,7 @@ public class AndroidInstallerActivity extends FragmentActivity {
|
||||
getPackageManager().getPackageInfo(packageName, 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e(TAG, "NameNotFoundException", e);
|
||||
sendBroadcastUninstall(packageName, InstallHelper.ACTION_UNINSTALL_INTERRUPTED,
|
||||
installer.sendBroadcastUninstall(packageName, Installer.ACTION_UNINSTALL_INTERRUPTED,
|
||||
"Package that is scheduled for uninstall is not installed!");
|
||||
finish();
|
||||
return;
|
||||
@ -167,7 +166,7 @@ public class AndroidInstallerActivity extends FragmentActivity {
|
||||
startActivityForResult(intent, REQUEST_CODE_UNINSTALL);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.e(TAG, "ActivityNotFoundException", e);
|
||||
sendBroadcastUninstall(packageName, InstallHelper.ACTION_UNINSTALL_INTERRUPTED,
|
||||
installer.sendBroadcastUninstall(packageName, Installer.ACTION_UNINSTALL_INTERRUPTED,
|
||||
"This Android rom does not support ACTION_UNINSTALL_PACKAGE!");
|
||||
finish();
|
||||
}
|
||||
@ -183,34 +182,34 @@ public class AndroidInstallerActivity extends FragmentActivity {
|
||||
* never executed on Androids < 4.0
|
||||
*/
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
|
||||
InstallHelper.ACTION_INSTALL_COMPLETE);
|
||||
installer.sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
|
||||
Installer.ACTION_INSTALL_COMPLETE);
|
||||
break;
|
||||
}
|
||||
|
||||
// Fallback on N for https://gitlab.com/fdroid/fdroidclient/issues/631
|
||||
if ("N".equals(Build.VERSION.CODENAME)) {
|
||||
sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
|
||||
InstallHelper.ACTION_INSTALL_COMPLETE);
|
||||
installer.sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
|
||||
Installer.ACTION_INSTALL_COMPLETE);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (resultCode) {
|
||||
case Activity.RESULT_OK: {
|
||||
sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
|
||||
InstallHelper.ACTION_INSTALL_COMPLETE);
|
||||
installer.sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
|
||||
Installer.ACTION_INSTALL_COMPLETE);
|
||||
break;
|
||||
}
|
||||
case Activity.RESULT_CANCELED: {
|
||||
sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
|
||||
InstallHelper.ACTION_INSTALL_INTERRUPTED);
|
||||
installer.sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
|
||||
Installer.ACTION_INSTALL_INTERRUPTED);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
case Activity.RESULT_FIRST_USER: {
|
||||
// AOSP actually returns Activity.RESULT_FIRST_USER if something breaks
|
||||
sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
|
||||
InstallHelper.ACTION_INSTALL_INTERRUPTED, "error");
|
||||
installer.sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
|
||||
Installer.ACTION_INSTALL_INTERRUPTED, "error");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -220,28 +219,28 @@ public class AndroidInstallerActivity extends FragmentActivity {
|
||||
case REQUEST_CODE_UNINSTALL: {
|
||||
// resultCode is always 0 on Android < 4.0.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
sendBroadcastUninstall(mUninstallPackageName,
|
||||
InstallHelper.ACTION_UNINSTALL_COMPLETE);
|
||||
installer.sendBroadcastUninstall(mUninstallPackageName,
|
||||
Installer.ACTION_UNINSTALL_COMPLETE);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (resultCode) {
|
||||
case Activity.RESULT_OK: {
|
||||
sendBroadcastUninstall(mUninstallPackageName,
|
||||
InstallHelper.ACTION_UNINSTALL_COMPLETE);
|
||||
installer.sendBroadcastUninstall(mUninstallPackageName,
|
||||
Installer.ACTION_UNINSTALL_COMPLETE);
|
||||
break;
|
||||
}
|
||||
case Activity.RESULT_CANCELED: {
|
||||
sendBroadcastUninstall(mUninstallPackageName,
|
||||
InstallHelper.ACTION_UNINSTALL_INTERRUPTED);
|
||||
installer.sendBroadcastUninstall(mUninstallPackageName,
|
||||
Installer.ACTION_UNINSTALL_INTERRUPTED);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
case Activity.RESULT_FIRST_USER: {
|
||||
// AOSP UninstallAppProgress actually returns
|
||||
// Activity.RESULT_FIRST_USER if something breaks
|
||||
sendBroadcastUninstall(mUninstallPackageName,
|
||||
InstallHelper.ACTION_UNINSTALL_INTERRUPTED,
|
||||
installer.sendBroadcastUninstall(mUninstallPackageName,
|
||||
Installer.ACTION_UNINSTALL_INTERRUPTED,
|
||||
"error");
|
||||
break;
|
||||
}
|
||||
@ -258,34 +257,4 @@ public class AndroidInstallerActivity extends FragmentActivity {
|
||||
finish();
|
||||
}
|
||||
|
||||
private void sendBroadcastInstall(Uri uri, Uri originatingUri, String action) {
|
||||
sendBroadcastInstall(uri, originatingUri, action, null);
|
||||
}
|
||||
|
||||
private void sendBroadcastInstall(Uri uri, Uri originatingUri, String action, String errorMessage) {
|
||||
Intent intent = new Intent(action);
|
||||
intent.setData(uri);
|
||||
intent.putExtra(InstallHelper.EXTRA_ORIGINATING_URI, originatingUri);
|
||||
if (!TextUtils.isEmpty(errorMessage)) {
|
||||
intent.putExtra(InstallHelper.EXTRA_ERROR_MESSAGE, errorMessage);
|
||||
}
|
||||
localBroadcastManager.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
private void sendBroadcastUninstall(String packageName, String action) {
|
||||
sendBroadcastUninstall(packageName, action, null);
|
||||
}
|
||||
|
||||
private void sendBroadcastUninstall(String packageName, String action, String errorMessage) {
|
||||
Uri uri = Uri.fromParts("package", packageName, null);
|
||||
|
||||
Intent intent = new Intent(action);
|
||||
intent.setData(uri); // for broadcast filter
|
||||
intent.putExtra(InstallHelper.EXTRA_UNINSTALL_PACKAGE_NAME, packageName);
|
||||
if (!TextUtils.isEmpty(errorMessage)) {
|
||||
intent.putExtra(InstallHelper.EXTRA_ERROR_MESSAGE, errorMessage);
|
||||
}
|
||||
localBroadcastManager.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,100 +1,97 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package org.fdroid.fdroid.installer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.fdroid.fdroid.BuildConfig;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.privileged.install.InstallExtensionDialogActivity;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* For Android < 4: Default Installer using the public PackageManager API of
|
||||
* Android to install/delete packages. This starts a Activity from the Android
|
||||
* OS showing all permissions/changed permissions. The the user needs to
|
||||
* manually press an install button, this Installer cannot be used for
|
||||
* unattended installations.
|
||||
*/
|
||||
public class DefaultInstaller extends Installer {
|
||||
private final Activity mActivity;
|
||||
|
||||
public DefaultInstaller(Activity activity, PackageManager pm, InstallerCallback callback)
|
||||
throws InstallFailedException {
|
||||
super(activity, pm, callback);
|
||||
this.mActivity = activity;
|
||||
private static final String TAG = "DefaultInstaller";
|
||||
|
||||
DefaultInstaller(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
private static final int REQUEST_CODE_INSTALL = 0;
|
||||
private static final int REQUEST_CODE_DELETE = 1;
|
||||
|
||||
@Override
|
||||
protected void installPackageInternal(File apkFile) throws InstallFailedException {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(Uri.fromFile(apkFile),
|
||||
"application/vnd.android.package-archive");
|
||||
protected void installPackage(Uri uri, Uri originatingUri, String packageName) {
|
||||
|
||||
sendBroadcastInstall(uri, originatingUri, Installer.ACTION_INSTALL_STARTED);
|
||||
|
||||
Utils.debugLog(TAG, "ACTION_INSTALL uri: " + uri + " file: " + new File(uri.getPath()));
|
||||
|
||||
// TODO: rework for uri
|
||||
File sanitizedFile = null;
|
||||
try {
|
||||
mActivity.startActivityForResult(intent, REQUEST_CODE_INSTALL);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
throw new InstallFailedException(e);
|
||||
sanitizedFile = Installer.prepareApkFile(mContext, new File(uri.getPath()), packageName);
|
||||
} catch (Installer.InstallFailedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
Uri sanitizedUri = Uri.fromFile(sanitizedFile);
|
||||
|
||||
@Override
|
||||
protected void deletePackageInternal(String packageName) throws InstallFailedException {
|
||||
try {
|
||||
PackageInfo pkgInfo = mPm.getPackageInfo(packageName, 0);
|
||||
Utils.debugLog(TAG, "ACTION_INSTALL sanitizedUri: " + sanitizedUri);
|
||||
|
||||
Uri uri = Uri.fromParts("package", pkgInfo.packageName, null);
|
||||
Intent intent = new Intent(Intent.ACTION_DELETE, uri);
|
||||
try {
|
||||
mActivity.startActivityForResult(intent, REQUEST_CODE_DELETE);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
throw new InstallFailedException(e);
|
||||
Intent installIntent;
|
||||
// special case: F-Droid Privileged Extension
|
||||
if (packageName != null && packageName.equals(PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME)) {
|
||||
|
||||
// extension must be signed with the same public key as main F-Droid
|
||||
// NOTE: Disabled for debug builds to be able to use official extension from repo
|
||||
ApkSignatureVerifier signatureVerifier = new ApkSignatureVerifier(mContext);
|
||||
if (!BuildConfig.DEBUG && !signatureVerifier.hasFDroidSignature(sanitizedFile)) {
|
||||
throw new RuntimeException("APK signature of extension not correct!");
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// already checked in super class
|
||||
|
||||
installIntent = new Intent(mContext, InstallExtensionDialogActivity.class);
|
||||
installIntent.setAction(InstallExtensionDialogActivity.ACTION_INSTALL);
|
||||
installIntent.putExtra(InstallExtensionDialogActivity.EXTRA_INSTALL_APK,
|
||||
sanitizedFile.getAbsolutePath());
|
||||
} else {
|
||||
installIntent = new Intent(mContext, AndroidInstallerActivity.class);
|
||||
installIntent.setAction(AndroidInstallerActivity.ACTION_INSTALL_PACKAGE);
|
||||
installIntent.putExtra(AndroidInstallerActivity.EXTRA_ORIGINATING_URI, originatingUri);
|
||||
installIntent.setData(sanitizedUri);
|
||||
}
|
||||
|
||||
PendingIntent installPendingIntent = PendingIntent.getActivity(
|
||||
mContext.getApplicationContext(),
|
||||
uri.hashCode(),
|
||||
installIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
sendBroadcastInstall(uri, originatingUri,
|
||||
Installer.ACTION_INSTALL_USER_INTERACTION, installPendingIntent);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
/**
|
||||
* resultCode is always 0 on Android < 4.0. See
|
||||
* com.android.packageinstaller.PackageInstallerActivity: setResult is
|
||||
* never executed on Androids before 4.0
|
||||
*/
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_INSTALL:
|
||||
mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL);
|
||||
protected void uninstallPackage(String packageName) {
|
||||
sendBroadcastUninstall(packageName, Installer.ACTION_UNINSTALL_STARTED);
|
||||
|
||||
return true;
|
||||
case REQUEST_CODE_DELETE:
|
||||
mCallback.onSuccess(InstallerCallback.OPERATION_DELETE);
|
||||
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
Intent uninstallIntent;
|
||||
// special case: F-Droid Privileged Extension
|
||||
if (packageName != null && packageName.equals(PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME)) {
|
||||
uninstallIntent = new Intent(mContext, InstallExtensionDialogActivity.class);
|
||||
uninstallIntent.setAction(InstallExtensionDialogActivity.ACTION_UNINSTALL);
|
||||
} else {
|
||||
uninstallIntent = new Intent(mContext, AndroidInstallerActivity.class);
|
||||
uninstallIntent.setAction(AndroidInstallerActivity.ACTION_UNINSTALL_PACKAGE);
|
||||
uninstallIntent.putExtra(
|
||||
AndroidInstallerActivity.EXTRA_UNINSTALL_PACKAGE_NAME, packageName);
|
||||
}
|
||||
PendingIntent uninstallPendingIntent = PendingIntent.getActivity(
|
||||
mContext.getApplicationContext(),
|
||||
packageName.hashCode(),
|
||||
uninstallIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
sendBroadcastUninstall(packageName,
|
||||
Installer.ACTION_UNINSTALL_USER_INTERACTION, uninstallPendingIntent);
|
||||
}
|
||||
}
|
||||
|
@ -1,134 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package org.fdroid.fdroid.installer;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* For Android >= 4.0: Default Installer using the public PackageManager API of
|
||||
* Android to install/delete packages. This starts a Activity from the Android
|
||||
* OS showing all permissions/changed permissions. The the user needs to
|
||||
* manually press an install button, this Installer cannot be used for
|
||||
* unattended installations.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
||||
public class DefaultSdk14Installer extends Installer {
|
||||
private final Activity mActivity;
|
||||
|
||||
public DefaultSdk14Installer(Activity activity, PackageManager pm, InstallerCallback callback)
|
||||
throws InstallFailedException {
|
||||
super(activity, pm, callback);
|
||||
this.mActivity = activity;
|
||||
}
|
||||
|
||||
private static final int REQUEST_CODE_INSTALL = 0;
|
||||
private static final int REQUEST_CODE_DELETE = 1;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
protected void installPackageInternal(File apkFile) throws InstallFailedException {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_INSTALL_PACKAGE);
|
||||
intent.setData(Uri.fromFile(apkFile));
|
||||
// EXTRA_RETURN_RESULT throws a RuntimeException on N
|
||||
// https://gitlab.com/fdroid/fdroidclient/issues/631
|
||||
if (!"N".equals(Build.VERSION.CODENAME)) {
|
||||
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
|
||||
}
|
||||
|
||||
// following extras only work when being installed as system-app
|
||||
// https://code.google.com/p/android/issues/detail?id=42253
|
||||
intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
|
||||
if (Build.VERSION.SDK_INT < 16) {
|
||||
// deprecated in Android 4.1
|
||||
intent.putExtra(Intent.EXTRA_ALLOW_REPLACE, true);
|
||||
}
|
||||
try {
|
||||
mActivity.startActivityForResult(intent, REQUEST_CODE_INSTALL);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
throw new InstallFailedException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deletePackageInternal(String packageName) throws InstallFailedException {
|
||||
try {
|
||||
PackageInfo pkgInfo = mPm.getPackageInfo(packageName, 0);
|
||||
|
||||
Uri uri = Uri.fromParts("package", pkgInfo.packageName, null);
|
||||
Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, uri);
|
||||
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
|
||||
try {
|
||||
mActivity.startActivityForResult(intent, REQUEST_CODE_DELETE);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
throw new InstallFailedException(e);
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// already checked in super class
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_INSTALL:
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL);
|
||||
} else if (resultCode == Activity.RESULT_CANCELED) {
|
||||
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
InstallerCallback.ERROR_CODE_CANCELED);
|
||||
} else {
|
||||
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
InstallerCallback.ERROR_CODE_OTHER);
|
||||
}
|
||||
// Fallback on N for https://gitlab.com/fdroid/fdroidclient/issues/631
|
||||
if ("N".equals(Build.VERSION.CODENAME)) {
|
||||
mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL);
|
||||
}
|
||||
|
||||
return true;
|
||||
case REQUEST_CODE_DELETE:
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
mCallback.onSuccess(InstallerCallback.OPERATION_DELETE);
|
||||
} else if (resultCode == Activity.RESULT_CANCELED) {
|
||||
mCallback.onError(InstallerCallback.OPERATION_DELETE,
|
||||
InstallerCallback.ERROR_CODE_CANCELED);
|
||||
} else {
|
||||
// UninstallAppProgress actually returns
|
||||
// Activity.RESULT_FIRST_USER if something breaks
|
||||
mCallback.onError(InstallerCallback.OPERATION_DELETE,
|
||||
InstallerCallback.ERROR_CODE_OTHER);
|
||||
}
|
||||
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,128 +0,0 @@
|
||||
package org.fdroid.fdroid.installer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.fdroid.fdroid.AndroidXMLDecompress;
|
||||
import org.fdroid.fdroid.BuildConfig;
|
||||
import org.fdroid.fdroid.Hasher;
|
||||
import org.fdroid.fdroid.compat.FileCompat;
|
||||
import org.fdroid.fdroid.data.SanitizedFile;
|
||||
import org.fdroid.fdroid.privileged.install.InstallExtensionDialogActivity;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Map;
|
||||
|
||||
public class InstallHelper {
|
||||
|
||||
|
||||
public static final String ACTION_INSTALL_STARTED = "org.fdroid.fdroid.installer.Installer.action.INSTALL_STARTED";
|
||||
public static final String ACTION_INSTALL_COMPLETE = "org.fdroid.fdroid.installer.Installer.action.INSTALL_COMPLETE";
|
||||
public static final String ACTION_INSTALL_INTERRUPTED = "org.fdroid.fdroid.installer.Installer.action.INSTALL_INTERRUPTED";
|
||||
public static final String ACTION_INSTALL_USER_INTERACTION = "org.fdroid.fdroid.installer.Installer.action.INSTALL_USER_INTERACTION";
|
||||
|
||||
public static final String ACTION_UNINSTALL_STARTED = "org.fdroid.fdroid.installer.Installer.action.UNINSTALL_STARTED";
|
||||
public static final String ACTION_UNINSTALL_COMPLETE = "org.fdroid.fdroid.installer.Installer.action.UNINSTALL_COMPLETE";
|
||||
public static final String ACTION_UNINSTALL_INTERRUPTED = "org.fdroid.fdroid.installer.Installer.action.UNINSTALL_INTERRUPTED";
|
||||
public static final String ACTION_UNINSTALL_USER_INTERACTION = "org.fdroid.fdroid.installer.Installer.action.UNINSTALL_USER_INTERACTION";
|
||||
|
||||
/**
|
||||
* Same as http://developer.android.com/reference/android/content/Intent.html#EXTRA_ORIGINATING_URI
|
||||
* In InstallManagerService often called urlString
|
||||
*/
|
||||
public static final String EXTRA_ORIGINATING_URI = "org.fdroid.fdroid.installer.InstallerService.extra.ORIGINATING_URI";
|
||||
public static final String EXTRA_UNINSTALL_PACKAGE_NAME = "org.fdroid.fdroid.installer.InstallerService.extra.UNINSTALL_PACKAGE_NAME";
|
||||
|
||||
public static final String EXTRA_USER_INTERACTION_PI = "org.fdroid.fdroid.installer.InstallerService.extra.USER_INTERACTION_PI";
|
||||
|
||||
|
||||
public static final String EXTRA_ERROR_MESSAGE = "org.fdroid.fdroid.net.Downloader.extra.ERROR_MESSAGE";
|
||||
|
||||
|
||||
|
||||
public static SanitizedFile preparePackage(Context context, File apkFile, String packageName, String urlString)
|
||||
throws Installer.InstallFailedException {
|
||||
SanitizedFile apkToInstall;
|
||||
try {
|
||||
Map<String, Object> attributes = AndroidXMLDecompress.getManifestHeaderAttributes(apkFile.getAbsolutePath());
|
||||
|
||||
/* This isn't really needed, but might as well since we have the data already */
|
||||
// if (attributes.containsKey("packageName") && !TextUtils.equals(packageName, (String) attributes.get("packageName"))) {
|
||||
// throw new Installer.InstallFailedException(apkFile + " has packageName that clashes with " + packageName);
|
||||
// }
|
||||
|
||||
if (!attributes.containsKey("versionCode")) {
|
||||
throw new Installer.InstallFailedException(apkFile + " is missing versionCode!");
|
||||
}
|
||||
int versionCode = (Integer) attributes.get("versionCode");
|
||||
// Apk apk = ApkProvider.Helper.find(context, packageName, versionCode, new String[]{
|
||||
// ApkProvider.DataColumns.HASH,
|
||||
// ApkProvider.DataColumns.HASH_TYPE,
|
||||
// });
|
||||
/* Always copy the APK to the safe location inside of the protected area
|
||||
* of the app to prevent attacks based on other apps swapping the file
|
||||
* out during the install process. Most likely, apkFile was just downloaded,
|
||||
* so it should still be in the RAM disk cache */
|
||||
apkToInstall = SanitizedFile.knownSanitized(File.createTempFile("install-", ".apk", context.getFilesDir()));
|
||||
FileUtils.copyFile(apkFile, apkToInstall);
|
||||
// if (!verifyApkFile(apkToInstall, apk.hash, apk.hashType)) {
|
||||
// FileUtils.deleteQuietly(apkFile);
|
||||
// throw new Installer.InstallFailedException(apkFile + " failed to verify!");
|
||||
// }
|
||||
apkFile = null; // ensure this is not used now that its copied to apkToInstall
|
||||
|
||||
// special case: F-Droid Privileged Extension
|
||||
if (packageName != null && packageName.equals(PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME)) {
|
||||
|
||||
// extension must be signed with the same public key as main F-Droid
|
||||
// NOTE: Disabled for debug builds to be able to use official extension from repo
|
||||
ApkSignatureVerifier signatureVerifier = new ApkSignatureVerifier(context);
|
||||
if (!BuildConfig.DEBUG && !signatureVerifier.hasFDroidSignature(apkToInstall)) {
|
||||
throw new Installer.InstallFailedException("APK signature of extension not correct!");
|
||||
}
|
||||
|
||||
Activity activity = (Activity) context;
|
||||
Intent installIntent = new Intent(activity, InstallExtensionDialogActivity.class);
|
||||
installIntent.setAction(InstallExtensionDialogActivity.ACTION_INSTALL);
|
||||
installIntent.putExtra(InstallExtensionDialogActivity.EXTRA_INSTALL_APK, apkToInstall.getAbsolutePath());
|
||||
activity.startActivity(installIntent);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Need the apk to be world readable, so that the installer is able to read it.
|
||||
// Note that saving it into external storage for the purpose of letting the installer
|
||||
// have access is insecure, because apps with permission to write to the external
|
||||
// storage can overwrite the app between F-Droid asking for it to be installed and
|
||||
// the installer actually installing it.
|
||||
FileCompat.setReadable(apkToInstall, true);
|
||||
|
||||
NotificationManager nm = (NotificationManager)
|
||||
context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
nm.cancel(urlString.hashCode());
|
||||
} catch (NumberFormatException | IOException e) {
|
||||
throw new Installer.InstallFailedException(e);
|
||||
} catch (ClassCastException e) {
|
||||
throw new Installer.InstallFailedException("F-Droid Privileged can only be updated using an activity!");
|
||||
}
|
||||
|
||||
return apkToInstall;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks the APK file against the provided hash, returning whether it is a match.
|
||||
*/
|
||||
public static boolean verifyApkFile(File apkFile, String hash, String hashType)
|
||||
throws NoSuchAlgorithmException {
|
||||
if (!apkFile.exists()) {
|
||||
return false;
|
||||
}
|
||||
Hasher hasher = new Hasher(hashType, apkFile);
|
||||
return hasher.match(hash);
|
||||
}
|
||||
}
|
@ -168,7 +168,7 @@ public class InstallManagerService extends Service {
|
||||
sendBroadcast(intent.getData(), Downloader.ACTION_STARTED, apkFilePath);
|
||||
sendBroadcast(intent.getData(), Downloader.ACTION_COMPLETE, apkFilePath);
|
||||
} else {
|
||||
Utils.debugLog(TAG, " delete and download again " + urlString + " " + apkFilePath);
|
||||
Utils.debugLog(TAG, "delete and download again " + urlString + " " + apkFilePath);
|
||||
apkFilePath.delete();
|
||||
DownloaderService.queue(this, urlString);
|
||||
}
|
||||
@ -233,14 +233,15 @@ public class InstallManagerService extends Service {
|
||||
unregisterDownloaderReceivers(intent.getDataString());
|
||||
|
||||
registerInstallerReceivers(localUri);
|
||||
InstallerService.install(context, localUri, originatingUri);
|
||||
Apk apk = ACTIVE_APKS.get(originatingUri.toString());
|
||||
InstallerService.install(context, localUri, originatingUri, apk.packageName);
|
||||
}
|
||||
};
|
||||
BroadcastReceiver interruptedReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String urlString = intent.getDataString();
|
||||
Apk apk = removeFromActive(urlString);
|
||||
removeFromActive(urlString);
|
||||
unregisterDownloaderReceivers(urlString);
|
||||
cancelNotification(urlString);
|
||||
}
|
||||
@ -266,16 +267,12 @@ public class InstallManagerService extends Service {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()) {
|
||||
case InstallHelper.ACTION_INSTALL_STARTED: {
|
||||
Utils.debugLog(TAG, "ACTION_INSTALL_STARTED");
|
||||
|
||||
case Installer.ACTION_INSTALL_STARTED: {
|
||||
break;
|
||||
}
|
||||
case InstallHelper.ACTION_INSTALL_COMPLETE: {
|
||||
Utils.debugLog(TAG, "ACTION_INSTALL_COMPLETE");
|
||||
|
||||
case Installer.ACTION_INSTALL_COMPLETE: {
|
||||
Uri originatingUri =
|
||||
intent.getParcelableExtra(InstallHelper.EXTRA_ORIGINATING_URI);
|
||||
intent.getParcelableExtra(Installer.EXTRA_ORIGINATING_URI);
|
||||
String urlString = originatingUri.toString();
|
||||
removeFromActive(urlString);
|
||||
|
||||
@ -283,29 +280,24 @@ public class InstallManagerService extends Service {
|
||||
|
||||
break;
|
||||
}
|
||||
case InstallHelper.ACTION_INSTALL_INTERRUPTED: {
|
||||
Utils.debugLog(TAG, "ACTION_INSTALL_INTERRUPTED");
|
||||
|
||||
case Installer.ACTION_INSTALL_INTERRUPTED: {
|
||||
localBroadcastManager.unregisterReceiver(this);
|
||||
|
||||
break;
|
||||
}
|
||||
case InstallHelper.ACTION_INSTALL_USER_INTERACTION: {
|
||||
Utils.debugLog(TAG, "ACTION_INSTALL_USER_INTERACTION");
|
||||
|
||||
case Installer.ACTION_INSTALL_USER_INTERACTION: {
|
||||
Uri originatingUri =
|
||||
intent.getParcelableExtra(InstallHelper.EXTRA_ORIGINATING_URI);
|
||||
intent.getParcelableExtra(Installer.EXTRA_ORIGINATING_URI);
|
||||
PendingIntent installPendingIntent =
|
||||
intent.getParcelableExtra(InstallHelper.EXTRA_USER_INTERACTION_PI);
|
||||
// TODO
|
||||
String urlString = originatingUri.toString();
|
||||
Apk apk = getFromActive(urlString);
|
||||
Utils.debugLog(TAG, "urlString: " + urlString);
|
||||
intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI);
|
||||
Utils.debugLog(TAG, "originatingUri: " + originatingUri);
|
||||
|
||||
Apk apk = getFromActive(originatingUri.toString());
|
||||
// show notification if app details is not visible
|
||||
if (AppDetails.isAppVisible(apk.packageName)) {
|
||||
cancelNotification(urlString);
|
||||
cancelNotification(originatingUri.toString());
|
||||
} else {
|
||||
notifyDownloadComplete(apk, urlString, installPendingIntent);
|
||||
notifyDownloadComplete(apk, originatingUri.toString(), installPendingIntent);
|
||||
}
|
||||
|
||||
break;
|
||||
@ -318,7 +310,7 @@ public class InstallManagerService extends Service {
|
||||
};
|
||||
|
||||
localBroadcastManager.registerReceiver(installReceiver,
|
||||
InstallerService.getInstallIntentFilter(uri));
|
||||
Installer.getInstallIntentFilter(uri));
|
||||
}
|
||||
|
||||
private NotificationCompat.Builder createNotificationBuilder(String urlString, Apk apk) {
|
||||
@ -327,7 +319,7 @@ public class InstallManagerService extends Service {
|
||||
.setAutoCancel(false)
|
||||
.setOngoing(true)
|
||||
.setContentIntent(getAppDetailsIntent(downloadUrlId, apk))
|
||||
.setContentTitle(getString(R.string.downloading_apk, getAppName(urlString, apk)))
|
||||
.setContentTitle(getString(R.string.downloading_apk, getAppName(apk)))
|
||||
.addAction(R.drawable.ic_cancel_black_24dp, getString(R.string.cancel),
|
||||
DownloaderService.getCancelPendingIntent(this, urlString))
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
@ -335,7 +327,7 @@ public class InstallManagerService extends Service {
|
||||
.setProgress(100, 0, true);
|
||||
}
|
||||
|
||||
private String getAppName(String urlString, Apk apk) {
|
||||
private String getAppName(Apk apk) {
|
||||
App app = ACTIVE_APPS.get(apk.packageName);
|
||||
return app.name;
|
||||
}
|
||||
@ -371,7 +363,7 @@ public class InstallManagerService extends Service {
|
||||
title = String.format(getString(R.string.tap_to_update_format),
|
||||
pm.getApplicationLabel(pm.getApplicationInfo(apk.packageName, 0)));
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
title = String.format(getString(R.string.tap_to_install_format), getAppName(urlString, apk));
|
||||
title = String.format(getString(R.string.tap_to_install_format), getAppName(apk));
|
||||
}
|
||||
|
||||
int downloadUrlId = urlString.hashCode();
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2016 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -19,47 +19,55 @@
|
||||
|
||||
package org.fdroid.fdroid.installer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.PatternMatcher;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.fdroid.fdroid.AndroidXMLDecompress;
|
||||
import org.fdroid.fdroid.BuildConfig;
|
||||
import org.fdroid.fdroid.Hasher;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.data.ApkProvider;
|
||||
import org.fdroid.fdroid.data.SanitizedFile;
|
||||
import org.fdroid.fdroid.privileged.install.InstallExtensionDialogActivity;
|
||||
import org.fdroid.fdroid.privileged.views.AppDiff;
|
||||
import org.fdroid.fdroid.privileged.views.AppSecurityPermissions;
|
||||
import org.fdroid.fdroid.privileged.views.InstallConfirmActivity;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Abstract Installer class. Also provides static methods to automatically
|
||||
* instantiate a working Installer based on F-Droids granted permissions.
|
||||
*/
|
||||
public abstract class Installer {
|
||||
final Context mContext;
|
||||
final PackageManager mPm;
|
||||
final InstallerCallback mCallback;
|
||||
private LocalBroadcastManager localBroadcastManager;
|
||||
|
||||
private static final String TAG = "Installer";
|
||||
public static final String ACTION_INSTALL_STARTED = "org.fdroid.fdroid.installer.Installer.action.INSTALL_STARTED";
|
||||
public static final String ACTION_INSTALL_COMPLETE = "org.fdroid.fdroid.installer.Installer.action.INSTALL_COMPLETE";
|
||||
public static final String ACTION_INSTALL_INTERRUPTED = "org.fdroid.fdroid.installer.Installer.action.INSTALL_INTERRUPTED";
|
||||
public static final String ACTION_INSTALL_USER_INTERACTION = "org.fdroid.fdroid.installer.Installer.action.INSTALL_USER_INTERACTION";
|
||||
|
||||
public static final String ACTION_UNINSTALL_STARTED = "org.fdroid.fdroid.installer.Installer.action.UNINSTALL_STARTED";
|
||||
public static final String ACTION_UNINSTALL_COMPLETE = "org.fdroid.fdroid.installer.Installer.action.UNINSTALL_COMPLETE";
|
||||
public static final String ACTION_UNINSTALL_INTERRUPTED = "org.fdroid.fdroid.installer.Installer.action.UNINSTALL_INTERRUPTED";
|
||||
public static final String ACTION_UNINSTALL_USER_INTERACTION = "org.fdroid.fdroid.installer.Installer.action.UNINSTALL_USER_INTERACTION";
|
||||
|
||||
/**
|
||||
* This is thrown when an Installer is not compatible with the Android OS it
|
||||
* is running on. This could be due to a broken superuser in case of
|
||||
* RootInstaller or due to an incompatible Android version in case of
|
||||
* SystemPermissionInstaller
|
||||
* Same as http://developer.android.com/reference/android/content/Intent.html#EXTRA_ORIGINATING_URI
|
||||
* In InstallManagerService often called urlString
|
||||
*/
|
||||
public static final String EXTRA_ORIGINATING_URI = "org.fdroid.fdroid.installer.InstallerService.extra.ORIGINATING_URI";
|
||||
public static final String EXTRA_PACKAGE_NAME = "org.fdroid.fdroid.installer.InstallerService.extra.PACKAGE_NAME";
|
||||
public static final String EXTRA_USER_INTERACTION_PI = "org.fdroid.fdroid.installer.InstallerService.extra.USER_INTERACTION_PI";
|
||||
public static final String EXTRA_ERROR_MESSAGE = "org.fdroid.fdroid.net.Downloader.extra.ERROR_MESSAGE";
|
||||
|
||||
public static class InstallFailedException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -8343133906463328027L;
|
||||
@ -77,97 +85,28 @@ public abstract class Installer {
|
||||
* Callback from Installer. NOTE: This callback can be in a different thread
|
||||
* than the UI thread
|
||||
*/
|
||||
public interface InstallerCallback {
|
||||
// public interface InstallerCallback {
|
||||
//
|
||||
// int OPERATION_INSTALL = 1;
|
||||
// int OPERATION_DELETE = 2;
|
||||
//
|
||||
// // Avoid using [-1,1] as they may conflict with Activity.RESULT_*
|
||||
// int ERROR_CODE_CANCELED = 2;
|
||||
// int ERROR_CODE_OTHER = 3;
|
||||
// int ERROR_CODE_CANNOT_PARSE = 4;
|
||||
//
|
||||
// void onSuccess(int operation);
|
||||
//
|
||||
// void onError(int operation, int errorCode);
|
||||
// }
|
||||
|
||||
int OPERATION_INSTALL = 1;
|
||||
int OPERATION_DELETE = 2;
|
||||
|
||||
// Avoid using [-1,1] as they may conflict with Activity.RESULT_*
|
||||
int ERROR_CODE_CANCELED = 2;
|
||||
int ERROR_CODE_OTHER = 3;
|
||||
int ERROR_CODE_CANNOT_PARSE = 4;
|
||||
|
||||
void onSuccess(int operation);
|
||||
|
||||
void onError(int operation, int errorCode);
|
||||
}
|
||||
|
||||
Installer(Context context, PackageManager pm, InstallerCallback callback)
|
||||
throws InstallFailedException {
|
||||
Installer(Context context) {
|
||||
this.mContext = context;
|
||||
this.mPm = pm;
|
||||
this.mCallback = callback;
|
||||
this.mPm = context.getPackageManager();
|
||||
localBroadcastManager = LocalBroadcastManager.getInstance(context);
|
||||
}
|
||||
|
||||
public static Installer getActivityInstaller(Activity activity, InstallerCallback callback) {
|
||||
return getActivityInstaller(activity, activity.getPackageManager(), callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Installer for installing/deleting processes starting from
|
||||
* an Activity
|
||||
*/
|
||||
public static Installer getActivityInstaller(Activity activity, PackageManager pm,
|
||||
InstallerCallback callback) {
|
||||
|
||||
// system permissions and pref enabled -> SystemInstaller
|
||||
boolean isSystemInstallerEnabled = Preferences.get().isPrivilegedInstallerEnabled();
|
||||
if (isSystemInstallerEnabled) {
|
||||
if (PrivilegedInstaller.isExtensionInstalledCorrectly(activity)
|
||||
== PrivilegedInstaller.IS_EXTENSION_INSTALLED_YES) {
|
||||
Utils.debugLog(TAG, "system permissions -> SystemInstaller");
|
||||
|
||||
try {
|
||||
return new PrivilegedInstaller(activity, pm, callback);
|
||||
} catch (InstallFailedException e) {
|
||||
Log.e(TAG, "Android not compatible with SystemInstaller!", e);
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "SystemInstaller is enabled in prefs, but system-perms are not granted!");
|
||||
}
|
||||
}
|
||||
|
||||
// else -> DefaultInstaller
|
||||
if (android.os.Build.VERSION.SDK_INT >= 14) {
|
||||
// Default installer on Android >= 4.0
|
||||
try {
|
||||
Utils.debugLog(TAG, "try default installer for android >= 14");
|
||||
|
||||
return new DefaultSdk14Installer(activity, pm, callback);
|
||||
} catch (InstallFailedException e) {
|
||||
Log.e(TAG, "Android not compatible with DefaultInstallerSdk14!", e);
|
||||
}
|
||||
} else {
|
||||
// Default installer on Android < 4.0 (android-14)
|
||||
try {
|
||||
Utils.debugLog(TAG, "try default installer for android < 14");
|
||||
|
||||
return new DefaultInstaller(activity, pm, callback);
|
||||
} catch (InstallFailedException e) {
|
||||
Log.e(TAG, "Android not compatible with DefaultInstaller!", e);
|
||||
}
|
||||
}
|
||||
|
||||
// this should not happen!
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the APK file against the provided hash, returning whether it is a match.
|
||||
*/
|
||||
public static boolean verifyApkFile(File apkFile, String hash, String hashType)
|
||||
throws NoSuchAlgorithmException {
|
||||
if (!apkFile.exists()) {
|
||||
return false;
|
||||
}
|
||||
Hasher hasher = new Hasher(hashType, apkFile);
|
||||
return hasher.match(hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the safe, single point of entry for submitting an APK file to be installed.
|
||||
*/
|
||||
public void installPackage(File apkFile, String packageName, String urlString)
|
||||
public static SanitizedFile prepareApkFile(Context context, File apkFile, String packageName)
|
||||
throws InstallFailedException {
|
||||
SanitizedFile apkToInstall = null;
|
||||
try {
|
||||
@ -182,7 +121,7 @@ public abstract class Installer {
|
||||
throw new InstallFailedException(apkFile + " is missing versionCode!");
|
||||
}
|
||||
int versionCode = (Integer) attributes.get("versionCode");
|
||||
Apk apk = ApkProvider.Helper.find(mContext, packageName, versionCode, new String[]{
|
||||
Apk apk = ApkProvider.Helper.find(context, packageName, versionCode, new String[]{
|
||||
ApkProvider.DataColumns.HASH,
|
||||
ApkProvider.DataColumns.HASH_TYPE,
|
||||
});
|
||||
@ -190,7 +129,8 @@ public abstract class Installer {
|
||||
* of the app to prevent attacks based on other apps swapping the file
|
||||
* out during the install process. Most likely, apkFile was just downloaded,
|
||||
* so it should still be in the RAM disk cache */
|
||||
apkToInstall = SanitizedFile.knownSanitized(File.createTempFile("install-", ".apk", mContext.getFilesDir()));
|
||||
apkToInstall = SanitizedFile.knownSanitized(File.createTempFile("install-", ".apk",
|
||||
context.getFilesDir()));
|
||||
FileUtils.copyFile(apkFile, apkToInstall);
|
||||
if (!verifyApkFile(apkToInstall, apk.hash, apk.hashType)) {
|
||||
FileUtils.deleteQuietly(apkFile);
|
||||
@ -198,36 +138,14 @@ public abstract class Installer {
|
||||
}
|
||||
apkFile = null; // ensure this is not used now that its copied to apkToInstall
|
||||
|
||||
// special case: F-Droid Privileged Extension
|
||||
if (packageName != null && packageName.equals(PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME)) {
|
||||
|
||||
// extension must be signed with the same public key as main F-Droid
|
||||
// NOTE: Disabled for debug builds to be able to use official extension from repo
|
||||
ApkSignatureVerifier signatureVerifier = new ApkSignatureVerifier(mContext);
|
||||
if (!BuildConfig.DEBUG && !signatureVerifier.hasFDroidSignature(apkToInstall)) {
|
||||
throw new InstallFailedException("APK signature of extension not correct!");
|
||||
}
|
||||
|
||||
Activity activity = (Activity) mContext;
|
||||
Intent installIntent = new Intent(activity, InstallExtensionDialogActivity.class);
|
||||
installIntent.setAction(InstallExtensionDialogActivity.ACTION_INSTALL);
|
||||
installIntent.putExtra(InstallExtensionDialogActivity.EXTRA_INSTALL_APK, apkToInstall.getAbsolutePath());
|
||||
activity.startActivity(installIntent);
|
||||
return;
|
||||
}
|
||||
|
||||
// Need the apk to be world readable, so that the installer is able to read it.
|
||||
// Note that saving it into external storage for the purpose of letting the installer
|
||||
// have access is insecure, because apps with permission to write to the external
|
||||
// storage can overwrite the app between F-Droid asking for it to be installed and
|
||||
// the installer actually installing it.
|
||||
apkToInstall.setReadable(true, false);
|
||||
installPackageInternal(apkToInstall);
|
||||
|
||||
NotificationManager nm = (NotificationManager)
|
||||
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
nm.cancel(urlString.hashCode());
|
||||
} catch (NumberFormatException | NoSuchAlgorithmException | IOException e) {
|
||||
} catch (NumberFormatException | IOException | NoSuchAlgorithmException e) {
|
||||
throw new InstallFailedException(e);
|
||||
} catch (ClassCastException e) {
|
||||
throw new InstallFailedException("F-Droid Privileged can only be updated using an activity!");
|
||||
@ -248,41 +166,139 @@ public abstract class Installer {
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
return apkToInstall;
|
||||
}
|
||||
|
||||
public void deletePackage(String packageName) throws InstallFailedException {
|
||||
// check if package exists before proceeding...
|
||||
try {
|
||||
mPm.getPackageInfo(packageName, 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e(TAG, "Couldn't find package " + packageName + " to delete.");
|
||||
return;
|
||||
}
|
||||
|
||||
// special case: F-Droid Privileged Extension
|
||||
if (packageName != null && packageName.equals(PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME)) {
|
||||
Activity activity;
|
||||
try {
|
||||
activity = (Activity) mContext;
|
||||
} catch (ClassCastException e) {
|
||||
Utils.debugLog(TAG, "F-Droid Privileged can only be uninstalled using an activity!");
|
||||
return;
|
||||
}
|
||||
|
||||
Intent uninstallIntent = new Intent(activity, InstallExtensionDialogActivity.class);
|
||||
uninstallIntent.setAction(InstallExtensionDialogActivity.ACTION_UNINSTALL);
|
||||
activity.startActivity(uninstallIntent);
|
||||
return;
|
||||
}
|
||||
|
||||
deletePackageInternal(packageName);
|
||||
public PendingIntent getPermissionScreen(Apk apk) {
|
||||
// old code:
|
||||
// Uri packageUri = Uri.fromFile(apkFile);
|
||||
// int count = newPermissionCount(packageUri);
|
||||
// if (count < 0) {
|
||||
// mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
// InstallerCallback.ERROR_CODE_CANNOT_PARSE);
|
||||
// return;
|
||||
// }
|
||||
// if (count > 0) {
|
||||
// Intent intent = new Intent(mContext, InstallConfirmActivity.class);
|
||||
// intent.setData(packageUri);
|
||||
// mActivity.startActivityForResult(intent, REQUEST_CONFIRM_PERMS);
|
||||
// } else {
|
||||
// try {
|
||||
// doInstallPackageInternal(packageUri);
|
||||
// } catch (InstallFailedException e) {
|
||||
// mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
// InstallerCallback.ERROR_CODE_OTHER);
|
||||
// }
|
||||
// }
|
||||
return null;
|
||||
}
|
||||
|
||||
protected abstract void installPackageInternal(File apkFile)
|
||||
throws InstallFailedException;
|
||||
|
||||
protected abstract void deletePackageInternal(String packageName)
|
||||
throws InstallFailedException;
|
||||
private int newPermissionCount(Uri packageUri) {
|
||||
AppDiff appDiff = new AppDiff(mContext.getPackageManager(), packageUri);
|
||||
if (appDiff.mPkgInfo == null) {
|
||||
// could not get diff because we couldn't parse the package
|
||||
return -1;
|
||||
}
|
||||
AppSecurityPermissions perms = new AppSecurityPermissions(mContext, appDiff.mPkgInfo);
|
||||
if (appDiff.mInstalledAppInfo != null) {
|
||||
// update to an existing app
|
||||
return perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW);
|
||||
}
|
||||
// default: even if there aren't any permissions, we want to make the
|
||||
// user always confirm installing new apps
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the APK file against the provided hash, returning whether it is a match.
|
||||
*/
|
||||
public static boolean verifyApkFile(File apkFile, String hash, String hashType)
|
||||
throws NoSuchAlgorithmException {
|
||||
if (!apkFile.exists()) {
|
||||
return false;
|
||||
}
|
||||
Hasher hasher = new Hasher(hashType, apkFile);
|
||||
return hasher.match(hash);
|
||||
}
|
||||
|
||||
public void sendBroadcastInstall(Uri uri, Uri originatingUri, String action,
|
||||
PendingIntent pendingIntent) {
|
||||
sendBroadcastInstall(uri, originatingUri, action, pendingIntent, null);
|
||||
}
|
||||
|
||||
public void sendBroadcastInstall(Uri uri, Uri originatingUri, String action) {
|
||||
sendBroadcastInstall(uri, originatingUri, action, null, null);
|
||||
}
|
||||
|
||||
public void sendBroadcastInstall(Uri uri, Uri originatingUri, String action, String errorMessage) {
|
||||
sendBroadcastInstall(uri, originatingUri, action, null, errorMessage);
|
||||
}
|
||||
|
||||
public void sendBroadcastInstall(Uri uri, Uri originatingUri, String action,
|
||||
PendingIntent pendingIntent, String errorMessage) {
|
||||
Intent intent = new Intent(action);
|
||||
intent.setData(uri);
|
||||
intent.putExtra(Installer.EXTRA_ORIGINATING_URI, originatingUri);
|
||||
intent.putExtra(Installer.EXTRA_USER_INTERACTION_PI, pendingIntent);
|
||||
if (!TextUtils.isEmpty(errorMessage)) {
|
||||
intent.putExtra(Installer.EXTRA_ERROR_MESSAGE, errorMessage);
|
||||
}
|
||||
localBroadcastManager.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
public void sendBroadcastUninstall(String packageName, String action, String errorMessage) {
|
||||
sendBroadcastUninstall(packageName, action, null, errorMessage);
|
||||
}
|
||||
|
||||
public void sendBroadcastUninstall(String packageName, String action) {
|
||||
sendBroadcastUninstall(packageName, action, null, null);
|
||||
}
|
||||
|
||||
public void sendBroadcastUninstall(String packageName, String action,
|
||||
PendingIntent pendingIntent) {
|
||||
sendBroadcastUninstall(packageName, action, pendingIntent, null);
|
||||
}
|
||||
|
||||
public void sendBroadcastUninstall(String packageName, String action,
|
||||
PendingIntent pendingIntent, String errorMessage) {
|
||||
Uri uri = Uri.fromParts("package", packageName, null);
|
||||
|
||||
Intent intent = new Intent(action);
|
||||
intent.setData(uri); // for broadcast filtering
|
||||
intent.putExtra(Installer.EXTRA_PACKAGE_NAME, packageName);
|
||||
intent.putExtra(Installer.EXTRA_USER_INTERACTION_PI, pendingIntent);
|
||||
if (!TextUtils.isEmpty(errorMessage)) {
|
||||
intent.putExtra(Installer.EXTRA_ERROR_MESSAGE, errorMessage);
|
||||
}
|
||||
localBroadcastManager.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
public static IntentFilter getInstallIntentFilter(Uri uri) {
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(Installer.ACTION_INSTALL_STARTED);
|
||||
intentFilter.addAction(Installer.ACTION_INSTALL_COMPLETE);
|
||||
intentFilter.addAction(Installer.ACTION_INSTALL_INTERRUPTED);
|
||||
intentFilter.addAction(Installer.ACTION_INSTALL_USER_INTERACTION);
|
||||
intentFilter.addDataScheme(uri.getScheme());
|
||||
intentFilter.addDataPath(uri.getPath(), PatternMatcher.PATTERN_LITERAL);
|
||||
return intentFilter;
|
||||
}
|
||||
|
||||
public static IntentFilter getUninstallIntentFilter(String packageName) {
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(Installer.ACTION_UNINSTALL_STARTED);
|
||||
intentFilter.addAction(Installer.ACTION_UNINSTALL_COMPLETE);
|
||||
intentFilter.addAction(Installer.ACTION_UNINSTALL_INTERRUPTED);
|
||||
intentFilter.addAction(Installer.ACTION_UNINSTALL_USER_INTERACTION);
|
||||
intentFilter.addDataScheme("package");
|
||||
intentFilter.addDataPath(packageName, PatternMatcher.PATTERN_LITERAL);
|
||||
return intentFilter;
|
||||
}
|
||||
|
||||
protected abstract void installPackage(Uri uri, Uri originatingUri, String packageName);
|
||||
|
||||
protected abstract void uninstallPackage(String packageName);
|
||||
|
||||
public abstract boolean handleOnActivityResult(int requestCode, int resultCode, Intent data);
|
||||
}
|
||||
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package org.fdroid.fdroid.installer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
|
||||
public class InstallerFactory {
|
||||
|
||||
private static final String TAG = "InstallerFactory";
|
||||
|
||||
public static Installer create(Context context) {
|
||||
Installer installer;
|
||||
|
||||
if (isPrivilegedInstallerEnabled()) {
|
||||
if (PrivilegedInstaller.isExtensionInstalledCorrectly(context)
|
||||
== PrivilegedInstaller.IS_EXTENSION_INSTALLED_YES) {
|
||||
Utils.debugLog(TAG, "privileged extension correctly installed -> PrivilegedInstaller");
|
||||
|
||||
installer = new PrivilegedInstaller(context);
|
||||
} else {
|
||||
Log.e(TAG, "PrivilegedInstaller is enabled in prefs, but permissions are not granted!");
|
||||
|
||||
// fallback to default installer
|
||||
installer = new DefaultInstaller(context);
|
||||
}
|
||||
} else {
|
||||
installer = new DefaultInstaller(context);
|
||||
}
|
||||
|
||||
return installer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension has privileged permissions and preference is enabled?
|
||||
*/
|
||||
private static boolean isPrivilegedInstallerEnabled() {
|
||||
return Preferences.get().isPrivilegedInstallerEnabled();
|
||||
}
|
||||
|
||||
}
|
@ -18,27 +18,21 @@
|
||||
|
||||
package org.fdroid.fdroid.installer;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.PatternMatcher;
|
||||
import android.os.Process;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.Utils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* InstallerService based on DownloaderService
|
||||
*/
|
||||
@ -51,6 +45,7 @@ public class InstallerService extends Service {
|
||||
private volatile Looper serviceLooper;
|
||||
private static volatile ServiceHandler serviceHandler;
|
||||
private LocalBroadcastManager localBroadcastManager;
|
||||
private Installer installer;
|
||||
|
||||
private final class ServiceHandler extends Handler {
|
||||
ServiceHandler(Looper looper) {
|
||||
@ -76,6 +71,7 @@ public class InstallerService extends Service {
|
||||
serviceLooper = thread.getLooper();
|
||||
serviceHandler = new ServiceHandler(serviceLooper);
|
||||
localBroadcastManager = LocalBroadcastManager.getInstance(this);
|
||||
installer = InstallerFactory.create(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -92,7 +88,7 @@ public class InstallerService extends Service {
|
||||
serviceHandler.sendMessage(msg);
|
||||
Utils.debugLog(TAG, "Start install of " + uri.toString());
|
||||
} else if (ACTION_UNINSTALL.equals(intent.getAction())) {
|
||||
String packageName = intent.getStringExtra(InstallHelper.EXTRA_UNINSTALL_PACKAGE_NAME);
|
||||
String packageName = intent.getStringExtra(Installer.EXTRA_PACKAGE_NAME);
|
||||
|
||||
Message msg = serviceHandler.obtainMessage();
|
||||
msg.arg1 = startId;
|
||||
@ -125,140 +121,37 @@ public class InstallerService extends Service {
|
||||
switch (intent.getAction()) {
|
||||
case ACTION_INSTALL: {
|
||||
Uri uri = intent.getData();
|
||||
Uri originatingUri = intent.getParcelableExtra(InstallHelper.EXTRA_ORIGINATING_URI);
|
||||
sendBroadcastInstall(uri, originatingUri, InstallHelper.ACTION_INSTALL_STARTED);
|
||||
|
||||
Utils.debugLog(TAG, "ACTION_INSTALL uri: " + uri + " file: " + new File(uri.getPath()));
|
||||
|
||||
// TODO: rework for uri
|
||||
Uri sanitizedUri = null;
|
||||
try {
|
||||
File file = InstallHelper.preparePackage(this, new File(uri.getPath()), null,
|
||||
originatingUri.toString());
|
||||
sanitizedUri = Uri.fromFile(file);
|
||||
} catch (Installer.InstallFailedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Utils.debugLog(TAG, "ACTION_INSTALL sanitizedUri: " + sanitizedUri);
|
||||
|
||||
|
||||
Intent installIntent = new Intent(this, AndroidInstallerActivity.class);
|
||||
installIntent.setAction(AndroidInstallerActivity.ACTION_INSTALL_PACKAGE);
|
||||
installIntent.putExtra(AndroidInstallerActivity.EXTRA_ORIGINATING_URI, originatingUri);
|
||||
installIntent.setData(sanitizedUri);
|
||||
PendingIntent installPendingIntent = PendingIntent.getActivity(this.getApplicationContext(),
|
||||
uri.hashCode(),
|
||||
installIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
sendBroadcastInstall(uri, originatingUri, InstallHelper.ACTION_INSTALL_USER_INTERACTION,
|
||||
installPendingIntent);
|
||||
Uri originatingUri = intent.getParcelableExtra(Installer.EXTRA_ORIGINATING_URI);
|
||||
String packageName = intent.getStringExtra(Installer.EXTRA_PACKAGE_NAME);
|
||||
|
||||
installer.installPackage(uri, originatingUri, packageName);
|
||||
break;
|
||||
}
|
||||
|
||||
case ACTION_UNINSTALL: {
|
||||
String packageName =
|
||||
intent.getStringExtra(InstallHelper.EXTRA_UNINSTALL_PACKAGE_NAME);
|
||||
sendBroadcastUninstall(packageName, InstallHelper.ACTION_UNINSTALL_STARTED);
|
||||
|
||||
|
||||
Intent installIntent = new Intent(this, AndroidInstallerActivity.class);
|
||||
installIntent.setAction(AndroidInstallerActivity.ACTION_UNINSTALL_PACKAGE);
|
||||
installIntent.putExtra(
|
||||
AndroidInstallerActivity.EXTRA_UNINSTALL_PACKAGE_NAME, packageName);
|
||||
PendingIntent uninstallPendingIntent = PendingIntent.getActivity(this.getApplicationContext(),
|
||||
packageName.hashCode(),
|
||||
installIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
sendBroadcastUninstall(packageName, InstallHelper.ACTION_UNINSTALL_USER_INTERACTION,
|
||||
uninstallPendingIntent);
|
||||
|
||||
intent.getStringExtra(Installer.EXTRA_PACKAGE_NAME);
|
||||
|
||||
installer.uninstallPackage(packageName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendBroadcastInstall(Uri uri, Uri originatingUri, String action,
|
||||
PendingIntent pendingIntent) {
|
||||
sendBroadcastInstall(uri, originatingUri, action, pendingIntent, null);
|
||||
}
|
||||
|
||||
private void sendBroadcastInstall(Uri uri, Uri originatingUri, String action) {
|
||||
sendBroadcastInstall(uri, originatingUri, action, null, null);
|
||||
}
|
||||
|
||||
private void sendBroadcastInstall(Uri uri, Uri originatingUri, String action,
|
||||
PendingIntent pendingIntent, String errorMessage) {
|
||||
Intent intent = new Intent(action);
|
||||
intent.setData(uri);
|
||||
intent.putExtra(InstallHelper.EXTRA_ORIGINATING_URI, originatingUri);
|
||||
intent.putExtra(InstallHelper.EXTRA_USER_INTERACTION_PI, pendingIntent);
|
||||
if (!TextUtils.isEmpty(errorMessage)) {
|
||||
intent.putExtra(InstallHelper.EXTRA_ERROR_MESSAGE, errorMessage);
|
||||
}
|
||||
localBroadcastManager.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
private void sendBroadcastUninstall(String packageName, String action) {
|
||||
sendBroadcastUninstall(packageName, action, null, null);
|
||||
}
|
||||
|
||||
private void sendBroadcastUninstall(String packageName, String action,
|
||||
PendingIntent pendingIntent) {
|
||||
sendBroadcastUninstall(packageName, action, pendingIntent, null);
|
||||
}
|
||||
|
||||
private void sendBroadcastUninstall(String packageName, String action,
|
||||
PendingIntent pendingIntent, String errorMessage) {
|
||||
Uri uri = Uri.fromParts("package", packageName, null);
|
||||
|
||||
Intent intent = new Intent(action);
|
||||
intent.setData(uri); // for broadcast filtering
|
||||
intent.putExtra(InstallHelper.EXTRA_UNINSTALL_PACKAGE_NAME, packageName);
|
||||
intent.putExtra(InstallHelper.EXTRA_USER_INTERACTION_PI, pendingIntent);
|
||||
if (!TextUtils.isEmpty(errorMessage)) {
|
||||
intent.putExtra(InstallHelper.EXTRA_ERROR_MESSAGE, errorMessage);
|
||||
}
|
||||
localBroadcastManager.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
public static void install(Context context, Uri uri, Uri originatingUri) {
|
||||
public static void install(Context context, Uri uri, Uri originatingUri, String packageName) {
|
||||
Intent intent = new Intent(context, InstallerService.class);
|
||||
intent.setAction(ACTION_INSTALL);
|
||||
intent.setData(uri);
|
||||
intent.putExtra(InstallHelper.EXTRA_ORIGINATING_URI, originatingUri);
|
||||
intent.putExtra(Installer.EXTRA_ORIGINATING_URI, originatingUri);
|
||||
intent.putExtra(Installer.EXTRA_PACKAGE_NAME, packageName);
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
public static void uninstall(Context context, String packageName) {
|
||||
Intent intent = new Intent(context, InstallerService.class);
|
||||
intent.setAction(ACTION_UNINSTALL);
|
||||
intent.putExtra(InstallHelper.EXTRA_UNINSTALL_PACKAGE_NAME, packageName);
|
||||
intent.putExtra(Installer.EXTRA_PACKAGE_NAME, packageName);
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
public static IntentFilter getInstallIntentFilter(Uri uri) {
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(InstallHelper.ACTION_INSTALL_STARTED);
|
||||
intentFilter.addAction(InstallHelper.ACTION_INSTALL_COMPLETE);
|
||||
intentFilter.addAction(InstallHelper.ACTION_INSTALL_INTERRUPTED);
|
||||
intentFilter.addAction(InstallHelper.ACTION_INSTALL_USER_INTERACTION);
|
||||
intentFilter.addDataScheme(uri.getScheme());
|
||||
intentFilter.addDataPath(uri.getPath(), PatternMatcher.PATTERN_LITERAL);
|
||||
return intentFilter;
|
||||
}
|
||||
|
||||
public static IntentFilter getUninstallIntentFilter(String packageName) {
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(InstallHelper.ACTION_UNINSTALL_STARTED);
|
||||
intentFilter.addAction(InstallHelper.ACTION_UNINSTALL_COMPLETE);
|
||||
intentFilter.addAction(InstallHelper.ACTION_UNINSTALL_INTERRUPTED);
|
||||
intentFilter.addAction(InstallHelper.ACTION_UNINSTALL_USER_INTERACTION);
|
||||
intentFilter.addDataScheme("package");
|
||||
intentFilter.addDataPath(packageName, PatternMatcher.PATTERN_LITERAL);
|
||||
return intentFilter;
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@
|
||||
|
||||
package org.fdroid.fdroid.installer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
@ -39,9 +38,6 @@ import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.privileged.IPrivilegedCallback;
|
||||
import org.fdroid.fdroid.privileged.IPrivilegedService;
|
||||
import org.fdroid.fdroid.privileged.views.AppDiff;
|
||||
import org.fdroid.fdroid.privileged.views.AppSecurityPermissions;
|
||||
import org.fdroid.fdroid.privileged.views.InstallConfirmActivity;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@ -52,7 +48,7 @@ import java.io.File;
|
||||
* <li>android.permission.INSTALL_PACKAGES</li>
|
||||
* <li>android.permission.DELETE_PACKAGES</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p/>
|
||||
* Both permissions are protected by systemOrSignature (in newer versions:
|
||||
* system|signature) and only granted on F-Droid's install in the following
|
||||
* cases:
|
||||
@ -62,7 +58,7 @@ import java.io.File;
|
||||
* <li>On Android < 4.4 also when moved into /system/app/</li>
|
||||
* <li>On Android >= 4.4 also when moved into /system/priv-app/</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p/>
|
||||
* Sources for Android 4.4 change:
|
||||
* https://groups.google.com/forum/#!msg/android-
|
||||
* security-discuss/r7uL_OEMU5c/LijNHvxeV80J
|
||||
@ -76,19 +72,13 @@ public class PrivilegedInstaller extends Installer {
|
||||
private static final String PRIVILEGED_EXTENSION_SERVICE_INTENT = "org.fdroid.fdroid.privileged.IPrivilegedService";
|
||||
public static final String PRIVILEGED_EXTENSION_PACKAGE_NAME = "org.fdroid.fdroid.privileged";
|
||||
|
||||
private final Activity mActivity;
|
||||
|
||||
private static final int REQUEST_CONFIRM_PERMS = 0;
|
||||
|
||||
public static final int IS_EXTENSION_INSTALLED_NO = 0;
|
||||
public static final int IS_EXTENSION_INSTALLED_YES = 1;
|
||||
public static final int IS_EXTENSION_INSTALLED_SIGNATURE_PROBLEM = 2;
|
||||
public static final int IS_EXTENSION_INSTALLED_PERMISSIONS_PROBLEM = 3;
|
||||
|
||||
public PrivilegedInstaller(Activity activity, PackageManager pm,
|
||||
InstallerCallback callback) throws InstallFailedException {
|
||||
super(activity, pm, callback);
|
||||
this.mActivity = activity;
|
||||
public PrivilegedInstaller(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public static boolean isExtensionInstalled(Context context) {
|
||||
@ -155,46 +145,10 @@ public class PrivilegedInstaller extends Installer {
|
||||
return IS_EXTENSION_INSTALLED_YES;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void installPackageInternal(File apkFile) throws InstallFailedException {
|
||||
Uri packageUri = Uri.fromFile(apkFile);
|
||||
int count = newPermissionCount(packageUri);
|
||||
if (count < 0) {
|
||||
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
InstallerCallback.ERROR_CODE_CANNOT_PARSE);
|
||||
return;
|
||||
}
|
||||
if (count > 0) {
|
||||
Intent intent = new Intent(mContext, InstallConfirmActivity.class);
|
||||
intent.setData(packageUri);
|
||||
mActivity.startActivityForResult(intent, REQUEST_CONFIRM_PERMS);
|
||||
} else {
|
||||
try {
|
||||
doInstallPackageInternal(packageUri);
|
||||
} catch (InstallFailedException e) {
|
||||
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
InstallerCallback.ERROR_CODE_OTHER);
|
||||
}
|
||||
}
|
||||
}
|
||||
protected void installPackage(final Uri uri, Uri originatingUri, String packageName) {
|
||||
|
||||
private int newPermissionCount(Uri packageUri) {
|
||||
AppDiff appDiff = new AppDiff(mContext.getPackageManager(), packageUri);
|
||||
if (appDiff.mPkgInfo == null) {
|
||||
// could not get diff because we couldn't parse the package
|
||||
return -1;
|
||||
}
|
||||
AppSecurityPermissions perms = new AppSecurityPermissions(mContext, appDiff.mPkgInfo);
|
||||
if (appDiff.mInstalledAppInfo != null) {
|
||||
// update to an existing app
|
||||
return perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW);
|
||||
}
|
||||
// default: even if there aren't any permissions, we want to make the
|
||||
// user always confirm installing new apps
|
||||
return 1;
|
||||
}
|
||||
|
||||
private void doInstallPackageInternal(final Uri packageURI) throws InstallFailedException {
|
||||
ServiceConnection mServiceConnection = new ServiceConnection() {
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
IPrivilegedService privService = IPrivilegedService.Stub.asInterface(service);
|
||||
@ -205,17 +159,17 @@ public class PrivilegedInstaller extends Installer {
|
||||
// TODO: propagate other return codes?
|
||||
if (returnCode == INSTALL_SUCCEEDED) {
|
||||
Utils.debugLog(TAG, "Install succeeded");
|
||||
mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL);
|
||||
// mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL);
|
||||
} else {
|
||||
Log.e(TAG, "Install failed with returnCode " + returnCode);
|
||||
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
InstallerCallback.ERROR_CODE_OTHER);
|
||||
// mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
// InstallerCallback.ERROR_CODE_OTHER);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
privService.installPackage(packageURI, INSTALL_REPLACE_EXISTING, null, callback);
|
||||
privService.installPackage(uri, INSTALL_REPLACE_EXISTING, null, callback);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "RemoteException", e);
|
||||
}
|
||||
@ -232,8 +186,7 @@ public class PrivilegedInstaller extends Installer {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deletePackageInternal(final String packageName)
|
||||
throws InstallFailedException {
|
||||
protected void uninstallPackage(final String packageName) {
|
||||
ApplicationInfo appInfo;
|
||||
try {
|
||||
//noinspection WrongConstant (lint is actually wrong here!)
|
||||
@ -248,8 +201,8 @@ public class PrivilegedInstaller extends Installer {
|
||||
|
||||
if (isSystem && !isUpdate) {
|
||||
// Cannot remove system apps unless we're uninstalling updates
|
||||
mCallback.onError(InstallerCallback.OPERATION_DELETE,
|
||||
InstallerCallback.ERROR_CODE_OTHER);
|
||||
// mCallback.onError(InstallerCallback.OPERATION_DELETE,
|
||||
// InstallerCallback.ERROR_CODE_OTHER);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -270,8 +223,8 @@ public class PrivilegedInstaller extends Installer {
|
||||
try {
|
||||
doDeletePackageInternal(packageName);
|
||||
} catch (InstallFailedException e) {
|
||||
mCallback.onError(InstallerCallback.OPERATION_DELETE,
|
||||
InstallerCallback.ERROR_CODE_OTHER);
|
||||
// mCallback.onError(InstallerCallback.OPERATION_DELETE,
|
||||
// InstallerCallback.ERROR_CODE_OTHER);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -280,8 +233,8 @@ public class PrivilegedInstaller extends Installer {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.cancel();
|
||||
mCallback.onError(InstallerCallback.OPERATION_DELETE,
|
||||
InstallerCallback.ERROR_CODE_CANCELED);
|
||||
// mCallback.onError(InstallerCallback.OPERATION_DELETE,
|
||||
// InstallerCallback.ERROR_CODE_CANCELED);
|
||||
}
|
||||
});
|
||||
builder.setMessage(messageId);
|
||||
@ -301,11 +254,11 @@ public class PrivilegedInstaller extends Installer {
|
||||
if (returnCode == DELETE_SUCCEEDED) {
|
||||
Utils.debugLog(TAG, "Delete succeeded");
|
||||
|
||||
mCallback.onSuccess(InstallerCallback.OPERATION_DELETE);
|
||||
// mCallback.onSuccess(InstallerCallback.OPERATION_DELETE);
|
||||
} else {
|
||||
Log.e(TAG, "Delete failed with returnCode " + returnCode);
|
||||
mCallback.onError(InstallerCallback.OPERATION_DELETE,
|
||||
InstallerCallback.ERROR_CODE_OTHER);
|
||||
// mCallback.onError(InstallerCallback.OPERATION_DELETE,
|
||||
// InstallerCallback.ERROR_CODE_OTHER);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -327,30 +280,30 @@ public class PrivilegedInstaller extends Installer {
|
||||
Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CONFIRM_PERMS:
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
final Uri packageUri = data.getData();
|
||||
try {
|
||||
doInstallPackageInternal(packageUri);
|
||||
} catch (InstallFailedException e) {
|
||||
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
InstallerCallback.ERROR_CODE_OTHER);
|
||||
}
|
||||
} else if (resultCode == InstallConfirmActivity.RESULT_CANNOT_PARSE) {
|
||||
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
InstallerCallback.ERROR_CODE_CANNOT_PARSE);
|
||||
} else { // Activity.RESULT_CANCELED
|
||||
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
InstallerCallback.ERROR_CODE_CANCELED);
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// @Override
|
||||
// public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
// switch (requestCode) {
|
||||
// case REQUEST_CONFIRM_PERMS:
|
||||
// if (resultCode == Activity.RESULT_OK) {
|
||||
// final Uri packageUri = data.getData();
|
||||
// try {
|
||||
// doInstallPackageInternal(packageUri);
|
||||
// } catch (InstallFailedException e) {
|
||||
// mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
// InstallerCallback.ERROR_CODE_OTHER);
|
||||
// }
|
||||
// } else if (resultCode == InstallConfirmActivity.RESULT_CANNOT_PARSE) {
|
||||
// mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
// InstallerCallback.ERROR_CODE_CANNOT_PARSE);
|
||||
// } else { // Activity.RESULT_CANCELED
|
||||
// mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
// InstallerCallback.ERROR_CODE_CANCELED);
|
||||
// }
|
||||
// return true;
|
||||
// default:
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
public static final int INSTALL_REPLACE_EXISTING = 2;
|
||||
|
||||
@ -644,7 +597,7 @@ public class PrivilegedInstaller extends Installer {
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
|
||||
* if the system failed to install the package because it is attempting to define a
|
||||
* permission that is already defined by some existing package.
|
||||
*
|
||||
* <p/>
|
||||
* The package name of the app which has already defined the permission is passed to
|
||||
* a {@link PackageInstallObserver}, if any, as the {@link #EXTRA_EXISTING_PACKAGE}
|
||||
* string extra; and the name of the permission being redefined is passed in the
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.fdroid.fdroid.views.swap;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
@ -37,12 +38,14 @@ import org.fdroid.fdroid.NfcHelper;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.compat.PackageManagerCompat;
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.data.ApkProvider;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.fdroid.data.NewRepoConfig;
|
||||
import org.fdroid.fdroid.installer.InstallManagerService;
|
||||
import org.fdroid.fdroid.installer.Installer;
|
||||
import org.fdroid.fdroid.installer.InstallerService;
|
||||
import org.fdroid.fdroid.localrepo.LocalRepoManager;
|
||||
import org.fdroid.fdroid.localrepo.SwapService;
|
||||
import org.fdroid.fdroid.localrepo.peers.Peer;
|
||||
@ -786,25 +789,49 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void handleDownloadComplete(File apkFile, String packageName, String urlString) {
|
||||
Uri originatingUri = Uri.parse(urlString);
|
||||
Uri localUri = Uri.fromFile(apkFile);
|
||||
|
||||
try {
|
||||
Installer.getActivityInstaller(this, new Installer.InstallerCallback() {
|
||||
@Override
|
||||
public void onSuccess(int operation) {
|
||||
// TODO: Don't reload the view weely-neely, but rather get the view to listen
|
||||
// for broadcasts that say the install was complete.
|
||||
showRelevantView(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int operation, int errorCode) {
|
||||
// TODO: Boo!
|
||||
}
|
||||
}).installPackage(apkFile, packageName, urlString);
|
||||
localBroadcastManager.unregisterReceiver(downloadCompleteReceiver);
|
||||
} catch (Installer.InstallFailedException e) {
|
||||
// TODO: Handle exception properly
|
||||
}
|
||||
localBroadcastManager.registerReceiver(installReceiver,
|
||||
Installer.getInstallIntentFilter(Uri.fromFile(apkFile)));
|
||||
InstallerService.install(this, localUri, originatingUri, packageName);
|
||||
}
|
||||
|
||||
private final BroadcastReceiver installReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()) {
|
||||
case Installer.ACTION_INSTALL_STARTED: {
|
||||
break;
|
||||
}
|
||||
case Installer.ACTION_INSTALL_COMPLETE: {
|
||||
localBroadcastManager.unregisterReceiver(this);
|
||||
|
||||
showRelevantView(true);
|
||||
break;
|
||||
}
|
||||
case Installer.ACTION_INSTALL_INTERRUPTED: {
|
||||
localBroadcastManager.unregisterReceiver(this);
|
||||
// TODO: handle errors!
|
||||
break;
|
||||
}
|
||||
case Installer.ACTION_INSTALL_USER_INTERACTION: {
|
||||
PendingIntent installPendingIntent =
|
||||
intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI);
|
||||
|
||||
try {
|
||||
installPendingIntent.send();
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Log.e(TAG, "PI canceled", e);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new RuntimeException("intent action not handled!");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user