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
	 Hans-Christoph Steiner
						Hans-Christoph Steiner