InstallerService
This commit is contained in:
parent
4ef0642134
commit
6d2f2d20a8
@ -401,6 +401,9 @@
|
|||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
<activity
|
||||||
|
android:name=".installer.AndroidInstallerActivity"
|
||||||
|
android:theme="@style/AppThemeTransparent" />
|
||||||
|
|
||||||
<receiver android:name=".receiver.StartupReceiver" >
|
<receiver android:name=".receiver.StartupReceiver" >
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@ -440,6 +443,9 @@
|
|||||||
<service
|
<service
|
||||||
android:name=".net.DownloaderService"
|
android:name=".net.DownloaderService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
<service
|
||||||
|
android:name=".installer.InstallerService"
|
||||||
|
android:exported="false" />
|
||||||
<service
|
<service
|
||||||
android:name=".CleanCacheService"
|
android:name=".CleanCacheService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
package org.fdroid.fdroid;
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.PendingIntent;
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.BroadcastReceiver;
|
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.AppProvider;
|
||||||
import org.fdroid.fdroid.data.InstalledAppProvider;
|
import org.fdroid.fdroid.data.InstalledAppProvider;
|
||||||
import org.fdroid.fdroid.data.RepoProvider;
|
import org.fdroid.fdroid.data.RepoProvider;
|
||||||
|
import org.fdroid.fdroid.installer.InstallHelper;
|
||||||
import org.fdroid.fdroid.installer.InstallManagerService;
|
import org.fdroid.fdroid.installer.InstallManagerService;
|
||||||
import org.fdroid.fdroid.installer.Installer;
|
import org.fdroid.fdroid.installer.InstallerService;
|
||||||
import org.fdroid.fdroid.installer.Installer.InstallFailedException;
|
|
||||||
import org.fdroid.fdroid.installer.Installer.InstallerCallback;
|
|
||||||
import org.fdroid.fdroid.net.Downloader;
|
import org.fdroid.fdroid.net.Downloader;
|
||||||
import org.fdroid.fdroid.net.DownloaderService;
|
import org.fdroid.fdroid.net.DownloaderService;
|
||||||
|
|
||||||
@ -319,7 +319,6 @@ public class AppDetails extends AppCompatActivity {
|
|||||||
private int startingIgnoreThis;
|
private int startingIgnoreThis;
|
||||||
|
|
||||||
private final Context context = this;
|
private final Context context = this;
|
||||||
private Installer installer;
|
|
||||||
|
|
||||||
private AppDetailsHeaderFragment headerFragment;
|
private AppDetailsHeaderFragment headerFragment;
|
||||||
|
|
||||||
@ -375,8 +374,6 @@ public class AppDetails extends AppCompatActivity {
|
|||||||
|
|
||||||
packageManager = getPackageManager();
|
packageManager = getPackageManager();
|
||||||
|
|
||||||
installer = Installer.getActivityInstaller(this, packageManager, myInstallerCallback);
|
|
||||||
|
|
||||||
// Get the preferences we're going to use in this Activity...
|
// Get the preferences we're going to use in this Activity...
|
||||||
ConfigurationChangeHelper previousData = (ConfigurationChangeHelper) getLastCustomNonConfigurationInstance();
|
ConfigurationChangeHelper previousData = (ConfigurationChangeHelper) getLastCustomNonConfigurationInstance();
|
||||||
if (previousData != null) {
|
if (previousData != null) {
|
||||||
@ -530,13 +527,12 @@ public class AppDetails extends AppCompatActivity {
|
|||||||
private final BroadcastReceiver completeReceiver = new BroadcastReceiver() {
|
private final BroadcastReceiver completeReceiver = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
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();
|
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() {
|
private void onAppChanged() {
|
||||||
if (!reset(app.packageName)) {
|
if (!reset(app.packageName)) {
|
||||||
this.finish();
|
this.finish();
|
||||||
@ -796,7 +951,7 @@ public class AppDetails extends AppCompatActivity {
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case UNINSTALL:
|
case UNINSTALL:
|
||||||
removeApk(app.packageName);
|
uninstallApk(app.packageName);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case IGNOREALL:
|
case IGNOREALL:
|
||||||
@ -881,71 +1036,12 @@ public class AppDetails extends AppCompatActivity {
|
|||||||
InstallManagerService.queue(this, app, apk);
|
InstallManagerService.queue(this, app, apk);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeApk(String packageName) {
|
private void uninstallApk(String packageName) {
|
||||||
try {
|
localBroadcastManager.registerReceiver(uninstallReceiver,
|
||||||
installer.deletePackage(packageName);
|
InstallerService.getUninstallIntentFilter(packageName));
|
||||||
} catch (InstallFailedException e) {
|
InstallerService.uninstall(context, packageName);
|
||||||
Log.e(TAG, "Android not compatible with this Installer!", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
private void launchApk(String packageName) {
|
||||||
Intent intent = packageManager.getLaunchIntentForPackage(packageName);
|
Intent intent = packageManager.getLaunchIntentForPackage(packageName);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
@ -963,11 +1059,6 @@ public class AppDetails extends AppCompatActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
// handle cases for install manager first
|
|
||||||
if (installer.handleOnActivityResult(requestCode, resultCode, data)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
case REQUEST_ENABLE_BLUETOOTH:
|
case REQUEST_ENABLE_BLUETOOTH:
|
||||||
fdroidApp.sendViaBluetooth(this, resultCode, app.packageName);
|
fdroidApp.sendViaBluetooth(this, resultCode, app.packageName);
|
||||||
@ -1606,7 +1697,7 @@ public class AppDetails extends AppCompatActivity {
|
|||||||
// If "launchable", launch
|
// If "launchable", launch
|
||||||
activity.launchApk(app.packageName);
|
activity.launchApk(app.packageName);
|
||||||
} else {
|
} else {
|
||||||
activity.removeApk(app.packageName);
|
activity.uninstallApk(app.packageName);
|
||||||
}
|
}
|
||||||
} else if (app.suggestedVersionCode > 0) {
|
} else if (app.suggestedVersionCode > 0) {
|
||||||
// If not installed, install
|
// If not installed, install
|
||||||
@ -1635,7 +1726,7 @@ public class AppDetails extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void remove() {
|
void remove() {
|
||||||
appDetails.removeApk(appDetails.getApp().packageName);
|
appDetails.uninstallApk(appDetails.getApp().packageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
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 LocalBroadcastManager localBroadcastManager;
|
||||||
private NotificationManager notificationManager;
|
private NotificationManager notificationManager;
|
||||||
|
|
||||||
@ -234,15 +222,18 @@ public class InstallManagerService extends Service {
|
|||||||
BroadcastReceiver completeReceiver = new BroadcastReceiver() {
|
BroadcastReceiver completeReceiver = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
String urlString = intent.getDataString();
|
// elsewhere called urlString
|
||||||
// TODO these need to be removed based on whether they are fed to InstallerService or not
|
Uri originatingUri = intent.getData();
|
||||||
Apk apk = removeFromActive(urlString);
|
File localFile = new File(intent.getStringExtra(Downloader.EXTRA_DOWNLOAD_PATH));
|
||||||
if (AppDetails.isAppVisible(apk.packageName)) {
|
Uri localUri = Uri.fromFile(localFile);
|
||||||
cancelNotification(urlString);
|
|
||||||
} else {
|
Utils.debugLog(TAG, "download completed of " + originatingUri
|
||||||
notifyDownloadComplete(urlString, apk);
|
+ " to " + localUri);
|
||||||
}
|
|
||||||
unregisterDownloaderReceivers(urlString);
|
unregisterDownloaderReceivers(intent.getDataString());
|
||||||
|
|
||||||
|
registerInstallerReceivers(localUri);
|
||||||
|
InstallerService.install(context, localUri, originatingUri);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
BroadcastReceiver interruptedReceiver = new BroadcastReceiver() {
|
BroadcastReceiver interruptedReceiver = new BroadcastReceiver() {
|
||||||
@ -265,6 +256,69 @@ public class InstallManagerService extends Service {
|
|||||||
receivers.put(urlString, new BroadcastReceiver[]{
|
receivers.put(urlString, new BroadcastReceiver[]{
|
||||||
startedReceiver, progressReceiver, completeReceiver, interruptedReceiver,
|
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) {
|
private NotificationCompat.Builder createNotificationBuilder(String urlString, Apk apk) {
|
||||||
@ -283,16 +337,7 @@ public class InstallManagerService extends Service {
|
|||||||
|
|
||||||
private String getAppName(String urlString, Apk apk) {
|
private String getAppName(String urlString, Apk apk) {
|
||||||
App app = ACTIVE_APPS.get(apk.packageName);
|
App app = ACTIVE_APPS.get(apk.packageName);
|
||||||
if (app == null || TextUtils.isEmpty(app.name)) {
|
return 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -319,7 +364,7 @@ public class InstallManagerService extends Service {
|
|||||||
* Removing the progress bar from a notification should cause the notification's content
|
* Removing the progress bar from a notification should cause the notification's content
|
||||||
* text to return to normal size</a>
|
* 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;
|
String title;
|
||||||
try {
|
try {
|
||||||
PackageManager pm = getPackageManager();
|
PackageManager pm = getPackageManager();
|
||||||
@ -335,7 +380,7 @@ public class InstallManagerService extends Service {
|
|||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setOngoing(false)
|
.setOngoing(false)
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
.setContentIntent(getAppDetailsIntent(downloadUrlId, apk))
|
.setContentIntent(installPendingIntent)
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
.setContentText(getString(R.string.tap_to_install))
|
.setContentText(getString(R.string.tap_to_install))
|
||||||
.build();
|
.build();
|
||||||
@ -354,7 +399,10 @@ public class InstallManagerService extends Service {
|
|||||||
private static void addToActive(String urlString, App app, Apk apk) {
|
private static void addToActive(String urlString, App app, Apk apk) {
|
||||||
ACTIVE_APKS.put(urlString, apk);
|
ACTIVE_APKS.put(urlString, apk);
|
||||||
ACTIVE_APPS.put(app.packageName, app);
|
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="perms_description_app">Provided by %1$s.</string>
|
||||||
<string name="downloading">Downloading…</string>
|
<string name="downloading">Downloading…</string>
|
||||||
<string name="downloading_apk">Downloading %1$s</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_never">Never</string>
|
||||||
<string name="interval_1h">Hourly</string>
|
<string name="interval_1h">Hourly</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user