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 @@
+
+
+
+
+
+