diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 450f0d1f135..9882779e952 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -129,7 +129,7 @@ tools:ignore="ProtectedPermissions" /> - + @@ -152,6 +152,10 @@ + + + + @@ -1374,6 +1378,20 @@ android:name="android.appwidget.provider" android:resource="@xml/recording_widget_info" /> + + + + + + + diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/PackageInstallerCompat.java b/app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/PackageInstallerCompat.java index c536eadcf36..bc8534d5ec6 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/PackageInstallerCompat.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/apk/installer/PackageInstallerCompat.java @@ -60,6 +60,7 @@ import io.github.muntashirakon.AppManager.R; import io.github.muntashirakon.AppManager.apk.ApkFile; import io.github.muntashirakon.AppManager.apk.ApkUtils; +import io.github.muntashirakon.AppManager.compat.DevicePolicyManagerCompat; import io.github.muntashirakon.AppManager.compat.ManifestCompat; import io.github.muntashirakon.AppManager.compat.PackageManagerCompat; import io.github.muntashirakon.AppManager.ipc.ProxyBinder; @@ -953,7 +954,7 @@ public boolean installExisting(@NonNull String packageName, @UserIdInt int userI } mInstallWatcher = new CountDownLatch(0); mInteractionWatcher = new CountDownLatch(0); - if (!SelfPermissions.canInstallExistingPackages()) { + if (!SelfPermissions.canInstallExistingPackages() || !DevicePolicyManagerCompat.canModifyPermissions()) { installCompleted(mSessionId, STATUS_FAILURE_BLOCKED, "android", "STATUS_FAILURE_BLOCKED: Insufficient permission."); Log.d(TAG, "InstallExisting: Requires INSTALL_PACKAGES permission."); return false; diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/backup/BackupOp.java b/app/src/main/java/io/github/muntashirakon/AppManager/backup/BackupOp.java index 6e995f6b801..37009163c50 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/backup/BackupOp.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/backup/BackupOp.java @@ -48,6 +48,7 @@ import io.github.muntashirakon.AppManager.compat.AppOpsManagerCompat; import io.github.muntashirakon.AppManager.compat.BackupCompat; import io.github.muntashirakon.AppManager.compat.DeviceIdleManagerCompat; +import io.github.muntashirakon.AppManager.compat.DevicePolicyManagerCompat; import io.github.muntashirakon.AppManager.compat.ManifestCompat; import io.github.muntashirakon.AppManager.compat.NetworkPolicyManagerCompat; import io.github.muntashirakon.AppManager.compat.PackageManagerCompat; @@ -483,7 +484,7 @@ private void backupExtras() throws BackupException { } boolean isGranted = (permissionFlags[i] & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0; int permFlags; - if (SelfPermissions.checkGetGrantRevokeRuntimePermissions()) { + if (SelfPermissions.checkGetGrantRevokeRuntimePermissions() || DevicePolicyManagerCompat.canModifyPermissions()) { permFlags = PermissionCompat.getPermissionFlags(info.name, mPackageName, mUserId); } else permFlags = PermissionCompat.FLAG_PERMISSION_NONE; rules.setPermission(permissions[i], isGranted, permFlags); diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/compat/ActivityManagerCompat.java b/app/src/main/java/io/github/muntashirakon/AppManager/compat/ActivityManagerCompat.java index 86b54998b5d..475504df667 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/compat/ActivityManagerCompat.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/compat/ActivityManagerCompat.java @@ -44,6 +44,7 @@ import io.github.muntashirakon.AppManager.logs.Log; import io.github.muntashirakon.AppManager.runner.Runner; import io.github.muntashirakon.AppManager.self.SelfPermissions; +import io.github.muntashirakon.AppManager.settings.Ops; import io.github.muntashirakon.AppManager.users.Users; import io.github.muntashirakon.AppManager.utils.ExUtils; import io.github.muntashirakon.AppManager.utils.ThreadUtils; @@ -63,7 +64,7 @@ public static boolean startActivityViaAssist(@NonNull Context context, @NonNull @Nullable ActivityLaunchUserInteractionRequiredCallback callback) throws SecurityException { // Need two permissions: WRITE_SECURE_SETTINGS and INJECT_EVENTS - SelfPermissions.requireSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS); + boolean canWriteSecure = SelfPermissions.checkSelfOrRemotePermission(Manifest.permission.WRITE_SECURE_SETTINGS); boolean canInjectEvents = SelfPermissions.checkSelfOrRemotePermission(ManifestCompat.permission.INJECT_EVENTS); ContentResolver resolver = context.getContentResolver(); // Backup assistant value @@ -72,14 +73,23 @@ public static boolean startActivityViaAssist(@NonNull Context context, @NonNull ThreadUtils.postOnBackgroundThread(() -> { try { // Set assistant value to the target activity component - Settings.Secure.putString(resolver, "assistant", activity.flattenToShortString()); + if (canWriteSecure) { + Settings.Secure.putString(resolver, "assistant", activity.flattenToShortString()); + } else if (Ops.isDpc()) { + DevicePolicyManagerCompat.setSecureSetting("assistant", activity.flattenToShortString()); + } // Run it as an assistant by injecting KEYCODE_ASSIST (219) InputManagerCompat.sendKeyEvent(KeyEvent.KEYCODE_ASSIST, false); // Wait until system opens the new assistant (i.e., activity), this is an empirical value SystemClock.sleep(500); } finally { // Restore assistant value - Settings.Secure.putString(resolver, "assistant", assistantComponent); + if (canWriteSecure) { + Settings.Secure.putString(resolver, "assistant", assistantComponent); + } else if (Ops.isDpc()) { + DevicePolicyManagerCompat.setSecureSetting("assistant", assistantComponent); + } + } }); } else if (callback != null) { @@ -87,12 +97,20 @@ public static boolean startActivityViaAssist(@NonNull Context context, @NonNull ThreadUtils.postOnBackgroundThread(() -> { try { // Set assistant value to the target activity component - Settings.Secure.putString(resolver, "assistant", activity.flattenToShortString()); + if (canWriteSecure) { + Settings.Secure.putString(resolver, "assistant", activity.flattenToShortString()); + } else if (Ops.isDpc()) { + DevicePolicyManagerCompat.setSecureSetting("assistant", activity.flattenToShortString()); + } // Trigger callback callback.onInteraction(); } finally { // Restore assistant value - Settings.Secure.putString(resolver, "assistant", assistantComponent); + if (canWriteSecure) { + Settings.Secure.putString(resolver, "assistant", assistantComponent); + } else if (Ops.isDpc()) { + DevicePolicyManagerCompat.setSecureSetting("assistant", assistantComponent); + } } }); } // else do nothing diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/compat/DevicePolicyManagerCompat.java b/app/src/main/java/io/github/muntashirakon/AppManager/compat/DevicePolicyManagerCompat.java new file mode 100644 index 00000000000..388c0e8d784 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/AppManager/compat/DevicePolicyManagerCompat.java @@ -0,0 +1,101 @@ +package io.github.muntashirakon.AppManager.compat; + +import android.annotation.SuppressLint; +import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyManager.OnClearApplicationUserDataListener; +import android.content.ComponentName; +import android.content.Context; +import android.os.Build; +import android.os.Bundle; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Executor; + +import io.github.muntashirakon.AppManager.BuildConfig; +import io.github.muntashirakon.AppManager.R; +import io.github.muntashirakon.AppManager.dpc.DpcReceiver; +import io.github.muntashirakon.AppManager.settings.Ops; +import io.github.muntashirakon.AppManager.utils.ContextUtils; +import io.github.muntashirakon.AppManager.utils.ThreadUtils; +import io.github.muntashirakon.AppManager.utils.UIUtils; + +public class DevicePolicyManagerCompat { + public static final String LOG_TAG = "DPC"; + public static final ComponentName DPC_ADMIN = new ComponentName(BuildConfig.APPLICATION_ID, DpcReceiver.class.getCanonicalName()); + public static final String DPC_COMMAND = String.format("dpm set-device-owner %s", DevicePolicyManagerCompat.DPC_ADMIN.flattenToString()); + private static DevicePolicyManager dpm; + @SuppressLint("WrongConstant") + public static DevicePolicyManager getDevicePolicyManager() { + if (dpm==null) {dpm=(DevicePolicyManager) ContextUtils.getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);} + return dpm; + } + + public static boolean isOwnerApp() { + return getDevicePolicyManager().isDeviceOwnerApp(BuildConfig.APPLICATION_ID); + } + + public static void setSecureSetting(String setting, String value) { + getDevicePolicyManager().setSecureSetting(DPC_ADMIN, setting, value); + } + public static void setGlobalSetting(String setting, String value) { + getDevicePolicyManager().setGlobalSetting(DPC_ADMIN, setting, value); + } + @RequiresApi(api = Build.VERSION_CODES.P) + public static void setSystemSetting(String setting, String value) { + getDevicePolicyManager().setSystemSetting(DPC_ADMIN, setting, value); + } + + public static void clearDeviceOwnerApp() { + getDevicePolicyManager().clearDeviceOwnerApp(BuildConfig.APPLICATION_ID); + } + + public static boolean canModifyPermissions() { + return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Ops.isDpc()); + } + + @RequiresApi(api = Build.VERSION_CODES.M) + public static void revokePermission(String packageName, String permission) { + getDevicePolicyManager().setPermissionGrantState(DPC_ADMIN, packageName, permission, DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED); + } + + @RequiresApi(api = Build.VERSION_CODES.M) + public static void grantPermission(String packageName, String permission) { + getDevicePolicyManager().setPermissionGrantState(DPC_ADMIN, packageName, permission, DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED); + } + + @RequiresApi(api = Build.VERSION_CODES.N) + public static void setOrganizationName(String name) { + getDevicePolicyManager().setOrganizationName(DPC_ADMIN, name); + } + + @RequiresApi(api = Build.VERSION_CODES.N) + public static void setOrganizationColor(int color) { + getDevicePolicyManager().setOrganizationColor(DPC_ADMIN, color); + } + + @NonNull + @RequiresApi(api = Build.VERSION_CODES.N) + public static Bundle getUserRestrictions() { + return getDevicePolicyManager().getUserRestrictions(DPC_ADMIN); + } + + public static boolean setApplicationHidden(String packageName, boolean hidden) { + return getDevicePolicyManager().setApplicationHidden(DPC_ADMIN, packageName, hidden); + } + + @RequiresApi(api = Build.VERSION_CODES.N) + public static boolean setPackageSuspended(String packageName, boolean suspended) { + return !Arrays.stream(getDevicePolicyManager().setPackagesSuspended(DPC_ADMIN, new String[]{packageName}, suspended)).anyMatch((s)-> Objects.equals(s, packageName)); + } + + @RequiresApi(api = Build.VERSION_CODES.P) + public static void clearApplicationUserData(String packageName, DevicePolicyManager.OnClearApplicationUserDataListener listener) { + getDevicePolicyManager().clearApplicationUserData(DPC_ADMIN, packageName, ThreadUtils.getBackgroundThreadExecutor(), listener); + } +} diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/compat/PermissionCompat.java b/app/src/main/java/io/github/muntashirakon/AppManager/compat/PermissionCompat.java index 3c500076298..29be66781c9 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/compat/PermissionCompat.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/compat/PermissionCompat.java @@ -30,6 +30,8 @@ import dev.rikka.tools.refine.Refine; import io.github.muntashirakon.AppManager.ipc.ProxyBinder; +import io.github.muntashirakon.AppManager.self.SelfPermissions; +import io.github.muntashirakon.AppManager.settings.Ops; import io.github.muntashirakon.AppManager.utils.ContextUtils; import io.github.muntashirakon.AppManager.utils.ExUtils; @@ -278,6 +280,9 @@ public final class PermissionCompat { public static int getPermissionFlags(@NonNull String permissionName, @NonNull String packageName, @UserIdInt int userId) throws SecurityException { + if (Ops.isDpc()) { + return FLAG_PERMISSION_NONE; + } try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { IPermissionManager permissionManager = getPermissionManager(); @@ -363,6 +368,10 @@ public static void grantPermission(@NonNull String packageName, @NonNull String permissionName, @UserIdInt int userId) throws RemoteException { + if (!SelfPermissions.canModifyPermissions() && DevicePolicyManagerCompat.canModifyPermissions()) { + DevicePolicyManagerCompat.grantPermission(packageName, permissionName); + return; + } IPackageManager pm = PackageManagerCompat.getPackageManager(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { IPermissionManager permissionManager = getPermissionManager(); @@ -406,6 +415,10 @@ public static void revokePermission(@NonNull String packageName, @UserIdInt int userId, @Nullable String reason) throws RemoteException { IPackageManager pm = PackageManagerCompat.getPackageManager(); + if (!SelfPermissions.canModifyPermissions() && DevicePolicyManagerCompat.canModifyPermissions()) { + DevicePolicyManagerCompat.revokePermission(packageName, permissionName); + return; + } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { IPermissionManager permissionManager = getPermissionManager(); try { diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/details/AppDetailsPermissionsFragment.java b/app/src/main/java/io/github/muntashirakon/AppManager/details/AppDetailsPermissionsFragment.java index 35aa8d8d845..2433b77b80e 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/details/AppDetailsPermissionsFragment.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/details/AppDetailsPermissionsFragment.java @@ -41,6 +41,7 @@ import io.github.muntashirakon.AppManager.R; import io.github.muntashirakon.AppManager.compat.AppOpsManagerCompat; +import io.github.muntashirakon.AppManager.compat.DevicePolicyManagerCompat; import io.github.muntashirakon.AppManager.compat.ManifestCompat; import io.github.muntashirakon.AppManager.compat.PermissionCompat; import io.github.muntashirakon.AppManager.details.struct.AppDetailsAppOpItem; @@ -350,7 +351,7 @@ private int getHelpString(@PermissionProperty int index) { if (!TipsPrefs.getInstance().displayInUsesPermissionsTab()) { return 0; } - if (SelfPermissions.canModifyPermissions()) { + if (SelfPermissions.canModifyPermissions() || DevicePolicyManagerCompat.canModifyPermissions()) { return R.string.help_uses_permissions_tab; } else return 0; case PERMISSIONS: diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/details/AppDetailsViewModel.java b/app/src/main/java/io/github/muntashirakon/AppManager/details/AppDetailsViewModel.java index 8cdb09b1b03..f2d2a96aca5 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/details/AppDetailsViewModel.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/details/AppDetailsViewModel.java @@ -69,6 +69,7 @@ import io.github.muntashirakon.AppManager.compat.ActivityManagerCompat; import io.github.muntashirakon.AppManager.compat.AppOpsManagerCompat; import io.github.muntashirakon.AppManager.compat.ApplicationInfoCompat; +import io.github.muntashirakon.AppManager.compat.DevicePolicyManagerCompat; import io.github.muntashirakon.AppManager.compat.ManifestCompat; import io.github.muntashirakon.AppManager.compat.OverlayManagerCompact; import io.github.muntashirakon.AppManager.compat.PackageManagerCompat; @@ -1223,7 +1224,8 @@ private void loadActivities() { CharSequence appLabel = packageInfo.applicationInfo.loadLabel(mPackageManager); boolean canStartAnyActivity = SelfPermissions.checkSelfOrRemotePermission(ManifestCompat.permission.START_ANY_ACTIVITY); boolean canStartViaAssist = UserHandleHidden.myUserId() == mUserId && - SelfPermissions.checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS); + (SelfPermissions.checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) + || Ops.isDpc()); for (ActivityInfo activityInfo : packageInfo.activities) { AppDetailsActivityItem componentItem = new AppDetailsActivityItem(activityInfo); componentItem.label = getComponentLabel(activityInfo, appLabel); @@ -1453,7 +1455,7 @@ private void loadAppOps() { mAppOps.postValue(Collections.emptyList()); return; } - boolean canGetGrantRevokeRuntimePermissions = SelfPermissions.checkGetGrantRevokeRuntimePermissions(); + boolean canGetGrantRevokeRuntimePermissions = SelfPermissions.checkGetGrantRevokeRuntimePermissions() || DevicePolicyManagerCompat.canModifyPermissions(); synchronized (mAppOpItems) { mAppOpItems.clear(); try { @@ -1612,7 +1614,7 @@ private AppDetailsPermissionItem getPermissionItem(@NonNull String permissionNam int appOp = AppOpsManagerCompat.permissionToOpCode(permissionName); int permissionFlags; boolean appOpAllowed = false; - if (!mExternalApk && SelfPermissions.checkGetGrantRevokeRuntimePermissions()) { + if (!mExternalApk && (SelfPermissions.checkGetGrantRevokeRuntimePermissions() || DevicePolicyManagerCompat.canModifyPermissions())) { permissionFlags = PermissionCompat.getPermissionFlags( permissionName, packageInfo.packageName, mUserId); } else permissionFlags = PermissionCompat.FLAG_PERMISSION_NONE; diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/details/info/AppInfoFragment.java b/app/src/main/java/io/github/muntashirakon/AppManager/details/info/AppInfoFragment.java index 4dd902d94f3..65dd90c469a 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/details/info/AppInfoFragment.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/details/info/AppInfoFragment.java @@ -94,8 +94,8 @@ import io.github.muntashirakon.AppManager.apk.ApkSource; import io.github.muntashirakon.AppManager.apk.ApkUtils; import io.github.muntashirakon.AppManager.apk.behavior.FreezeUnfreeze; -import io.github.muntashirakon.AppManager.apk.dexopt.DexOptDialog; import io.github.muntashirakon.AppManager.apk.behavior.FreezeUnfreezeShortcutInfo; +import io.github.muntashirakon.AppManager.apk.dexopt.DexOptDialog; import io.github.muntashirakon.AppManager.apk.installer.PackageInstallerActivity; import io.github.muntashirakon.AppManager.apk.installer.PackageInstallerCompat; import io.github.muntashirakon.AppManager.apk.signing.SignerInfo; @@ -107,6 +107,7 @@ import io.github.muntashirakon.AppManager.compat.ActivityManagerCompat; import io.github.muntashirakon.AppManager.compat.ApplicationInfoCompat; import io.github.muntashirakon.AppManager.compat.DeviceIdleManagerCompat; +import io.github.muntashirakon.AppManager.compat.DevicePolicyManagerCompat; import io.github.muntashirakon.AppManager.compat.DomainVerificationManagerCompat; import io.github.muntashirakon.AppManager.compat.InstallSourceInfoCompat; import io.github.muntashirakon.AppManager.compat.ManifestCompat; @@ -139,6 +140,7 @@ import io.github.muntashirakon.AppManager.self.SelfPermissions; import io.github.muntashirakon.AppManager.self.imagecache.ImageLoader; import io.github.muntashirakon.AppManager.settings.FeatureController; +import io.github.muntashirakon.AppManager.settings.Ops; import io.github.muntashirakon.AppManager.settings.Prefs; import io.github.muntashirakon.AppManager.sharedpref.SharedPrefsActivity; import io.github.muntashirakon.AppManager.shortcut.CreateShortcutDialogFragment; @@ -409,7 +411,7 @@ public void onPrepareMenu(@NonNull Menu menu) { netPolicyMenu.setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N); } if (installMenu != null) { - installMenu.setVisible(Users.getUsersIds().length > 1 && SelfPermissions.canInstallExistingPackages()); + installMenu.setVisible(Users.getUsersIds().length > 1 && (SelfPermissions.canInstallExistingPackages()) || DevicePolicyManagerCompat.canModifyPermissions()); } if (optimizeMenu != null) { optimizeMenu.setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N @@ -1321,7 +1323,7 @@ private List getHorizontalActions() { if (!mIsExternalApk) { boolean isStaticSharedLib = ApplicationInfoCompat.isStaticSharedLibrary(mApplicationInfo); boolean isFrozen = FreezeUtils.isFrozen(mApplicationInfo); - boolean canFreeze = !isStaticSharedLib && SelfPermissions.canFreezeUnfreezePackages(); + boolean canFreeze = !isStaticSharedLib && (SelfPermissions.canFreezeUnfreezePackages() || Ops.isDpc()); // Set open Intent launchIntent = PackageManagerCompat.getLaunchIntentForPackage(mPackageName, mUserId); if (launchIntent != null && !isFrozen) { @@ -1450,7 +1452,7 @@ private List getHorizontalActions() { } } if (!isStaticSharedLib && (SelfPermissions.checkSelfOrRemotePermission(ManifestCompat.permission.CLEAR_APP_USER_DATA) - || accessibilityServiceRunning)) { + || accessibilityServiceRunning || Ops.isDpc())) { // Clear data ActionItem clearDataAction = new ActionItem(R.string.clear_data, R.drawable.ic_clear_data); actionItems.add(clearDataAction); @@ -1468,6 +1470,12 @@ private List getHorizontalActions() { } else UIUtils.displayShortToast(R.string.failed); }); }); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && Ops.isDpc()) { + ThreadUtils.postOnBackgroundThread(() -> { + DevicePolicyManagerCompat.clearApplicationUserData(mPackageName, ((packageName, succeeded) -> { + ThreadUtils.postOnMainThread(() -> UIUtils.displayShortToast(succeeded?R.string.done:R.string.failed)); + })); + }); } else { // Use accessibility AccessibilityMultiplexer.getInstance().enableNavigateToStorageAndCache(true); diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/dpc/DpcReceiver.java b/app/src/main/java/io/github/muntashirakon/AppManager/dpc/DpcReceiver.java new file mode 100644 index 00000000000..08758b01bcd --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/AppManager/dpc/DpcReceiver.java @@ -0,0 +1,49 @@ +package io.github.muntashirakon.AppManager.dpc; + +import static io.github.muntashirakon.AppManager.compat.DevicePolicyManagerCompat.DPC_ADMIN; + +import android.app.admin.DeviceAdminReceiver; +import android.app.admin.DevicePolicyManager; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.widget.Toast; + +import androidx.annotation.NonNull; + +import java.util.List; + +import io.github.muntashirakon.AppManager.BuildConfig; +import io.github.muntashirakon.AppManager.R; + +public class DpcReceiver extends DeviceAdminReceiver { + @Override + public void onEnabled(@NonNull Context context, @NonNull Intent intent) { + DevicePolicyManager dpc = getManager(context); + if (!dpc.isDeviceOwnerApp(BuildConfig.APPLICATION_ID)) { + throw new RuntimeException("Not Device Owner but onEnabled was called"); + } + //Setup DPC, and reset restrictions + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + dpc.setOrganizationName(DPC_ADMIN, context.getString(R.string.app_name)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + dpc.setOrganizationId(BuildConfig.APPLICATION_ID); + } + dpc.setShortSupportMessage(DPC_ADMIN, context.getText(R.string.dpc_short_support_message)); + dpc.setLongSupportMessage(DPC_ADMIN, context.getText(R.string.dpc_long_support_message)); + for (String s : dpc.getUserRestrictions(DPC_ADMIN).keySet()) { + dpc.clearUserRestriction(DPC_ADMIN, s); + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + dpc.setUserControlDisabledPackages(DPC_ADMIN, List.of()); + } + Toast.makeText(context, R.string.dpc_on_enabled, Toast.LENGTH_SHORT).show(); + } + + @Override + public void onDisabled(@NonNull Context context, @NonNull Intent intent) { + Toast.makeText(context, R.string.dpc_on_disabled, Toast.LENGTH_SHORT).show(); + + } +} diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/main/MainRecyclerAdapter.java b/app/src/main/java/io/github/muntashirakon/AppManager/main/MainRecyclerAdapter.java index b36b5cd1c8d..188e07e8d87 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/main/MainRecyclerAdapter.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/main/MainRecyclerAdapter.java @@ -44,6 +44,7 @@ import io.github.muntashirakon.AppManager.apk.installer.PackageInstallerCompat; import io.github.muntashirakon.AppManager.backup.dialog.BackupRestoreDialogFragment; import io.github.muntashirakon.AppManager.compat.ApplicationInfoCompat; +import io.github.muntashirakon.AppManager.compat.DevicePolicyManagerCompat; import io.github.muntashirakon.AppManager.compat.PackageManagerCompat; import io.github.muntashirakon.AppManager.db.entity.Backup; import io.github.muntashirakon.AppManager.details.AppDetailsActivity; @@ -407,7 +408,7 @@ private void handleClick(@NonNull ApplicationItem item) { } // 2. If the app can be installed, offer it to install again. if (FeatureController.isInstallerEnabled()) { - if (ApplicationInfoCompat.isSystemApp(info) && SelfPermissions.canInstallExistingPackages()) { + if (ApplicationInfoCompat.isSystemApp(info) && (SelfPermissions.canInstallExistingPackages() || DevicePolicyManagerCompat.canModifyPermissions())) { // Install existing app instead of installing as an update mActivity.startActivity(PackageInstallerActivity.getLaunchableInstance(mActivity, item.packageName)); return; diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/permission/PermUtils.java b/app/src/main/java/io/github/muntashirakon/AppManager/permission/PermUtils.java index 5d47b9f259f..19cfe23a40b 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/permission/PermUtils.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/permission/PermUtils.java @@ -31,6 +31,7 @@ import io.github.muntashirakon.AppManager.compat.ActivityManagerCompat; import io.github.muntashirakon.AppManager.compat.AppOpsManagerCompat; +import io.github.muntashirakon.AppManager.compat.DevicePolicyManagerCompat; import io.github.muntashirakon.AppManager.compat.ManifestCompat; import io.github.muntashirakon.AppManager.compat.PermissionCompat; import io.github.muntashirakon.AppManager.logs.Log; @@ -309,7 +310,11 @@ private static void persistChanges(@NonNull ApplicationInfo applicationInfo, if (!permission.isReadOnly()) { if (permission.isGranted()) { - PermissionCompat.grantPermission(applicationInfo.packageName, permission.getName(), userId); + if (!SelfPermissions.canModifyPermissions() && DevicePolicyManagerCompat.canModifyPermissions()) { + DevicePolicyManagerCompat.grantPermission(applicationInfo.packageName, permission.getName()); + } else { + PermissionCompat.grantPermission(applicationInfo.packageName, permission.getName(), userId); + } Log.d("PERM", "Granted %s", permission.getName()); } else { boolean isCurrentlyGranted = PermissionCompat.checkPermission(permission.getName(), @@ -317,16 +322,25 @@ private static void persistChanges(@NonNull ApplicationInfo applicationInfo, if (isCurrentlyGranted) { if (revokeReason == null) { - PermissionCompat.revokePermission(applicationInfo.packageName, permission.getName(), userId); + if (!SelfPermissions.canModifyPermissions() && DevicePolicyManagerCompat.canModifyPermissions()) { + DevicePolicyManagerCompat.revokePermission(applicationInfo.packageName, permission.getName()); + } else { + PermissionCompat.revokePermission(applicationInfo.packageName, permission.getName(), userId); + } + } else { - PermissionCompat.revokePermission(applicationInfo.packageName, permission.getName(), userId, revokeReason); + if (!SelfPermissions.canModifyPermissions() && DevicePolicyManagerCompat.canModifyPermissions()) { + DevicePolicyManagerCompat.revokePermission(applicationInfo.packageName, permission.getName()); + } else { + PermissionCompat.revokePermission(applicationInfo.packageName, permission.getName(), userId, revokeReason); + } } Log.d("PERM", "Revoked %s", permission.getName()); } } } - if (!permission.readOnly) { + if (!permission.readOnly && !(!SelfPermissions.canModifyPermissions() && DevicePolicyManagerCompat.canModifyPermissions())) { // Flags of the system fixed permissions may also be updated updateFlags(applicationInfo, permission, userId); } @@ -435,6 +449,6 @@ public static boolean systemSupportsRuntimePermissions() { public static boolean isModifiable(@NonNull Permission permission) { // Non-readonly permissions or permissions with app ops are modifiable - return SelfPermissions.canModifyPermissions() && (!permission.isReadOnly() || permission.affectsAppOp()); + return (SelfPermissions.canModifyPermissions() || (DevicePolicyManagerCompat.canModifyPermissions() && !(permission instanceof DevelopmentPermission))) && (!permission.isReadOnly() || permission.affectsAppOp()); } } diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/settings/MainPreferences.java b/app/src/main/java/io/github/muntashirakon/AppManager/settings/MainPreferences.java index 8793357e9ba..6d041fc3f03 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/settings/MainPreferences.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/settings/MainPreferences.java @@ -38,7 +38,8 @@ public static MainPreferences getInstance(@Nullable String key, boolean dualPane Ops.MODE_ROOT, Ops.MODE_ADB_OVER_TCP, Ops.MODE_ADB_WIFI, - Ops.MODE_NO_ROOT); + Ops.MODE_NO_ROOT, + Ops.MODE_DPC); private FragmentActivity mActivity; private Preference mModePref; diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/settings/ModeOfOpsPreference.java b/app/src/main/java/io/github/muntashirakon/AppManager/settings/ModeOfOpsPreference.java index 2b2fbefde23..0a9bb108d75 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/settings/ModeOfOpsPreference.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/settings/ModeOfOpsPreference.java @@ -2,6 +2,8 @@ package io.github.muntashirakon.AppManager.settings; +import static io.github.muntashirakon.AppManager.settings.Ops.MODE_NO_ROOT; + import android.content.res.ColorStateList; import android.os.Build; import android.os.Bundle; @@ -21,6 +23,7 @@ import com.google.android.material.button.MaterialButton; import com.google.android.material.color.MaterialColors; +import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textview.MaterialTextView; @@ -29,6 +32,7 @@ import java.util.List; import io.github.muntashirakon.AppManager.R; +import io.github.muntashirakon.AppManager.compat.DevicePolicyManagerCompat; import io.github.muntashirakon.AppManager.ipc.LocalServices; import io.github.muntashirakon.AppManager.servermanager.LocalServer; import io.github.muntashirakon.AppManager.servermanager.ServerConfig; @@ -46,12 +50,14 @@ public class ModeOfOpsPreference extends Fragment { Ops.MODE_ROOT, Ops.MODE_ADB_OVER_TCP, Ops.MODE_ADB_WIFI, - Ops.MODE_NO_ROOT); + Ops.MODE_NO_ROOT, + Ops.MODE_DPC); private MaterialTextView mInferredModeView; private MaterialTextView mRemoteServerStatusView; private MaterialTextView mRemoteServicesStatusView; private MaterialTextView mModeOfOpsView; + private TextInputEditText mDPCCommandView; private MainPreferencesViewModel mModel; private AlertDialog mModeOfOpsAlertDialog; private String[] mModes; @@ -108,6 +114,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat mRemoteServerStatusView = view.findViewById(R.id.remote_server_status); mRemoteServicesStatusView = view.findViewById(R.id.remote_services_status); mModeOfOpsView = view.findViewById(R.id.op_name); + mDPCCommandView = view.findViewById(R.id.dpc_command); MaterialButton changeModeView = view.findViewById(R.id.action_settings); List disabledItems; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || Utils.isTv(requireContext())) { @@ -148,6 +155,21 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat Utils.copyToClipboard(requireContext(), "command", command); } }); + TextInputLayout customCommand2Layout = TextInputLayoutCompat.fromTextInputEditText(mDPCCommandView); + customCommand2Layout.setEndIconOnClickListener(v -> { + if (!Ops.isDpc()) { + Utils.copyToClipboard(requireContext(), "command", DevicePolicyManagerCompat.DPC_COMMAND); + } else { // Clear and reset AM Ops Mode + DevicePolicyManagerCompat.clearDeviceOwnerApp(); + mCurrentMode = Ops.MODE_AUTO; + Ops.setMode(mCurrentMode); + mModeOfOpsAlertDialog.show(); + mConnecting = true; + updateViews(); + mModel.setModeOfOps(); + } + }); + mModel.loadCustomCommands(); updateViews(); // Mode of ops @@ -232,6 +254,11 @@ private void updateViews() { mModeOfOpsView.setCompoundDrawablesRelativeWithIntrinsicBounds(mIconInactive, 0, 0, 0); mModeOfOpsView.setText(getString(R.string.status_not_connected_via_mode, mModes[MODE_NAMES.indexOf(mCurrentMode)])); } + if (Ops.isDpc()) { + mDPCCommandView.setText(R.string.dpc_remove_msg); + } else { + mDPCCommandView.setText(DevicePolicyManagerCompat.DPC_COMMAND); + } } // Server if (serverRequired) { @@ -260,7 +287,7 @@ private static boolean requireRemoteServer(@NonNull String mode) { } private static boolean requireRemoteServices(@NonNull String mode) { - return !Ops.MODE_AUTO.equals(mode) && !Ops.MODE_NO_ROOT.equals(mode); + return !Ops.MODE_AUTO.equals(mode) && !MODE_NO_ROOT.equals(mode) && !Ops.MODE_DPC.equals(mode); } private static boolean badInferredMode(@NonNull String mode, int uid) { diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/settings/Ops.java b/app/src/main/java/io/github/muntashirakon/AppManager/settings/Ops.java index 86e5e48944a..f59aca098ad 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/settings/Ops.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/settings/Ops.java @@ -38,6 +38,7 @@ import io.github.muntashirakon.AppManager.adb.AdbConnectionManager; import io.github.muntashirakon.AppManager.adb.AdbPairingService; import io.github.muntashirakon.AppManager.adb.AdbUtils; +import io.github.muntashirakon.AppManager.compat.DevicePolicyManagerCompat; import io.github.muntashirakon.AppManager.compat.ManifestCompat; import io.github.muntashirakon.AppManager.ipc.LocalServices; import io.github.muntashirakon.AppManager.logcat.helper.ServiceHelper; @@ -66,7 +67,7 @@ public class Ops { public static final String TAG = Ops.class.getSimpleName(); - @StringDef({MODE_AUTO, MODE_ROOT, MODE_ADB_OVER_TCP, MODE_ADB_WIFI, MODE_NO_ROOT}) + @StringDef({MODE_AUTO, MODE_ROOT, MODE_ADB_OVER_TCP, MODE_ADB_WIFI, MODE_NO_ROOT, MODE_DPC}) @Retention(RetentionPolicy.SOURCE) public @interface Mode { } @@ -76,7 +77,7 @@ public class Ops { public static final String MODE_ADB_OVER_TCP = "adb_tcp"; public static final String MODE_ADB_WIFI = "adb_wifi"; public static final String MODE_NO_ROOT = "no-root"; - + public static final String MODE_DPC = "dpc"; @IntDef({ STATUS_SUCCESS, STATUS_FAILURE, @@ -85,6 +86,7 @@ public class Ops { STATUS_ADB_PAIRING_REQUIRED, STATUS_ADB_CONNECT_REQUIRED, STATUS_FAILURE_ADB_NEED_MORE_PERMS, + STATUS_FAILURE_DPC_NEED_SETUP }) @Retention(RetentionPolicy.SOURCE) public @interface Status { @@ -97,6 +99,7 @@ public class Ops { public static final int STATUS_ADB_PAIRING_REQUIRED = 4; public static final int STATUS_ADB_CONNECT_REQUIRED = 5; public static final int STATUS_FAILURE_ADB_NEED_MORE_PERMS = 6; + public static final int STATUS_FAILURE_DPC_NEED_SETUP = 7; public static int ROOT_UID = 0; public static int SHELL_UID = 2000; @@ -108,6 +111,7 @@ public class Ops { private static boolean sIsAdb = false; // UID = 2000 private static boolean sIsSystem = false; // UID = 1000 private static boolean sIsRoot = false; // UID = 0 + private static boolean sIsDeviceOwner = false; // Security private static final Object sSecurityLock = new Object(); @@ -166,6 +170,9 @@ public static boolean isAdb() { return sIsAdb; } + @AnyThread + public static boolean isDpc() {return sIsDeviceOwner;} + /** * Whether the current App Manager session is authenticated by the user. It does two things: *
    @@ -213,6 +220,10 @@ public static CharSequence getInferredMode(@NonNull Context context) { + (uidStr.length() > 1 ? uidStr.substring(1) : ""); } } + if (isDpc()) { + return context.getString(R.string.device_policy_controller); + } + return context.getString(R.string.no_root); } @@ -246,6 +257,11 @@ public static int init(@NonNull Context context, boolean force) { autoDetectRootSystemOrAdbAndPersist(context); return sIsAdb ? STATUS_SUCCESS : initPermissionsWithSuccess(); } + if (MODE_DPC.equals(mode)) { + sIsDeviceOwner = DevicePolicyManagerCompat.isOwnerApp(); + sDirectRoot = sIsSystem = sIsRoot = sIsAdb = false; + return sIsDeviceOwner ? STATUS_SUCCESS: STATUS_FAILURE; + } if (MODE_NO_ROOT.equals(mode)) { sDirectRoot = false; sIsAdb = sIsSystem = sIsRoot = false; diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/utils/FreezeUtils.java b/app/src/main/java/io/github/muntashirakon/AppManager/utils/FreezeUtils.java index 58d12f414df..d8f19258358 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/utils/FreezeUtils.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/utils/FreezeUtils.java @@ -19,11 +19,13 @@ import io.github.muntashirakon.AppManager.BuildConfig; import io.github.muntashirakon.AppManager.compat.ApplicationInfoCompat; +import io.github.muntashirakon.AppManager.compat.DevicePolicyManagerCompat; import io.github.muntashirakon.AppManager.compat.ManifestCompat; import io.github.muntashirakon.AppManager.compat.PackageManagerCompat; import io.github.muntashirakon.AppManager.db.AppsDb; import io.github.muntashirakon.AppManager.db.entity.FreezeType; import io.github.muntashirakon.AppManager.self.SelfPermissions; +import io.github.muntashirakon.AppManager.settings.Ops; import io.github.muntashirakon.AppManager.settings.Prefs; public final class FreezeUtils { @@ -87,6 +89,11 @@ public static void freeze(@NonNull String packageName, @UserIdInt int userId, @F if (SelfPermissions.checkSelfOrRemotePermission(ManifestCompat.permission.MANAGE_USERS)) { PackageManagerCompat.hidePackage(packageName, userId, true); return; + } else if (Ops.isDpc()) { + if (DevicePolicyManagerCompat.setApplicationHidden(packageName, true)) { + throw new RemoteException("Failed to hide application with DPC"); + } + return; } // No permission, fall-through } else if ((freezeType == FREEZE_SUSPEND || freezeType == FREEZE_ADV_SUSPEND) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { @@ -100,6 +107,11 @@ public static void freeze(@NonNull String packageName, @UserIdInt int userId, @F if (SelfPermissions.checkSelfOrRemotePermission(ManifestCompat.permission.SUSPEND_APPS)) { PackageManagerCompat.suspendPackages(new String[]{packageName}, userId, true); return; + } else if (Ops.isDpc()) { + if (!DevicePolicyManagerCompat.setPackageSuspended(packageName, true)) { + throw new RemoteException("Failed to suspend Application"); + } + return; } // No permission, fall-through } else { @@ -115,6 +127,12 @@ public static void freeze(@NonNull String packageName, @UserIdInt int userId, @F public static void unfreeze(@NonNull String packageName, @UserIdInt int userId) throws RemoteException { // Ignore checking preference, unfreeze for all types + if (Ops.isDpc()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + DevicePolicyManagerCompat.setPackageSuspended(packageName, false); + } + DevicePolicyManagerCompat.setApplicationHidden(packageName, false); + } if (PackageManagerCompat.isPackageHidden(packageName, userId)) { PackageManagerCompat.hidePackage(packageName, userId, false); } diff --git a/app/src/main/res/layout/fragment_mode_of_ops.xml b/app/src/main/res/layout/fragment_mode_of_ops.xml index 3c65152206e..00206ffaa5f 100644 --- a/app/src/main/res/layout/fragment_mode_of_ops.xml +++ b/app/src/main/res/layout/fragment_mode_of_ops.xml @@ -147,6 +147,32 @@ + + + + + + + + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 4b6287b254a..fd5646ce894 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -36,6 +36,7 @@ @string/adb_over_tcp @string/wireless_debugging @string/no_root + @string/device_policy_controller @string/type_boolean diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 83b4d6306e3..37dc8016d71 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1558,4 +1558,12 @@ Selected %d out of %d items ADB local server port Waiting for Wi-Fi + Device Policy Controller + Use the following command to setup AppManager as DPC + Click Here To Remove App Manager as DPC + AM is no longer DPC + AM is now DPC + App Manager DPC Service + App Manager has control of this Resource + App Manager Dpc Long Support Message diff --git a/app/src/main/res/xml/device_admin.xml b/app/src/main/res/xml/device_admin.xml new file mode 100644 index 00000000000..151bd8df490 --- /dev/null +++ b/app/src/main/res/xml/device_admin.xml @@ -0,0 +1,6 @@ + + + + + +