Merge branch 'install-history' into 'master'
optionally keep install history This adds the functionality for keeping the install/uninstall history along with a preference to enable it as a custom build option for allowing another app to read it. Keeping the history has many uses, including: * "popularity contest" * displaying install history locally in F-Droid * reporting to IT device manager for tracking activity for malware, etc. This is ready to be merged, but it is based on !386, so its marked WIP. @dschuermann this touches some of your architecture, please review :) See merge request !392
This commit is contained in:
commit
d35c84b2e1
@ -109,6 +109,15 @@
|
|||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
android:resource="@xml/apk_file_provider" />
|
android:resource="@xml/apk_file_provider" />
|
||||||
</provider>
|
</provider>
|
||||||
|
<provider
|
||||||
|
android:name="android.support.v4.content.FileProvider"
|
||||||
|
android:authorities="org.fdroid.fdroid.installer"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/install_history_file_provider" />
|
||||||
|
</provider>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.app.default_searchable"
|
android:name="android.app.default_searchable"
|
||||||
@ -468,6 +477,9 @@
|
|||||||
<service
|
<service
|
||||||
android:name=".installer.InstallManagerService"
|
android:name=".installer.InstallManagerService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
<service
|
||||||
|
android:name=".installer.InstallHistoryService"
|
||||||
|
android:exported="false" />
|
||||||
<service
|
<service
|
||||||
android:name=".localrepo.CacheSwapAppsService"
|
android:name=".localrepo.CacheSwapAppsService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
@ -862,7 +862,7 @@ public class AppDetails extends AppCompatActivity {
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case UNINSTALL:
|
case UNINSTALL:
|
||||||
uninstallApk(app.packageName);
|
uninstallApk();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case IGNOREALL:
|
case IGNOREALL:
|
||||||
@ -942,7 +942,7 @@ public class AppDetails extends AppCompatActivity {
|
|||||||
|
|
||||||
private void initiateInstall(Apk apk) {
|
private void initiateInstall(Apk apk) {
|
||||||
Installer installer = InstallerFactory.create(this, apk);
|
Installer installer = InstallerFactory.create(this, apk);
|
||||||
Intent intent = installer.getPermissionScreen(apk);
|
Intent intent = installer.getPermissionScreen();
|
||||||
if (intent != null) {
|
if (intent != null) {
|
||||||
// permission screen required
|
// permission screen required
|
||||||
Utils.debugLog(TAG, "permission screen required");
|
Utils.debugLog(TAG, "permission screen required");
|
||||||
@ -959,9 +959,13 @@ public class AppDetails extends AppCompatActivity {
|
|||||||
InstallManagerService.queue(this, app, apk);
|
InstallManagerService.queue(this, app, apk);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void uninstallApk(String packageName) {
|
/**
|
||||||
Installer installer = InstallerFactory.create(this, null);
|
* Queue for uninstall based on the instance variable {@link #app}
|
||||||
Intent intent = installer.getUninstallScreen(packageName);
|
*/
|
||||||
|
private void uninstallApk() {
|
||||||
|
Apk apk = app.installedApk;
|
||||||
|
Installer installer = InstallerFactory.create(this, apk);
|
||||||
|
Intent intent = installer.getUninstallScreen();
|
||||||
if (intent != null) {
|
if (intent != null) {
|
||||||
// uninstall screen required
|
// uninstall screen required
|
||||||
Utils.debugLog(TAG, "screen screen required");
|
Utils.debugLog(TAG, "screen screen required");
|
||||||
@ -975,7 +979,7 @@ public class AppDetails extends AppCompatActivity {
|
|||||||
private void startUninstall() {
|
private void startUninstall() {
|
||||||
localBroadcastManager.registerReceiver(uninstallReceiver,
|
localBroadcastManager.registerReceiver(uninstallReceiver,
|
||||||
Installer.getUninstallIntentFilter(app.packageName));
|
Installer.getUninstallIntentFilter(app.packageName));
|
||||||
InstallerService.uninstall(context, app.packageName);
|
InstallerService.uninstall(context, app.installedApk);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void launchApk(String packageName) {
|
private void launchApk(String packageName) {
|
||||||
@ -1630,7 +1634,7 @@ public class AppDetails extends AppCompatActivity {
|
|||||||
// If "launchable", launch
|
// If "launchable", launch
|
||||||
activity.launchApk(app.packageName);
|
activity.launchApk(app.packageName);
|
||||||
} else {
|
} else {
|
||||||
activity.uninstallApk(app.packageName);
|
activity.uninstallApk();
|
||||||
}
|
}
|
||||||
} else if (app.suggestedVersionCode > 0) {
|
} else if (app.suggestedVersionCode > 0) {
|
||||||
// If not installed, install
|
// If not installed, install
|
||||||
@ -1658,10 +1662,6 @@ public class AppDetails extends AppCompatActivity {
|
|||||||
appDetails = (AppDetails) activity;
|
appDetails = (AppDetails) activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove() {
|
|
||||||
appDetails.uninstallApk(appDetails.getApp().packageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
// A bit of a hack, but we can't add the header view in setupSummaryHeader(),
|
// A bit of a hack, but we can't add the header view in setupSummaryHeader(),
|
||||||
@ -1689,7 +1689,7 @@ public class AppDetails extends AppCompatActivity {
|
|||||||
App app = appDetails.getApp();
|
App app = appDetails.getApp();
|
||||||
final Apk apk = appDetails.getApks().getItem(position - l.getHeaderViewsCount());
|
final Apk apk = appDetails.getApks().getItem(position - l.getHeaderViewsCount());
|
||||||
if (app.installedVersionCode == apk.versionCode) {
|
if (app.installedVersionCode == apk.versionCode) {
|
||||||
remove();
|
appDetails.uninstallApk();
|
||||||
} else if (app.installedVersionCode > apk.versionCode) {
|
} else if (app.installedVersionCode > apk.versionCode) {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
builder.setMessage(R.string.installDowngrade);
|
builder.setMessage(R.string.installDowngrade);
|
||||||
|
@ -56,6 +56,7 @@ import org.fdroid.fdroid.compat.PRNGFixes;
|
|||||||
import org.fdroid.fdroid.data.AppProvider;
|
import org.fdroid.fdroid.data.AppProvider;
|
||||||
import org.fdroid.fdroid.data.InstalledAppProviderService;
|
import org.fdroid.fdroid.data.InstalledAppProviderService;
|
||||||
import org.fdroid.fdroid.data.Repo;
|
import org.fdroid.fdroid.data.Repo;
|
||||||
|
import org.fdroid.fdroid.installer.InstallHistoryService;
|
||||||
import org.fdroid.fdroid.net.IconDownloader;
|
import org.fdroid.fdroid.net.IconDownloader;
|
||||||
import org.fdroid.fdroid.net.WifiStateChangeService;
|
import org.fdroid.fdroid.net.WifiStateChangeService;
|
||||||
|
|
||||||
@ -299,6 +300,21 @@ public class FDroidApp extends Application {
|
|||||||
});
|
});
|
||||||
|
|
||||||
configureTor(Preferences.get().isTorEnabled());
|
configureTor(Preferences.get().isTorEnabled());
|
||||||
|
|
||||||
|
if (Preferences.get().isKeepingInstallHistory()) {
|
||||||
|
InstallHistoryService.register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
String packageName = getString(R.string.install_history_reader_packageName);
|
||||||
|
String unset = getString(R.string.install_history_reader_packageName_UNSET);
|
||||||
|
if (!TextUtils.equals(packageName, unset)) {
|
||||||
|
int modeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
|
||||||
|
if (Build.VERSION.SDK_INT >= 19) {
|
||||||
|
modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
|
||||||
|
}
|
||||||
|
grantUriPermission(packageName, InstallHistoryService.LOG_URI, modeFlags);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(18)
|
@TargetApi(18)
|
||||||
|
@ -55,6 +55,7 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
|
|||||||
public static final String PREF_IGN_TOUCH = "ignoreTouchscreen";
|
public static final String PREF_IGN_TOUCH = "ignoreTouchscreen";
|
||||||
public static final String PREF_KEEP_CACHE_TIME = "keepCacheFor";
|
public static final String PREF_KEEP_CACHE_TIME = "keepCacheFor";
|
||||||
public static final String PREF_UNSTABLE_UPDATES = "unstableUpdates";
|
public static final String PREF_UNSTABLE_UPDATES = "unstableUpdates";
|
||||||
|
public static final String PREF_KEEP_INSTALL_HISTORY = "keepInstallHistory";
|
||||||
public static final String PREF_EXPERT = "expert";
|
public static final String PREF_EXPERT = "expert";
|
||||||
public static final String PREF_PRIVILEGED_INSTALLER = "privilegedInstaller";
|
public static final String PREF_PRIVILEGED_INSTALLER = "privilegedInstaller";
|
||||||
public static final String PREF_UNINSTALL_PRIVILEGED_APP = "uninstallPrivilegedApp";
|
public static final String PREF_UNINSTALL_PRIVILEGED_APP = "uninstallPrivilegedApp";
|
||||||
@ -75,6 +76,7 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
|
|||||||
//private static final boolean DEFAULT_LOCAL_REPO_BONJOUR = true;
|
//private static final boolean DEFAULT_LOCAL_REPO_BONJOUR = true;
|
||||||
private static final long DEFAULT_KEEP_CACHE_TIME = TimeUnit.DAYS.toMillis(1);
|
private static final long DEFAULT_KEEP_CACHE_TIME = TimeUnit.DAYS.toMillis(1);
|
||||||
private static final boolean DEFAULT_UNSTABLE_UPDATES = false;
|
private static final boolean DEFAULT_UNSTABLE_UPDATES = false;
|
||||||
|
private static final boolean DEFAULT_KEEP_INSTALL_HISTORY = false;
|
||||||
//private static final boolean DEFAULT_LOCAL_REPO_HTTPS = false;
|
//private static final boolean DEFAULT_LOCAL_REPO_HTTPS = false;
|
||||||
private static final boolean DEFAULT_INCOMP_VER = false;
|
private static final boolean DEFAULT_INCOMP_VER = false;
|
||||||
private static final boolean DEFAULT_EXPERT = false;
|
private static final boolean DEFAULT_EXPERT = false;
|
||||||
@ -184,6 +186,10 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
|
|||||||
return preferences.getBoolean(PREF_UNSTABLE_UPDATES, DEFAULT_UNSTABLE_UPDATES);
|
return preferences.getBoolean(PREF_UNSTABLE_UPDATES, DEFAULT_UNSTABLE_UPDATES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isKeepingInstallHistory() {
|
||||||
|
return preferences.getBoolean(PREF_KEEP_INSTALL_HISTORY, DEFAULT_KEEP_INSTALL_HISTORY);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean showIncompatibleVersions() {
|
public boolean showIncompatibleVersions() {
|
||||||
return preferences.getBoolean(PREF_INCOMP_VER, DEFAULT_INCOMP_VER);
|
return preferences.getBoolean(PREF_INCOMP_VER, DEFAULT_INCOMP_VER);
|
||||||
}
|
}
|
||||||
|
@ -483,7 +483,9 @@ public class RepoUpdater {
|
|||||||
}
|
}
|
||||||
if (repoPushRequest.versionCode == null
|
if (repoPushRequest.versionCode == null
|
||||||
|| repoPushRequest.versionCode == packageInfo.versionCode) {
|
|| repoPushRequest.versionCode == packageInfo.versionCode) {
|
||||||
InstallerService.uninstall(context, packageName);
|
Apk apk = ApkProvider.Helper.find(context, repoPushRequest.packageName,
|
||||||
|
packageInfo.versionCode);
|
||||||
|
InstallerService.uninstall(context, apk);
|
||||||
} else {
|
} else {
|
||||||
Utils.debugLog(TAG, "ignoring request based on versionCode:" + repoPushRequest);
|
Utils.debugLog(TAG, "ignoring request based on versionCode:" + repoPushRequest);
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,8 @@ public class FileCompat {
|
|||||||
dest.getAbsolutePath(),
|
dest.getAbsolutePath(),
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
Utils.debugLog(TAG, "Executing command: " + commands[0] + " " + commands[1] + " " + commands[2]);
|
Utils.debugLog(TAG, "Executing command: " + commands[0] + " " + commands[1]
|
||||||
|
+ " " + commands[2] + " " + commands[3]);
|
||||||
Process proc = Runtime.getRuntime().exec(commands);
|
Process proc = Runtime.getRuntime().exec(commands);
|
||||||
Utils.consumeStream(proc.getInputStream());
|
Utils.consumeStream(proc.getInputStream());
|
||||||
Utils.consumeStream(proc.getErrorStream());
|
Utils.consumeStream(proc.getErrorStream());
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2016 Dominik Schürmann <dominik@dominikschuermann.de>
|
* Copyright (C) 2016 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
|
* Copyright (C) 2016 Blue Jay Wireless
|
||||||
*
|
*
|
||||||
* 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
|
||||||
@ -25,11 +26,8 @@ import android.content.Intent;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import org.fdroid.fdroid.Utils;
|
|
||||||
import org.fdroid.fdroid.data.Apk;
|
import org.fdroid.fdroid.data.Apk;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default installer of F-Droid. It uses the normal Intents APIs of Android
|
* The default installer of F-Droid. It uses the normal Intents APIs of Android
|
||||||
* to install apks. Its main inner workings are encapsulated in DefaultInstallerActivity.
|
* to install apks. Its main inner workings are encapsulated in DefaultInstallerActivity.
|
||||||
@ -39,21 +37,19 @@ import java.io.File;
|
|||||||
*/
|
*/
|
||||||
public class DefaultInstaller extends Installer {
|
public class DefaultInstaller extends Installer {
|
||||||
|
|
||||||
private static final String TAG = "DefaultInstaller";
|
public static final String TAG = "DefaultInstaller";
|
||||||
|
|
||||||
DefaultInstaller(Context context) {
|
DefaultInstaller(Context context, Apk apk) {
|
||||||
super(context);
|
super(context, apk);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void installPackageInternal(Uri localApkUri, Uri downloadUri, Apk apk) {
|
protected void installPackageInternal(Uri localApkUri, Uri downloadUri) {
|
||||||
sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_STARTED);
|
|
||||||
|
|
||||||
Utils.debugLog(TAG, "DefaultInstaller uri: " + localApkUri + " file: " + new File(localApkUri.getPath()));
|
|
||||||
|
|
||||||
Intent installIntent = new Intent(context, DefaultInstallerActivity.class);
|
Intent installIntent = new Intent(context, DefaultInstallerActivity.class);
|
||||||
installIntent.setAction(DefaultInstallerActivity.ACTION_INSTALL_PACKAGE);
|
installIntent.setAction(DefaultInstallerActivity.ACTION_INSTALL_PACKAGE);
|
||||||
installIntent.putExtra(Installer.EXTRA_DOWNLOAD_URI, downloadUri);
|
installIntent.putExtra(Installer.EXTRA_DOWNLOAD_URI, downloadUri);
|
||||||
|
installIntent.putExtra(Installer.EXTRA_APK, apk);
|
||||||
installIntent.setData(localApkUri);
|
installIntent.setData(localApkUri);
|
||||||
|
|
||||||
PendingIntent installPendingIntent = PendingIntent.getActivity(
|
PendingIntent installPendingIntent = PendingIntent.getActivity(
|
||||||
@ -67,21 +63,19 @@ public class DefaultInstaller extends Installer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void uninstallPackage(String packageName) {
|
protected void uninstallPackage() {
|
||||||
sendBroadcastUninstall(packageName, Installer.ACTION_UNINSTALL_STARTED);
|
sendBroadcastUninstall(Installer.ACTION_UNINSTALL_STARTED);
|
||||||
|
|
||||||
Intent uninstallIntent = new Intent(context, DefaultInstallerActivity.class);
|
Intent uninstallIntent = new Intent(context, DefaultInstallerActivity.class);
|
||||||
uninstallIntent.setAction(DefaultInstallerActivity.ACTION_UNINSTALL_PACKAGE);
|
uninstallIntent.setAction(DefaultInstallerActivity.ACTION_UNINSTALL_PACKAGE);
|
||||||
uninstallIntent.putExtra(
|
uninstallIntent.putExtra(Installer.EXTRA_APK, apk);
|
||||||
DefaultInstallerActivity.EXTRA_UNINSTALL_PACKAGE_NAME, packageName);
|
|
||||||
PendingIntent uninstallPendingIntent = PendingIntent.getActivity(
|
PendingIntent uninstallPendingIntent = PendingIntent.getActivity(
|
||||||
context.getApplicationContext(),
|
context.getApplicationContext(),
|
||||||
packageName.hashCode(),
|
apk.packageName.hashCode(),
|
||||||
uninstallIntent,
|
uninstallIntent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
|
||||||
sendBroadcastUninstall(packageName,
|
sendBroadcastUninstall(Installer.ACTION_UNINSTALL_USER_INTERACTION, uninstallPendingIntent);
|
||||||
Installer.ACTION_UNINSTALL_USER_INTERACTION, uninstallPendingIntent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2014-2016 Dominik Schürmann <dominik@dominikschuermann.de>
|
* Copyright (C) 2014-2016 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
|
* Copyright (C) 2016 Blue Jay Wireless
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* 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
|
||||||
@ -31,23 +32,21 @@ import android.support.v4.app.FragmentActivity;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
|
import org.fdroid.fdroid.data.Apk;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A transparent activity as a wrapper around Android's PackageInstaller Intents
|
* A transparent activity as a wrapper around Android's PackageInstaller Intents
|
||||||
*/
|
*/
|
||||||
public class DefaultInstallerActivity extends FragmentActivity {
|
public class DefaultInstallerActivity extends FragmentActivity {
|
||||||
private static final String TAG = "AndroidInstallerAct";
|
private static final String TAG = "DefaultInstallerActivit";
|
||||||
|
|
||||||
static final String ACTION_INSTALL_PACKAGE = "org.fdroid.fdroid.installer.DefaultInstaller.action.INSTALL_PACKAGE";
|
static final String ACTION_INSTALL_PACKAGE = "org.fdroid.fdroid.installer.DefaultInstaller.action.INSTALL_PACKAGE";
|
||||||
static final String ACTION_UNINSTALL_PACKAGE = "org.fdroid.fdroid.installer.DefaultInstaller.action.UNINSTALL_PACKAGE";
|
static final String ACTION_UNINSTALL_PACKAGE = "org.fdroid.fdroid.installer.DefaultInstaller.action.UNINSTALL_PACKAGE";
|
||||||
|
|
||||||
static final String EXTRA_UNINSTALL_PACKAGE_NAME = "org.fdroid.fdroid.installer.DefaultInstaller.extra.UNINSTALL_PACKAGE_NAME";
|
|
||||||
|
|
||||||
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 Uri downloadUri;
|
private Uri downloadUri;
|
||||||
private String uninstallPackageName;
|
|
||||||
|
|
||||||
// for the broadcasts
|
// for the broadcasts
|
||||||
private DefaultInstaller installer;
|
private DefaultInstaller installer;
|
||||||
@ -56,18 +55,16 @@ public class DefaultInstallerActivity extends FragmentActivity {
|
|||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
installer = new DefaultInstaller(this);
|
|
||||||
|
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
String action = intent.getAction();
|
String action = intent.getAction();
|
||||||
|
Apk apk = intent.getParcelableExtra(Installer.EXTRA_APK);
|
||||||
|
installer = new DefaultInstaller(this, apk);
|
||||||
if (ACTION_INSTALL_PACKAGE.equals(action)) {
|
if (ACTION_INSTALL_PACKAGE.equals(action)) {
|
||||||
Uri localApkUri = intent.getData();
|
Uri localApkUri = intent.getData();
|
||||||
downloadUri = intent.getParcelableExtra(Installer.EXTRA_DOWNLOAD_URI);
|
downloadUri = intent.getParcelableExtra(Installer.EXTRA_DOWNLOAD_URI);
|
||||||
installPackage(localApkUri);
|
installPackage(localApkUri);
|
||||||
} else if (ACTION_UNINSTALL_PACKAGE.equals(action)) {
|
} else if (ACTION_UNINSTALL_PACKAGE.equals(action)) {
|
||||||
uninstallPackageName = intent.getStringExtra(EXTRA_UNINSTALL_PACKAGE_NAME);
|
uninstallPackage(apk.packageName);
|
||||||
|
|
||||||
uninstallPackage(uninstallPackageName);
|
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("Intent action not specified!");
|
throw new IllegalStateException("Intent action not specified!");
|
||||||
}
|
}
|
||||||
@ -125,7 +122,6 @@ public class DefaultInstallerActivity extends FragmentActivity {
|
|||||||
"This Android rom does not support ACTION_INSTALL_PACKAGE!");
|
"This Android rom does not support ACTION_INSTALL_PACKAGE!");
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_STARTED);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void uninstallPackage(String packageName) {
|
private void uninstallPackage(String packageName) {
|
||||||
@ -134,7 +130,7 @@ public class DefaultInstallerActivity 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);
|
||||||
installer.sendBroadcastUninstall(packageName, Installer.ACTION_UNINSTALL_INTERRUPTED,
|
installer.sendBroadcastUninstall(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;
|
||||||
@ -155,7 +151,7 @@ public class DefaultInstallerActivity 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);
|
||||||
installer.sendBroadcastUninstall(packageName, Installer.ACTION_UNINSTALL_INTERRUPTED,
|
installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_INTERRUPTED,
|
||||||
"This Android rom does not support ACTION_UNINSTALL_PACKAGE!");
|
"This Android rom does not support ACTION_UNINSTALL_PACKAGE!");
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
@ -197,25 +193,21 @@ public class DefaultInstallerActivity 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 < 14) {
|
if (Build.VERSION.SDK_INT < 14) {
|
||||||
installer.sendBroadcastUninstall(uninstallPackageName,
|
installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_COMPLETE);
|
||||||
Installer.ACTION_UNINSTALL_COMPLETE);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (resultCode) {
|
switch (resultCode) {
|
||||||
case Activity.RESULT_OK:
|
case Activity.RESULT_OK:
|
||||||
installer.sendBroadcastUninstall(uninstallPackageName,
|
installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_COMPLETE);
|
||||||
Installer.ACTION_UNINSTALL_COMPLETE);
|
|
||||||
break;
|
break;
|
||||||
case Activity.RESULT_CANCELED:
|
case Activity.RESULT_CANCELED:
|
||||||
installer.sendBroadcastUninstall(uninstallPackageName,
|
installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_INTERRUPTED);
|
||||||
Installer.ACTION_UNINSTALL_INTERRUPTED);
|
|
||||||
break;
|
break;
|
||||||
case Activity.RESULT_FIRST_USER:
|
case Activity.RESULT_FIRST_USER:
|
||||||
default:
|
default:
|
||||||
// AOSP UninstallAppProgress returns RESULT_FIRST_USER on error
|
// AOSP UninstallAppProgress returns RESULT_FIRST_USER on error
|
||||||
installer.sendBroadcastUninstall(uninstallPackageName,
|
installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_INTERRUPTED,
|
||||||
Installer.ACTION_UNINSTALL_INTERRUPTED,
|
|
||||||
getString(R.string.uninstall_error_unknown));
|
getString(R.string.uninstall_error_unknown));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
|
* Copyright (C) 2016 Blue Jay Wireless
|
||||||
* Copyright (C) 2016 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
|
||||||
@ -39,12 +40,12 @@ import java.io.File;
|
|||||||
*/
|
*/
|
||||||
public class ExtensionInstaller extends Installer {
|
public class ExtensionInstaller extends Installer {
|
||||||
|
|
||||||
ExtensionInstaller(Context context) {
|
ExtensionInstaller(Context context, Apk apk) {
|
||||||
super(context);
|
super(context, apk);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void installPackageInternal(Uri localApkUri, Uri downloadUri, Apk apk) {
|
protected void installPackageInternal(Uri localApkUri, Uri downloadUri) {
|
||||||
// extension must be signed with the same public key as main F-Droid
|
// extension must be signed with the same public key as main F-Droid
|
||||||
// NOTE: Disabled for debug builds to be able to test official extension from repo
|
// NOTE: Disabled for debug builds to be able to test official extension from repo
|
||||||
ApkSignatureVerifier signatureVerifier = new ApkSignatureVerifier(context);
|
ApkSignatureVerifier signatureVerifier = new ApkSignatureVerifier(context);
|
||||||
@ -71,23 +72,22 @@ public class ExtensionInstaller extends Installer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void uninstallPackage(String packageName) {
|
protected void uninstallPackage() {
|
||||||
sendBroadcastUninstall(packageName, Installer.ACTION_UNINSTALL_STARTED);
|
sendBroadcastUninstall(Installer.ACTION_UNINSTALL_STARTED);
|
||||||
|
|
||||||
Intent uninstallIntent = new Intent(context, InstallExtensionDialogActivity.class);
|
Intent uninstallIntent = new Intent(context, InstallExtensionDialogActivity.class);
|
||||||
uninstallIntent.setAction(InstallExtensionDialogActivity.ACTION_UNINSTALL);
|
uninstallIntent.setAction(InstallExtensionDialogActivity.ACTION_UNINSTALL);
|
||||||
|
|
||||||
PendingIntent uninstallPendingIntent = PendingIntent.getActivity(
|
PendingIntent uninstallPendingIntent = PendingIntent.getActivity(
|
||||||
context.getApplicationContext(),
|
context.getApplicationContext(),
|
||||||
packageName.hashCode(),
|
apk.packageName.hashCode(),
|
||||||
uninstallIntent,
|
uninstallIntent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
|
||||||
sendBroadcastUninstall(packageName,
|
sendBroadcastUninstall(Installer.ACTION_UNINSTALL_USER_INTERACTION, uninstallPendingIntent);
|
||||||
Installer.ACTION_UNINSTALL_USER_INTERACTION, uninstallPendingIntent);
|
|
||||||
|
|
||||||
// don't use broadcasts for the rest of this special installer
|
// don't use broadcasts for the rest of this special installer
|
||||||
sendBroadcastUninstall(packageName, Installer.ACTION_UNINSTALL_COMPLETE);
|
sendBroadcastUninstall(Installer.ACTION_UNINSTALL_COMPLETE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Blue Jay Wireless
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 3
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
* MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.fdroid.fdroid.installer;
|
||||||
|
|
||||||
|
import android.app.IntentService;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Process;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.Utils;
|
||||||
|
import org.fdroid.fdroid.data.Apk;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves all activity of installs and uninstalls to the database for later use, like
|
||||||
|
* displaying in some kind of history viewer or reporting to a "popularity contest"
|
||||||
|
* app tracker.
|
||||||
|
*/
|
||||||
|
public class InstallHistoryService extends IntentService {
|
||||||
|
public static final String TAG = "InstallHistoryService";
|
||||||
|
|
||||||
|
public static final Uri LOG_URI = Uri.parse("content://org.fdroid.fdroid.installer/install_history/all");
|
||||||
|
|
||||||
|
private static BroadcastReceiver broadcastReceiver;
|
||||||
|
|
||||||
|
public static void register(Context context) {
|
||||||
|
if (broadcastReceiver != null) {
|
||||||
|
return; // already registered
|
||||||
|
}
|
||||||
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
|
intentFilter.addDataScheme("http");
|
||||||
|
intentFilter.addDataScheme("https");
|
||||||
|
intentFilter.addDataScheme("package");
|
||||||
|
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.addAction(Installer.ACTION_UNINSTALL_STARTED);
|
||||||
|
intentFilter.addAction(Installer.ACTION_UNINSTALL_COMPLETE);
|
||||||
|
intentFilter.addAction(Installer.ACTION_UNINSTALL_INTERRUPTED);
|
||||||
|
intentFilter.addAction(Installer.ACTION_UNINSTALL_USER_INTERACTION);
|
||||||
|
|
||||||
|
broadcastReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
queue(context, intent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
|
||||||
|
localBroadcastManager.registerReceiver(broadcastReceiver, intentFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void unregister(Context context) {
|
||||||
|
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
|
||||||
|
localBroadcastManager.unregisterReceiver(broadcastReceiver);
|
||||||
|
broadcastReceiver = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void queue(Context context, Intent intent) {
|
||||||
|
Utils.debugLog(TAG, "queue " + intent);
|
||||||
|
intent.setClass(context, InstallHistoryService.class);
|
||||||
|
context.startService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InstallHistoryService() {
|
||||||
|
super("InstallHistoryService");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onHandleIntent(Intent intent) {
|
||||||
|
Utils.debugLog(TAG, "onHandleIntent " + intent);
|
||||||
|
if (intent == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
|
Apk apk = intent.getParcelableExtra(Installer.EXTRA_APK);
|
||||||
|
String packageName = apk.packageName;
|
||||||
|
int versionCode = apk.versionCode;
|
||||||
|
|
||||||
|
List<String> values = new ArrayList<>(4);
|
||||||
|
values.add(String.valueOf(timestamp));
|
||||||
|
values.add(packageName);
|
||||||
|
values.add(String.valueOf(versionCode));
|
||||||
|
values.add(intent.getAction());
|
||||||
|
|
||||||
|
File installHistoryDir = new File(getCacheDir(), "install_history");
|
||||||
|
installHistoryDir.mkdir();
|
||||||
|
File logFile = new File(installHistoryDir, "all");
|
||||||
|
FileWriter fw = null;
|
||||||
|
PrintWriter out = null;
|
||||||
|
try {
|
||||||
|
fw = new FileWriter(logFile, true);
|
||||||
|
out = new PrintWriter(fw);
|
||||||
|
out.println(TextUtils.join(",", values));
|
||||||
|
} catch (IOException e) {
|
||||||
|
Utils.debugLog(TAG, e.getMessage());
|
||||||
|
} finally {
|
||||||
|
Utils.closeQuietly(out);
|
||||||
|
Utils.closeQuietly(fw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import android.app.NotificationManager;
|
|||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
@ -22,6 +23,7 @@ import org.fdroid.fdroid.Utils;
|
|||||||
import org.fdroid.fdroid.compat.PackageManagerCompat;
|
import org.fdroid.fdroid.compat.PackageManagerCompat;
|
||||||
import org.fdroid.fdroid.data.Apk;
|
import org.fdroid.fdroid.data.Apk;
|
||||||
import org.fdroid.fdroid.data.App;
|
import org.fdroid.fdroid.data.App;
|
||||||
|
import org.fdroid.fdroid.data.AppProvider;
|
||||||
import org.fdroid.fdroid.net.Downloader;
|
import org.fdroid.fdroid.net.Downloader;
|
||||||
import org.fdroid.fdroid.net.DownloaderService;
|
import org.fdroid.fdroid.net.DownloaderService;
|
||||||
|
|
||||||
@ -235,6 +237,7 @@ public class InstallManagerService extends Service {
|
|||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
String downloadUrl = intent.getDataString();
|
String downloadUrl = intent.getDataString();
|
||||||
|
Apk apk;
|
||||||
switch (intent.getAction()) {
|
switch (intent.getAction()) {
|
||||||
case Installer.ACTION_INSTALL_STARTED:
|
case Installer.ACTION_INSTALL_STARTED:
|
||||||
// nothing to do
|
// nothing to do
|
||||||
@ -247,12 +250,17 @@ public class InstallManagerService extends Service {
|
|||||||
localBroadcastManager.unregisterReceiver(this);
|
localBroadcastManager.unregisterReceiver(this);
|
||||||
break;
|
break;
|
||||||
case Installer.ACTION_INSTALL_INTERRUPTED:
|
case Installer.ACTION_INSTALL_INTERRUPTED:
|
||||||
|
apk = intent.getParcelableExtra(Installer.EXTRA_APK);
|
||||||
String errorMessage =
|
String errorMessage =
|
||||||
intent.getStringExtra(Installer.EXTRA_ERROR_MESSAGE);
|
intent.getStringExtra(Installer.EXTRA_ERROR_MESSAGE);
|
||||||
|
|
||||||
// show notification if app details is not visible
|
// show notification if app details is not visible
|
||||||
if (!TextUtils.isEmpty(errorMessage)) {
|
if (!TextUtils.isEmpty(errorMessage)) {
|
||||||
App app = getAppFromActive(downloadUrl);
|
App app = getAppFromActive(downloadUrl);
|
||||||
|
if (app == null) {
|
||||||
|
ContentResolver resolver = context.getContentResolver();
|
||||||
|
app = AppProvider.Helper.findByPackageName(resolver, apk.packageName);
|
||||||
|
}
|
||||||
// show notification if app details is not visible
|
// show notification if app details is not visible
|
||||||
if (app != null && AppDetails.isAppVisible(app.packageName)) {
|
if (app != null && AppDetails.isAppVisible(app.packageName)) {
|
||||||
cancelNotification(downloadUrl);
|
cancelNotification(downloadUrl);
|
||||||
@ -264,15 +272,15 @@ public class InstallManagerService extends Service {
|
|||||||
localBroadcastManager.unregisterReceiver(this);
|
localBroadcastManager.unregisterReceiver(this);
|
||||||
break;
|
break;
|
||||||
case Installer.ACTION_INSTALL_USER_INTERACTION:
|
case Installer.ACTION_INSTALL_USER_INTERACTION:
|
||||||
|
apk = intent.getParcelableExtra(Installer.EXTRA_APK);
|
||||||
PendingIntent installPendingIntent =
|
PendingIntent installPendingIntent =
|
||||||
intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI);
|
intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI);
|
||||||
|
|
||||||
Apk apkUserInteraction = getApkFromActive(downloadUrl);
|
|
||||||
// show notification if app details is not visible
|
// show notification if app details is not visible
|
||||||
if (AppDetails.isAppVisible(apkUserInteraction.packageName)) {
|
if (AppDetails.isAppVisible(apk.packageName)) {
|
||||||
cancelNotification(downloadUrl);
|
cancelNotification(downloadUrl);
|
||||||
} else {
|
} else {
|
||||||
notifyDownloadComplete(apkUserInteraction, downloadUrl, installPendingIntent);
|
notifyDownloadComplete(apk, downloadUrl, installPendingIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -335,10 +343,14 @@ 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) {
|
||||||
// TODO use packageName to fetch App instance from database if not cached
|
|
||||||
String name = getAppName(apk);
|
String name = getAppName(apk);
|
||||||
if (TextUtils.isEmpty(name) || name.equals(new App().name)) {
|
if (TextUtils.isEmpty(name) || name.equals(new App().name)) {
|
||||||
return; // do not have a name to display, so leave notification as is
|
ContentResolver resolver = getContentResolver();
|
||||||
|
App app = AppProvider.Helper.findByPackageName(resolver, apk.packageName);
|
||||||
|
if (app == null || TextUtils.isEmpty(app.name)) {
|
||||||
|
return; // do not have a name to display, so leave notification as is
|
||||||
|
}
|
||||||
|
name = app.name;
|
||||||
}
|
}
|
||||||
title = String.format(getString(R.string.tap_to_install_format), name);
|
title = String.format(getString(R.string.tap_to_install_format), name);
|
||||||
}
|
}
|
||||||
@ -450,10 +462,13 @@ public class InstallManagerService extends Service {
|
|||||||
*/
|
*/
|
||||||
public static void queue(Context context, App app, Apk apk) {
|
public static void queue(Context context, App app, Apk apk) {
|
||||||
String urlString = apk.getUrl();
|
String urlString = apk.getUrl();
|
||||||
|
Uri downloadUri = Uri.parse(urlString);
|
||||||
|
Installer.sendBroadcastInstall(context, downloadUri, Installer.ACTION_INSTALL_STARTED, apk,
|
||||||
|
null, null);
|
||||||
Utils.debugLog(TAG, "queue " + app.packageName + " " + apk.versionCode + " from " + urlString);
|
Utils.debugLog(TAG, "queue " + app.packageName + " " + apk.versionCode + " from " + urlString);
|
||||||
Intent intent = new Intent(context, InstallManagerService.class);
|
Intent intent = new Intent(context, InstallManagerService.class);
|
||||||
intent.setAction(ACTION_INSTALL);
|
intent.setAction(ACTION_INSTALL);
|
||||||
intent.setData(Uri.parse(urlString));
|
intent.setData(downloadUri);
|
||||||
intent.putExtra(EXTRA_APP, app);
|
intent.putExtra(EXTRA_APP, app);
|
||||||
intent.putExtra(EXTRA_APK, apk);
|
intent.putExtra(EXTRA_APK, apk);
|
||||||
context.startService(intent);
|
context.startService(intent);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
|
* Copyright (C) 2016 Blue Jay Wireless
|
||||||
* Copyright (C) 2016 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
|
||||||
@ -42,11 +43,11 @@ import java.io.IOException;
|
|||||||
* Handles the actual install process. Subclasses implement the details.
|
* Handles the actual install process. Subclasses implement the details.
|
||||||
*/
|
*/
|
||||||
public abstract class Installer {
|
public abstract class Installer {
|
||||||
final Context context;
|
|
||||||
private final LocalBroadcastManager localBroadcastManager;
|
|
||||||
|
|
||||||
private static final String TAG = "Installer";
|
private static final String TAG = "Installer";
|
||||||
|
|
||||||
|
final Context context;
|
||||||
|
final Apk apk;
|
||||||
|
|
||||||
public static final String ACTION_INSTALL_STARTED = "org.fdroid.fdroid.installer.Installer.action.INSTALL_STARTED";
|
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_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_INTERRUPTED = "org.fdroid.fdroid.installer.Installer.action.INSTALL_INTERRUPTED";
|
||||||
@ -67,29 +68,31 @@ public abstract class Installer {
|
|||||||
*/
|
*/
|
||||||
static final String EXTRA_DOWNLOAD_URI = "org.fdroid.fdroid.installer.Installer.extra.DOWNLOAD_URI";
|
static final String EXTRA_DOWNLOAD_URI = "org.fdroid.fdroid.installer.Installer.extra.DOWNLOAD_URI";
|
||||||
public static final String EXTRA_APK = "org.fdroid.fdroid.installer.Installer.extra.APK";
|
public static final String EXTRA_APK = "org.fdroid.fdroid.installer.Installer.extra.APK";
|
||||||
public static final String EXTRA_PACKAGE_NAME = "org.fdroid.fdroid.installer.Installer.extra.PACKAGE_NAME";
|
|
||||||
public static final String EXTRA_USER_INTERACTION_PI = "org.fdroid.fdroid.installer.Installer.extra.USER_INTERACTION_PI";
|
public static final String EXTRA_USER_INTERACTION_PI = "org.fdroid.fdroid.installer.Installer.extra.USER_INTERACTION_PI";
|
||||||
public static final String EXTRA_ERROR_MESSAGE = "org.fdroid.fdroid.net.installer.Installer.extra.ERROR_MESSAGE";
|
public static final String EXTRA_ERROR_MESSAGE = "org.fdroid.fdroid.net.installer.Installer.extra.ERROR_MESSAGE";
|
||||||
|
|
||||||
Installer(Context context) {
|
/**
|
||||||
|
* @param apk must be included so that all the phases of the install process
|
||||||
|
* can get all the data about the app, even after F-Droid was killed
|
||||||
|
*/
|
||||||
|
Installer(Context context, Apk apk) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
localBroadcastManager = LocalBroadcastManager.getInstance(context);
|
this.apk = apk;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns permission screen for given apk.
|
* Returns permission screen for given apk.
|
||||||
*
|
*
|
||||||
* @param apk instance of Apk
|
|
||||||
* @return Intent with Activity to show required permissions.
|
* @return Intent with Activity to show required permissions.
|
||||||
* Returns null if Installer handles that on itself, e.g., with DefaultInstaller,
|
* Returns null if Installer handles that on itself, e.g., with DefaultInstaller,
|
||||||
* or if no new permissions have been introduced during an update
|
* or if no new permissions have been introduced during an update
|
||||||
*/
|
*/
|
||||||
public Intent getPermissionScreen(Apk apk) {
|
public Intent getPermissionScreen() {
|
||||||
if (!isUnattended()) {
|
if (!isUnattended()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
int count = newPermissionCount(apk);
|
int count = newPermissionCount();
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
// no permission screen needed!
|
// no permission screen needed!
|
||||||
return null;
|
return null;
|
||||||
@ -101,7 +104,7 @@ public abstract class Installer {
|
|||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int newPermissionCount(Apk apk) {
|
private int newPermissionCount() {
|
||||||
boolean supportsRuntimePermissions = apk.targetSdkVersion >= 23;
|
boolean supportsRuntimePermissions = apk.targetSdkVersion >= 23;
|
||||||
if (supportsRuntimePermissions) {
|
if (supportsRuntimePermissions) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -125,69 +128,69 @@ public abstract class Installer {
|
|||||||
* Returns an Intent to start a dialog wrapped in an activity
|
* Returns an Intent to start a dialog wrapped in an activity
|
||||||
* for uninstall confirmation.
|
* for uninstall confirmation.
|
||||||
*
|
*
|
||||||
* @param packageName packageName of app to uninstall
|
|
||||||
* @return Intent with activity for uninstall confirmation
|
* @return Intent with activity for uninstall confirmation
|
||||||
* Returns null if Installer handles that on itself, e.g.,
|
* Returns null if Installer handles that on itself, e.g.,
|
||||||
* with DefaultInstaller.
|
* with DefaultInstaller.
|
||||||
*/
|
*/
|
||||||
public Intent getUninstallScreen(String packageName) {
|
public Intent getUninstallScreen() {
|
||||||
if (!isUnattended()) {
|
if (!isUnattended()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent intent = new Intent(context, UninstallDialogActivity.class);
|
Intent intent = new Intent(context, UninstallDialogActivity.class);
|
||||||
intent.putExtra(Installer.EXTRA_PACKAGE_NAME, packageName);
|
intent.putExtra(Installer.EXTRA_APK, apk);
|
||||||
|
|
||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendBroadcastInstall(Uri downloadUri, String action, PendingIntent pendingIntent) {
|
void sendBroadcastInstall(Uri downloadUri, String action, PendingIntent pendingIntent) {
|
||||||
sendBroadcastInstall(downloadUri, action, pendingIntent, null);
|
sendBroadcastInstall(context, downloadUri, action, apk, pendingIntent, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendBroadcastInstall(Uri downloadUri, String action) {
|
void sendBroadcastInstall(Uri downloadUri, String action) {
|
||||||
sendBroadcastInstall(downloadUri, action, null, null);
|
sendBroadcastInstall(context, downloadUri, action, apk, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendBroadcastInstall(Uri downloadUri, String action, String errorMessage) {
|
void sendBroadcastInstall(Uri downloadUri, String action, String errorMessage) {
|
||||||
sendBroadcastInstall(downloadUri, action, null, errorMessage);
|
sendBroadcastInstall(context, downloadUri, action, apk, null, errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendBroadcastInstall(Uri downloadUri, String action,
|
static void sendBroadcastInstall(Context context,
|
||||||
PendingIntent pendingIntent, String errorMessage) {
|
Uri downloadUri, String action, Apk apk,
|
||||||
|
PendingIntent pendingIntent, String errorMessage) {
|
||||||
Intent intent = new Intent(action);
|
Intent intent = new Intent(action);
|
||||||
intent.setData(downloadUri);
|
intent.setData(downloadUri);
|
||||||
intent.putExtra(Installer.EXTRA_USER_INTERACTION_PI, pendingIntent);
|
intent.putExtra(Installer.EXTRA_USER_INTERACTION_PI, pendingIntent);
|
||||||
|
intent.putExtra(Installer.EXTRA_APK, apk);
|
||||||
if (!TextUtils.isEmpty(errorMessage)) {
|
if (!TextUtils.isEmpty(errorMessage)) {
|
||||||
intent.putExtra(Installer.EXTRA_ERROR_MESSAGE, errorMessage);
|
intent.putExtra(Installer.EXTRA_ERROR_MESSAGE, errorMessage);
|
||||||
}
|
}
|
||||||
localBroadcastManager.sendBroadcast(intent);
|
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendBroadcastUninstall(String packageName, String action, String errorMessage) {
|
void sendBroadcastUninstall(String action, String errorMessage) {
|
||||||
sendBroadcastUninstall(packageName, action, null, errorMessage);
|
sendBroadcastUninstall(action, null, errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendBroadcastUninstall(String packageName, String action) {
|
void sendBroadcastUninstall(String action) {
|
||||||
sendBroadcastUninstall(packageName, action, null, null);
|
sendBroadcastUninstall(action, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendBroadcastUninstall(String packageName, String action, PendingIntent pendingIntent) {
|
void sendBroadcastUninstall(String action, PendingIntent pendingIntent) {
|
||||||
sendBroadcastUninstall(packageName, action, pendingIntent, null);
|
sendBroadcastUninstall(action, pendingIntent, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendBroadcastUninstall(String packageName, String action,
|
void sendBroadcastUninstall(String action, PendingIntent pendingIntent, String errorMessage) {
|
||||||
PendingIntent pendingIntent, String errorMessage) {
|
Uri uri = Uri.fromParts("package", apk.packageName, null);
|
||||||
Uri uri = Uri.fromParts("package", packageName, null);
|
|
||||||
|
|
||||||
Intent intent = new Intent(action);
|
Intent intent = new Intent(action);
|
||||||
intent.setData(uri); // for broadcast filtering
|
intent.setData(uri); // for broadcast filtering
|
||||||
intent.putExtra(Installer.EXTRA_PACKAGE_NAME, packageName);
|
intent.putExtra(Installer.EXTRA_APK, apk);
|
||||||
intent.putExtra(Installer.EXTRA_USER_INTERACTION_PI, pendingIntent);
|
intent.putExtra(Installer.EXTRA_USER_INTERACTION_PI, pendingIntent);
|
||||||
if (!TextUtils.isEmpty(errorMessage)) {
|
if (!TextUtils.isEmpty(errorMessage)) {
|
||||||
intent.putExtra(Installer.EXTRA_ERROR_MESSAGE, errorMessage);
|
intent.putExtra(Installer.EXTRA_ERROR_MESSAGE, errorMessage);
|
||||||
}
|
}
|
||||||
localBroadcastManager.sendBroadcast(intent);
|
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -223,9 +226,8 @@ public abstract class Installer {
|
|||||||
* @param localApkUri points to the local copy of the APK to be installed
|
* @param localApkUri points to the local copy of the APK to be installed
|
||||||
* @param downloadUri serves as the unique ID for all actions related to the
|
* @param downloadUri serves as the unique ID for all actions related to the
|
||||||
* installation of that specific APK
|
* installation of that specific APK
|
||||||
* @param apk apk object of the app that should be installed
|
|
||||||
*/
|
*/
|
||||||
public void installPackage(Uri localApkUri, Uri downloadUri, Apk apk) {
|
public void installPackage(Uri localApkUri, Uri downloadUri) {
|
||||||
try {
|
try {
|
||||||
// verify that permissions of the apk file match the ones from the apk object
|
// verify that permissions of the apk file match the ones from the apk object
|
||||||
ApkVerifier apkVerifier = new ApkVerifier(context, localApkUri, apk);
|
ApkVerifier apkVerifier = new ApkVerifier(context, localApkUri, apk);
|
||||||
@ -242,8 +244,8 @@ public abstract class Installer {
|
|||||||
if (isUnattended()) {
|
if (isUnattended()) {
|
||||||
Log.e(TAG, e.getMessage(), e);
|
Log.e(TAG, e.getMessage(), e);
|
||||||
Log.e(TAG, "Falling back to AOSP DefaultInstaller!");
|
Log.e(TAG, "Falling back to AOSP DefaultInstaller!");
|
||||||
DefaultInstaller defaultInstaller = new DefaultInstaller(context);
|
DefaultInstaller defaultInstaller = new DefaultInstaller(context, apk);
|
||||||
defaultInstaller.installPackageInternal(localApkUri, downloadUri, apk);
|
defaultInstaller.installPackageInternal(localApkUri, downloadUri);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -260,17 +262,16 @@ public abstract class Installer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
installPackageInternal(sanitizedUri, downloadUri, apk);
|
installPackageInternal(sanitizedUri, downloadUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void installPackageInternal(Uri localApkUri, Uri downloadUri, Apk apk);
|
protected abstract void installPackageInternal(Uri localApkUri, Uri downloadUri);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uninstall app
|
* Uninstall app as defined by {@link Installer#apk} in
|
||||||
*
|
* {@link Installer#Installer(Context, Apk)}
|
||||||
* @param packageName package name of the app that should be uninstalled
|
|
||||||
*/
|
*/
|
||||||
protected abstract void uninstallPackage(String packageName);
|
protected abstract void uninstallPackage();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This {@link Installer} instance is capable of "unattended" install and
|
* This {@link Installer} instance is capable of "unattended" install and
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
|
* Copyright (C) 2016 Blue Jay Wireless
|
||||||
* Copyright (C) 2016 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
|
||||||
@ -20,6 +21,7 @@
|
|||||||
package org.fdroid.fdroid.installer;
|
package org.fdroid.fdroid.installer;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
import org.fdroid.fdroid.data.Apk;
|
import org.fdroid.fdroid.data.Apk;
|
||||||
@ -34,22 +36,23 @@ public class InstallerFactory {
|
|||||||
* case to install the "F-Droid Privileged Extension" ExtensionInstaller.
|
* case to install the "F-Droid Privileged Extension" ExtensionInstaller.
|
||||||
*
|
*
|
||||||
* @param context current {@link Context}
|
* @param context current {@link Context}
|
||||||
* @param apk apk to be installed. Required to select the ExtensionInstaller.
|
* @param apk to be installed, always required.
|
||||||
* If this is null, the ExtensionInstaller will never be returned.
|
|
||||||
* @return instance of an Installer
|
* @return instance of an Installer
|
||||||
*/
|
*/
|
||||||
public static Installer create(Context context, Apk apk) {
|
public static Installer create(Context context, Apk apk) {
|
||||||
Installer installer;
|
if (apk == null || TextUtils.isEmpty(apk.packageName)) {
|
||||||
|
throw new IllegalArgumentException("packageName must not be empty!");
|
||||||
|
}
|
||||||
|
|
||||||
if (apk != null
|
Installer installer;
|
||||||
&& apk.packageName.equals(PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME)) {
|
if (apk.packageName.equals(PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME)) {
|
||||||
// special case for "F-Droid Privileged Extension"
|
// special case for "F-Droid Privileged Extension"
|
||||||
installer = new ExtensionInstaller(context);
|
installer = new ExtensionInstaller(context, apk);
|
||||||
} else if (PrivilegedInstaller.isDefault(context)) {
|
} else if (PrivilegedInstaller.isDefault(context)) {
|
||||||
Utils.debugLog(TAG, "privileged extension correctly installed -> PrivilegedInstaller");
|
Utils.debugLog(TAG, "privileged extension correctly installed -> PrivilegedInstaller");
|
||||||
installer = new PrivilegedInstaller(context);
|
installer = new PrivilegedInstaller(context, apk);
|
||||||
} else {
|
} else {
|
||||||
installer = new DefaultInstaller(context);
|
installer = new DefaultInstaller(context, apk);
|
||||||
}
|
}
|
||||||
|
|
||||||
return installer;
|
return installer;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
|
* Copyright (C) 2016 Blue Jay Wireless
|
||||||
* Copyright (C) 2016 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
|
||||||
@ -63,10 +64,9 @@ public class InstallerService extends IntentService {
|
|||||||
if (ACTION_INSTALL.equals(intent.getAction())) {
|
if (ACTION_INSTALL.equals(intent.getAction())) {
|
||||||
Uri uri = intent.getData();
|
Uri uri = intent.getData();
|
||||||
Uri downloadUri = intent.getParcelableExtra(Installer.EXTRA_DOWNLOAD_URI);
|
Uri downloadUri = intent.getParcelableExtra(Installer.EXTRA_DOWNLOAD_URI);
|
||||||
installer.installPackage(uri, downloadUri, apk);
|
installer.installPackage(uri, downloadUri);
|
||||||
} else if (ACTION_UNINSTALL.equals(intent.getAction())) {
|
} else if (ACTION_UNINSTALL.equals(intent.getAction())) {
|
||||||
String packageName = intent.getStringExtra(Installer.EXTRA_PACKAGE_NAME);
|
installer.uninstallPackage();
|
||||||
installer.uninstallPackage(packageName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,13 +90,13 @@ public class InstallerService extends IntentService {
|
|||||||
/**
|
/**
|
||||||
* Uninstall an app
|
* Uninstall an app
|
||||||
*
|
*
|
||||||
* @param context this app's {@link Context}
|
* @param context this app's {@link Context}
|
||||||
* @param packageName package name of the app that will be uninstalled
|
* @param apk {@link Apk} instance of the app that will be uninstalled
|
||||||
*/
|
*/
|
||||||
public static void uninstall(Context context, String packageName) {
|
public static void uninstall(Context context, Apk apk) {
|
||||||
Intent intent = new Intent(context, InstallerService.class);
|
Intent intent = new Intent(context, InstallerService.class);
|
||||||
intent.setAction(ACTION_UNINSTALL);
|
intent.setAction(ACTION_UNINSTALL);
|
||||||
intent.putExtra(Installer.EXTRA_PACKAGE_NAME, packageName);
|
intent.putExtra(Installer.EXTRA_APK, apk);
|
||||||
context.startService(intent);
|
context.startService(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
|
* Copyright (C) 2016 Blue Jay Wireless
|
||||||
* Copyright (C) 2014-2016 Dominik Schürmann <dominik@dominikschuermann.de>
|
* Copyright (C) 2014-2016 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
* Copyright (C) 2015 Daniel Martí <mvdan@mvdan.cc>
|
* Copyright (C) 2015 Daniel Martí <mvdan@mvdan.cc>
|
||||||
*
|
*
|
||||||
@ -255,8 +256,8 @@ public class PrivilegedInstaller extends Installer {
|
|||||||
"device owner has marked the package as uninstallable.");
|
"device owner has marked the package as uninstallable.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public PrivilegedInstaller(Context context) {
|
public PrivilegedInstaller(Context context, Apk apk) {
|
||||||
super(context);
|
super(context, apk);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isExtensionInstalled(Context context) {
|
public static boolean isExtensionInstalled(Context context) {
|
||||||
@ -306,9 +307,7 @@ public class PrivilegedInstaller extends Installer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void installPackageInternal(final Uri localApkUri, final Uri downloadUri, Apk apk) {
|
protected void installPackageInternal(final Uri localApkUri, final Uri downloadUri) {
|
||||||
sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_STARTED);
|
|
||||||
|
|
||||||
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);
|
||||||
@ -354,8 +353,8 @@ public class PrivilegedInstaller extends Installer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void uninstallPackage(final String packageName) {
|
protected void uninstallPackage() {
|
||||||
sendBroadcastUninstall(packageName, Installer.ACTION_UNINSTALL_STARTED);
|
sendBroadcastUninstall(Installer.ACTION_UNINSTALL_STARTED);
|
||||||
|
|
||||||
ServiceConnection mServiceConnection = new ServiceConnection() {
|
ServiceConnection mServiceConnection = new ServiceConnection() {
|
||||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||||
@ -365,9 +364,9 @@ public class PrivilegedInstaller extends Installer {
|
|||||||
@Override
|
@Override
|
||||||
public void handleResult(String packageName, int returnCode) throws RemoteException {
|
public void handleResult(String packageName, int returnCode) throws RemoteException {
|
||||||
if (returnCode == DELETE_SUCCEEDED) {
|
if (returnCode == DELETE_SUCCEEDED) {
|
||||||
sendBroadcastUninstall(packageName, ACTION_UNINSTALL_COMPLETE);
|
sendBroadcastUninstall(ACTION_UNINSTALL_COMPLETE);
|
||||||
} else {
|
} else {
|
||||||
sendBroadcastUninstall(packageName, ACTION_UNINSTALL_INTERRUPTED,
|
sendBroadcastUninstall(ACTION_UNINSTALL_INTERRUPTED,
|
||||||
"Error " + returnCode + ": "
|
"Error " + returnCode + ": "
|
||||||
+ UNINSTALL_RETURN_CODES.get(returnCode));
|
+ UNINSTALL_RETURN_CODES.get(returnCode));
|
||||||
}
|
}
|
||||||
@ -377,15 +376,15 @@ public class PrivilegedInstaller extends Installer {
|
|||||||
try {
|
try {
|
||||||
boolean hasPermissions = privService.hasPrivilegedPermissions();
|
boolean hasPermissions = privService.hasPrivilegedPermissions();
|
||||||
if (!hasPermissions) {
|
if (!hasPermissions) {
|
||||||
sendBroadcastUninstall(packageName, ACTION_UNINSTALL_INTERRUPTED,
|
sendBroadcastUninstall(ACTION_UNINSTALL_INTERRUPTED,
|
||||||
context.getString(R.string.system_install_denied_permissions));
|
context.getString(R.string.system_install_denied_permissions));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
privService.deletePackage(packageName, 0, callback);
|
privService.deletePackage(apk.packageName, 0, callback);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
Log.e(TAG, "RemoteException", e);
|
Log.e(TAG, "RemoteException", e);
|
||||||
sendBroadcastUninstall(packageName, ACTION_UNINSTALL_INTERRUPTED,
|
sendBroadcastUninstall(ACTION_UNINSTALL_INTERRUPTED,
|
||||||
"connecting to privileged service failed");
|
"connecting to privileged service failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ import android.view.ContextThemeWrapper;
|
|||||||
|
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
|
import org.fdroid.fdroid.data.Apk;
|
||||||
import org.fdroid.fdroid.installer.Installer;
|
import org.fdroid.fdroid.installer.Installer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,14 +49,15 @@ public class UninstallDialogActivity extends FragmentActivity {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
final Intent intent = getIntent();
|
final Intent intent = getIntent();
|
||||||
final String packageName = intent.getStringExtra(Installer.EXTRA_PACKAGE_NAME);
|
final Apk apk = intent.getParcelableExtra(Installer.EXTRA_APK);
|
||||||
|
|
||||||
PackageManager pm = getPackageManager();
|
PackageManager pm = getPackageManager();
|
||||||
|
|
||||||
ApplicationInfo appInfo;
|
ApplicationInfo appInfo;
|
||||||
try {
|
try {
|
||||||
//noinspection WrongConstant (lint is actually wrong here!)
|
//noinspection WrongConstant (lint is actually wrong here!)
|
||||||
appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES);
|
appInfo = pm.getApplicationInfo(apk.packageName,
|
||||||
|
PackageManager.GET_UNINSTALLED_PACKAGES);
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
throw new RuntimeException("Failed to get ApplicationInfo for uninstalling");
|
throw new RuntimeException("Failed to get ApplicationInfo for uninstalling");
|
||||||
}
|
}
|
||||||
@ -86,7 +88,7 @@ public class UninstallDialogActivity extends FragmentActivity {
|
|||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
Intent data = new Intent();
|
Intent data = new Intent();
|
||||||
data.putExtra(Installer.EXTRA_PACKAGE_NAME, packageName);
|
data.putExtra(Installer.EXTRA_APK, apk);
|
||||||
setResult(Activity.RESULT_OK, intent);
|
setResult(Activity.RESULT_OK, intent);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import org.fdroid.fdroid.FDroidApp;
|
|||||||
import org.fdroid.fdroid.Preferences;
|
import org.fdroid.fdroid.Preferences;
|
||||||
import org.fdroid.fdroid.PreferencesActivity;
|
import org.fdroid.fdroid.PreferencesActivity;
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
|
import org.fdroid.fdroid.installer.InstallHistoryService;
|
||||||
import org.fdroid.fdroid.installer.PrivilegedInstaller;
|
import org.fdroid.fdroid.installer.PrivilegedInstaller;
|
||||||
|
|
||||||
import info.guardianproject.netcipher.NetCipher;
|
import info.guardianproject.netcipher.NetCipher;
|
||||||
@ -193,6 +194,14 @@ public class PreferencesFragment extends PreferenceFragment
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Preferences.PREF_KEEP_INSTALL_HISTORY:
|
||||||
|
CheckBoxPreference p = (CheckBoxPreference) findPreference(key);
|
||||||
|
if (p.isChecked()) {
|
||||||
|
InstallHistoryService.register(getContext());
|
||||||
|
} else {
|
||||||
|
InstallHistoryService.unregister(getContext());
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<string name="source_link">https://gitlab.com/fdroid/fdroidclient</string>
|
<string name="source_link">https://gitlab.com/fdroid/fdroidclient</string>
|
||||||
<string name="team_email">team@f-droid.org</string>
|
<string name="team_email">team@f-droid.org</string>
|
||||||
<string name="license_gplv3_later">GNU General Public License version\u00A03 or later</string>
|
<string name="license_gplv3_later">GNU General Public License version\u00A03 or later</string>
|
||||||
|
|
||||||
<string name="transition_app_item_icon">transition_app_item_icon</string>
|
<string name="transition_app_item_icon">transition_app_item_icon</string>
|
||||||
|
|
||||||
<string name="https">https://</string>
|
<string name="https">https://</string>
|
||||||
@ -15,6 +15,11 @@
|
|||||||
|
|
||||||
<string name="app_details_subject">%1$s on F-Droid</string>
|
<string name="app_details_subject">%1$s on F-Droid</string>
|
||||||
|
|
||||||
|
<!-- The unset value is set to a string that is impossible to be a valid Java package name. This
|
||||||
|
prevents abuse by creating malware with the same packageName as the unset string. -->
|
||||||
|
<string name="install_history_reader_packageName_UNSET">1-THIS MEANS NO APP IS GRANTED ACCESS!</string>
|
||||||
|
<string name="install_history_reader_packageName">@string/install_history_reader_packageName_UNSET</string>
|
||||||
|
|
||||||
<string-array name="updateIntervalValues">
|
<string-array name="updateIntervalValues">
|
||||||
<item>0</item>
|
<item>0</item>
|
||||||
<item>1</item>
|
<item>1</item>
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
<string name="updates">Updates</string>
|
<string name="updates">Updates</string>
|
||||||
<string name="unstable_updates">Unstable updates</string>
|
<string name="unstable_updates">Unstable updates</string>
|
||||||
<string name="unstable_updates_summary">Suggest updates to unstable versions</string>
|
<string name="unstable_updates_summary">Suggest updates to unstable versions</string>
|
||||||
|
<string name="keep_install_history">Keep install history</string>
|
||||||
|
<string name="keep_install_history_summary">Store a log of all installs and uninstalls inside F-Droid</string>
|
||||||
<string name="other">Other</string>
|
<string name="other">Other</string>
|
||||||
|
|
||||||
<string name="update_interval">Automatic update interval</string>
|
<string name="update_interval">Automatic update interval</string>
|
||||||
|
6
app/src/main/res/xml/install_history_file_provider.xml
Normal file
6
app/src/main/res/xml/install_history_file_provider.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths>
|
||||||
|
<cache-path
|
||||||
|
name="install_history"
|
||||||
|
path="install_history" />
|
||||||
|
</paths>
|
@ -91,6 +91,12 @@
|
|||||||
android:summary="@string/unstable_updates_summary"
|
android:summary="@string/unstable_updates_summary"
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:dependency="expert" />
|
android:dependency="expert" />
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:key="keepInstallHistory"
|
||||||
|
android:title="@string/keep_install_history"
|
||||||
|
android:summary="@string/keep_install_history_summary"
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:dependency="expert" />
|
||||||
<CheckBoxPreference android:title="@string/system_installer"
|
<CheckBoxPreference android:title="@string/system_installer"
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="privilegedInstaller"
|
android:key="privilegedInstaller"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user