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