Install F-Droid as system-app via root access

This commit is contained in:
Dominik Schürmann 2015-05-10 23:47:14 +02:00
parent 9f7536a926
commit f328a9de5e
10 changed files with 532 additions and 16 deletions

View File

@ -403,6 +403,16 @@
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".FDroid" /> android:value=".FDroid" />
</activity> </activity>
<!-- Note: Theme.NoDisplay, this activity shows dialogs only -->
<activity
android:name=".installer.InstallIntoSystemDialogActivity"
android:theme="@android:style/Theme.NoDisplay" />
<receiver
android:name=".installer.InstallIntoSystemBootReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<activity <activity
android:name=".SearchResults" android:name=".SearchResults"
android:label="@string/search_results" android:label="@string/search_results"

View File

@ -29,6 +29,8 @@
<string name="root_installer_on">Request root access to install, update, and remove packages</string> <string name="root_installer_on">Request root access to install, update, and remove packages</string>
<string name="system_installer">Install using system-permissions</string> <string name="system_installer">Install using system-permissions</string>
<string name="system_installer_on">Use system permissions to install, update, and remove packages</string> <string name="system_installer_on">Use system permissions to install, update, and remove packages</string>
<string name="uninstall_system">Uninstall F-Droid</string>
<string name="uninstall_system_summary">Uninstall F-Droid when installed as a system-app.</string>
<string name="local_repo_bonjour">Broadcast Local Repo</string> <string name="local_repo_bonjour">Broadcast Local Repo</string>
<string name="local_repo_bonjour_on">Advertise your local repo using Bonjour (mDNS)</string> <string name="local_repo_bonjour_on">Advertise your local repo using Bonjour (mDNS)</string>
<string name="local_repo_name">Name of your Local Repo</string> <string name="local_repo_name">Name of your Local Repo</string>
@ -287,7 +289,6 @@
<string name="empty_available_app_list">No apps in this category.\n\nTry selecting a different category or updating your repositories to get a fresh list of apps.</string> <string name="empty_available_app_list">No apps in this category.\n\nTry selecting a different category or updating your repositories to get a fresh list of apps.</string>
<string name="empty_can_update_app_list">All apps up to date.\n\nCongratulations! All of your apps are up to date (or your repositories are out of date).</string> <string name="empty_can_update_app_list">All apps up to date.\n\nCongratulations! All of your apps are up to date (or your repositories are out of date).</string>
<string name="requesting_root_access_title">Root access</string>
<string name="requesting_root_access_body">Requesting root access…</string> <string name="requesting_root_access_body">Requesting root access…</string>
<string name="root_access_denied_title">Root access denied</string> <string name="root_access_denied_title">Root access denied</string>
<string name="root_access_denied_body">Either your Android device is not rooted or you have denied root access for F-Droid.</string> <string name="root_access_denied_body">Either your Android device is not rooted or you have denied root access for F-Droid.</string>
@ -296,6 +297,19 @@
<string name="installer_error_body">The (de-)installation failed. If you are using root access, try disabling this setting!</string> <string name="installer_error_body">The (de-)installation failed. If you are using root access, try disabling this setting!</string>
<string name="system_permission_denied_title">System permissions denied</string> <string name="system_permission_denied_title">System permissions denied</string>
<string name="system_permission_denied_body">This option is only available when F-Droid is installed as a system-app.</string> <string name="system_permission_denied_body">This option is only available when F-Droid is installed as a system-app.</string>
<string name="system_permission_install_via_root">Install as system-app</string>
<string name="system_install_post_success">Successful installation as system-app</string>
<string name="system_install_post_fail">Installation as system-app failed</string>
<string name="system_install_post_success_message">F-Droid has been successfully installed into the system partition. This enables extended features, such as automatic app updates.</string>
<string name="system_install_post_fail_message">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.</string>
<string name="system_install_installing">installing…</string>
<string name="system_install_uninstalling">uninstalling…</string>
<string name="system_install_question">Do you want to install F-Droid as a system-app?\nThis takes up to 10 seconds where &lt;b>no user interface&lt;/b> is shown.</string>
<string name="system_install_question_lollipop">Do you want to install F-Droid as a system-app?\nThis takes up to 10 seconds where &lt;b>no user interface&lt;/b> is shown and the device will be &lt;b>rebooted&lt;/b> afterwards.</string>
<string name="system_install_first_time_message">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.</string>
<string name="system_uninstall">Do you want to uninstall F-Droid?</string>
<string name="system_uninstall_message">This will uninstall F-Droid completely.</string>
<string name="system_uninstall_button">Uninstall</string>
<string name="app_description">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.</string> <string name="app_description">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.</string>
<string name="swap_nfc_description">If your friend has <b>F-Droid and NFC turned on</b> touch your phones together.</string> <string name="swap_nfc_description">If your friend has <b>F-Droid and NFC turned on</b> touch your phones together.</string>

