diff --git a/F-Droid/AndroidManifest.xml b/F-Droid/AndroidManifest.xml
index 9ad429def..cd48da400 100644
--- a/F-Droid/AndroidManifest.xml
+++ b/F-Droid/AndroidManifest.xml
@@ -403,6 +403,16 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".FDroid" />
+
+
+
+
+
+
+
Request root access to install, update, and remove packages
Install using system-permissions
Use system permissions to install, update, and remove packages
+ Uninstall F-Droid
+ Uninstall F-Droid when installed as a system-app.
Broadcast Local Repo
Advertise your local repo using Bonjour (mDNS)
Name of your Local Repo
@@ -287,7 +289,6 @@
No apps in this category.\n\nTry selecting a different category or updating your repositories to get a fresh list of apps.
All apps up to date.\n\nCongratulations! All of your apps are up to date (or your repositories are out of date).
- Root access
Requesting root access…
Root access denied
Either your Android device is not rooted or you have denied root access for F-Droid.
@@ -296,6 +297,19 @@
The (de-)installation failed. If you are using root access, try disabling this setting!
System permissions denied
This option is only available when F-Droid is installed as a system-app.
+ Install as system-app
+ Successful installation as system-app
+ Installation as system-app failed
+ F-Droid has been successfully installed into the system partition. This enables extended features, such as automatic app updates.
+ The installation of F-Droid into the system partition failed. The installation method is not supported by all Android distributions, please consult the F-Droid bug tracker for more information.
+ installing…
+ uninstalling…
+ Do you want to install F-Droid as a system-app?\nThis takes up to 10 seconds where <b>no user interface</b> is shown.
+ Do you want to install F-Droid as a system-app?\nThis takes up to 10 seconds where <b>no user interface</b> is shown and the device will be <b>rebooted</b> afterwards.
+ Looks like you have root access on your device. You could install F-Droid as a system-app to enable extended features, such as automatic app updates.
+ Do you want to uninstall F-Droid?
+ This will uninstall F-Droid completely.
+ Uninstall
F-Droid is an installable catalogue of FOSS (Free and Open Source Software) applications for the Android platform. The client makes it easy to browse, install, and keep track of updates on your device.
If your friend has F-Droid and NFC turned on touch your phones together.
diff --git a/F-Droid/res/xml/preferences.xml b/F-Droid/res/xml/preferences.xml
index 9d94bcbc0..f89fb09bd 100644
--- a/F-Droid/res/xml/preferences.xml
+++ b/F-Droid/res/xml/preferences.xml
@@ -92,5 +92,9 @@
android:defaultValue="false"
android:key="systemInstaller"
android:dependency="expert" />
+
diff --git a/F-Droid/src/org/fdroid/fdroid/FDroid.java b/F-Droid/src/org/fdroid/fdroid/FDroid.java
index a532dbbc2..87581b7f3 100644
--- a/F-Droid/src/org/fdroid/fdroid/FDroid.java
+++ b/F-Droid/src/org/fdroid/fdroid/FDroid.java
@@ -47,6 +47,7 @@ import android.widget.Toast;
import org.fdroid.fdroid.compat.TabManager;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.NewRepoConfig;
+import org.fdroid.fdroid.installer.InstallIntoSystemDialogActivity;
import org.fdroid.fdroid.views.AppListFragmentPagerAdapter;
import org.fdroid.fdroid.views.ManageReposActivity;
import org.fdroid.fdroid.views.swap.ConnectSwapActivity;
@@ -101,6 +102,12 @@ public class FDroid extends ActionBarActivity {
Uri uri = AppProvider.getContentUri();
getContentResolver().registerContentObserver(uri, true, new AppObserver());
+
+ if (Preferences.get().isFirstTime()) {
+ Intent installIntent = new Intent(this, InstallIntoSystemDialogActivity.class);
+ installIntent.setAction(InstallIntoSystemDialogActivity.ACTION_FIRST_TIME);
+ startActivity(installIntent);
+ }
}
@Override
diff --git a/F-Droid/src/org/fdroid/fdroid/FDroidApp.java b/F-Droid/src/org/fdroid/fdroid/FDroidApp.java
index 6f99b3106..3410e3df5 100644
--- a/F-Droid/src/org/fdroid/fdroid/FDroidApp.java
+++ b/F-Droid/src/org/fdroid/fdroid/FDroidApp.java
@@ -98,23 +98,26 @@ public class FDroidApp extends Application {
}
public void applyTheme(Activity activity) {
- switch (curTheme) {
- case dark:
- activity.setTheme(R.style.AppThemeDark);
- break;
- case light:
- activity.setTheme(R.style.AppThemeLight);
- break;
- case lightWithDarkActionBar:
- activity.setTheme(R.style.AppThemeLightWithDarkActionBar);
- break;
- }
+ activity.setTheme(getCurThemeResId());
}
public static Theme getCurTheme() {
return curTheme;
}
+ public static int getCurThemeResId() {
+ switch (curTheme) {
+ case dark:
+ return R.style.AppThemeDark;
+ case light:
+ return R.style.AppThemeLight;
+ case lightWithDarkActionBar:
+ return R.style.AppThemeLightWithDarkActionBar;
+ default:
+ return R.style.AppThemeDark;
+ }
+ }
+
public static void enableSpongyCastle() {
Security.addProvider(spongyCastleProvider);
}
diff --git a/F-Droid/src/org/fdroid/fdroid/Preferences.java b/F-Droid/src/org/fdroid/fdroid/Preferences.java
index 2502d8eac..fae56de5a 100644
--- a/F-Droid/src/org/fdroid/fdroid/Preferences.java
+++ b/F-Droid/src/org/fdroid/fdroid/Preferences.java
@@ -52,6 +52,7 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
public static final String PREF_UPD_LAST = "lastUpdateCheck";
public static final String PREF_ROOT_INSTALLER = "rootInstaller";
public static final String PREF_SYSTEM_INSTALLER = "systemInstaller";
+ public static final String PREF_UNINSTALL_SYSTEM_APP = "uninstallSystemApp";
public static final String PREF_LOCAL_REPO_BONJOUR = "localRepoBonjour";
public static final String PREF_LOCAL_REPO_NAME = "localRepoName";
public static final String PREF_LOCAL_REPO_HTTPS = "localRepoHttps";
@@ -60,6 +61,8 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
public static final String PREF_PROXY_HOST = "proxyHost";
public static final String PREF_PROXY_PORT = "proxyPort";
public static final String PREF_SHOW_NFC_DURING_SWAP = "showNfcDuringSwap";
+ public static final String PREF_FIRST_TIME = "firstTime";
+ public static final String PREF_POST_SYSTEM_INSTALL = "postSystemInstall";
private static final boolean DEFAULT_COMPACT_LAYOUT = false;
private static final boolean DEFAULT_ROOTED = true;
@@ -77,6 +80,8 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
public static final String DEFAULT_PROXY_HOST = "127.0.0.1";
public static final int DEFAULT_PROXY_PORT = 8118;
public static final boolean DEFAULT_SHOW_NFC_DURING_SWAP = true;
+ private static final boolean DEFAULT_FIRST_TIME = true;
+ private static final boolean DEFAULT_POST_SYSTEM_INSTALL = false;
private boolean compactLayout = DEFAULT_COMPACT_LAYOUT;
private boolean filterAppsRequiringRoot = DEFAULT_ROOTED;
@@ -110,6 +115,26 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
return preferences.getBoolean(PREF_SYSTEM_INSTALLER, DEFAULT_SYSTEM_INSTALLER);
}
+ public void setSystemInstallerEnabled(boolean enable) {
+ preferences.edit().putBoolean(PREF_SYSTEM_INSTALLER, enable).commit();
+ }
+
+ public boolean isFirstTime() {
+ return preferences.getBoolean(PREF_FIRST_TIME, DEFAULT_FIRST_TIME);
+ }
+
+ public void setFirstTime(boolean firstTime) {
+ preferences.edit().putBoolean(PREF_FIRST_TIME, firstTime).commit();
+ }
+
+ public boolean isPostSystemInstall() {
+ return preferences.getBoolean(PREF_POST_SYSTEM_INSTALL, DEFAULT_POST_SYSTEM_INSTALL);
+ }
+
+ public void setPostSystemInstall(boolean postInstall) {
+ preferences.edit().putBoolean(PREF_POST_SYSTEM_INSTALL, postInstall).commit();
+ }
+
public boolean isLocalRepoBonjourEnabled() {
return preferences.getBoolean(PREF_LOCAL_REPO_BONJOUR, DEFAULT_LOCAL_REPO_BONJOUR);
}
diff --git a/F-Droid/src/org/fdroid/fdroid/installer/CheckRootAsyncTask.java b/F-Droid/src/org/fdroid/fdroid/installer/CheckRootAsyncTask.java
index 6fe5e2134..01d8ff6cd 100644
--- a/F-Droid/src/org/fdroid/fdroid/installer/CheckRootAsyncTask.java
+++ b/F-Droid/src/org/fdroid/fdroid/installer/CheckRootAsyncTask.java
@@ -22,7 +22,9 @@ package org.fdroid.fdroid.installer;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
+import android.view.ContextThemeWrapper;
+import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.R;
import eu.chainfire.libsuperuser.Shell;
@@ -46,8 +48,11 @@ public class CheckRootAsyncTask extends AsyncTask {
protected void onPreExecute() {
super.onPreExecute();
- mDialog = new ProgressDialog(mContext);
- mDialog.setTitle(R.string.requesting_root_access_title);
+ // if the dialog is displayed from the application class, design is missing
+ // hack to get holo design (which is not automatically applied due to activity's Theme.NoDisplay
+ ContextThemeWrapper theme = new ContextThemeWrapper(mContext, FDroidApp.getCurThemeResId());
+
+ mDialog = new ProgressDialog(theme);
mDialog.setMessage(mContext.getString(R.string.requesting_root_access_body));
mDialog.setIndeterminate(true);
mDialog.setCancelable(false);
diff --git a/F-Droid/src/org/fdroid/fdroid/installer/InstallIntoSystemBootReceiver.java b/F-Droid/src/org/fdroid/fdroid/installer/InstallIntoSystemBootReceiver.java
new file mode 100644
index 000000000..42f7e57cc
--- /dev/null
+++ b/F-Droid/src/org/fdroid/fdroid/installer/InstallIntoSystemBootReceiver.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+package org.fdroid.fdroid.installer;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import org.fdroid.fdroid.Preferences;
+
+public class InstallIntoSystemBootReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
+ if (Preferences.get().isPostSystemInstall()) {
+ Preferences.get().setPostSystemInstall(false);
+
+ Intent postInstall = new Intent(context.getApplicationContext(), InstallIntoSystemDialogActivity.class);
+ postInstall.setAction(InstallIntoSystemDialogActivity.ACTION_POST_INSTALL);
+ postInstall.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(postInstall);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/F-Droid/src/org/fdroid/fdroid/installer/InstallIntoSystemDialogActivity.java b/F-Droid/src/org/fdroid/fdroid/installer/InstallIntoSystemDialogActivity.java
new file mode 100644
index 000000000..bc0a567c6
--- /dev/null
+++ b/F-Droid/src/org/fdroid/fdroid/installer/InstallIntoSystemDialogActivity.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann
+ *
+ * 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.Activity;
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.text.Html;
+import android.util.Log;
+import android.view.ContextThemeWrapper;
+
+import org.fdroid.fdroid.FDroid;
+import org.fdroid.fdroid.FDroidApp;
+import org.fdroid.fdroid.Preferences;
+import org.fdroid.fdroid.R;
+
+import eu.chainfire.libsuperuser.Shell;
+
+/**
+ * Note: This activity has no view on its own, it displays consecutive dialogs.
+ *
+ * Partly based on https://github.com/omerjerk/RemoteDroid/blob/master/app/src/main/java/in/omerjerk/remotedroid/app/MainActivity.java
+ * http://omerjerk.in/2014/08/how-to-install-an-app-to-system-partition/
+ *
+ * Info for lollipop:
+ * http://stackoverflow.com/q/26487750
+ *
+ * Removed apk observers in
+ * https://github.com/android/platform_frameworks_base/commit/84e71d1d61c53cd947becc7879e05947be681103
+ *
+ * History of PackageManagerService:
+ * https://github.com/android/platform_frameworks_base/commits/lollipop-release/services/core/java/com/android/server/pm/PackageManagerService.java
+ */
+public class InstallIntoSystemDialogActivity 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";
+ public static final String ACTION_FIRST_TIME = "first_time";
+
+ private static String SYSTEM_FOLDER;
+
+ static {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+ SYSTEM_FOLDER = "/system/app/";
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
+ && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ // new folder introduced in
+ // https://github.com/android/platform_frameworks_base/commit/ccbf84f44c9e6a5ed3c08673614826bb237afc54
+ SYSTEM_FOLDER = "/system/priv-app/";
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ // new cluster based installation and app dirs
+ SYSTEM_FOLDER = "/system/priv-app/FDroid/";
+ }
+ }
+
+ private static final String APK_NAME = "FDroid.apk";
+
+ private static final String CMD_SCRIPT_KITKAT = "mount -o rw,remount /system\n" +
+ "cat %s > " + SYSTEM_FOLDER + APK_NAME + ".tmp\n" +
+ "chmod 655 " + SYSTEM_FOLDER + APK_NAME + ".tmp\n" +
+ "pm uninstall -k %s\n" + // -k to retain data
+ "mv " + SYSTEM_FOLDER + APK_NAME + ".tmp " + SYSTEM_FOLDER + APK_NAME + "\n" +
+ "pm install -r " + SYSTEM_FOLDER + APK_NAME + "\n" +
+ "sleep 5\n" +
+ "mount -o ro,remount /system\n" +
+ "am start -n org.fdroid.fdroid/.installer.InstallIntoSystemDialogActivity --ez post_install true";
+
+ // TODO: Currently only works with reboot. Find a way how this could work without.
+ // See http://stackoverflow.com/q/26487750
+ private static final String CMD_SCRIPT_LOLLIPOP_REBOOT = "am broadcast -a android.intent.action.ACTION_SHUTDOWN\n" +
+ "sleep 1\n" +
+ "reboot";
+
+ private static final String CMD_SCRIPT_LOLLIPOP = "mount -o rw,remount /system\n" +
+ "mkdir " + SYSTEM_FOLDER + "\n" + // cluster based app directories
+ "cat %s > " + SYSTEM_FOLDER + APK_NAME + ".tmp\n" +
+ "chmod 655 " + SYSTEM_FOLDER + APK_NAME + ".tmp\n" +
+ "pm uninstall -k %s\n" + // -k to retain data
+ "mv " + SYSTEM_FOLDER + APK_NAME + ".tmp " + SYSTEM_FOLDER + APK_NAME + "\n" +
+ "pm install -r " + SYSTEM_FOLDER + APK_NAME + "\n" +
+ "sleep 5\n" + CMD_SCRIPT_LOLLIPOP_REBOOT;
+
+ private static final String CMD_UNINSTALL = "mount -o rw,remount /system\n" +
+ "pm uninstall %s\n" +
+ "rm -f " + SYSTEM_FOLDER + APK_NAME + "\n" +
+ "sleep 5\n" +
+ "mount -o ro,remount /system";
+
+ String action;
+
+ @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;
+ }
+
+ action = getIntent().getAction();
+ if (ACTION_UNINSTALL.equals(action)) {
+ uninstall();
+ } else if (ACTION_INSTALL.equals(action)) {
+ checkRootTask.execute();
+ } else if (ACTION_FIRST_TIME.equals(action)) {
+ Preferences.get().setFirstTime(false);
+ checkRootTask.execute();
+ } else if (ACTION_POST_INSTALL.equals(action)) {
+ postInstall();
+ }
+ }
+
+ /**
+ * first time
+ */
+ private void firstTime() {
+ // hack to get holo design (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);
+ String message = getString(R.string.system_install_first_time_message) +
+ "
";
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ message += getString(R.string.system_install_question_lollipop);
+ } else {
+ message += getString(R.string.system_install_question);
+ }
+ builder.setMessage(Html.fromHtml(message));
+ builder.setPositiveButton(R.string.system_permission_install_via_root, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ installTask.execute();
+ }
+ });
+ builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ }
+ });
+ builder.create().show();
+ }
+
+ /**
+ * 1. Check for root access
+ */
+ public AsyncTask checkRootTask = new AsyncTask() {
+ ProgressDialog mProgressDialog;
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+
+ // hack to get holo design (which is not automatically applied due to activity's Theme.NoDisplay
+ ContextThemeWrapper theme = new ContextThemeWrapper(InstallIntoSystemDialogActivity.this,
+ FDroidApp.getCurThemeResId());
+
+ mProgressDialog = new ProgressDialog(theme);
+ mProgressDialog.setMessage(getString(R.string.requesting_root_access_body));
+ mProgressDialog.setIndeterminate(true);
+ mProgressDialog.setCancelable(false);
+ mProgressDialog.show();
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ return Shell.SU.available();
+ }
+
+ @Override
+ protected void onPostExecute(Boolean rootGranted) {
+ super.onPostExecute(rootGranted);
+
+ mProgressDialog.dismiss();
+
+ if (rootGranted) {
+ // root access granted
+
+ if (ACTION_UNINSTALL.equals(action)) {
+ uninstallTask.execute();
+ } else if (ACTION_INSTALL.equals(action)) {
+ installTask.execute();
+ } else if (ACTION_FIRST_TIME.equals(action)) {
+ firstTime();
+ }
+ } else {
+ // root access denied
+
+ if (!ACTION_FIRST_TIME.equals(action)) {
+ // hack to get holo design (which is not automatically applied due to activity's Theme.NoDisplay
+ ContextThemeWrapper theme = new ContextThemeWrapper(InstallIntoSystemDialogActivity.this,
+ FDroidApp.getCurThemeResId());
+
+ AlertDialog.Builder alertBuilder = new AlertDialog.Builder(theme);
+ alertBuilder.setTitle(R.string.root_access_denied_title);
+ alertBuilder.setMessage(getString(R.string.root_access_denied_body));
+ alertBuilder.setNeutralButton(android.R.string.ok, null);
+ alertBuilder.create().show();
+ }
+ }
+ }
+ };
+
+ /**
+ * 2. Install into system
+ */
+ AsyncTask installTask = new AsyncTask() {
+ ProgressDialog mProgressDialog;
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+
+ // hack to get holo design (which is not automatically applied due to activity's Theme.NoDisplay
+ ContextThemeWrapper theme = new ContextThemeWrapper(InstallIntoSystemDialogActivity.this,
+ FDroidApp.getCurThemeResId());
+
+ mProgressDialog = new ProgressDialog(theme);
+ mProgressDialog.setMessage(getString(R.string.system_install_installing));
+ mProgressDialog.setIndeterminate(true);
+ mProgressDialog.setCancelable(false);
+ mProgressDialog.show();
+ }
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ // execute postInstall after reboot
+ Preferences.get().setPostSystemInstall(true);
+
+ Shell.SU.run(String.format(CMD_SCRIPT_LOLLIPOP,
+ new String[]{
+ InstallIntoSystemDialogActivity.this.getPackageCodePath(),
+ InstallIntoSystemDialogActivity.this.getPackageName()
+ }));
+ } else {
+ Shell.SU.run(String.format(CMD_SCRIPT_KITKAT,
+ new String[]{
+ InstallIntoSystemDialogActivity.this.getPackageCodePath(),
+ InstallIntoSystemDialogActivity.this.getPackageName()
+ }));
+ }
+ return null;
+ }
+ };
+
+ /**
+ * 3. Verify that install worked
+ */
+ private void postInstall() {
+ // hack to get holo design (which is not automatically applied due to activity's Theme.NoDisplay
+ ContextThemeWrapper theme = new ContextThemeWrapper(this, FDroidApp.getCurThemeResId());
+
+ final boolean success = Installer.hasSystemPermissions(this, this.getPackageManager());
+
+ // enable system installer on installation success
+ Preferences.get().setSystemInstallerEnabled(success);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(theme);
+ builder.setTitle(success ? R.string.system_install_post_success : R.string.system_install_post_fail);
+ builder.setMessage(success ? R.string.system_install_post_success_message : R.string.system_install_post_fail_message);
+ builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ InstallIntoSystemDialogActivity.this.setResult(success ? Activity.RESULT_OK : Activity.RESULT_CANCELED);
+ InstallIntoSystemDialogActivity.this.finish();
+ startActivity(new Intent(InstallIntoSystemDialogActivity.this, FDroid.class));
+ }
+ });
+ builder.create().show();
+ }
+
+ private void uninstall() {
+ // hack to get holo design (which is not automatically applied due to activity's Theme.NoDisplay
+ ContextThemeWrapper theme = new ContextThemeWrapper(this, FDroidApp.getCurThemeResId());
+
+ final boolean systemApp = Installer.hasSystemPermissions(this, this.getPackageManager());
+
+ if (systemApp) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(theme);
+ builder.setTitle(R.string.system_uninstall);
+ builder.setMessage(R.string.system_uninstall_message);
+ builder.setPositiveButton(R.string.system_uninstall_button, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ checkRootTask.execute();
+ }
+ });
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.create().show();
+ } else {
+ AlertDialog.Builder builder = new AlertDialog.Builder(theme);
+ builder.setTitle(R.string.system_permission_denied_title);
+ builder.setMessage(getString(R.string.system_permission_denied_body));
+ builder.setPositiveButton(R.string.ok, null);
+ builder.create().show();
+ }
+ }
+
+ AsyncTask uninstallTask = new AsyncTask() {
+ ProgressDialog mProgressDialog;
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+
+ // hack to get holo design (which is not automatically applied due to activity's Theme.NoDisplay
+ ContextThemeWrapper theme = new ContextThemeWrapper(InstallIntoSystemDialogActivity.this,
+ FDroidApp.getCurThemeResId());
+
+ mProgressDialog = new ProgressDialog(theme);
+ mProgressDialog.setMessage(getString(R.string.system_install_uninstalling));
+ mProgressDialog.setIndeterminate(true);
+ mProgressDialog.setCancelable(false);
+ mProgressDialog.show();
+ }
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ Shell.SU.run(String.format(CMD_UNINSTALL,
+ new String[]{
+ InstallIntoSystemDialogActivity.this.getPackageName()
+ }));
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void unused) {
+ super.onPostExecute(unused);
+
+ mProgressDialog.dismiss();
+
+ // app is uninstalled but still display, kill it!
+ System.exit(0);
+ }
+ };
+
+}
+
diff --git a/F-Droid/src/org/fdroid/fdroid/views/fragments/PreferencesFragment.java b/F-Droid/src/org/fdroid/fdroid/views/fragments/PreferencesFragment.java
index 294733b0d..e0fd1098d 100644
--- a/F-Droid/src/org/fdroid/fdroid/views/fragments/PreferencesFragment.java
+++ b/F-Droid/src/org/fdroid/fdroid/views/fragments/PreferencesFragment.java
@@ -1,13 +1,17 @@
package org.fdroid.fdroid.views.fragments;
import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
import android.content.SharedPreferences;
+import android.os.Build;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.support.v4.preference.PreferenceFragment;
+import android.text.Html;
import android.text.TextUtils;
import org.fdroid.fdroid.FDroidApp;
@@ -16,6 +20,7 @@ import org.fdroid.fdroid.PreferencesActivity;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.installer.CheckRootAsyncTask;
+import org.fdroid.fdroid.installer.InstallIntoSystemDialogActivity;
import org.fdroid.fdroid.installer.Installer;
import java.util.Locale;
@@ -277,8 +282,23 @@ public class PreferencesFragment extends PreferenceFragment
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getActivity());
alertBuilder.setTitle(R.string.system_permission_denied_title);
- alertBuilder.setMessage(getActivity().getString(R.string.system_permission_denied_body));
- alertBuilder.setNeutralButton(android.R.string.ok, null);
+ String message = getActivity().getString(R.string.system_permission_denied_body) +
+ "
";
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ message += getActivity().getString(R.string.system_install_question_lollipop);
+ } else {
+ message += getActivity().getString(R.string.system_install_question);
+ }
+ alertBuilder.setMessage(Html.fromHtml(message));
+ alertBuilder.setPositiveButton(R.string.system_permission_install_via_root, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Intent installIntent = new Intent(getActivity(), InstallIntoSystemDialogActivity.class);
+ installIntent.setAction(InstallIntoSystemDialogActivity.ACTION_INSTALL);
+ startActivity(installIntent);
+ }
+ });
+ alertBuilder.setNegativeButton(R.string.cancel, null);
alertBuilder.create().show();
}
} else {
@@ -293,6 +313,23 @@ public class PreferencesFragment extends PreferenceFragment
});
}
+ protected void initUninstallSystemAppPreference() {
+ Preference pref = findPreference(Preferences.PREF_UNINSTALL_SYSTEM_APP);
+ pref.setPersistent(false);
+
+ pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ Intent uninstallIntent = new Intent(getActivity(), InstallIntoSystemDialogActivity.class);
+ uninstallIntent.setAction(InstallIntoSystemDialogActivity.ACTION_UNINSTALL);
+ startActivity(uninstallIntent);
+
+ return true;
+ }
+ });
+ }
+
private void langSpinner(String key) {
final ListPreference pref = (ListPreference)findPreference(key);
final String[] langValues = getResources().getStringArray(R.array.languageValues);
@@ -317,6 +354,7 @@ public class PreferencesFragment extends PreferenceFragment
initRootInstallerPreference();
initSystemInstallerPreference();
+ initUninstallSystemAppPreference();
}
@Override