Merge branch 'whitelabel-privileged-extension' into 'master'
support whitelabeled Privileged Extension Closes #982 See merge request fdroid/fdroidclient!716
This commit is contained in:
commit
bda755584f
@ -17,6 +17,8 @@ def preDexEnabled = "true".equals(System.getProperty("pre-dex", "true"))
|
||||
|
||||
def fullApplicationId = "org.fdroid.fdroid"
|
||||
def basicApplicationId = "org.fdroid.basic"
|
||||
// yes, this actually needs both quotes https://stackoverflow.com/a/41391841
|
||||
def privilegedExtensionApplicationId = '"org.fdroid.fdroid.privileged"'
|
||||
|
||||
android {
|
||||
compileSdkVersion 27
|
||||
@ -35,6 +37,7 @@ android {
|
||||
minifyEnabled true
|
||||
useProguard true
|
||||
shrinkResources true
|
||||
buildConfigField "String", "PRIVILEGED_EXTENSION_PACKAGE_NAME", privilegedExtensionApplicationId
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
testProguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro', 'src/androidTest/proguard-rules.pro'
|
||||
}
|
||||
|
@ -181,11 +181,6 @@
|
||||
android:key="privilegedInstaller"
|
||||
android:persistent="false"
|
||||
android:dependency="expert"/>
|
||||
<Preference
|
||||
android:title="@string/uninstall_system"
|
||||
android:summary="@string/uninstall_system_summary"
|
||||
android:key="uninstallPrivilegedApp"
|
||||
android:dependency="expert"/>
|
||||
</android.support.v7.preference.PreferenceCategory>
|
||||
|
||||
</android.support.v7.preference.PreferenceScreen>
|
||||
|
@ -43,11 +43,6 @@
|
||||
<uses-permission android:name="android.permission.NFC"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
|
||||
<!-- Indicate that F-Droid may request root access (introduced by Koush's Superuser app)
|
||||
This permission is deprecated, but necessary for some old superuser
|
||||
apps to actually grant superuser access to F-Droid. -->
|
||||
<uses-permission android:name="android.permission.ACCESS_SUPERUSER"/>
|
||||
|
||||
<application>
|
||||
|
||||
<activity
|
||||
|
@ -191,17 +191,8 @@
|
||||
android:finishOnTaskLaunch="true"/>
|
||||
<activity android:name=".views.ScreenShotsActivity"/>
|
||||
<!-- Note: AppThemeTransparent, this activity shows dialogs only -->
|
||||
<activity
|
||||
android:name=".privileged.install.InstallExtensionDialogActivity"
|
||||
android:theme="@style/AppThemeTransparent"/>
|
||||
<activity android:name=".data.ObbUrlActivity"
|
||||
android:theme="@android:style/Theme.NoDisplay"/>
|
||||
<receiver
|
||||
android:name=".privileged.install.InstallExtensionBootReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<!-- Note: AppThemeTransparent, this activity shows dialogs only -->
|
||||
<activity
|
||||
android:name=".installer.DefaultInstallerActivity"
|
||||
|
@ -91,7 +91,6 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
|
||||
public static final String PREF_EXPERT = "expert";
|
||||
public static final String PREF_FORCE_OLD_INDEX = "forceOldIndex";
|
||||
public static final String PREF_PRIVILEGED_INSTALLER = "privilegedInstaller";
|
||||
public static final String PREF_UNINSTALL_PRIVILEGED_APP = "uninstallPrivilegedApp";
|
||||
public static final String PREF_LOCAL_REPO_NAME = "localRepoName";
|
||||
public static final String PREF_LOCAL_REPO_HTTPS = "localRepoHttps";
|
||||
public static final String PREF_LANGUAGE = "language";
|
||||
|
@ -5,7 +5,6 @@ import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.BuildConfig;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.installer.PrivilegedInstaller;
|
||||
@ -33,7 +32,7 @@ public class PackageManagerCompat {
|
||||
if (Build.VERSION.SDK_INT < 11) return;
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= 24 && PrivilegedInstaller.isDefault(context)) {
|
||||
mPm.setInstallerPackageName(packageName, "org.fdroid.fdroid.privileged");
|
||||
mPm.setInstallerPackageName(packageName, PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME);
|
||||
} else {
|
||||
mPm.setInstallerPackageName(packageName, BuildConfig.APPLICATION_ID);
|
||||
}
|
||||
|
@ -86,7 +86,8 @@ public class ApkFileProvider extends FileProvider {
|
||||
private static Uri getSafeUri(Context context, SanitizedFile tempFile, boolean useContentUri) {
|
||||
if (useContentUri) {
|
||||
Uri apkUri = getUriForFile(context, AUTHORITY, tempFile);
|
||||
context.grantUriPermission("org.fdroid.fdroid.privileged", apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
context.grantUriPermission(PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME,
|
||||
apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
context.grantUriPermission("com.android.bluetooth", apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
return apkUri;
|
||||
}
|
||||
|
@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Blue Jay Wireless
|
||||
* 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.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import org.fdroid.fdroid.BuildConfig;
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.privileged.install.InstallExtensionDialogActivity;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Special Installer that is only useful to install the Privileged Extension apk
|
||||
* as a privileged app into the system partition of Android. It is deprecated
|
||||
* because it cannot work on Android versions newer than {@code android-20} or so,
|
||||
* due to increased SELinux enforcement that restricts what even root can do.
|
||||
* <p/>
|
||||
* This is installer requires user interaction and thus install/uninstall directly
|
||||
* return PendingIntents.
|
||||
*
|
||||
* @see <a href="https://www.androidauthority.com/chainfire-rooting-android-lollipop-541458/">Chainfire talks Android Lollipop and the future of rooting</a>
|
||||
*/
|
||||
@Deprecated
|
||||
public class ExtensionInstaller extends Installer {
|
||||
|
||||
ExtensionInstaller(Context context, @NonNull Apk apk) {
|
||||
super(context, apk);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void installPackageInternal(Uri localApkUri, Uri downloadUri) {
|
||||
// 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
|
||||
ApkSignatureVerifier signatureVerifier = new ApkSignatureVerifier(context);
|
||||
if (!BuildConfig.DEBUG &&
|
||||
!signatureVerifier.hasFDroidSignature(new File(localApkUri.getPath()))) {
|
||||
sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED,
|
||||
"APK signature of extension not correct!");
|
||||
}
|
||||
Intent installIntent = new Intent(context, InstallExtensionDialogActivity.class);
|
||||
installIntent.setAction(InstallExtensionDialogActivity.ACTION_INSTALL);
|
||||
installIntent.setData(localApkUri);
|
||||
|
||||
PendingIntent installPendingIntent = PendingIntent.getActivity(
|
||||
context.getApplicationContext(),
|
||||
localApkUri.hashCode(),
|
||||
installIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
sendBroadcastInstall(downloadUri,
|
||||
Installer.ACTION_INSTALL_USER_INTERACTION, installPendingIntent);
|
||||
|
||||
// don't use broadcasts for the rest of this special installer
|
||||
sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_COMPLETE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void uninstallPackage() {
|
||||
Intent uninstallIntent = new Intent(context, InstallExtensionDialogActivity.class);
|
||||
uninstallIntent.setAction(InstallExtensionDialogActivity.ACTION_UNINSTALL);
|
||||
|
||||
PendingIntent uninstallPendingIntent = PendingIntent.getActivity(
|
||||
context.getApplicationContext(),
|
||||
apk.packageName.hashCode(),
|
||||
uninstallIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
sendBroadcastUninstall(Installer.ACTION_UNINSTALL_USER_INTERACTION, uninstallPendingIntent);
|
||||
|
||||
// don't use broadcasts for the rest of this special installer
|
||||
sendBroadcastUninstall(Installer.ACTION_UNINSTALL_COMPLETE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isUnattended() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -51,9 +51,6 @@ public class InstallerFactory {
|
||||
} else if (PrivilegedInstaller.isDefault(context)) {
|
||||
Utils.debugLog(TAG, "privileged extension correctly installed -> PrivilegedInstaller");
|
||||
installer = new PrivilegedInstaller(context, apk);
|
||||
} else if (apk.packageName.equals(PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME)) {
|
||||
// special case for installing "Privileged Extension" with root
|
||||
installer = new ExtensionInstaller(context, apk);
|
||||
} else {
|
||||
installer = new DefaultInstaller(context, apk);
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
import org.fdroid.fdroid.BuildConfig;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
@ -67,8 +68,7 @@ public class PrivilegedInstaller extends Installer {
|
||||
|
||||
private static final String PRIVILEGED_EXTENSION_SERVICE_INTENT
|
||||
= "org.fdroid.fdroid.privileged.IPrivilegedService";
|
||||
public static final String PRIVILEGED_EXTENSION_PACKAGE_NAME
|
||||
= "org.fdroid.fdroid.privileged";
|
||||
public static final String PRIVILEGED_EXTENSION_PACKAGE_NAME = BuildConfig.PRIVILEGED_EXTENSION_PACKAGE_NAME;
|
||||
|
||||
public static final int IS_EXTENSION_INSTALLED_NO = 0;
|
||||
public static final int IS_EXTENSION_INSTALLED_YES = 1;
|
||||
|
@ -1,248 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.privileged.install;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.installer.PrivilegedInstaller;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import eu.chainfire.libsuperuser.Shell;
|
||||
|
||||
/**
|
||||
* Partly based on
|
||||
* http://omerjerk.in/2014/08/how-to-install-an-app-to-system-partition/
|
||||
* https://github.com/omerjerk/RemoteDroid/blob/master/app/src/main/java/in/omerjerk/remotedroid/app/MainActivity.java
|
||||
*/
|
||||
@SuppressWarnings("LineLength")
|
||||
abstract class InstallExtension {
|
||||
|
||||
final Context context;
|
||||
|
||||
private static final String BASE_NAME = "FDroidPrivilegedExtension";
|
||||
private static final String APK_FILE_NAME = BASE_NAME + ".apk";
|
||||
|
||||
InstallExtension(final Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public static InstallExtension create(final Context context) {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
return new LollipopImpl(context);
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 19) {
|
||||
return new KitKatToLollipopImpl(context);
|
||||
}
|
||||
return new PreKitKatImpl(context);
|
||||
}
|
||||
|
||||
final void runInstall(String apkPath) {
|
||||
onPreInstall();
|
||||
Shell.SU.run(getInstallCommands(apkPath));
|
||||
}
|
||||
|
||||
final void runUninstall() {
|
||||
Shell.SU.run(getUninstallCommands());
|
||||
}
|
||||
|
||||
protected abstract String getSystemFolder();
|
||||
|
||||
void onPreInstall() {
|
||||
// To be overridden by relevant base class[es]
|
||||
}
|
||||
|
||||
public String getWarningString() {
|
||||
return context.getString(R.string.system_install_warning);
|
||||
}
|
||||
|
||||
public String getInstallingString() {
|
||||
return context.getString(R.string.installing);
|
||||
}
|
||||
|
||||
String getInstallPath() {
|
||||
return getSystemFolder() + APK_FILE_NAME;
|
||||
}
|
||||
|
||||
private List<String> getInstallCommands(String apkPath) {
|
||||
final List<String> commands = new ArrayList<>();
|
||||
commands.add("mount -o rw,remount " + FDroidApp.SYSTEM_DIR_NAME); // remount as read-write
|
||||
commands.addAll(getCopyToSystemCommands(apkPath));
|
||||
commands.add("mv " + getInstallPath() + ".tmp " + getInstallPath());
|
||||
commands.add("sleep 5"); // wait until the app is really installed
|
||||
commands.add("mount -o ro,remount " + FDroidApp.SYSTEM_DIR_NAME); // remount as read-only
|
||||
commands.add("am force-stop " + PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME);
|
||||
commands.addAll(getPostInstallCommands());
|
||||
return commands;
|
||||
}
|
||||
|
||||
List<String> getCopyToSystemCommands(String apkPath) {
|
||||
final List<String> commands = new ArrayList<>(2);
|
||||
commands.add("cat " + apkPath + " > " + getInstallPath() + ".tmp");
|
||||
commands.add("chmod 644 " + getInstallPath() + ".tmp");
|
||||
return commands;
|
||||
}
|
||||
|
||||
List<String> getPostInstallCommands() {
|
||||
final List<String> commands = new ArrayList<>(1);
|
||||
commands.add("am start -n org.fdroid.fdroid/.privileged.install.InstallExtensionDialogActivity --ez "
|
||||
+ InstallExtensionDialogActivity.ACTION_POST_INSTALL + " true");
|
||||
return commands;
|
||||
}
|
||||
|
||||
private List<String> getUninstallCommands() {
|
||||
final List<String> commands = new ArrayList<>();
|
||||
commands.add("am force-stop " + PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME);
|
||||
commands.add("pm clear " + PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME);
|
||||
commands.add("mount -o rw,remount " + FDroidApp.SYSTEM_DIR_NAME);
|
||||
commands.addAll(getCleanUninstallCommands());
|
||||
commands.add("sleep 5");
|
||||
commands.add("mount -o ro,remount " + FDroidApp.SYSTEM_DIR_NAME);
|
||||
commands.addAll(getPostUninstallCommands());
|
||||
return commands;
|
||||
}
|
||||
|
||||
List<String> getCleanUninstallCommands() {
|
||||
final List<String> commands = new ArrayList<>(1);
|
||||
commands.add("rm -f " + getInstallPath());
|
||||
return commands;
|
||||
}
|
||||
|
||||
List<String> getPostUninstallCommands() {
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
|
||||
private static class PreKitKatImpl extends InstallExtension {
|
||||
|
||||
PreKitKatImpl(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSystemFolder() {
|
||||
return FDroidApp.SYSTEM_DIR_NAME + "/app/";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class KitKatToLollipopImpl extends InstallExtension {
|
||||
|
||||
KitKatToLollipopImpl(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* On KitKat, "Some system apps are more system than others"
|
||||
* https://github.com/android/platform_frameworks_base/commit/ccbf84f44c9e6a5ed3c08673614826bb237afc54
|
||||
*/
|
||||
@Override
|
||||
protected String getSystemFolder() {
|
||||
return FDroidApp.SYSTEM_DIR_NAME + "/priv-app/";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* History of PackageManagerService in Lollipop:
|
||||
* https://github.com/android/platform_frameworks_base/commits/lollipop-release/services/core/java/com/android/server/pm/PackageManagerService.java
|
||||
*/
|
||||
private static class LollipopImpl extends InstallExtension {
|
||||
|
||||
LollipopImpl(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreInstall() {
|
||||
// Setup preference to execute postInstall after reboot
|
||||
Preferences.get().setPostPrivilegedInstall(true);
|
||||
}
|
||||
|
||||
public String getWarningString() {
|
||||
return context.getString(R.string.system_install_warning_lollipop);
|
||||
}
|
||||
|
||||
public String getInstallingString() {
|
||||
return context.getString(R.string.system_install_installing_rebooting);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cluster-style layout where each app is placed in a unique directory
|
||||
*/
|
||||
@Override
|
||||
protected String getSystemFolder() {
|
||||
return FDroidApp.SYSTEM_DIR_NAME + "/priv-app/" + BASE_NAME + "/";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create app directory
|
||||
*/
|
||||
@Override
|
||||
protected List<String> getCopyToSystemCommands(String apkPath) {
|
||||
List<String> commands = new ArrayList<>(4);
|
||||
commands.add("mkdir -p " + getSystemFolder()); // create app directory if not existing
|
||||
commands.add("chmod 755 " + getSystemFolder());
|
||||
commands.add("cat " + apkPath + " > " + getInstallPath() + ".tmp");
|
||||
commands.add("chmod 644 " + getInstallPath() + ".tmp");
|
||||
return commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: Only works with reboot
|
||||
*
|
||||
* File observers on /system/priv-app/ have been removed because they don't work with the new
|
||||
* cluser-style layout. See
|
||||
* https://github.com/android/platform_frameworks_base/commit/84e71d1d61c53cd947becc7879e05947be681103
|
||||
*
|
||||
* Related stack overflow post: http://stackoverflow.com/q/26487750
|
||||
*/
|
||||
@Override
|
||||
protected List<String> getPostInstallCommands() {
|
||||
List<String> commands = new ArrayList<>(3);
|
||||
commands.add("am broadcast -a android.intent.action.ACTION_SHUTDOWN");
|
||||
commands.add("sleep 1");
|
||||
commands.add("reboot");
|
||||
return commands;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getCleanUninstallCommands() {
|
||||
final List<String> commands = new ArrayList<>(1);
|
||||
commands.add("rm -rf " + getSystemFolder());
|
||||
return commands;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getPostUninstallCommands() {
|
||||
List<String> commands = new ArrayList<>(3);
|
||||
commands.add("am broadcast -a android.intent.action.ACTION_SHUTDOWN");
|
||||
commands.add("sleep 1");
|
||||
commands.add("reboot");
|
||||
return commands;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.privileged.install;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
|
||||
public class InstallExtensionBootReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED) && Preferences.get().isPostPrivilegedInstall()) {
|
||||
Preferences.get().setPostPrivilegedInstall(false);
|
||||
|
||||
Intent postInstall = new Intent(context.getApplicationContext(), InstallExtensionDialogActivity.class);
|
||||
postInstall.setAction(InstallExtensionDialogActivity.ACTION_POST_INSTALL);
|
||||
postInstall.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(postInstall);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,336 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.privileged.install;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.Html;
|
||||
import android.util.Log;
|
||||
import android.view.ContextThemeWrapper;
|
||||
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.installer.PrivilegedInstaller;
|
||||
import org.fdroid.fdroid.views.main.MainActivity;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import eu.chainfire.libsuperuser.Shell;
|
||||
|
||||
/**
|
||||
* Note: This activity has no view on its own, it displays consecutive dialogs.
|
||||
*/
|
||||
public class InstallExtensionDialogActivity extends FragmentActivity {
|
||||
|
||||
private static final String TAG = "InstallIntoSystem";
|
||||
|
||||
public static final String ACTION_INSTALL = "install";
|
||||
|
||||
public static final String ACTION_UNINSTALL = "uninstall";
|
||||
public static final String ACTION_POST_INSTALL = "post_install";
|
||||
|
||||
private String apkPath;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// this activity itself has no content view (see manifest)
|
||||
|
||||
if (getIntent().getAction() == null) {
|
||||
Log.e(TAG, "Please define an action!");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Uri dataUri = getIntent().getData();
|
||||
if (dataUri != null) {
|
||||
File apkFile = new File(dataUri.getPath());
|
||||
apkPath = apkFile.getAbsolutePath();
|
||||
}
|
||||
|
||||
switch (getIntent().getAction()) {
|
||||
case ACTION_UNINSTALL:
|
||||
uninstall();
|
||||
break;
|
||||
case ACTION_INSTALL:
|
||||
askBeforeInstall();
|
||||
break;
|
||||
case ACTION_POST_INSTALL:
|
||||
postInstall();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void askBeforeInstall() {
|
||||
// hack to get theme applied (which is not automatically applied due to activity's Theme.NoDisplay
|
||||
ContextThemeWrapper theme = new ContextThemeWrapper(this, FDroidApp.getCurThemeResId());
|
||||
|
||||
// not support on Android >= 5.1
|
||||
if (android.os.Build.VERSION.SDK_INT >= 22) {
|
||||
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(theme);
|
||||
alertBuilder.setMessage(R.string.system_install_not_supported);
|
||||
alertBuilder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
InstallExtensionDialogActivity.this.setResult(Activity.RESULT_CANCELED);
|
||||
InstallExtensionDialogActivity.this.finish();
|
||||
}
|
||||
});
|
||||
alertBuilder.create().show();
|
||||
return;
|
||||
}
|
||||
|
||||
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(theme);
|
||||
alertBuilder.setTitle(R.string.system_install_question);
|
||||
String message = InstallExtension.create(getApplicationContext()).getWarningString();
|
||||
alertBuilder.setMessage(Html.fromHtml(message));
|
||||
alertBuilder.setPositiveButton(R.string.system_install_button_install, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
checkRootTask.execute();
|
||||
}
|
||||
});
|
||||
alertBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
InstallExtensionDialogActivity.this.setResult(Activity.RESULT_CANCELED);
|
||||
InstallExtensionDialogActivity.this.finish();
|
||||
}
|
||||
});
|
||||
alertBuilder.create().show();
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Check for root access
|
||||
*/
|
||||
private final AsyncTask<Void, Void, Boolean> checkRootTask = new AsyncTask<Void, Void, Boolean>() {
|
||||
ProgressDialog progressDialog;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
|
||||
// hack to get theme applied (which is not automatically applied due to activity's Theme.NoDisplay
|
||||
ContextThemeWrapper theme = new ContextThemeWrapper(InstallExtensionDialogActivity.this,
|
||||
FDroidApp.getCurThemeResId());
|
||||
|
||||
progressDialog = new ProgressDialog(theme);
|
||||
progressDialog.setMessage(getString(R.string.requesting_root_access_body));
|
||||
progressDialog.setIndeterminate(true);
|
||||
progressDialog.setCancelable(false);
|
||||
progressDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
return Shell.SU.available();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean rootGranted) {
|
||||
super.onPostExecute(rootGranted);
|
||||
|
||||
progressDialog.dismiss();
|
||||
|
||||
if (rootGranted) {
|
||||
// root access granted
|
||||
|
||||
switch (getIntent().getAction()) {
|
||||
case ACTION_UNINSTALL:
|
||||
uninstallTask.execute();
|
||||
break;
|
||||
case ACTION_INSTALL:
|
||||
installTask.execute();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// root access denied
|
||||
// hack to get theme applied (which is not automatically applied due to activity's Theme.NoDisplay
|
||||
ContextThemeWrapper theme = new ContextThemeWrapper(InstallExtensionDialogActivity.this,
|
||||
FDroidApp.getCurThemeResId());
|
||||
|
||||
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(theme)
|
||||
.setTitle(R.string.root_access_denied_title)
|
||||
.setMessage(getString(R.string.root_access_denied_body))
|
||||
.setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
InstallExtensionDialogActivity.this.setResult(Activity.RESULT_CANCELED);
|
||||
InstallExtensionDialogActivity.this.finish();
|
||||
}
|
||||
});
|
||||
alertBuilder.create().show();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 2. Install into system
|
||||
*/
|
||||
private final AsyncTask<Void, Void, Void> installTask = new AsyncTask<Void, Void, Void>() {
|
||||
ProgressDialog progressDialog;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
|
||||
// hack to get theme applied (which is not automatically applied due to activity's Theme.NoDisplay
|
||||
ContextThemeWrapper theme = new ContextThemeWrapper(InstallExtensionDialogActivity.this,
|
||||
FDroidApp.getCurThemeResId());
|
||||
|
||||
progressDialog = new ProgressDialog(theme);
|
||||
progressDialog.setMessage(InstallExtension.create(getApplicationContext()).getInstallingString());
|
||||
progressDialog.setIndeterminate(true);
|
||||
progressDialog.setCancelable(false);
|
||||
progressDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
InstallExtension.create(getApplicationContext()).runInstall(apkPath);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 3. Verify that install worked
|
||||
*/
|
||||
private void postInstall() {
|
||||
int isInstalledCorrectly =
|
||||
PrivilegedInstaller.isExtensionInstalledCorrectly(this);
|
||||
|
||||
String title;
|
||||
String message;
|
||||
final int result;
|
||||
switch (isInstalledCorrectly) {
|
||||
case PrivilegedInstaller.IS_EXTENSION_INSTALLED_YES:
|
||||
title = getString(R.string.system_install_post_success);
|
||||
message = getString(R.string.system_install_post_success_message);
|
||||
result = Activity.RESULT_OK;
|
||||
break;
|
||||
case PrivilegedInstaller.IS_EXTENSION_INSTALLED_NO:
|
||||
title = getString(R.string.system_install_post_fail);
|
||||
message = getString(R.string.system_install_post_fail_message);
|
||||
result = Activity.RESULT_CANCELED;
|
||||
break;
|
||||
case PrivilegedInstaller.IS_EXTENSION_INSTALLED_SIGNATURE_PROBLEM:
|
||||
title = getString(R.string.system_install_post_fail);
|
||||
message = getString(R.string.system_install_post_fail_message) +
|
||||
"\n\n" + getString(R.string.system_install_denied_signature);
|
||||
result = Activity.RESULT_CANCELED;
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("unhandled return");
|
||||
}
|
||||
|
||||
// hack to get theme applied (which is not automatically applied due to activity's Theme.NoDisplay
|
||||
ContextThemeWrapper theme = new ContextThemeWrapper(this, FDroidApp.getCurThemeResId());
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(theme)
|
||||
.setTitle(title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
InstallExtensionDialogActivity.this.setResult(result);
|
||||
InstallExtensionDialogActivity.this.finish();
|
||||
startActivity(new Intent(InstallExtensionDialogActivity.this, MainActivity.class));
|
||||
}
|
||||
})
|
||||
.setCancelable(false);
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
private void uninstall() {
|
||||
// hack to get theme applied (which is not automatically applied due to activity's Theme.NoDisplay
|
||||
ContextThemeWrapper theme = new ContextThemeWrapper(this, FDroidApp.getCurThemeResId());
|
||||
|
||||
final boolean isInstalled = PrivilegedInstaller.isExtensionInstalled(this);
|
||||
|
||||
if (isInstalled) {
|
||||
String message = InstallExtension.create(getApplicationContext()).getWarningString();
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(theme)
|
||||
.setTitle(R.string.system_uninstall)
|
||||
.setMessage(Html.fromHtml(message))
|
||||
.setPositiveButton(R.string.system_uninstall_button, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
checkRootTask.execute();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
InstallExtensionDialogActivity.this.setResult(Activity.RESULT_CANCELED);
|
||||
InstallExtensionDialogActivity.this.finish();
|
||||
}
|
||||
});
|
||||
builder.create().show();
|
||||
} else {
|
||||
throw new RuntimeException("Uninstall invoked, but extension is not installed!");
|
||||
}
|
||||
}
|
||||
|
||||
private final AsyncTask<Void, Void, Void> uninstallTask = new AsyncTask<Void, Void, Void>() {
|
||||
ProgressDialog progressDialog;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
|
||||
// hack to get theme applied (which is not automatically applied due to activity's Theme.NoDisplay
|
||||
ContextThemeWrapper theme = new ContextThemeWrapper(InstallExtensionDialogActivity.this,
|
||||
FDroidApp.getCurThemeResId());
|
||||
|
||||
progressDialog = new ProgressDialog(theme);
|
||||
progressDialog.setMessage(getString(R.string.uninstalling));
|
||||
progressDialog.setIndeterminate(true);
|
||||
progressDialog.setCancelable(false);
|
||||
progressDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
InstallExtension.create(getApplicationContext()).runUninstall();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void unused) {
|
||||
super.onPostExecute(unused);
|
||||
|
||||
progressDialog.dismiss();
|
||||
|
||||
// app is uninstalled but still display, kill it!
|
||||
System.exit(0);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -44,7 +44,6 @@ import android.text.TextUtils;
|
||||
import android.view.WindowManager;
|
||||
import info.guardianproject.netcipher.NetCipher;
|
||||
import info.guardianproject.netcipher.proxy.OrbotHelper;
|
||||
import org.fdroid.fdroid.AppDetails2;
|
||||
import org.fdroid.fdroid.CleanCacheService;
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.Languages;
|
||||
@ -100,7 +99,6 @@ public class PreferencesFragment extends PreferenceFragment
|
||||
private SwitchPreference enableProxyCheckPref;
|
||||
private SwitchPreference useTorCheckPref;
|
||||
private Preference updateAutoDownloadPref;
|
||||
private Preference updatePrivilegedExtensionPref;
|
||||
private CheckBoxPreference keepInstallHistoryPref;
|
||||
private Preference installHistoryPref;
|
||||
private long currentKeepCacheTime;
|
||||
@ -123,7 +121,6 @@ public class PreferencesFragment extends PreferenceFragment
|
||||
useTorCheckPref = (SwitchPreference) findPreference(Preferences.PREF_USE_TOR);
|
||||
enableProxyCheckPref = (SwitchPreference) findPreference(Preferences.PREF_ENABLE_PROXY);
|
||||
updateAutoDownloadPref = findPreference(Preferences.PREF_AUTO_DOWNLOAD_INSTALL_UPDATES);
|
||||
updatePrivilegedExtensionPref = findPreference(Preferences.PREF_UNINSTALL_PRIVILEGED_APP);
|
||||
|
||||
overWifiSeekBar = (LiveSeekBarPreference) findPreference(Preferences.PREF_OVER_WIFI);
|
||||
overWifiPrevious = overWifiSeekBar.getValue();
|
||||
@ -402,28 +399,6 @@ public class PreferencesFragment extends PreferenceFragment
|
||||
}
|
||||
}
|
||||
|
||||
private void initUpdatePrivilegedExtensionPreference() {
|
||||
if (Build.VERSION.SDK_INT > 19) {
|
||||
// this will never work on newer Android versions, so hide it
|
||||
otherPrefGroup.removePreference(updatePrivilegedExtensionPref);
|
||||
return;
|
||||
}
|
||||
updatePrivilegedExtensionPref.setPersistent(false);
|
||||
updatePrivilegedExtensionPref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
// Open details of F-Droid Privileged
|
||||
Intent intent = new Intent(getActivity(), AppDetails2.class);
|
||||
intent.putExtra(AppDetails2.EXTRA_APPID,
|
||||
PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME);
|
||||
startActivity(intent);
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If a user specifies they want to fetch updates automatically, then start the download of relevant
|
||||
* updates as soon as they enable the feature.
|
||||
@ -490,7 +465,6 @@ public class PreferencesFragment extends PreferenceFragment
|
||||
|
||||
initAutoFetchUpdatesPreference();
|
||||
initPrivilegedInstallerPreference();
|
||||
initUpdatePrivilegedExtensionPreference();
|
||||
initUseTorPreference();
|
||||
}
|
||||
|
||||
|
@ -207,11 +207,6 @@
|
||||
android:key="privilegedInstaller"
|
||||
android:persistent="false"
|
||||
android:dependency="expert"/>
|
||||
<Preference
|
||||
android:title="@string/uninstall_system"
|
||||
android:summary="@string/uninstall_system_summary"
|
||||
android:key="uninstallPrivilegedApp"
|
||||
android:dependency="expert"/>
|
||||
</android.support.v7.preference.PreferenceCategory>
|
||||
|
||||
</android.support.v7.preference.PreferenceScreen>
|
||||
|
Loading…
x
Reference in New Issue
Block a user