diff --git a/F-Droid/res/values/strings.xml b/F-Droid/res/values/strings.xml index 737cbdc04..3c181aa36 100644 --- a/F-Droid/res/values/strings.xml +++ b/F-Droid/res/values/strings.xml @@ -308,8 +308,8 @@ The installation of F-Droid as a privileged app 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 privileged app?\nThis takes up to 10 seconds where <b>no user interface</b> is shown. - Do you want to install F-Droid as a privileged app?\nThis takes up to 10 seconds where <b>no user interface</b> is shown and the device will be <b>rebooted</b> afterwards. + Do you want to install F-Droid as a privileged app?\nThis takes up to 10 seconds. + Do you want to install F-Droid as a privileged app?\nThis takes up to 10 seconds and the device will be <b>rebooted</b> afterwards. Looks like you have root access on your device. You can now install F-Droid as a privileged app, tightly coupled with the Android operating system. This allows F-Droid to install, upgrade and uninstall apps on its own. Do you want to uninstall F-Droid? This will uninstall F-Droid completely. diff --git a/F-Droid/src/org/fdroid/fdroid/installer/Installer.java b/F-Droid/src/org/fdroid/fdroid/installer/Installer.java index 7eb69a6d5..77f3f9143 100644 --- a/F-Droid/src/org/fdroid/fdroid/installer/Installer.java +++ b/F-Droid/src/org/fdroid/fdroid/installer/Installer.java @@ -109,7 +109,7 @@ abstract public class Installer { // system permissions and pref enabled -> SystemInstaller boolean isSystemInstallerEnabled = Preferences.get().isSystemInstallerEnabled(); if (isSystemInstallerEnabled) { - if (hasSystemPermissions(activity, pm)) { + if (PrivilegedInstaller.isAvailable(activity)) { Utils.DebugLog(TAG, "system permissions -> SystemInstaller"); try { @@ -147,17 +147,6 @@ abstract public class Installer { return null; } - public static boolean hasSystemPermissions(Context context, PackageManager pm) { - boolean hasInstallPermission = - (pm.checkPermission(permission.INSTALL_PACKAGES, context.getPackageName()) - == PackageManager.PERMISSION_GRANTED); - boolean hasDeletePermission = - (pm.checkPermission(permission.DELETE_PACKAGES, context.getPackageName()) - == PackageManager.PERMISSION_GRANTED); - - return (hasInstallPermission && hasDeletePermission); - } - public void installPackage(File apkFile) throws AndroidNotCompatibleException { // check if file exists... if (!apkFile.exists()) { diff --git a/F-Droid/src/org/fdroid/fdroid/installer/PrivilegedInstaller.java b/F-Droid/src/org/fdroid/fdroid/installer/PrivilegedInstaller.java index 1fa06dce8..23830c26b 100644 --- a/F-Droid/src/org/fdroid/fdroid/installer/PrivilegedInstaller.java +++ b/F-Droid/src/org/fdroid/fdroid/installer/PrivilegedInstaller.java @@ -29,6 +29,7 @@ import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.net.Uri; +import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.support.v7.app.AlertDialog; @@ -84,7 +85,54 @@ public class PrivilegedInstaller extends Installer { InstallerCallback callback) throws AndroidNotCompatibleException { super(activity, pm, callback); this.mActivity = activity; + } + public static boolean isAvailable(Context context) { + + // check if installed + PackageManager pm = context.getPackageManager(); + try { + pm.getPackageInfo(PRIVILEGED_PACKAGE_NAME, PackageManager.GET_ACTIVITIES); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + + // check if it has the privileged permissions granted + final Object mutex = new Object(); + final Bundle returnBundle = new Bundle(); + ServiceConnection mServiceConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName name, IBinder service) { + IPrivilegedService privService = IPrivilegedService.Stub.asInterface(service); + + try { + boolean hasPermissions = privService.hasPrivilegedPermissions(); + returnBundle.putBoolean("has_permissions", hasPermissions); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); + } + + synchronized (mutex) { + mutex.notify(); + } + } + + public void onServiceDisconnected(ComponentName name) { + } + }; + Intent serviceIntent = new Intent(PRIVILEGED_INTENT); + serviceIntent.setPackage(PRIVILEGED_PACKAGE_NAME); + context.getApplicationContext().bindService(serviceIntent, mServiceConnection, + Context.BIND_AUTO_CREATE); + + synchronized (mutex) { + try { + mutex.wait(3000); + } catch (InterruptedException e) { + // don't care + } + } + + return (returnBundle.getBoolean("has_permission", false)); } @Override diff --git a/F-Droid/src/org/fdroid/fdroid/privileged/install/InstallPrivileged.java b/F-Droid/src/org/fdroid/fdroid/privileged/install/InstallPrivileged.java index 0b7d13bd3..c69292f55 100644 --- a/F-Droid/src/org/fdroid/fdroid/privileged/install/InstallPrivileged.java +++ b/F-Droid/src/org/fdroid/fdroid/privileged/install/InstallPrivileged.java @@ -38,18 +38,22 @@ import eu.chainfire.libsuperuser.Shell; abstract class InstallPrivileged { protected final Context context; + protected final String apkPath; - public InstallPrivileged(final Context context) { + private static final String PACKAGE_NAME = "org.fdroid.fdroid.privileged"; + + public InstallPrivileged(final Context context, final String apkPath) { this.context = context; + this.apkPath = apkPath; } - public static InstallPrivileged create(final Context context) { + public static InstallPrivileged create(final Context context, final String apkPath) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - return new LollipopImpl(context); + return new LollipopImpl(context, apkPath); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - return new KitKatToLollipopImpl(context); + return new KitKatToLollipopImpl(context, apkPath); } else { - return new PreKitKatImpl(context); + return new PreKitKatImpl(context, apkPath); } } @@ -65,10 +69,10 @@ abstract class InstallPrivileged { final void runUninstall() { final String[] commands = { - "am force-stop org.fdroid.fdroid", - "pm clear org.fdroid.fdroid", + "am force-stop " + PACKAGE_NAME, + "pm clear " + PACKAGE_NAME, "mount -o rw,remount /system", - "pm uninstall " + context.getPackageName(), + "pm uninstall " + PACKAGE_NAME, "rm -f " + getInstallPath(), "sleep 5", "mount -o ro,remount /system" @@ -82,26 +86,26 @@ abstract class InstallPrivileged { } protected String getInstallPath() { - return getSystemFolder() + "FDroid.apk"; + return getSystemFolder() + "FDroidPrivileged.apk"; } private List getInstallCommands() { final List commands = new ArrayList<>(); commands.add("mount -o rw,remount /system"); commands.addAll(getCopyToSystemCommands()); - commands.add("pm uninstall -k " + context.getPackageName()); // -k to retain data + commands.add("pm uninstall " + PACKAGE_NAME); commands.add("mv " + getInstallPath() + ".tmp " + getInstallPath()); commands.add("pm install -r " + getInstallPath()); commands.add("sleep 5"); // wait until the app is really installed commands.add("mount -o ro,remount /system"); - commands.add("am force-stop org.fdroid.fdroid"); + commands.add("am force-stop " + PACKAGE_NAME); commands.addAll(getPostInstallCommands()); return commands; } protected List getCopyToSystemCommands() { final List commands = new ArrayList<>(2); - commands.add("cat " + context.getPackageCodePath() + " > " + getInstallPath() + ".tmp"); + commands.add("cat " + apkPath + " > " + getInstallPath() + ".tmp"); commands.add("chmod 644 " + getInstallPath() + ".tmp"); return commands; } @@ -114,8 +118,8 @@ abstract class InstallPrivileged { private static class PreKitKatImpl extends InstallPrivileged { - public PreKitKatImpl(Context context) { - super(context); + public PreKitKatImpl(Context context, String apkPath) { + super(context, apkPath); } @Override @@ -127,8 +131,8 @@ abstract class InstallPrivileged { private static class KitKatToLollipopImpl extends InstallPrivileged { - public KitKatToLollipopImpl(Context context) { - super(context); + public KitKatToLollipopImpl(Context context, String apkPath) { + super(context, apkPath); } /** @@ -148,8 +152,8 @@ abstract class InstallPrivileged { */ private static class LollipopImpl extends InstallPrivileged { - public LollipopImpl(Context context) { - super(context); + public LollipopImpl(Context context, String apkPath) { + super(context, apkPath); } @Override @@ -167,7 +171,7 @@ abstract class InstallPrivileged { */ @Override protected String getSystemFolder() { - return "/system/priv-app/FDroid/"; + return "/system/priv-app/FDroidPrivileged/"; } /** @@ -178,13 +182,13 @@ abstract class InstallPrivileged { List commands = new ArrayList<>(3); commands.add("mkdir -p " + getSystemFolder()); // create app directory if not existing commands.add("chmod 755 " + getSystemFolder()); - commands.add("cat " + context.getPackageCodePath() + " > " + getInstallPath() + ".tmp"); + commands.add("cat " + apkPath + " > " + getInstallPath() + ".tmp"); commands.add("chmod 644 " + getInstallPath() + ".tmp"); return commands; } /** - * TODO: Currently only works with reboot + * 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 diff --git a/F-Droid/src/org/fdroid/fdroid/privileged/install/InstallPrivilegedDialogActivity.java b/F-Droid/src/org/fdroid/fdroid/privileged/install/InstallPrivilegedDialogActivity.java index c5680e5be..b413cad0b 100644 --- a/F-Droid/src/org/fdroid/fdroid/privileged/install/InstallPrivilegedDialogActivity.java +++ b/F-Droid/src/org/fdroid/fdroid/privileged/install/InstallPrivilegedDialogActivity.java @@ -41,6 +41,7 @@ import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.R; import org.fdroid.fdroid.installer.Installer; +import org.fdroid.fdroid.installer.PrivilegedInstaller; import eu.chainfire.libsuperuser.Shell; @@ -86,7 +87,7 @@ public class InstallPrivilegedDialogActivity extends FragmentActivity { if (Preferences.get().isFirstTime()) { Preferences.get().setFirstTime(false); - if (Installer.hasSystemPermissions(context, context.getPackageManager())) { + if (PrivilegedInstaller.isAvailable(context)) { Preferences.get().setSystemInstallerEnabled(true); } else { runFirstTime(context); @@ -158,7 +159,8 @@ public class InstallPrivilegedDialogActivity extends FragmentActivity { // hack to get holo design (which is not automatically applied due to activity's Theme.NoDisplay ContextThemeWrapper theme = new ContextThemeWrapper(this, FDroidApp.getCurThemeResId()); - String message = getString(R.string.system_install_first_time_message) + "

" + InstallPrivileged.create(getApplicationContext()).getWarningInfo(); + String message = getString(R.string.system_install_first_time_message) + "

" + + InstallPrivileged.create(getApplicationContext(), null).getWarningInfo(); AlertDialog.Builder builder = new AlertDialog.Builder(theme) .setMessage(Html.fromHtml(message)) @@ -279,7 +281,7 @@ public class InstallPrivilegedDialogActivity extends FragmentActivity { // 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()); + final boolean success = PrivilegedInstaller.isAvailable(this); // enable system installer on installation success Preferences.get().setSystemInstallerEnabled(success); @@ -303,7 +305,7 @@ public class InstallPrivilegedDialogActivity extends FragmentActivity { // 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()); + final boolean systemApp = PrivilegedInstaller.isAvailable(this); if (systemApp) { AlertDialog.Builder builder = new AlertDialog.Builder(theme) 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 40804dd88..f87644e75 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/fragments/PreferencesFragment.java +++ b/F-Droid/src/org/fdroid/fdroid/views/fragments/PreferencesFragment.java @@ -19,6 +19,7 @@ import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.PreferencesActivity; import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; +import org.fdroid.fdroid.installer.PrivilegedInstaller; import org.fdroid.fdroid.privileged.install.InstallPrivilegedDialogActivity; import org.fdroid.fdroid.installer.Installer; @@ -195,7 +196,7 @@ public class PreferencesFragment extends PreferenceFragment final CheckBoxPreference pref = (CheckBoxPreference) preference; if (pref.isChecked()) { - if (Installer.hasSystemPermissions(getActivity(), getActivity().getPackageManager())) { + if (PrivilegedInstaller.isAvailable(getActivity())) { // system-permission are granted, i.e. F-Droid is a system-app SharedPreferences.Editor editor = pref.getSharedPreferences().edit(); editor.putBoolean(Preferences.PREF_SYSTEM_INSTALLER, true);