InstallerService
This commit is contained in:
parent
4ef0642134
commit
6d2f2d20a8
@ -401,6 +401,9 @@
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<activity
|
||||
android:name=".installer.AndroidInstallerActivity"
|
||||
android:theme="@style/AppThemeTransparent" />
|
||||
|
||||
<receiver android:name=".receiver.StartupReceiver" >
|
||||
<intent-filter>
|
||||
@ -440,6 +443,9 @@
|
||||
<service
|
||||
android:name=".net.DownloaderService"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name=".installer.InstallerService"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name=".CleanCacheService"
|
||||
android:exported="false" />
|
||||
|
@ -22,6 +22,7 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.BroadcastReceiver;
|
||||
@ -85,10 +86,9 @@ 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.InstallManagerService;
|
||||
import org.fdroid.fdroid.installer.Installer;
|
||||
import org.fdroid.fdroid.installer.Installer.InstallFailedException;
|
||||
import org.fdroid.fdroid.installer.Installer.InstallerCallback;
|
||||
import org.fdroid.fdroid.installer.InstallerService;
|
||||
import org.fdroid.fdroid.net.Downloader;
|
||||
import org.fdroid.fdroid.net.DownloaderService;
|
||||
|
||||
@ -319,7 +319,6 @@ public class AppDetails extends AppCompatActivity {
|
||||
private int startingIgnoreThis;
|
||||
|
||||
private final Context context = this;
|
||||
private Installer installer;
|
||||
|
||||
private AppDetailsHeaderFragment headerFragment;
|
||||
|
||||
@ -375,8 +374,6 @@ public class AppDetails extends AppCompatActivity {
|
||||
|
||||
packageManager = getPackageManager();
|
||||
|
||||
installer = Installer.getActivityInstaller(this, packageManager, myInstallerCallback);
|
||||
|
||||
// Get the preferences we're going to use in this Activity...
|
||||
ConfigurationChangeHelper previousData = (ConfigurationChangeHelper) getLastCustomNonConfigurationInstance();
|
||||
if (previousData != null) {
|
||||
@ -530,13 +527,12 @@ public class AppDetails extends AppCompatActivity {
|
||||
private final BroadcastReceiver completeReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
File localFile = new File(intent.getStringExtra(Downloader.EXTRA_DOWNLOAD_PATH));
|
||||
try {
|
||||
installer.installPackage(localFile, app.packageName, intent.getDataString());
|
||||
} catch (InstallFailedException e) {
|
||||
Log.e(TAG, "Android not compatible with this Installer!", e);
|
||||
}
|
||||
cleanUpFinishedDownload();
|
||||
|
||||
Uri localUri =
|
||||
Uri.fromFile(new File(intent.getStringExtra(Downloader.EXTRA_DOWNLOAD_PATH)));
|
||||
localBroadcastManager.registerReceiver(installReceiver,
|
||||
InstallerService.getInstallIntentFilter(localUri));
|
||||
}
|
||||
};
|
||||
|
||||
@ -555,6 +551,165 @@ public class AppDetails extends AppCompatActivity {
|
||||
}
|
||||
};
|
||||
|
||||
private final BroadcastReceiver installReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()) {
|
||||
case InstallHelper.ACTION_INSTALL_STARTED: {
|
||||
headerFragment.startProgress();
|
||||
headerFragment.showIndeterminateProgress(getString(R.string.installing));
|
||||
break;
|
||||
}
|
||||
case InstallHelper.ACTION_INSTALL_COMPLETE: {
|
||||
headerFragment.removeProgress();
|
||||
localBroadcastManager.unregisterReceiver(this);
|
||||
|
||||
PackageManagerCompat.setInstaller(packageManager, app.packageName);
|
||||
|
||||
onAppChanged();
|
||||
break;
|
||||
}
|
||||
case InstallHelper.ACTION_INSTALL_INTERRUPTED: {
|
||||
headerFragment.removeProgress();
|
||||
localBroadcastManager.unregisterReceiver(this);
|
||||
|
||||
|
||||
// TODO: old error handling code:
|
||||
// if (errorCode == InstallerCallback.ERROR_CODE_CANCELED) {
|
||||
// return;
|
||||
// }
|
||||
// final int title, body;
|
||||
// if (operation == InstallerCallback.OPERATION_INSTALL) {
|
||||
// title = R.string.install_error_title;
|
||||
// switch (errorCode) {
|
||||
// case ERROR_CODE_CANNOT_PARSE:
|
||||
// body = R.string.install_error_cannot_parse;
|
||||
// break;
|
||||
// default: // ERROR_CODE_OTHER
|
||||
// body = R.string.install_error_unknown;
|
||||
// break;
|
||||
// }
|
||||
// } else { // InstallerCallback.OPERATION_DELETE
|
||||
// title = R.string.uninstall_error_title;
|
||||
// switch (errorCode) {
|
||||
// default: // ERROR_CODE_OTHER
|
||||
// body = R.string.uninstall_error_unknown;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// runOnUiThread(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// onAppChanged();
|
||||
//
|
||||
// Log.e(TAG, "Installer aborted with errorCode: " + errorCode);
|
||||
//
|
||||
// AlertDialog.Builder alertBuilder = new AlertDialog.Builder(AppDetails.this);
|
||||
// alertBuilder.setTitle(title);
|
||||
// alertBuilder.setMessage(body);
|
||||
// alertBuilder.setNeutralButton(android.R.string.ok, null);
|
||||
// alertBuilder.create().show();
|
||||
// }
|
||||
// });
|
||||
break;
|
||||
}
|
||||
case InstallHelper.ACTION_INSTALL_USER_INTERACTION: {
|
||||
PendingIntent installPendingIntent =
|
||||
intent.getParcelableExtra(InstallHelper.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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final BroadcastReceiver uninstallReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()) {
|
||||
case InstallHelper.ACTION_UNINSTALL_STARTED: {
|
||||
headerFragment.startProgress();
|
||||
headerFragment.showIndeterminateProgress(getString(R.string.uninstalling));
|
||||
break;
|
||||
}
|
||||
case InstallHelper.ACTION_UNINSTALL_COMPLETE: {
|
||||
headerFragment.removeProgress();
|
||||
localBroadcastManager.unregisterReceiver(this);
|
||||
|
||||
onAppChanged();
|
||||
break;
|
||||
}
|
||||
case InstallHelper.ACTION_UNINSTALL_INTERRUPTED: {
|
||||
headerFragment.removeProgress();
|
||||
localBroadcastManager.unregisterReceiver(this);
|
||||
|
||||
// TODO: old error handling code:
|
||||
// if (errorCode == InstallerCallback.ERROR_CODE_CANCELED) {
|
||||
// return;
|
||||
// }
|
||||
// final int title, body;
|
||||
// if (operation == InstallerCallback.OPERATION_INSTALL) {
|
||||
// title = R.string.install_error_title;
|
||||
// switch (errorCode) {
|
||||
// case ERROR_CODE_CANNOT_PARSE:
|
||||
// body = R.string.install_error_cannot_parse;
|
||||
// break;
|
||||
// default: // ERROR_CODE_OTHER
|
||||
// body = R.string.install_error_unknown;
|
||||
// break;
|
||||
// }
|
||||
// } else { // InstallerCallback.OPERATION_DELETE
|
||||
// title = R.string.uninstall_error_title;
|
||||
// switch (errorCode) {
|
||||
// default: // ERROR_CODE_OTHER
|
||||
// body = R.string.uninstall_error_unknown;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// runOnUiThread(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// onAppChanged();
|
||||
//
|
||||
// Log.e(TAG, "Installer aborted with errorCode: " + errorCode);
|
||||
//
|
||||
// AlertDialog.Builder alertBuilder = new AlertDialog.Builder(AppDetails.this);
|
||||
// alertBuilder.setTitle(title);
|
||||
// alertBuilder.setMessage(body);
|
||||
// alertBuilder.setNeutralButton(android.R.string.ok, null);
|
||||
// alertBuilder.create().show();
|
||||
// }
|
||||
// });
|
||||
break;
|
||||
}
|
||||
case InstallHelper.ACTION_UNINSTALL_USER_INTERACTION: {
|
||||
PendingIntent uninstallPendingIntent =
|
||||
intent.getParcelableExtra(InstallHelper.EXTRA_USER_INTERACTION_PI);
|
||||
|
||||
try {
|
||||
uninstallPendingIntent.send();
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Log.e(TAG, "PI canceled", e);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new RuntimeException("intent action not handled!");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void onAppChanged() {
|
||||
if (!reset(app.packageName)) {
|
||||
this.finish();
|
||||
@ -796,7 +951,7 @@ public class AppDetails extends AppCompatActivity {
|
||||
return true;
|
||||
|
||||
case UNINSTALL:
|
||||
removeApk(app.packageName);
|
||||
uninstallApk(app.packageName);
|
||||
return true;
|
||||
|
||||
case IGNOREALL:
|
||||
@ -881,71 +1036,12 @@ public class AppDetails extends AppCompatActivity {
|
||||
InstallManagerService.queue(this, app, apk);
|
||||
}
|
||||
|
||||
private void removeApk(String packageName) {
|
||||
try {
|
||||
installer.deletePackage(packageName);
|
||||
} catch (InstallFailedException e) {
|
||||
Log.e(TAG, "Android not compatible with this Installer!", e);
|
||||
}
|
||||
private void uninstallApk(String packageName) {
|
||||
localBroadcastManager.registerReceiver(uninstallReceiver,
|
||||
InstallerService.getUninstallIntentFilter(packageName));
|
||||
InstallerService.uninstall(context, packageName);
|
||||
}
|
||||
|
||||
private final Installer.InstallerCallback myInstallerCallback = new Installer.InstallerCallback() {
|
||||
|
||||
@Override
|
||||
public void onSuccess(final int operation) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (operation == Installer.InstallerCallback.OPERATION_INSTALL) {
|
||||
PackageManagerCompat.setInstaller(packageManager, app.packageName);
|
||||
}
|
||||
|
||||
onAppChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int operation, final int errorCode) {
|
||||
if (errorCode == InstallerCallback.ERROR_CODE_CANCELED) {
|
||||
return;
|
||||
}
|
||||
final int title, body;
|
||||
if (operation == InstallerCallback.OPERATION_INSTALL) {
|
||||
title = R.string.install_error_title;
|
||||
switch (errorCode) {
|
||||
case ERROR_CODE_CANNOT_PARSE:
|
||||
body = R.string.install_error_cannot_parse;
|
||||
break;
|
||||
default: // ERROR_CODE_OTHER
|
||||
body = R.string.install_error_unknown;
|
||||
break;
|
||||
}
|
||||
} else { // InstallerCallback.OPERATION_DELETE
|
||||
title = R.string.uninstall_error_title;
|
||||
switch (errorCode) {
|
||||
default: // ERROR_CODE_OTHER
|
||||
body = R.string.uninstall_error_unknown;
|
||||
break;
|
||||
}
|
||||
}
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onAppChanged();
|
||||
|
||||
Log.e(TAG, "Installer aborted with errorCode: " + errorCode);
|
||||
|
||||
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(AppDetails.this);
|
||||
alertBuilder.setTitle(title);
|
||||
alertBuilder.setMessage(body);
|
||||
alertBuilder.setNeutralButton(android.R.string.ok, null);
|
||||
alertBuilder.create().show();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private void launchApk(String packageName) {
|
||||
Intent intent = packageManager.getLaunchIntentForPackage(packageName);
|
||||
startActivity(intent);
|
||||
@ -963,11 +1059,6 @@ public class AppDetails extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
// handle cases for install manager first
|
||||
if (installer.handleOnActivityResult(requestCode, resultCode, data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (requestCode) {
|
||||
case REQUEST_ENABLE_BLUETOOTH:
|
||||
fdroidApp.sendViaBluetooth(this, resultCode, app.packageName);
|
||||
@ -1606,7 +1697,7 @@ public class AppDetails extends AppCompatActivity {
|
||||
// If "launchable", launch
|
||||
activity.launchApk(app.packageName);
|
||||
} else {
|
||||
activity.removeApk(app.packageName);
|
||||
activity.uninstallApk(app.packageName);
|
||||
}
|
||||
} else if (app.suggestedVersionCode > 0) {
|
||||
// If not installed, install
|
||||
@ -1635,7 +1726,7 @@ public class AppDetails extends AppCompatActivity {
|
||||
}
|
||||
|
||||
void remove() {
|
||||
appDetails.removeApk(appDetails.getApp().packageName);
|
||||
appDetails.uninstallApk(appDetails.getApp().packageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,291 @@
|
||||
/*
|
||||
* Copyright (C) 2014-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.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.support.v4.content.LocalBroadcastManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.Utils;
|
||||
|
||||
/**
|
||||
* A transparent activity as a wrapper around AOSP's PackageInstaller Intents
|
||||
*/
|
||||
public class AndroidInstallerActivity extends FragmentActivity {
|
||||
public static final String TAG = "AndroidInstallerAct";
|
||||
|
||||
public static final String ACTION_INSTALL_PACKAGE = "org.fdroid.fdroid.INSTALL_PACKAGE";
|
||||
public static final String ACTION_UNINSTALL_PACKAGE = "org.fdroid.fdroid.UNINSTALL_PACKAGE";
|
||||
|
||||
public static final String EXTRA_UNINSTALL_PACKAGE_NAME = "uninstallPackageName";
|
||||
public static final String EXTRA_ORIGINATING_URI = "originatingUri";
|
||||
|
||||
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;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
localBroadcastManager = LocalBroadcastManager.getInstance(this);
|
||||
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getAction();
|
||||
switch (action) {
|
||||
case ACTION_INSTALL_PACKAGE: {
|
||||
mInstallUri = intent.getData();
|
||||
mInstallOriginatingUri = intent.getParcelableExtra(EXTRA_ORIGINATING_URI);
|
||||
|
||||
installPackage(mInstallUri, mInstallOriginatingUri);
|
||||
break;
|
||||
}
|
||||
|
||||
case ACTION_UNINSTALL_PACKAGE: {
|
||||
mUninstallPackageName = intent.getStringExtra(EXTRA_UNINSTALL_PACKAGE_NAME);
|
||||
|
||||
uninstallPackage(mUninstallPackageName);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new IllegalStateException("Intent action not specified!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
private void installPackage(Uri uri, Uri originatingUri) {
|
||||
Utils.debugLog(TAG, "Installing from " + 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 <= Build.VERSION_CODES.M)
|
||||
&& (!uri.getScheme().equals("file"))) {
|
||||
throw new RuntimeException("PackageInstaller <= Android 6 only supports file scheme!");
|
||||
}
|
||||
if (("N".equals(Build.VERSION.CODENAME))
|
||||
&& (!uri.getScheme().equals("content"))) {
|
||||
throw new RuntimeException("PackageInstaller >= Android N only supports content scheme!");
|
||||
}
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setData(uri);
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.setType("application/vnd.android.package-archive");
|
||||
} else {
|
||||
intent.setAction(Intent.ACTION_INSTALL_PACKAGE);
|
||||
|
||||
// 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 < Build.VERSION_CODES.JELLY_BEAN) {
|
||||
// deprecated in Android 4.1
|
||||
intent.putExtra(Intent.EXTRA_ALLOW_REPLACE, true);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
startActivityForResult(intent, REQUEST_CODE_INSTALL);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.e(TAG, "ActivityNotFoundException", e);
|
||||
sendBroadcastInstall(uri, originatingUri, InstallHelper.ACTION_INSTALL_INTERRUPTED,
|
||||
"This Android rom does not support ACTION_INSTALL_PACKAGE!");
|
||||
finish();
|
||||
}
|
||||
sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
|
||||
InstallHelper.ACTION_INSTALL_STARTED);
|
||||
}
|
||||
|
||||
protected void uninstallPackage(String packageName) {
|
||||
Intent intent = new Intent();
|
||||
|
||||
// check that the package is installed
|
||||
try {
|
||||
getPackageManager().getPackageInfo(packageName, 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e(TAG, "NameNotFoundException", e);
|
||||
sendBroadcastUninstall(packageName, InstallHelper.ACTION_UNINSTALL_INTERRUPTED,
|
||||
"Package that is scheduled for uninstall is not installed!");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Uri uri = Uri.fromParts("package", packageName, null);
|
||||
intent.setData(uri);
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
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);
|
||||
sendBroadcastUninstall(packageName, InstallHelper.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 < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
|
||||
InstallHelper.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);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (resultCode) {
|
||||
case Activity.RESULT_OK: {
|
||||
sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
|
||||
InstallHelper.ACTION_INSTALL_COMPLETE);
|
||||
break;
|
||||
}
|
||||
case Activity.RESULT_CANCELED: {
|
||||
sendBroadcastInstall(mInstallUri, mInstallOriginatingUri,
|
||||
InstallHelper.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");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
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);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (resultCode) {
|
||||
case Activity.RESULT_OK: {
|
||||
sendBroadcastUninstall(mUninstallPackageName,
|
||||
InstallHelper.ACTION_UNINSTALL_COMPLETE);
|
||||
break;
|
||||
}
|
||||
case Activity.RESULT_CANCELED: {
|
||||
sendBroadcastUninstall(mUninstallPackageName,
|
||||
InstallHelper.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,
|
||||
"error");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new RuntimeException("Invalid request code!");
|
||||
}
|
||||
}
|
||||
|
||||
// after doing the broadcasts, finish this transparent wrapper activity
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
128
app/src/main/java/org/fdroid/fdroid/installer/InstallHelper.java
Normal file
128
app/src/main/java/org/fdroid/fdroid/installer/InstallHelper.java
Normal file
@ -0,0 +1,128 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -85,18 +85,6 @@ public class InstallManagerService extends Service {
|
||||
*/
|
||||
private final HashMap<String, BroadcastReceiver[]> receivers = new HashMap<>(3);
|
||||
|
||||
/**
|
||||
* Get the app name based on a {@code urlString} key. The app name needs
|
||||
* to be kept around for the final notification update, but {@link App}
|
||||
* and {@link Apk} instances have already removed by the time that final
|
||||
* notification update comes around. Once there is a proper
|
||||
* {@code InstallerService} and its integrated here, this must go away,
|
||||
* since the {@link App} and {@link Apk} instances will be available.
|
||||
* <p>
|
||||
* TODO <b>delete me once InstallerService exists</b>
|
||||
*/
|
||||
private static final HashMap<String, String> TEMP_HACK_APP_NAMES = new HashMap<>(3);
|
||||
|
||||
private LocalBroadcastManager localBroadcastManager;
|
||||
private NotificationManager notificationManager;
|
||||
|
||||
@ -234,15 +222,18 @@ public class InstallManagerService extends Service {
|
||||
BroadcastReceiver completeReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String urlString = intent.getDataString();
|
||||
// TODO these need to be removed based on whether they are fed to InstallerService or not
|
||||
Apk apk = removeFromActive(urlString);
|
||||
if (AppDetails.isAppVisible(apk.packageName)) {
|
||||
cancelNotification(urlString);
|
||||
} else {
|
||||
notifyDownloadComplete(urlString, apk);
|
||||
}
|
||||
unregisterDownloaderReceivers(urlString);
|
||||
// elsewhere called urlString
|
||||
Uri originatingUri = intent.getData();
|
||||
File localFile = new File(intent.getStringExtra(Downloader.EXTRA_DOWNLOAD_PATH));
|
||||
Uri localUri = Uri.fromFile(localFile);
|
||||
|
||||
Utils.debugLog(TAG, "download completed of " + originatingUri
|
||||
+ " to " + localUri);
|
||||
|
||||
unregisterDownloaderReceivers(intent.getDataString());
|
||||
|
||||
registerInstallerReceivers(localUri);
|
||||
InstallerService.install(context, localUri, originatingUri);
|
||||
}
|
||||
};
|
||||
BroadcastReceiver interruptedReceiver = new BroadcastReceiver() {
|
||||
@ -265,6 +256,69 @@ public class InstallManagerService extends Service {
|
||||
receivers.put(urlString, new BroadcastReceiver[]{
|
||||
startedReceiver, progressReceiver, completeReceiver, interruptedReceiver,
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void registerInstallerReceivers(Uri uri) {
|
||||
|
||||
BroadcastReceiver installReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()) {
|
||||
case InstallHelper.ACTION_INSTALL_STARTED: {
|
||||
Utils.debugLog(TAG, "ACTION_INSTALL_STARTED");
|
||||
|
||||
break;
|
||||
}
|
||||
case InstallHelper.ACTION_INSTALL_COMPLETE: {
|
||||
Utils.debugLog(TAG, "ACTION_INSTALL_COMPLETE");
|
||||
|
||||
Uri originatingUri =
|
||||
intent.getParcelableExtra(InstallHelper.EXTRA_ORIGINATING_URI);
|
||||
String urlString = originatingUri.toString();
|
||||
removeFromActive(urlString);
|
||||
|
||||
localBroadcastManager.unregisterReceiver(this);
|
||||
|
||||
break;
|
||||
}
|
||||
case InstallHelper.ACTION_INSTALL_INTERRUPTED: {
|
||||
Utils.debugLog(TAG, "ACTION_INSTALL_INTERRUPTED");
|
||||
|
||||
localBroadcastManager.unregisterReceiver(this);
|
||||
|
||||
break;
|
||||
}
|
||||
case InstallHelper.ACTION_INSTALL_USER_INTERACTION: {
|
||||
Utils.debugLog(TAG, "ACTION_INSTALL_USER_INTERACTION");
|
||||
|
||||
Uri originatingUri =
|
||||
intent.getParcelableExtra(InstallHelper.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);
|
||||
|
||||
if (AppDetails.isAppVisible(apk.packageName)) {
|
||||
cancelNotification(urlString);
|
||||
} else {
|
||||
notifyDownloadComplete(apk, urlString, installPendingIntent);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new RuntimeException("intent action not handled!");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
localBroadcastManager.registerReceiver(installReceiver,
|
||||
InstallerService.getInstallIntentFilter(uri));
|
||||
}
|
||||
|
||||
private NotificationCompat.Builder createNotificationBuilder(String urlString, Apk apk) {
|
||||
@ -283,16 +337,7 @@ public class InstallManagerService extends Service {
|
||||
|
||||
private String getAppName(String urlString, Apk apk) {
|
||||
App app = ACTIVE_APPS.get(apk.packageName);
|
||||
if (app == null || TextUtils.isEmpty(app.name)) {
|
||||
if (TEMP_HACK_APP_NAMES.containsKey(urlString)) {
|
||||
return TEMP_HACK_APP_NAMES.get(urlString);
|
||||
} else {
|
||||
// this is ugly, but its better than nothing as a failsafe
|
||||
return urlString;
|
||||
}
|
||||
} else {
|
||||
return app.name;
|
||||
}
|
||||
return app.name;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -319,7 +364,7 @@ public class InstallManagerService extends Service {
|
||||
* Removing the progress bar from a notification should cause the notification's content
|
||||
* text to return to normal size</a>
|
||||
*/
|
||||
private void notifyDownloadComplete(String urlString, Apk apk) {
|
||||
private void notifyDownloadComplete(Apk apk, String urlString, PendingIntent installPendingIntent) {
|
||||
String title;
|
||||
try {
|
||||
PackageManager pm = getPackageManager();
|
||||
@ -335,7 +380,7 @@ public class InstallManagerService extends Service {
|
||||
.setAutoCancel(true)
|
||||
.setOngoing(false)
|
||||
.setContentTitle(title)
|
||||
.setContentIntent(getAppDetailsIntent(downloadUrlId, apk))
|
||||
.setContentIntent(installPendingIntent)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
.setContentText(getString(R.string.tap_to_install))
|
||||
.build();
|
||||
@ -354,7 +399,10 @@ public class InstallManagerService extends Service {
|
||||
private static void addToActive(String urlString, App app, Apk apk) {
|
||||
ACTIVE_APKS.put(urlString, apk);
|
||||
ACTIVE_APPS.put(app.packageName, app);
|
||||
TEMP_HACK_APP_NAMES.put(urlString, app.name); // TODO delete me once InstallerService exists
|
||||
}
|
||||
|
||||
private static Apk getFromActive(String urlString) {
|
||||
return ACTIVE_APKS.get(urlString);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,264 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
* Copyright (C) 2016 Hans-Christoph Steiner
|
||||
* Copyright (C) 2016 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
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
|
||||
*/
|
||||
public class InstallerService extends Service {
|
||||
private static final String TAG = "InstallerService";
|
||||
|
||||
private static final String ACTION_INSTALL = "org.fdroid.fdroid.installer.InstallerService.action.INSTALL";
|
||||
private static final String ACTION_UNINSTALL = "org.fdroid.fdroid.installer.InstallerService.action.UNINSTALL";
|
||||
|
||||
private volatile Looper serviceLooper;
|
||||
private static volatile ServiceHandler serviceHandler;
|
||||
private LocalBroadcastManager localBroadcastManager;
|
||||
|
||||
private final class ServiceHandler extends Handler {
|
||||
ServiceHandler(Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
Utils.debugLog(TAG, "Handling message with ID of " + msg.what);
|
||||
handleIntent((Intent) msg.obj);
|
||||
stopSelf(msg.arg1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
Utils.debugLog(TAG, "Creating installer service.");
|
||||
|
||||
HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
|
||||
thread.start();
|
||||
|
||||
serviceLooper = thread.getLooper();
|
||||
serviceHandler = new ServiceHandler(serviceLooper);
|
||||
localBroadcastManager = LocalBroadcastManager.getInstance(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Utils.debugLog(TAG, "Received Intent for installing/uninstalling: " + intent + " (with a startId of " + startId + ")");
|
||||
|
||||
if (ACTION_INSTALL.equals(intent.getAction())) {
|
||||
Uri uri = intent.getData();
|
||||
|
||||
Message msg = serviceHandler.obtainMessage();
|
||||
msg.arg1 = startId;
|
||||
msg.obj = intent;
|
||||
msg.what = uri.hashCode();
|
||||
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);
|
||||
|
||||
Message msg = serviceHandler.obtainMessage();
|
||||
msg.arg1 = startId;
|
||||
msg.obj = intent;
|
||||
msg.what = packageName.hashCode();
|
||||
serviceHandler.sendMessage(msg);
|
||||
Utils.debugLog(TAG, "Start uninstall of " + packageName);
|
||||
} else {
|
||||
Log.e(TAG, "Received Intent with unknown action: " + intent);
|
||||
}
|
||||
|
||||
return START_REDELIVER_INTENT; // if killed before completion, retry Intent
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
Utils.debugLog(TAG, "Destroying installer service. Will move to background and stop our Looper.");
|
||||
serviceLooper.quit(); //NOPMD - this is copied from IntentService, no super call needed
|
||||
}
|
||||
|
||||
/**
|
||||
* This service does not use binding, so no need to implement this method
|
||||
*/
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void handleIntent(Intent intent) {
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
|
||||
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) {
|
||||
Intent intent = new Intent(context, InstallerService.class);
|
||||
intent.setAction(ACTION_INSTALL);
|
||||
intent.setData(uri);
|
||||
intent.putExtra(InstallHelper.EXTRA_ORIGINATING_URI, originatingUri);
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
@ -370,6 +370,8 @@
|
||||
<string name="perms_description_app">Provided by %1$s.</string>
|
||||
<string name="downloading">Downloading…</string>
|
||||
<string name="downloading_apk">Downloading %1$s</string>
|
||||
<string name="installing">Installing…</string>
|
||||
<string name="uninstalling">Uninstalling…</string>
|
||||
|
||||
<string name="interval_never">Never</string>
|
||||
<string name="interval_1h">Hourly</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user