View File

@ -92,5 +92,9 @@
android:defaultValue="false" android:defaultValue="false"
android:key="systemInstaller" android:key="systemInstaller"
android:dependency="expert" /> android:dependency="expert" />
<Preference android:title="@string/uninstall_system"
android:summary="@string/uninstall_system_summary"
android:key="uninstallSystemApp"
android:dependency="expert" />
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View File

@ -47,6 +47,7 @@ import android.widget.Toast;
import org.fdroid.fdroid.compat.TabManager; import org.fdroid.fdroid.compat.TabManager;
import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.NewRepoConfig; import org.fdroid.fdroid.data.NewRepoConfig;
import org.fdroid.fdroid.installer.InstallIntoSystemDialogActivity;
import org.fdroid.fdroid.views.AppListFragmentPagerAdapter; import org.fdroid.fdroid.views.AppListFragmentPagerAdapter;
import org.fdroid.fdroid.views.ManageReposActivity; import org.fdroid.fdroid.views.ManageReposActivity;
import org.fdroid.fdroid.views.swap.ConnectSwapActivity; import org.fdroid.fdroid.views.swap.ConnectSwapActivity;
@ -101,6 +102,12 @@ public class FDroid extends ActionBarActivity {
Uri uri = AppProvider.getContentUri(); Uri uri = AppProvider.getContentUri();
getContentResolver().registerContentObserver(uri, true, new AppObserver()); 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 @Override

View File

@ -98,23 +98,26 @@ public class FDroidApp extends Application {
} }
public void applyTheme(Activity activity) { public void applyTheme(Activity activity) {
switch (curTheme) { activity.setTheme(getCurThemeResId());
case dark:
activity.setTheme(R.style.AppThemeDark);
break;
case light:
activity.setTheme(R.style.AppThemeLight);
break;
case lightWithDarkActionBar:
activity.setTheme(R.style.AppThemeLightWithDarkActionBar);
break;
}
} }
public static Theme getCurTheme() { public static Theme getCurTheme() {
return curTheme; 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() { public static void enableSpongyCastle() {
Security.addProvider(spongyCastleProvider); Security.addProvider(spongyCastleProvider);
} }

View File

@ -52,6 +52,7 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
public static final String PREF_UPD_LAST = "lastUpdateCheck"; public static final String PREF_UPD_LAST = "lastUpdateCheck";
public static final String PREF_ROOT_INSTALLER = "rootInstaller"; public static final String PREF_ROOT_INSTALLER = "rootInstaller";
public static final String PREF_SYSTEM_INSTALLER = "systemInstaller"; 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_BONJOUR = "localRepoBonjour";
public static final String PREF_LOCAL_REPO_NAME = "localRepoName"; public static final String PREF_LOCAL_REPO_NAME = "localRepoName";
public static final String PREF_LOCAL_REPO_HTTPS = "localRepoHttps"; 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_HOST = "proxyHost";
public static final String PREF_PROXY_PORT = "proxyPort"; public static final String PREF_PROXY_PORT = "proxyPort";
public static final String PREF_SHOW_NFC_DURING_SWAP = "showNfcDuringSwap"; 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_COMPACT_LAYOUT = false;
private static final boolean DEFAULT_ROOTED = true; 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 String DEFAULT_PROXY_HOST = "127.0.0.1";
public static final int DEFAULT_PROXY_PORT = 8118; public static final int DEFAULT_PROXY_PORT = 8118;
public static final boolean DEFAULT_SHOW_NFC_DURING_SWAP = true; 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 compactLayout = DEFAULT_COMPACT_LAYOUT;
private boolean filterAppsRequiringRoot = DEFAULT_ROOTED; private boolean filterAppsRequiringRoot = DEFAULT_ROOTED;
@ -110,6 +115,26 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
return preferences.getBoolean(PREF_SYSTEM_INSTALLER, DEFAULT_SYSTEM_INSTALLER); 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() { public boolean isLocalRepoBonjourEnabled() {
return preferences.getBoolean(PREF_LOCAL_REPO_BONJOUR, DEFAULT_LOCAL_REPO_BONJOUR); return preferences.getBoolean(PREF_LOCAL_REPO_BONJOUR, DEFAULT_LOCAL_REPO_BONJOUR);
} }

View File

@ -22,7 +22,9 @@ package org.fdroid.fdroid.installer;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.view.ContextThemeWrapper;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.R; import org.fdroid.fdroid.R;
import eu.chainfire.libsuperuser.Shell; import eu.chainfire.libsuperuser.Shell;
@ -46,8 +48,11 @@ public class CheckRootAsyncTask extends AsyncTask<Void, Void, Boolean> {
protected void onPreExecute() { protected void onPreExecute() {
super.onPreExecute(); super.onPreExecute();
mDialog = new ProgressDialog(mContext); // if the dialog is displayed from the application class, design is missing
mDialog.setTitle(R.string.requesting_root_access_title); // 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.setMessage(mContext.getString(R.string.requesting_root_access_body));
mDialog.setIndeterminate(true); mDialog.setIndeterminate(true);
mDialog.setCancelable(false); mDialog.setCancelable(false);

View File

@ -0,0 +1,43 @@
/*
* 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.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);
}
}
}
}

View File

@ -0,0 +1,367 @@
/*
* 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.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.
* <p/>
* 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/
* <p/>
* Info for lollipop:
* http://stackoverflow.com/q/26487750
* <p/>
* Removed apk observers in
* https://github.com/android/platform_frameworks_base/commit/84e71d1d61c53cd947becc7879e05947be681103
* <p/>
* 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) +
"<br/><br/>";
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<Void, Void, Boolean> checkRootTask = new AsyncTask<Void, Void, Boolean>() {
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<Void, Void, Void> installTask = new AsyncTask<Void, Void, Void>() {
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<Void, Void, Void> uninstallTask = new AsyncTask<Void, Void, Void>() {
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);
}
};
}

View File

@ -1,13 +1,17 @@
package org.fdroid.fdroid.views.fragments; package org.fdroid.fdroid.views.fragments;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.preference.CheckBoxPreference; import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference; import android.preference.EditTextPreference;
import android.preference.ListPreference; import android.preference.ListPreference;
import android.preference.Preference; import android.preference.Preference;
import android.support.v4.preference.PreferenceFragment; import android.support.v4.preference.PreferenceFragment;
import android.text.Html;
import android.text.TextUtils; import android.text.TextUtils;
import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.FDroidApp;
@ -16,6 +20,7 @@ import org.fdroid.fdroid.PreferencesActivity;
import org.fdroid.fdroid.R; import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.installer.CheckRootAsyncTask; import org.fdroid.fdroid.installer.CheckRootAsyncTask;
import org.fdroid.fdroid.installer.InstallIntoSystemDialogActivity;
import org.fdroid.fdroid.installer.Installer; import org.fdroid.fdroid.installer.Installer;
import java.util.Locale; import java.util.Locale;
@ -277,8 +282,23 @@ public class PreferencesFragment extends PreferenceFragment
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getActivity()); AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getActivity());
alertBuilder.setTitle(R.string.system_permission_denied_title); alertBuilder.setTitle(R.string.system_permission_denied_title);
alertBuilder.setMessage(getActivity().getString(R.string.system_permission_denied_body)); String message = getActivity().getString(R.string.system_permission_denied_body) +
alertBuilder.setNeutralButton(android.R.string.ok, null); "<br/><br/>";
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(); alertBuilder.create().show();
} }
} else { } 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) { private void langSpinner(String key) {
final ListPreference pref = (ListPreference)findPreference(key); final ListPreference pref = (ListPreference)findPreference(key);
final String[] langValues = getResources().getStringArray(R.array.languageValues); final String[] langValues = getResources().getStringArray(R.array.languageValues);
@ -317,6 +354,7 @@ public class PreferencesFragment extends PreferenceFragment
initRootInstallerPreference(); initRootInstallerPreference();
initSystemInstallerPreference(); initSystemInstallerPreference();
initUninstallSystemAppPreference();
} }
@Override @Override