From 6ee8e9233c57f4643eb0ef4cd68b953523644c9f Mon Sep 17 00:00:00 2001 From: felipeal Date: Tue, 20 Oct 2020 18:47:29 -0700 Subject: [PATCH 01/75] Implement DeviceAdminService.dump() so it can be used to send commands. These commands are particularly useful when testing device owner on automotive, where TestDPC would be running on user 0 but the SystemUI on user 10. Test: adb shell dumpsys activity service com.afwsamples.testdpc help Test: adb shell dumpsys activity service com.afwsamples.testdpc create-user --flags 1 MyUser Bug: 171350084 Change-Id: I10228f295771300cf60eb2a81efe283049dfb0e8 --- .../testdpc/DeviceAdminService.java | 23 +++ .../com/afwsamples/testdpc/ShellCommand.java | 137 ++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 app/src/main/java/com/afwsamples/testdpc/ShellCommand.java diff --git a/app/src/main/java/com/afwsamples/testdpc/DeviceAdminService.java b/app/src/main/java/com/afwsamples/testdpc/DeviceAdminService.java index e7b852e9..2cb0eba7 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DeviceAdminService.java +++ b/app/src/main/java/com/afwsamples/testdpc/DeviceAdminService.java @@ -1,5 +1,23 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.afwsamples.testdpc; +import java.io.FileDescriptor; +import java.io.PrintWriter; + import android.content.BroadcastReceiver; import android.content.Intent; import android.content.IntentFilter; @@ -43,4 +61,9 @@ private void unregisterPackageChangesReceiver() { mPackageChangedReceiver = null; } } + + @Override + protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + new ShellCommand(getApplicationContext(), writer, args).run(); + } } diff --git a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java new file mode 100644 index 00000000..e48cb587 --- /dev/null +++ b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.afwsamples.testdpc; + +import java.io.PrintWriter; +import java.util.Arrays; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; +import android.os.UserHandle; +import android.util.Log; + +/** + * Provides a CLI (command-line interface) to TestDPC through {@code dumpsys}. + * + *

Usage: <@code adb shell dumpsys activity service com.afwsamples.testdpc}. + * + */ +final class ShellCommand { + // TODO(b/171350084): add unit tests + + private static final String TAG = "TestDPCShellCommand"; + + private static final String CMD_CREATE_USER = "create-user"; + private static final String CMD_HELP = "help"; + private static final String ARG_FLAGS = "--flags"; + + private final PrintWriter mWriter; + private final String[] mArgs; + private final DevicePolicyManager mDevicePolicyManager; + private final ComponentName mAdminComponentName; + + public ShellCommand(@NonNull Context context, @NonNull PrintWriter writer, + @Nullable String[] args) { + mWriter = writer; + mArgs = args; + mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class); + mAdminComponentName = DeviceAdminReceiver.getComponentName(context); + Log.d(TAG, "context: " + context + ", admin: " + mAdminComponentName + + ", args: " + Arrays.toString(args)); + } + + public void run() { + if (mArgs == null || mArgs.length == 0) { + showUsage(); + return; + } + String cmd = mArgs[0]; + switch (cmd) { + case CMD_HELP: + showUsage(); + break; + case CMD_CREATE_USER: + execute(() -> createUser()); + break; + default: + mWriter.printf("Invalid command: %s\n\n", cmd); + showUsage(); + } + } + + private void showUsage() { + mWriter.printf("Usage:\n\n"); + mWriter.printf("\t%s - show this help\n", CMD_HELP); + mWriter.printf("\t%s [%s FLAGS] [NAME] - create a user with the optional flags and name\n", + CMD_CREATE_USER, ARG_FLAGS); + } + + private void createUser() { + // TODO(b/171350084): once more commands are added, add a generic argument parsing + // mechanism like getRequiredArg(), getOptionalArg, etc... + int nextArgIndex = 1; + String nextArg = null; + + String name = null; + int flags = 0; + + if (mArgs.length > nextArgIndex) { + nextArg = mArgs[nextArgIndex++]; + if (ARG_FLAGS.equals(nextArg)) { + flags = Integer.parseInt(mArgs[nextArgIndex++]); + if (mArgs.length > nextArgIndex) { + name = mArgs[nextArgIndex++]; + } + } else { + name = nextArg; + } + } + Log.d(TAG, "createUser(): name=" + name + ", flags=" + flags); + + try { + UserHandle userHandle = mDevicePolicyManager.createAndManageUser(mAdminComponentName, + name, mAdminComponentName, /* adminExtras= */ null, flags); + onSuccess("User created: %s", userHandle); + } catch (Exception e) { + onError(e, "Error creating user %s", name); + } + } + + private void execute(@NonNull Runnable r) { + try { + r.run(); + } catch (Exception e) { + // Must explicitly catch and show generic exceptions (like NumberFormatException parsing + // args), otherwise they'dbe logcat'ed on AndroidRuntime and not surfaced to caller + onError(e, "error executing %s", Arrays.toString(mArgs)); + } + } + + private void onSuccess(@NonNull String pattern, @Nullable Object...args) { + String msg = String.format(pattern, args); + Log.d(TAG, msg); + mWriter.println(msg); + } + + private void onError(@NonNull Exception e, @NonNull String pattern, @Nullable Object...args) { + String msg = String.format(pattern, args); + Log.e(TAG, msg, e); + mWriter.printf("%s: %s\n", msg, e); + } +} From 7291e2d91fa8fdead3bd33bad736ec6dda136fdc Mon Sep 17 00:00:00 2001 From: felipeal Date: Wed, 21 Oct 2020 15:46:52 -0700 Subject: [PATCH 02/75] Added lock-now to ShellCommand. Test: adb shell dumpsys activity service com.afwsamples.testdpc lock-now Bug: 171350084 Change-Id: If5aae760b6f882ca08b46a970b01dfc45fab455a --- .../com/afwsamples/testdpc/ShellCommand.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java index e48cb587..22fd843f 100644 --- a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java +++ b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java @@ -39,6 +39,7 @@ final class ShellCommand { private static final String CMD_CREATE_USER = "create-user"; private static final String CMD_HELP = "help"; + private static final String CMD_LOCK_NOW = "lock-now"; private static final String ARG_FLAGS = "--flags"; private final PrintWriter mWriter; @@ -69,6 +70,9 @@ public void run() { case CMD_CREATE_USER: execute(() -> createUser()); break; + case CMD_LOCK_NOW: + execute(() -> lockNow()); + break; default: mWriter.printf("Invalid command: %s\n\n", cmd); showUsage(); @@ -80,6 +84,7 @@ private void showUsage() { mWriter.printf("\t%s - show this help\n", CMD_HELP); mWriter.printf("\t%s [%s FLAGS] [NAME] - create a user with the optional flags and name\n", CMD_CREATE_USER, ARG_FLAGS); + mWriter.printf("\t%s - locks the device (now! :-)\n", CMD_LOCK_NOW); } private void createUser() { @@ -113,6 +118,17 @@ private void createUser() { } } + private void lockNow() { + // TODO(b/171350084): add flags + Log.d(TAG, "lockNow()"); + try { + mDevicePolicyManager.lockNow(); + onSuccess("Device locked"); + } catch (Exception e) { + onError(e, "Error locking device"); + } + } + private void execute(@NonNull Runnable r) { try { r.run(); From ff0b5cc4db4d17965e377d3fbdc751b6773fbc86 Mon Sep 17 00:00:00 2001 From: felipeal Date: Thu, 22 Oct 2020 15:25:49 -0700 Subject: [PATCH 03/75] Refactored common calls to DPM into DevicePolicyManagerGateway. This interface (and its default implementation) will be used to call DevicePolicyManager and show the result, so it can be reused by different clients (like the UI and ShellCommand). Existing code will be refactored to use it as more options are added to ShellCommand. Test: adb shell dumpsys activity service com.afwsamples.testdpc lock-now Test: manual verification using lock now in the TestDPC app Bug: 171350084 Change-Id: Ibb03b399acdf3fdb904c0f500bf349c02691bb17 --- .../testdpc/DevicePolicyManagerGateway.java | 65 +++++++++++++++ .../DevicePolicyManagerGatewayImpl.java | 83 +++++++++++++++++++ .../com/afwsamples/testdpc/ShellCommand.java | 42 ++++------ .../policy/PolicyManagementFragment.java | 47 +++++++---- 4 files changed, 195 insertions(+), 42 deletions(-) create mode 100644 app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java create mode 100644 app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java new file mode 100644 index 00000000..3807a854 --- /dev/null +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.afwsamples.testdpc; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.function.Consumer; + +import android.os.UserHandle; + +/** + * Interface used to abstract calls to {@link android.app.admin.DevicePolicyManager}. + * + *

Each of its methods takes 2 callbacks: one called when the underlying call succeeds, the other + * one called when it throws an exception. + */ +public interface DevicePolicyManagerGateway { + + /** + * See {@link android.app.admin.DevicePolicyManager#createAndManageUser(android.content.ComponentName, String, android.content.ComponentName, android.os.PersistableBundle, int)}. + */ + void createAndManageUser(@Nullable String name, int flags, + @NonNull Consumer onSuccess, @NonNull Consumer onError); + + /** + * See {@link android.app.admin.DevicePolicyManager#lockNow()}. + */ + void lockNow(@NonNull Consumer onSuccess, @NonNull Consumer onError); + + /** + * Used on error callbacks to indicate that the operation returned {@code null}. + */ + public static final class NullResultException extends Exception { + + private final String mMethod; + + /** + * Default constructor. + * + * @param method method name (without parenthesis). + */ + public NullResultException(@NonNull String method) { + mMethod = method; + } + + @Override + public String toString() { + return "DPM." + mMethod + "() returned null"; + } + } +} diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java new file mode 100644 index 00000000..f4bc462e --- /dev/null +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.afwsamples.testdpc; + +import androidx.annotation.NonNull; + +import java.util.Arrays; +import java.util.function.Consumer; + +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; +import android.os.UserHandle; +import android.util.Log; + +public final class DevicePolicyManagerGatewayImpl implements DevicePolicyManagerGateway { + + private static final String TAG = DevicePolicyManagerGatewayImpl.class.getSimpleName(); + + private final DevicePolicyManager mDevicePolicyManager; + private final ComponentName mAdminComponentName; + + public DevicePolicyManagerGatewayImpl(@NonNull Context context) { + this(context.getSystemService(DevicePolicyManager.class), + DeviceAdminReceiver.getComponentName(context)); + } + + public DevicePolicyManagerGatewayImpl(@NonNull DevicePolicyManager dpm, + @NonNull ComponentName admin) { + mDevicePolicyManager = dpm; + mAdminComponentName = admin; + + Log.d(TAG, "constructor: admin=" + mAdminComponentName + ", dpm=" + dpm); + } + + @Override + public void createAndManageUser(String name, int flags, Consumer onSuccess, + Consumer onError) { + Log.d(TAG, "createAndManageUser(" + name + ", " + flags); + + try { + UserHandle userHandle = mDevicePolicyManager.createAndManageUser(mAdminComponentName, + name, mAdminComponentName, /* adminExtras= */ null, flags); + if (userHandle != null) { + onSuccess.accept(userHandle); + } else { + onError.accept(new NullResultException("createAndManageUser")); + } + } catch (Exception e) { + onError.accept(e); + } + } + + @Override + public void lockNow(Consumer onSuccess, Consumer onError) { + Log.d(TAG, "lockNow()"); + + try { + mDevicePolicyManager.lockNow(); + onSuccess.accept(null); + } catch (Exception e) { + onError.accept(e); + } + } + + @Override + public String toString() { + return "DevicePolicyManagerGatewayImpl[" + mAdminComponentName + "]"; + } +} diff --git a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java index 22fd843f..94ab6814 100644 --- a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java +++ b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java @@ -20,8 +20,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import android.app.admin.DevicePolicyManager; -import android.content.ComponentName; import android.content.Context; import android.os.UserHandle; import android.util.Log; @@ -44,17 +42,15 @@ final class ShellCommand { private final PrintWriter mWriter; private final String[] mArgs; - private final DevicePolicyManager mDevicePolicyManager; - private final ComponentName mAdminComponentName; + private final DevicePolicyManagerGateway mDevicePolicyManagerGateway; public ShellCommand(@NonNull Context context, @NonNull PrintWriter writer, @Nullable String[] args) { mWriter = writer; mArgs = args; - mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class); - mAdminComponentName = DeviceAdminReceiver.getComponentName(context); - Log.d(TAG, "context: " + context + ", admin: " + mAdminComponentName - + ", args: " + Arrays.toString(args)); + mDevicePolicyManagerGateway = new DevicePolicyManagerGatewayImpl(context); + + Log.d(TAG, "args=" + Arrays.toString(args)); } public void run() { @@ -93,7 +89,7 @@ private void createUser() { int nextArgIndex = 1; String nextArg = null; - String name = null; + final String name; int flags = 0; if (mArgs.length > nextArgIndex) { @@ -103,30 +99,28 @@ private void createUser() { if (mArgs.length > nextArgIndex) { name = mArgs[nextArgIndex++]; } + else { + name = null; + } } else { name = nextArg; } + } else { + name = null; } - Log.d(TAG, "createUser(): name=" + name + ", flags=" + flags); + Log.i(TAG, "createUser(): name=" + name + ", flags=" + flags); - try { - UserHandle userHandle = mDevicePolicyManager.createAndManageUser(mAdminComponentName, - name, mAdminComponentName, /* adminExtras= */ null, flags); - onSuccess("User created: %s", userHandle); - } catch (Exception e) { - onError(e, "Error creating user %s", name); - } + mDevicePolicyManagerGateway.createAndManageUser(name, flags, + (u) -> onSuccess("User created: %s", u), + (e) -> onError(e, "Error creating user %s", name)); } private void lockNow() { // TODO(b/171350084): add flags - Log.d(TAG, "lockNow()"); - try { - mDevicePolicyManager.lockNow(); - onSuccess("Device locked"); - } catch (Exception e) { - onError(e, "Error locking device"); - } + Log.i(TAG, "lockNow()"); + mDevicePolicyManagerGateway.lockNow( + (v) -> onSuccess("Device locked"), + (e) -> onError(e, "Error locking device")); } private void execute(@NonNull Runnable r) { diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java index b4fce5ff..29e77216 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java @@ -96,6 +96,8 @@ import com.afwsamples.testdpc.CrossProfileAppsFragment; import com.afwsamples.testdpc.CrossProfileAppsWhitelistFragment; import com.afwsamples.testdpc.DeviceAdminReceiver; +import com.afwsamples.testdpc.DevicePolicyManagerGateway; +import com.afwsamples.testdpc.DevicePolicyManagerGatewayImpl; import com.afwsamples.testdpc.R; import com.afwsamples.testdpc.SetupManagementActivity; import com.afwsamples.testdpc.common.AccountArrayAdapter; @@ -438,6 +440,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag } private DevicePolicyManager mDevicePolicyManager; + private DevicePolicyManagerGateway mDevicePolicyManagerGateway; private PackageManager mPackageManager; private String mPackageName; private ComponentName mAdminComponentName; @@ -512,6 +515,8 @@ public void onCreate(Bundle savedInstanceState) { } mDevicePolicyManager = (DevicePolicyManager) getActivity().getSystemService( Context.DEVICE_POLICY_SERVICE); + mDevicePolicyManagerGateway = new DevicePolicyManagerGatewayImpl(mDevicePolicyManager, + mAdminComponentName); mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE); mTelephonyManager = (TelephonyManager) getActivity() .getSystemService(Context.TELEPHONY_SERVICE); @@ -1345,13 +1350,15 @@ private void showPendingSystemUpdate() { private void lockNow() { if (Util.SDK_INT >= VERSION_CODES.O && Util.isManagedProfileOwner(getActivity())) { showLockNowPrompt(); - } else if (Util.SDK_INT >= VERSION_CODES.N + return; + } + DevicePolicyManagerGateway gateway = mDevicePolicyManagerGateway; + if (Util.SDK_INT >= VERSION_CODES.N && Util.isManagedProfileOwner(getActivity())) { // Always call lock now on the parent for managed profile on N - mDevicePolicyManager.getParentProfileInstance(mAdminComponentName).lockNow(); - } else { - mDevicePolicyManager.lockNow(); + gateway = getParentProfileDevicePolicyManagerGateway(); } + gateway.lockNow((v) -> onSuccessLog("lockNow"), (e) -> onErrorLog("lockNow", e)); } /** @@ -2087,20 +2094,10 @@ public void onClick(DialogInterface dialogInterface, int i) { flags |= DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED; } - UserHandle userHandle = mDevicePolicyManager.createAndManageUser( - mAdminComponentName, - name, - mAdminComponentName, - null, - flags); - - if (userHandle != null) { - long serialNumber = - mUserManager.getSerialNumberForUser(userHandle); - showToast(R.string.user_created, serialNumber); - return; - } - showToast(R.string.failed_to_create_user); + mDevicePolicyManagerGateway.createAndManageUser(name, flags, + (u) -> showToast(R.string.user_created, + mUserManager.getSerialNumberForUser(u)), + (e) -> showToast(R.string.failed_to_create_user)); } } }) @@ -4021,4 +4018,18 @@ private void setAutoTimeEnabled(boolean enabled) { private void setAutoTimeZoneEnabled(boolean enabled) { mDevicePolicyManager.setAutoTimeZoneEnabled(mAdminComponentName, enabled); } + + private DevicePolicyManagerGateway getParentProfileDevicePolicyManagerGateway() { + return new DevicePolicyManagerGatewayImpl( + mDevicePolicyManager.getParentProfileInstance(mAdminComponentName), + mAdminComponentName); + } + + private void onSuccessLog(String method) { + Log.d(TAG, method + "() succeeded"); + } + + private void onErrorLog(String method, Exception e) { + Log.d(TAG, method + "() failed: ", e); + } } From 05137f0ef570bb5e8e840b5b7e42c5ce1bb8c02d Mon Sep 17 00:00:00 2001 From: Eran Messeri Date: Thu, 29 Oct 2020 12:37:16 +0000 Subject: [PATCH 04/75] Switching to R constant. Change a few @TargetApi checks to use the VERSION_CODES constant rather than the locally-defined one for the R API level. Test: That it compiles and runs. Change-Id: I0bb7b01c5b282900e996d94a9cb4c1c3805a9559 --- .../FactoryResetProtectionPolicyFragment.java | 5 ++-- .../policy/PolicyManagementFragment.java | 26 +++++++++---------- ...UserRestrictionsParentDisplayFragment.java | 15 ++++++----- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/FactoryResetProtectionPolicyFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/FactoryResetProtectionPolicyFragment.java index 0a330acb..6f149523 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/FactoryResetProtectionPolicyFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/FactoryResetProtectionPolicyFragment.java @@ -22,6 +22,7 @@ import android.app.admin.FactoryResetProtectionPolicy; import android.content.ComponentName; import android.content.Context; +import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.text.TextUtils; import android.view.LayoutInflater; @@ -61,7 +62,7 @@ public class FactoryResetProtectionPolicyFragment extends Fragment private FrpAccountsAdapter mAccountsAdapter; private Spinner mFrpEnabledSpinner; - @RequiresApi(api = Util.R_VERSION_CODE) + @RequiresApi(api = VERSION_CODES.R) @Override public void onCreate(Bundle savedInstanceState) { mDevicePolicyManager = (DevicePolicyManager) @@ -71,7 +72,7 @@ public void onCreate(Bundle savedInstanceState) { getActivity().getActionBar().setTitle(R.string.factory_reset_protection_policy); } - @RequiresApi(api = Util.R_VERSION_CODE) + @RequiresApi(api = VERSION_CODES.R) @Override public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bundle savedInstanceState) { diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java index 29e77216..0cb06909 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java @@ -802,7 +802,7 @@ private void maybeUpdateProfileMaxTimeOff() { } } - @TargetApi(Util.R_VERSION_CODE) + @TargetApi(VERSION_CODES.R) private void reloadPersonalAppsSuspendedUi() { if (mSuspendPersonalApps.isEnabled()) { int suspendReasons = @@ -1537,7 +1537,7 @@ private void setCameraDisabled(boolean disabled) { mDevicePolicyManager.setCameraDisabled(mAdminComponentName, disabled); } - @TargetApi(Util.R_VERSION_CODE) + @TargetApi(VERSION_CODES.R) private void setCameraDisabledOnParent(boolean disabled) { DevicePolicyManager parentDpm = mDevicePolicyManager .getParentProfileInstance(mAdminComponentName); @@ -1576,7 +1576,7 @@ private void setScreenCaptureDisabled(boolean disabled) { mDevicePolicyManager.setScreenCaptureDisabled(mAdminComponentName, disabled); } - @TargetApi(Util.R_VERSION_CODE) + @TargetApi(VERSION_CODES.R) private void setScreenCaptureDisabledOnParent(boolean disabled) { DevicePolicyManager parentDpm = mDevicePolicyManager .getParentProfileInstance(mAdminComponentName); @@ -2358,7 +2358,7 @@ private void loadPasswordComplexity() { String summary; int complexity = PASSWORD_COMPLEXITY.get(mDevicePolicyManager.getPasswordComplexity()); - if (Util.isManagedProfileOwner(getActivity()) && Util.SDK_INT >= Util.R_VERSION_CODE) { + if (Util.isManagedProfileOwner(getActivity()) && Util.SDK_INT >= VERSION_CODES.R) { DevicePolicyManager parentDpm = mDevicePolicyManager.getParentProfileInstance(mAdminComponentName); int parentComplexity = PASSWORD_COMPLEXITY.get(parentDpm.getPasswordComplexity()); @@ -2417,13 +2417,13 @@ private void reloadLocationModeUi() { .setChecked(parseInt(locationMode, 0) != Secure.LOCATION_MODE_OFF); } - @TargetApi(Util.R_VERSION_CODE) + @TargetApi(VERSION_CODES.R) private void reloadLocationEnabledUi() { LocationManager locationManager = getActivity().getSystemService(LocationManager.class); mSetLocationEnabledPreference.setChecked(locationManager.isLocationEnabled()); } - @TargetApi(Util.R_VERSION_CODE) + @TargetApi(VERSION_CODES.R) private void reloadLockdownAdminConfiguredNetworksUi() { boolean lockdown = mDevicePolicyManager.hasLockdownAdminConfiguredNetworks( mAdminComponentName); @@ -2468,7 +2468,7 @@ private void reloadCameraDisableUi() { mDisableCameraSwitchPreference.setChecked(isCameraDisabled); } - @TargetApi(Util.R_VERSION_CODE) + @TargetApi(VERSION_CODES.R) private void reloadCameraDisableOnParentUi() { DevicePolicyManager parentDpm = mDevicePolicyManager.getParentProfileInstance(mAdminComponentName); @@ -2519,7 +2519,7 @@ private void reloadScreenCaptureDisableUi() { mDisableScreenCaptureSwitchPreference.setChecked(isScreenCaptureDisabled); } - @TargetApi(Util.R_VERSION_CODE) + @TargetApi(VERSION_CODES.R) private void reloadScreenCaptureDisableOnParentUi() { DevicePolicyManager parentDpm = mDevicePolicyManager.getParentProfileInstance(mAdminComponentName); @@ -2533,7 +2533,7 @@ private void reloadSetAutoTimeRequiredUi() { mSetAutoTimeRequiredPreference.setChecked(isAutoTimeRequired); } - @TargetApi(Util.R_VERSION_CODE) + @TargetApi(VERSION_CODES.R) private void reloadSetAutoTimeUi() { if (Util.SDK_INT < VERSION_CODES.R) { return; @@ -2549,7 +2549,7 @@ private void reloadSetAutoTimeUi() { } } - @TargetApi(Util.R_VERSION_CODE) + @TargetApi(VERSION_CODES.R) private void reloadSetAutoTimeZoneUi() { if (Util.SDK_INT < VERSION_CODES.R) { return; @@ -3199,7 +3199,7 @@ public void onClick(DialogInterface dialog, int position) { } } - @RequiresApi(api = Util.R_VERSION_CODE) + @RequiresApi(api = VERSION_CODES.R) private void showHideAppsOnParentPrompt(final boolean showHiddenApps) { final int dialogTitleResId; final int successResId; @@ -4009,12 +4009,12 @@ abstract static class ManageLockTaskListCallback { public abstract void onPositiveButtonClicked(String[] lockTaskArray); } - @RequiresApi(Util.R_VERSION_CODE) + @RequiresApi(VERSION_CODES.R) private void setAutoTimeEnabled(boolean enabled) { mDevicePolicyManager.setAutoTimeEnabled(mAdminComponentName, enabled); } - @RequiresApi(Util.R_VERSION_CODE) + @RequiresApi(VERSION_CODES.R) private void setAutoTimeZoneEnabled(boolean enabled) { mDevicePolicyManager.setAutoTimeZoneEnabled(mAdminComponentName, enabled); } diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsParentDisplayFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsParentDisplayFragment.java index df4055f3..0270a3d5 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsParentDisplayFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsParentDisplayFragment.java @@ -3,6 +3,7 @@ import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; +import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.util.Log; import android.widget.Toast; @@ -25,7 +26,7 @@ public class UserRestrictionsParentDisplayFragment extends BaseSearchablePolicyP private DevicePolicyManager mParentDevicePolicyManager; private ComponentName mAdminComponentName; - @RequiresApi(api = Util.R_VERSION_CODE) + @RequiresApi(api = VERSION_CODES.R) @Override public void onCreate(Bundle savedInstanceState) { DevicePolicyManager mDevicePolicyManager = (DevicePolicyManager) @@ -37,7 +38,7 @@ public void onCreate(Bundle savedInstanceState) { getActivity().getActionBar().setTitle(R.string.user_restrictions_management_title); } - @RequiresApi(api = Util.R_VERSION_CODE) + @RequiresApi(api = VERSION_CODES.R) @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen( @@ -57,7 +58,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { constrainPreferences(); } - @RequiresApi(api = Util.R_VERSION_CODE) + @RequiresApi(api = VERSION_CODES.R) @Override public void onResume() { super.onResume(); @@ -69,7 +70,7 @@ public boolean isAvailable(Context context) { return true; } - @RequiresApi(api = Util.R_VERSION_CODE) + @RequiresApi(api = VERSION_CODES.R) @Override public boolean onPreferenceChange(Preference preference, Object newValue) { String restriction = preference.getKey(); @@ -89,14 +90,14 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { } } - @RequiresApi(api = Util.R_VERSION_CODE) + @RequiresApi(api = VERSION_CODES.R) private void updateAllUserRestrictions() { for (UserRestriction restriction : UserRestriction.PROFILE_OWNER_ORG_DEVICE_RESTRICTIONS) { updateUserRestriction(restriction.key); } } - @RequiresApi(api = Util.R_VERSION_CODE) + @RequiresApi(api = VERSION_CODES.R) private void updateUserRestriction(String userRestriction) { DpcSwitchPreference preference = (DpcSwitchPreference) findPreference(userRestriction); Bundle restrictions = mParentDevicePolicyManager.getUserRestrictions(mAdminComponentName); @@ -106,7 +107,7 @@ private void updateUserRestriction(String userRestriction) { private void constrainPreferences() { for (String restriction : UserRestriction.PROFILE_OWNER_ORG_OWNED_RESTRICTIONS) { DpcPreferenceBase pref = (DpcPreferenceBase) findPreference(restriction); - pref.setMinSdkVersion(Util.R_VERSION_CODE); + pref.setMinSdkVersion(VERSION_CODES.R); } } } From 25d73ded53708d325d7a5e475852cb0ef6030911 Mon Sep 17 00:00:00 2001 From: Eran Messeri Date: Fri, 30 Oct 2020 11:47:04 +0000 Subject: [PATCH 05/75] TestDPC: Support setRequiredPasswordComplexity And getRequiredPasswordComplexity. Implementation follows the common approach: * New preference to display the required password complexity, using reflection to read it from the DPM. * New dialog to select the required complexity and set it on the DPM, using reflection. * New constants for S. Left to do: Add a way to set required complexity on the parent DPM instance. Test: Manual, installed TestDPC in DO mode and verified that the complexity can be set (and correctly read). Bug: 165573442 Change-Id: Iedc4697ed50c8464568fb492f6a649e535e4d152 --- .../com/afwsamples/testdpc/common/Util.java | 9 +- .../policy/PolicyManagementFragment.java | 83 +++++++++++++++++-- app/src/main/res/values/attrs.xml | 1 + app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/device_policy_header.xml | 11 +++ 5 files changed, 95 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/afwsamples/testdpc/common/Util.java b/app/src/main/java/com/afwsamples/testdpc/common/Util.java index 79997bbd..ec957bec 100644 --- a/app/src/main/java/com/afwsamples/testdpc/common/Util.java +++ b/app/src/main/java/com/afwsamples/testdpc/common/Util.java @@ -53,11 +53,8 @@ public class Util { private static final String TAG = "Util"; private static final int DEFAULT_BUFFER_SIZE = 4096; - // TODO: Update to S when VERSION_CODES.R becomes available. - public static final int R_VERSION_CODE = 30; - - private static final boolean IS_RUNNING_R = - VERSION.CODENAME.length() == 1 && VERSION.CODENAME.charAt(0) == 'R'; + private static final boolean IS_RUNNING_S = + VERSION.CODENAME.length() == 1 && VERSION.CODENAME.charAt(0) == 'S'; /** * A replacement for {@link VERSION.SDK_INT} that is compatible with pre-release SDKs @@ -66,7 +63,7 @@ public class Util { * int is not yet assigned. **/ public static final int SDK_INT = - IS_RUNNING_R ? VERSION_CODES.CUR_DEVELOPMENT : VERSION.SDK_INT; + IS_RUNNING_S ? VERSION_CODES.CUR_DEVELOPMENT : VERSION.SDK_INT; /** * Format a friendly datetime for the current locale according to device policy documentation. diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java index 0cb06909..a104b45c 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java @@ -106,6 +106,8 @@ import com.afwsamples.testdpc.common.CertificateUtil; import com.afwsamples.testdpc.common.MediaDisplayFragment; import com.afwsamples.testdpc.common.PackageInstallationUtils; +import com.afwsamples.testdpc.common.ReflectionUtil; +import com.afwsamples.testdpc.common.ReflectionUtil.ReflectionIsTemporaryException; import com.afwsamples.testdpc.common.UserArrayAdapter; import com.afwsamples.testdpc.common.Util; import com.afwsamples.testdpc.common.preference.CustomConstraint; @@ -276,6 +278,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag private static final String SECURITY_PATCH_KEY = "security_patch"; private static final String PASSWORD_COMPLIANT_KEY = "password_compliant"; private static final String PASSWORD_COMPLEXITY_KEY = "password_complexity"; + private static final String REQUIRED_PASSWORD_COMPLEXITY_KEY = "required_password_complexity"; private static final String SEPARATE_CHALLENGE_KEY = "separate_challenge"; private static final String DISABLE_CAMERA_KEY = "disable_camera"; private static final String DISABLE_CAMERA_ON_PARENT_KEY = "disable_camera_on_parent"; @@ -384,6 +387,8 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag private static final String SET_NEW_PASSWORD = "set_new_password"; private static final String SET_NEW_PASSWORD_WITH_COMPLEXITY = "set_new_password_with_complexity"; + private static final String SET_REQUIRED_PASSWORD_COMPLEXITY = + "set_required_password_complexity"; private static final String SET_PROFILE_PARENT_NEW_PASSWORD = "set_profile_parent_new_password"; private static final String BIND_DEVICE_ADMIN_POLICIES = "bind_device_admin_policies"; private static final String CROSS_PROFILE_APPS = "cross_profile_apps"; @@ -770,6 +775,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { maybeUpdateProfileMaxTimeOff(); onCreateSetNewPasswordWithComplexityPreference(); + onCreateSetRequiredPasswordComplexityPreference(); constrainSpecialCasePreferences(); maybeDisableLockTaskPreferences(); @@ -816,9 +822,7 @@ private void logAndShowToast(String message, Exception e) { showToast(message + ": " + e.getMessage()); } - private void onCreateSetNewPasswordWithComplexityPreference() { - ListPreference complexityPref = - (ListPreference) findPreference(SET_NEW_PASSWORD_WITH_COMPLEXITY); + private void addPasswordComplexityListToPreference(ListPreference pref) { List entries = new ArrayList<>(); List values = new ArrayList<>(); int size = PASSWORD_COMPLEXITY.size(); @@ -826,11 +830,24 @@ private void onCreateSetNewPasswordWithComplexityPreference() { entries.add(getString(PASSWORD_COMPLEXITY.valueAt(i))); values.add(Integer.toString(PASSWORD_COMPLEXITY.keyAt(i))); } - complexityPref.setEntries(entries.toArray(new CharSequence[size])); - complexityPref.setEntryValues(values.toArray(new CharSequence[size])); + pref.setEntries(entries.toArray(new CharSequence[size])); + pref.setEntryValues(values.toArray(new CharSequence[size])); + } + + private void onCreateSetNewPasswordWithComplexityPreference() { + ListPreference complexityPref = + (ListPreference) findPreference(SET_NEW_PASSWORD_WITH_COMPLEXITY); + addPasswordComplexityListToPreference(complexityPref); complexityPref.setOnPreferenceChangeListener(this); } + private void onCreateSetRequiredPasswordComplexityPreference() { + ListPreference requiredComplexityPref = + (ListPreference) findPreference(SET_REQUIRED_PASSWORD_COMPLEXITY); + addPasswordComplexityListToPreference(requiredComplexityPref); + requiredComplexityPref.setOnPreferenceChangeListener(this); + } + private void constrainSpecialCasePreferences() { // Reset password can be used in all contexts since N if (Util.SDK_INT >= VERSION_CODES.N) { @@ -879,6 +896,7 @@ public void onResume() { updateInstallNonMarketAppsPreference(); loadPasswordCompliant(); loadPasswordComplexity(); + loadRequiredPasswordComplexity(); loadSeparateChallenge(); reloadAffiliatedApis(); } @@ -1492,6 +1510,10 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { Integer.parseInt((String) newValue)); startActivity(intent); return true; + case SET_REQUIRED_PASSWORD_COMPLEXITY: + int requiredComplexity = Integer.parseInt((String) newValue); + setRequiredPasswordComplexity(requiredComplexity); + return true; case APP_FEEDBACK_NOTIFICATIONS: SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(getContext()).edit(); @@ -2370,6 +2392,57 @@ private void loadPasswordComplexity() { passwordComplexityPreference.setSummary(summary); } + @TargetApi(VERSION_CODES.S) + private int getRequiredComplexity(DevicePolicyManager dpm) { + try { + Integer complexity = + (Integer) ReflectionUtil.invoke(dpm, "getRequiredPasswordComplexity"); + return complexity; + } catch (ReflectionIsTemporaryException e) { + Log.e(TAG, "Error invoking getRequiredPasswordComplexity", e); + } + + return DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; + } + + private void loadRequiredPasswordComplexity() { + Preference requiredPasswordComplexityPreference = + findPreference(REQUIRED_PASSWORD_COMPLEXITY_KEY); + if (!requiredPasswordComplexityPreference.isEnabled()) { + return; + } + + String summary; + int complexity = PASSWORD_COMPLEXITY.get(getRequiredComplexity(mDevicePolicyManager)); + if (Util.isManagedProfileOwner(getActivity()) && Util.SDK_INT >= VERSION_CODES.S) { + DevicePolicyManager parentDpm + = mDevicePolicyManager.getParentProfileInstance(mAdminComponentName); + int parentComplexity = PASSWORD_COMPLEXITY.get(getRequiredComplexity(parentDpm)); + summary = String.format(getString(R.string.password_complexity_profile_summary), + getString(parentComplexity), getString(complexity)); + } else { + summary = getString(complexity); + } + + requiredPasswordComplexityPreference.setSummary(summary); + } + + // NOTE: The setRequiredPasswordComplexity call is gated by a check in device_policy_header.xml, + // where the minSdkVersion for it is specified. That prevents it from being callable on devices + // running older releases and obviates the need for a target sdk check here. + @TargetApi(VERSION_CODES.S) + private void setRequiredPasswordComplexity(int complexity) { + try { + ReflectionUtil.invoke(mDevicePolicyManager, "setRequiredPasswordComplexity", + new Class[]{Integer.TYPE}, complexity); + } catch (ReflectionIsTemporaryException e) { + Log.e(TAG, "Error invoking setRequiredPasswordComplexity", e); + } + loadPasswordCompliant(); + loadPasswordComplexity(); + loadRequiredPasswordComplexity(); + } + @TargetApi(VERSION_CODES.N) private void loadPasswordCompliant() { Preference passwordCompliantPreference = findPreference(PASSWORD_COMPLIANT_KEY); diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 58369bad..856d74df 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -30,6 +30,7 @@ + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f1b2c3f6..67d45943 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -973,6 +973,7 @@ Medium High Personal: %1$s, Work: %2$s + Required password complexity Separate challenge Separate challenge enabled: %s @@ -1053,6 +1054,7 @@ Request user to set new password Set new password and specify minimum complexity + Set required password complexity Request user to set profile parent new password diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml index 2313c902..4a51f5cd 100644 --- a/app/src/main/res/xml/device_policy_header.xml +++ b/app/src/main/res/xml/device_policy_header.xml @@ -356,6 +356,12 @@ android:selectable="false" testdpc:admin="any" testdpc:minSdkVersion="Q"/> + + Date: Fri, 6 Nov 2020 11:13:56 -0800 Subject: [PATCH 06/75] Updated TestDPC version to 7.0.03 That way it can be tested locally (and besides, it has new features like dumpsys integration). Test: ./gradlew assembleDebug && adb install --user all ./out/gradle/TestDPC/build/outputs/apk/normal/debug/TestDPC-normal-debug.apk Bug: 170333009 Change-Id: If405c6ca2bb631cf7c322a88c10243136d587884 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e4e61fa8..6eaa3fb7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,7 +9,7 @@ ext { // exactly 1 digit versionMinor = 0 // exactly 2 digits - versionBuild = 02 + versionBuild = 03 } android { From fcc2f341f11c463d1338626384c610a91e186685 Mon Sep 17 00:00:00 2001 From: felipeal Date: Fri, 6 Nov 2020 16:27:12 -0800 Subject: [PATCH 07/75] Added Shell command to wipe data. Test: adb shell dumpsys activity service com.afwsamples.testdpc wipe-data Bug: 171350084 Bug: 171603586 Change-Id: Ide38e855f83cb651e65679bd09aaab5c9b916fcb --- .../testdpc/DevicePolicyManagerGateway.java | 6 ++++++ .../testdpc/DevicePolicyManagerGatewayImpl.java | 12 ++++++++++++ .../java/com/afwsamples/testdpc/ShellCommand.java | 12 ++++++++++++ .../testdpc/policy/PolicyManagementFragment.java | 8 ++++++-- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java index 3807a854..bc0e1f45 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java @@ -41,6 +41,12 @@ void createAndManageUser(@Nullable String name, int flags, */ void lockNow(@NonNull Consumer onSuccess, @NonNull Consumer onError); + /** + * See {@link android.app.admin.DevicePolicyManager#wipeData()}. + */ + void wipeData(int flags, @NonNull Consumer onSuccess, + @NonNull Consumer onError); + /** * Used on error callbacks to indicate that the operation returned {@code null}. */ diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java index f4bc462e..1a59e7d3 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java @@ -76,6 +76,18 @@ public void lockNow(Consumer onSuccess, Consumer onError) { } } + @Override + public void wipeData(int flags, Consumer onSuccess, Consumer onError) { + Log.d(TAG, "wipeData(" + flags + ")"); + + try { + mDevicePolicyManager.wipeData(flags); + onSuccess.accept(null); + } catch (Exception e) { + onError.accept(e); + } + } + @Override public String toString() { return "DevicePolicyManagerGatewayImpl[" + mAdminComponentName + "]"; diff --git a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java index 94ab6814..3e01352c 100644 --- a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java +++ b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java @@ -39,6 +39,7 @@ final class ShellCommand { private static final String CMD_HELP = "help"; private static final String CMD_LOCK_NOW = "lock-now"; private static final String ARG_FLAGS = "--flags"; + private static final String CMD_WIPE_DATA = "wipe-data"; private final PrintWriter mWriter; private final String[] mArgs; @@ -69,6 +70,9 @@ public void run() { case CMD_LOCK_NOW: execute(() -> lockNow()); break; + case CMD_WIPE_DATA: + execute(() -> wipeData()); + break; default: mWriter.printf("Invalid command: %s\n\n", cmd); showUsage(); @@ -123,6 +127,14 @@ private void lockNow() { (e) -> onError(e, "Error locking device")); } + private void wipeData() { + // TODO(b/171350084): add flags + Log.i(TAG, "wipeData()"); + mDevicePolicyManagerGateway.wipeData(/* flags= */ 0, + (v) -> onSuccess("Data wiped"), + (e) -> onError(e, "Error wiping data")); + } + private void execute(@NonNull Runnable r) { try { r.run(); diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java index 0cb06909..93d58e28 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java @@ -1833,7 +1833,9 @@ public void onClick(DialogInterface dialogInterface, int i) { DevicePolicyManager.WIPE_EXTERNAL_STORAGE : 0); flags |= (resetProtectionCheckBox.isChecked() ? DevicePolicyManager.WIPE_RESET_PROTECTION_DATA : 0); - mDevicePolicyManager.wipeData(flags); + mDevicePolicyManagerGateway.wipeData(flags, + (v) -> onSuccessLog("wipeData"), + (e) -> onErrorLog("wipeData", e)); } }) .setNegativeButton(android.R.string.cancel, null) @@ -3975,7 +3977,9 @@ private int validateAffiliatedUserAfterP() { @TargetApi(30) private void factoryResetOrgOwnedDevice() { - mDevicePolicyManager.getParentProfileInstance(mAdminComponentName).wipeData(/*flags=*/ 0); + getParentProfileDevicePolicyManagerGateway().wipeData(/* flags= */ 0, + (v) -> onSuccessLog("wipeData"), + (e) -> onErrorLog("wipeData", e)); } private int validateDeviceOwnerBeforeO() { From c3d15b71a7166c62404135ba1a07ba57849a50e1 Mon Sep 17 00:00:00 2001 From: felipeal Date: Tue, 17 Nov 2020 12:32:05 -0800 Subject: [PATCH 08/75] Implemented PolicyManagementActivity.dump() It also provided the infra to dump just the relevant state (i.e., excluding core Android UI state). Test: adb shell dumpsys activity com.afwsamples.testdpc/.PolicyManagementActivity -q Bug: 170957342 Bug: 173541467 Change-Id: Ie0ace365550f071e47658f3f98b2d8583d7ef3ae --- .../testdpc/PolicyManagementActivity.java | 6 ++- ...aseSearchablePolicyPreferenceFragment.java | 42 +++++++++++++++- .../afwsamples/testdpc/common/Dumpable.java | 41 ++++++++++++++++ .../testdpc/common/DumpableActivity.java | 48 +++++++++++++++++++ .../com/afwsamples/testdpc/common/Util.java | 5 ++ .../policy/PolicyManagementFragment.java | 40 +++++++++++++--- 6 files changed, 173 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/com/afwsamples/testdpc/common/Dumpable.java create mode 100644 app/src/main/java/com/afwsamples/testdpc/common/DumpableActivity.java diff --git a/app/src/main/java/com/afwsamples/testdpc/PolicyManagementActivity.java b/app/src/main/java/com/afwsamples/testdpc/PolicyManagementActivity.java index b728d292..54d7e323 100644 --- a/app/src/main/java/com/afwsamples/testdpc/PolicyManagementActivity.java +++ b/app/src/main/java/com/afwsamples/testdpc/PolicyManagementActivity.java @@ -23,6 +23,10 @@ import android.view.Menu; import android.view.MenuItem; +import java.io.FileDescriptor; +import java.io.PrintWriter; + +import com.afwsamples.testdpc.common.DumpableActivity; import com.afwsamples.testdpc.common.OnBackPressedHandler; import com.afwsamples.testdpc.policy.PolicyManagementFragment; import com.afwsamples.testdpc.search.PolicySearchFragment; @@ -31,7 +35,7 @@ * An entry activity that shows a profile setup fragment if the app is not a profile or device * owner. Otherwise, a policy management fragment is shown. */ -public class PolicyManagementActivity extends Activity implements +public class PolicyManagementActivity extends DumpableActivity implements FragmentManager.OnBackStackChangedListener { @Override diff --git a/app/src/main/java/com/afwsamples/testdpc/common/BaseSearchablePolicyPreferenceFragment.java b/app/src/main/java/com/afwsamples/testdpc/common/BaseSearchablePolicyPreferenceFragment.java index 2b703a17..5a76ef2f 100644 --- a/app/src/main/java/com/afwsamples/testdpc/common/BaseSearchablePolicyPreferenceFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/common/BaseSearchablePolicyPreferenceFragment.java @@ -16,13 +16,19 @@ import android.view.MenuItem; import android.view.View; +import com.afwsamples.testdpc.common.Dumpable; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + import com.afwsamples.testdpc.R; /** * Base class of searchable policy preference fragment. With specified preference key, * it will scroll to the corresponding preference and highlight it. */ -public abstract class BaseSearchablePolicyPreferenceFragment extends PreferenceFragment { +public abstract class BaseSearchablePolicyPreferenceFragment extends PreferenceFragment + implements Dumpable { protected LinearLayoutManager mLayoutManager; private HighlightablePreferenceGroupAdapter mAdapter; private String mPreferenceKey; @@ -79,6 +85,40 @@ protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen return mAdapter; } + @Override // from Fragment + public final void dump(String prefix, FileDescriptor fd, PrintWriter pw, String[] args) { + dump(prefix, pw, fd, Dumpable.isQuietMode(args), args); + } + + @Override // from Dumpable + public final void dump(String prefix, PrintWriter pw, FileDescriptor fd, boolean quietModeOnly, + String[] args) { + // Dump its own state + if (mAdapter == null) { + pw.printf("%sno adapter\n", prefix); + } else { + pw.printf("%smHighlightPosition: %d\n", prefix, mAdapter.mHighlightPosition); + } + pw.printf("%smPreferenceKey: %s\n", prefix, mPreferenceKey); + pw.printf("%smPreferenceHighlighted: %b\n", prefix, mPreferenceHighlighted); + + // Dump subclass state + dump(prefix, pw, args); + + if (quietModeOnly) return; + + // Dump superclass state + super.dump(prefix, fd, pw, args); + + } + + /** + * Dumps just the subclass state. + */ + protected void dump(String prefix, PrintWriter writer, String[] args) { + + } + private void highlightPreferenceIfNeeded() { if (isAdded() && !mPreferenceHighlighted && !TextUtils.isEmpty(mPreferenceKey)) { highlightPreference(mPreferenceKey); diff --git a/app/src/main/java/com/afwsamples/testdpc/common/Dumpable.java b/app/src/main/java/com/afwsamples/testdpc/common/Dumpable.java new file mode 100644 index 00000000..3692ab64 --- /dev/null +++ b/app/src/main/java/com/afwsamples/testdpc/common/Dumpable.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.afwsamples.testdpc.common; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * Base class for all components that implements a custom {@code dump()} method. + */ +public interface Dumpable { + + /** + * Checks whether the arguments contains the option to just dump this app's state (and skipping + * Android SDK state). + */ + public static boolean isQuietMode(String[] args) { + return args != null && args.length > 0 + && (args[0].equals("-q") || args[0].equals("--quiet")); + } + + /** + * Custom dump that should only dump this app's state (and skip Android SDK state) when + * {@code quietModeOnly} is {@code true}. + */ + void dump(String prefix, PrintWriter pw, FileDescriptor fd, boolean quietModeOnly, + String[] args); +} diff --git a/app/src/main/java/com/afwsamples/testdpc/common/DumpableActivity.java b/app/src/main/java/com/afwsamples/testdpc/common/DumpableActivity.java new file mode 100644 index 00000000..e25eae58 --- /dev/null +++ b/app/src/main/java/com/afwsamples/testdpc/common/DumpableActivity.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.afwsamples.testdpc.common; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.List; + +import android.app.Activity; +import android.app.Fragment; + +/** + * Base class for all activities that implements {@code dump()}. + */ +public abstract class DumpableActivity extends Activity { + + @Override + public final void dump(String prefix, FileDescriptor fd, PrintWriter pw, String[] args) { + boolean quietMode = Dumpable.isQuietMode(args); + if (quietMode) { + List fragments = getFragmentManager().getFragments(); + pw.println("*** Dumping Dumpable fragments only ***"); + String prefix2 = prefix + prefix; + for (Fragment fragment : fragments) { + if (fragment instanceof Dumpable) { + pw.printf("%s%s:\n", prefix, fragment); + ((Dumpable) fragment).dump(prefix2, pw, fd, quietMode, args); + } + } + return; + } + + super.dump(prefix, fd, pw, args); + } +} diff --git a/app/src/main/java/com/afwsamples/testdpc/common/Util.java b/app/src/main/java/com/afwsamples/testdpc/common/Util.java index ec957bec..ca21f414 100644 --- a/app/src/main/java/com/afwsamples/testdpc/common/Util.java +++ b/app/src/main/java/com/afwsamples/testdpc/common/Util.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.BitmapFactory; import android.net.Uri; @@ -239,4 +240,8 @@ public static boolean isRunningOnTvDevice(Context context) { UiModeManager uiModeManager = (UiModeManager) context.getSystemService(Context.UI_MODE_SERVICE); return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION; } + + public static boolean isRunningOnAutomotiveDevice(Context context) { + return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); + } } diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java index 9f2a2621..7bf5b2ca 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java @@ -104,6 +104,7 @@ import com.afwsamples.testdpc.common.AppInfoArrayAdapter; import com.afwsamples.testdpc.common.BaseSearchablePolicyPreferenceFragment; import com.afwsamples.testdpc.common.CertificateUtil; +import com.afwsamples.testdpc.common.Dumpable; import com.afwsamples.testdpc.common.MediaDisplayFragment; import com.afwsamples.testdpc.common.PackageInstallationUtils; import com.afwsamples.testdpc.common.ReflectionUtil; @@ -146,9 +147,11 @@ import java.io.ByteArrayInputStream; import java.io.File; +import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.PrintWriter; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; @@ -245,7 +248,7 @@ * */ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFragment implements - Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener { + Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener, Dumpable { // Tag for creating this fragment. This tag can be used to retrieve this fragment. public static final String FRAGMENT_TAG = "PolicyManagementFragment"; @@ -798,6 +801,26 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { reloadPersonalAppsSuspendedUi(); } + @Override + public void dump(String prefix, PrintWriter pw, String[] args) { + // TODO(b/173541467): needs to compile against @SystemAPI SDK to get it + // pw.printf("%smUserId: %s\n", prefix, getActivity().getUserId()); + pw.printf("%smAdminComponentName: %s\n", prefix, mAdminComponentName); + pw.printf("%smImageUri: %s\n", prefix, mImageUri); + pw.printf("%smmVideoUri: %s\n", prefix, mVideoUri); + pw.printf("%smmVideoUri: %s\n", prefix, mVideoUri); + pw.printf("%sisManagedProfileOwner(): %s\n", prefix, isManagedProfileOwner()); + pw.printf("%sisDeviceOwner(): %s\n", prefix, Util.isDeviceOwner(getActivity())); + pw.printf("%sisSystemUser(): %s\n", prefix, mUserManager.isSystemUser()); + pw.printf("%sisPrimaryUser(): %s\n", prefix, Util.isPrimaryUser(getActivity())); + pw.printf("%sisRunningOnTvDevice(): %s\n", prefix, Util.isRunningOnTvDevice(getActivity())); + pw.printf("%sisRunningOnAutomotiveDevice(): %s\n", prefix, + Util.isRunningOnAutomotiveDevice(getActivity())); + // TODO(b/173541467): need to expose it + // pw.printf("%sisHeadlessSystemUserMode(): %s\n", prefix, + // mUserManager.isHeadlessSystemUserMode()); + } + private void maybeUpdateProfileMaxTimeOff() { if (mProfileMaxTimeOff.isEnabled()) { final String currentValueAsString = Long.toString( @@ -1364,15 +1387,18 @@ private void showPendingSystemUpdate() { } } + private boolean isManagedProfileOwner() { + return Util.isManagedProfileOwner(getActivity()); + } + @TargetApi(VERSION_CODES.O) private void lockNow() { - if (Util.SDK_INT >= VERSION_CODES.O && Util.isManagedProfileOwner(getActivity())) { + if (Util.SDK_INT >= VERSION_CODES.O && isManagedProfileOwner()) { showLockNowPrompt(); return; } DevicePolicyManagerGateway gateway = mDevicePolicyManagerGateway; - if (Util.SDK_INT >= VERSION_CODES.N - && Util.isManagedProfileOwner(getActivity())) { + if (Util.SDK_INT >= VERSION_CODES.N && isManagedProfileOwner()) { // Always call lock now on the parent for managed profile on N gateway = getParentProfileDevicePolicyManagerGateway(); } @@ -2382,7 +2408,7 @@ private void loadPasswordComplexity() { String summary; int complexity = PASSWORD_COMPLEXITY.get(mDevicePolicyManager.getPasswordComplexity()); - if (Util.isManagedProfileOwner(getActivity()) && Util.SDK_INT >= VERSION_CODES.R) { + if (isManagedProfileOwner() && Util.SDK_INT >= VERSION_CODES.R) { DevicePolicyManager parentDpm = mDevicePolicyManager.getParentProfileInstance(mAdminComponentName); int parentComplexity = PASSWORD_COMPLEXITY.get(parentDpm.getPasswordComplexity()); @@ -2416,7 +2442,7 @@ private void loadRequiredPasswordComplexity() { String summary; int complexity = PASSWORD_COMPLEXITY.get(getRequiredComplexity(mDevicePolicyManager)); - if (Util.isManagedProfileOwner(getActivity()) && Util.SDK_INT >= VERSION_CODES.S) { + if (isManagedProfileOwner() && Util.SDK_INT >= VERSION_CODES.S) { DevicePolicyManager parentDpm = mDevicePolicyManager.getParentProfileInstance(mAdminComponentName); int parentComplexity = PASSWORD_COMPLEXITY.get(getRequiredComplexity(parentDpm)); @@ -2454,7 +2480,7 @@ private void loadPasswordCompliant() { String summary; boolean compliant = mDevicePolicyManager.isActivePasswordSufficient(); - if (Util.isManagedProfileOwner(getActivity())) { + if (isManagedProfileOwner()) { DevicePolicyManager parentDpm = mDevicePolicyManager.getParentProfileInstance(mAdminComponentName); boolean parentCompliant = parentDpm.isActivePasswordSufficient(); From dcf2fd223104aa5f5a9225b41e6979447259f779 Mon Sep 17 00:00:00 2001 From: felipeal Date: Wed, 18 Nov 2020 15:08:13 -0800 Subject: [PATCH 09/75] Added Shell command to remove users. Test: adb shell dumpsys activity service com.afwsamples.testdpc remove-user SERIAL Bug: 171350084 Bug: 173651465 Change-Id: I8e52f88c17aba53c2354670d6bfd0474bc1003ff --- .../testdpc/DevicePolicyManagerGateway.java | 32 +++++++++++--- .../DevicePolicyManagerGatewayImpl.java | 42 +++++++++++++++++-- .../com/afwsamples/testdpc/ShellCommand.java | 14 +++++++ .../policy/PolicyManagementFragment.java | 23 ++++------ 4 files changed, 88 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java index bc0e1f45..d5d3c716 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java @@ -36,6 +36,20 @@ public interface DevicePolicyManagerGateway { void createAndManageUser(@Nullable String name, int flags, @NonNull Consumer onSuccess, @NonNull Consumer onError); + /** + * See {@link android.app.admin.DevicePolicyManager#removeUser(android.content.ComponentName, UserHandle)}. + */ + void removeUser(@NonNull UserHandle userHandle, + @NonNull Consumer onSuccess, @NonNull Consumer onError); + + /** + * Same as {@link #removeUser(UserHandle, Consumer, Consumer)}, but it uses + * {@link android.os.UserManager#getSerialNumberForUser(UserHandle)} + * to get the {@link UserHandle} associated with the {@code serialNumber}. + */ + void removeUser(@NonNull long serialNumber, + @NonNull Consumer onSuccess, @NonNull Consumer onError); + /** * See {@link android.app.admin.DevicePolicyManager#lockNow()}. */ @@ -48,24 +62,30 @@ void wipeData(int flags, @NonNull Consumer onSuccess, @NonNull Consumer onError); /** - * Used on error callbacks to indicate that the operation returned {@code null}. + * Used on error callbacks to indicate a {@link android.app.admin.DevicePolicyManager} method + * call failed. */ - public static final class NullResultException extends Exception { + @SuppressWarnings("serial") + public static final class InvalidResultException extends Exception { private final String mMethod; + private final String mResult; /** * Default constructor. * - * @param method method name (without parenthesis). + * @param method method name template. + * @param args method arguments. */ - public NullResultException(@NonNull String method) { - mMethod = method; + public InvalidResultException(@NonNull String result, @NonNull String method, + @NonNull Object...args) { + mResult = result; + mMethod = String.format(method, args); } @Override public String toString() { - return "DPM." + mMethod + "() returned null"; + return "DPM." + mMethod + " returned " + mResult; } } } diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java index 1a59e7d3..a76f682a 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java @@ -24,6 +24,7 @@ import android.content.ComponentName; import android.content.Context; import android.os.UserHandle; +import android.os.UserManager; import android.util.Log; public final class DevicePolicyManagerGatewayImpl implements DevicePolicyManagerGateway { @@ -31,16 +32,19 @@ public final class DevicePolicyManagerGatewayImpl implements DevicePolicyManager private static final String TAG = DevicePolicyManagerGatewayImpl.class.getSimpleName(); private final DevicePolicyManager mDevicePolicyManager; + private final UserManager mUserManager; private final ComponentName mAdminComponentName; public DevicePolicyManagerGatewayImpl(@NonNull Context context) { this(context.getSystemService(DevicePolicyManager.class), + context.getSystemService(UserManager.class), DeviceAdminReceiver.getComponentName(context)); } - public DevicePolicyManagerGatewayImpl(@NonNull DevicePolicyManager dpm, + public DevicePolicyManagerGatewayImpl(@NonNull DevicePolicyManager dpm, @NonNull UserManager um, @NonNull ComponentName admin) { mDevicePolicyManager = dpm; + mUserManager = um; mAdminComponentName = admin; Log.d(TAG, "constructor: admin=" + mAdminComponentName + ", dpm=" + dpm); @@ -49,7 +53,7 @@ public DevicePolicyManagerGatewayImpl(@NonNull DevicePolicyManager dpm, @Override public void createAndManageUser(String name, int flags, Consumer onSuccess, Consumer onError) { - Log.d(TAG, "createAndManageUser(" + name + ", " + flags); + Log.d(TAG, "createAndManageUser(" + name + ", " + flags + ")"); try { UserHandle userHandle = mDevicePolicyManager.createAndManageUser(mAdminComponentName, @@ -57,7 +61,39 @@ public void createAndManageUser(String name, int flags, Consumer onS if (userHandle != null) { onSuccess.accept(userHandle); } else { - onError.accept(new NullResultException("createAndManageUser")); + onError.accept( + new InvalidResultException("null", + "createAndManageUser(%s, %d)", name, flags)); + } + } catch (Exception e) { + onError.accept(e); + } + } + + @Override + public void removeUser(long serialNumber, Consumer onSuccess, + Consumer onError) { + Log.d(TAG, "removeUser(" + serialNumber + ")"); + UserHandle userHandle = mUserManager.getUserForSerialNumber(serialNumber); + if (userHandle == null) { + onError.accept(new InvalidResultException("null", "getUserForSerialNumber(%d)", + serialNumber)); + return; + } + removeUser(userHandle, onSuccess, onError); + } + + @Override + public void removeUser(UserHandle userHandle, Consumer onSuccess, + Consumer onError) { + Log.d(TAG, "removeUser(" + userHandle + ")"); + + try { + boolean success = mDevicePolicyManager.removeUser(mAdminComponentName, userHandle); + if (success) { + onSuccess.accept(null); + } else { + onError.accept(new InvalidResultException("false", "removeUser(%s)", userHandle)); } } catch (Exception e) { onError.accept(e); diff --git a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java index 3e01352c..b78d4ef9 100644 --- a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java +++ b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java @@ -36,6 +36,7 @@ final class ShellCommand { private static final String TAG = "TestDPCShellCommand"; private static final String CMD_CREATE_USER = "create-user"; + private static final String CMD_REMOVE_USER = "remove-user"; private static final String CMD_HELP = "help"; private static final String CMD_LOCK_NOW = "lock-now"; private static final String ARG_FLAGS = "--flags"; @@ -67,6 +68,9 @@ public void run() { case CMD_CREATE_USER: execute(() -> createUser()); break; + case CMD_REMOVE_USER: + execute(() -> removeUser()); + break; case CMD_LOCK_NOW: execute(() -> lockNow()); break; @@ -84,6 +88,7 @@ private void showUsage() { mWriter.printf("\t%s - show this help\n", CMD_HELP); mWriter.printf("\t%s [%s FLAGS] [NAME] - create a user with the optional flags and name\n", CMD_CREATE_USER, ARG_FLAGS); + mWriter.printf("\t%s - remove the given user\n", CMD_REMOVE_USER); mWriter.printf("\t%s - locks the device (now! :-)\n", CMD_LOCK_NOW); } @@ -119,6 +124,15 @@ private void createUser() { (e) -> onError(e, "Error creating user %s", name)); } + private void removeUser() { + long serialNumber = Long.parseLong(mArgs[1]); + Log.i(TAG, "removeUser(): serialNumber=" + serialNumber); + + mDevicePolicyManagerGateway.removeUser(serialNumber, + (u) -> onSuccess("User removed"), + (e) -> onError(e, "Error removing user %d", serialNumber)); + } + private void lockNow() { // TODO(b/171350084): add flags Log.i(TAG, "lockNow()"); diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java index 9f2a2621..9d99e4c3 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java @@ -520,9 +520,9 @@ public void onCreate(Bundle savedInstanceState) { } mDevicePolicyManager = (DevicePolicyManager) getActivity().getSystemService( Context.DEVICE_POLICY_SERVICE); - mDevicePolicyManagerGateway = new DevicePolicyManagerGatewayImpl(mDevicePolicyManager, - mAdminComponentName); mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE); + mDevicePolicyManagerGateway = new DevicePolicyManagerGatewayImpl(mDevicePolicyManager, + mUserManager, mAdminComponentName); mTelephonyManager = (TelephonyManager) getActivity() .getSystemService(Context.TELEPHONY_SERVICE); mAccountManager = AccountManager.get(getActivity()); @@ -2148,20 +2148,15 @@ private void showRemoveUserPromptLegacy() { .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { - boolean success = false; long serialNumber = -1; try { serialNumber = Long.parseLong(input.getText().toString()); - UserHandle userHandle = mUserManager - .getUserForSerialNumber(serialNumber); - if (userHandle != null) { - success = mDevicePolicyManager - .removeUser(mAdminComponentName, userHandle); - } + mDevicePolicyManagerGateway.removeUser(serialNumber, + (u) -> showToast(R.string.user_removed), + (e) -> showToast(R.string.failed_to_remove_user)); } catch (NumberFormatException e) { // Error message is printed in the next line. } - showToast(success ? R.string.user_removed : R.string.failed_to_remove_user); } }) .show(); @@ -2176,9 +2171,9 @@ public void onClick(DialogInterface dialogInterface, int i) { private void showRemoveUserPrompt() { if (Util.SDK_INT >= VERSION_CODES.P) { showChooseUserPrompt(R.string.remove_user, userHandle -> { - boolean success = - mDevicePolicyManager.removeUser(mAdminComponentName, userHandle); - showToast(success ? R.string.user_removed : R.string.failed_to_remove_user); + mDevicePolicyManagerGateway.removeUser(userHandle, + (u) -> showToast(R.string.user_removed), + (e) -> showToast(R.string.failed_to_remove_user)); }); } else { showRemoveUserPromptLegacy(); @@ -4099,7 +4094,7 @@ private void setAutoTimeZoneEnabled(boolean enabled) { private DevicePolicyManagerGateway getParentProfileDevicePolicyManagerGateway() { return new DevicePolicyManagerGatewayImpl( mDevicePolicyManager.getParentProfileInstance(mAdminComponentName), - mAdminComponentName); + mUserManager, mAdminComponentName); } private void onSuccessLog(String method) { From be280a81eda4296a4630182b790ef7e4bcdc0b4c Mon Sep 17 00:00:00 2001 From: felipeal Date: Thu, 19 Nov 2020 14:13:41 -0800 Subject: [PATCH 10/75] Added Shell commands to manage user restrictions. Test: adb shell dumpsys activity service com.afwsamples.testdpc list-user-restrictions Test: adb shell dumpsys activity service com.afwsamples.testdpc set-user-restriction no_add_user false Bug: 171350084 Change-Id: I0946ca35c54d9f5cf22b8996b096215e75fbe7af --- .../testdpc/DevicePolicyManagerGateway.java | 29 +++++++- .../DevicePolicyManagerGatewayImpl.java | 72 +++++++++++++++++-- .../com/afwsamples/testdpc/ShellCommand.java | 55 ++++++++++++-- .../policy/PolicyManagementFragment.java | 10 +-- .../UserRestrictionsDisplayFragment.java | 22 +++--- ...UserRestrictionsParentDisplayFragment.java | 28 ++++---- 6 files changed, 165 insertions(+), 51 deletions(-) diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java index d5d3c716..40f7269c 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java @@ -15,13 +15,14 @@ */ package com.afwsamples.testdpc; +import android.os.UserHandle; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.util.Set; import java.util.function.Consumer; -import android.os.UserHandle; - /** * Interface used to abstract calls to {@link android.app.admin.DevicePolicyManager}. * @@ -50,6 +51,30 @@ void removeUser(@NonNull UserHandle userHandle, void removeUser(@NonNull long serialNumber, @NonNull Consumer onSuccess, @NonNull Consumer onError); + /** + * See {@link android.app.admin.DevicePolicyManager#getUserRestrictions(android.content.ComponentName)}. + */ + @NonNull + Set getUserRestrictions(); + + /** + * See {@link android.app.admin.DevicePolicyManager#setUserRestriction(android.content.ComponentName, String)} + * and {@link android.app.admin.DevicePolicyManager#clearUserRestriction(android.content.ComponentName, String)}. + */ + void setUserRestriction(@NonNull String userRestriction, boolean enabled, + @NonNull Consumer onSuccess, @NonNull Consumer onError); + + /** + * Same as {@link #setUserRestriction(String, boolean, Consumer, Consumer)}, but ignoring + * callbacks. + */ + void setUserRestriction(@NonNull String userRestriction, boolean enabled); + + /** + * See {@link android.os.UserManager#hasUserRestriction(String)}. + */ + boolean hasUserRestriction(@NonNull String userRestriction); + /** * See {@link android.app.admin.DevicePolicyManager#lockNow()}. */ diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java index a76f682a..56d9d293 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java @@ -13,20 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.afwsamples.testdpc; - -import androidx.annotation.NonNull; -import java.util.Arrays; -import java.util.function.Consumer; +package com.afwsamples.testdpc; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; +import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; +import androidx.annotation.NonNull; + +import java.util.Arrays; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; + public final class DevicePolicyManagerGatewayImpl implements DevicePolicyManagerGateway { private static final String TAG = DevicePolicyManagerGatewayImpl.class.getSimpleName(); @@ -50,6 +54,14 @@ public DevicePolicyManagerGatewayImpl(@NonNull DevicePolicyManager dpm, @NonNull Log.d(TAG, "constructor: admin=" + mAdminComponentName + ", dpm=" + dpm); } + public static DevicePolicyManagerGatewayImpl forParentProfile(@NonNull Context context) { + ComponentName admin = DeviceAdminReceiver.getComponentName(context); + DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class) + .getParentProfileInstance(admin); + UserManager um = context.getSystemService(UserManager.class); + return new DevicePolicyManagerGatewayImpl(dpm, um, admin); + } + @Override public void createAndManageUser(String name, int flags, Consumer onSuccess, Consumer onError) { @@ -100,6 +112,47 @@ public void removeUser(UserHandle userHandle, Consumer onSuccess, } } + @Override + public Set getUserRestrictions() { + Log.d(TAG, "getUserRestrictions()"); + Bundle restrictions = mDevicePolicyManager.getUserRestrictions(mAdminComponentName); + return restrictions.keySet().stream().filter(k -> restrictions.getBoolean(k)) + .collect(Collectors.toSet()); + } + + @Override + public void setUserRestriction(String userRestriction, boolean enabled, + Consumer onSuccess, Consumer onError) { + Log.d(TAG, "setUserRestriction(" + userRestriction + ", " + enabled + ")"); + + try { + if (enabled) { + mDevicePolicyManager.addUserRestriction(mAdminComponentName, userRestriction); + } else { + mDevicePolicyManager.clearUserRestriction(mAdminComponentName, userRestriction); + } + onSuccess.accept(null); + } catch (Exception e) { + onError.accept(e); + } + } + + @Override + public void setUserRestriction(String userRestriction, boolean enabled) { + String message = String.format("setUserRestriction(%s, %b)", userRestriction, enabled); + setUserRestriction(userRestriction, enabled, + (v) -> onSuccessLog(message), + (e) -> onErrorLog(message)); + } + + @Override + public boolean hasUserRestriction(String userRestriction) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "hasUserRestriction(" + userRestriction + ")"); + } + return mUserManager.hasUserRestriction(userRestriction); + } + @Override public void lockNow(Consumer onSuccess, Consumer onError) { Log.d(TAG, "lockNow()"); @@ -128,4 +181,13 @@ public void wipeData(int flags, Consumer onSuccess, Consumer on public String toString() { return "DevicePolicyManagerGatewayImpl[" + mAdminComponentName + "]"; } + + private void onSuccessLog(String template, Object... args) { + Log.d(TAG, String.format(template, args) + " succeeded"); + } + + private void onErrorLog(String template, Object... args) { + Log.d(TAG, String.format(template, args) + " failed"); + } + } diff --git a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java index b78d4ef9..a5c29845 100644 --- a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java +++ b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java @@ -15,15 +15,17 @@ */ package com.afwsamples.testdpc; -import java.io.PrintWriter; -import java.util.Arrays; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.content.Context; import android.os.UserHandle; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.Collection; + /** * Provides a CLI (command-line interface) to TestDPC through {@code dumpsys}. * @@ -37,6 +39,8 @@ final class ShellCommand { private static final String CMD_CREATE_USER = "create-user"; private static final String CMD_REMOVE_USER = "remove-user"; + private static final String CMD_LIST_USER_RESTRICTIONS = "list-user-restrictions"; + private static final String CMD_SET_USER_RESTRICTION = "set-user-restriction"; private static final String CMD_HELP = "help"; private static final String CMD_LOCK_NOW = "lock-now"; private static final String ARG_FLAGS = "--flags"; @@ -71,6 +75,12 @@ public void run() { case CMD_REMOVE_USER: execute(() -> removeUser()); break; + case CMD_LIST_USER_RESTRICTIONS: + execute(() -> listUserRestrictions()); + break; + case CMD_SET_USER_RESTRICTION: + execute(() -> setUserRestriction()); + break; case CMD_LOCK_NOW: execute(() -> lockNow()); break; @@ -89,6 +99,9 @@ private void showUsage() { mWriter.printf("\t%s [%s FLAGS] [NAME] - create a user with the optional flags and name\n", CMD_CREATE_USER, ARG_FLAGS); mWriter.printf("\t%s - remove the given user\n", CMD_REMOVE_USER); + mWriter.printf("\t%s - list the user restrictions\n", CMD_LIST_USER_RESTRICTIONS); + mWriter.printf("\t%s - sets the given user restriction\n", + CMD_SET_USER_RESTRICTION); mWriter.printf("\t%s - locks the device (now! :-)\n", CMD_LOCK_NOW); } @@ -125,14 +138,33 @@ private void createUser() { } private void removeUser() { + // TODO(b/171350084): check args long serialNumber = Long.parseLong(mArgs[1]); Log.i(TAG, "removeUser(): serialNumber=" + serialNumber); mDevicePolicyManagerGateway.removeUser(serialNumber, - (u) -> onSuccess("User removed"), + (v) -> onSuccess("User %d removed", serialNumber), (e) -> onError(e, "Error removing user %d", serialNumber)); } + private void listUserRestrictions() { + Log.i(TAG, "listUserRestrictions()"); + + print("user restrictions", mDevicePolicyManagerGateway.getUserRestrictions()); + } + + private void setUserRestriction() { + // TODO(b/171350084): check args + String userRestriction = mArgs[1]; + boolean enabled = Boolean.parseBoolean(mArgs[2]); + Log.i(TAG, "setUserRestriction(" + userRestriction + ", " + enabled + ")"); + + mDevicePolicyManagerGateway.setUserRestriction(userRestriction, enabled, + (v) -> onSuccess("User restriction '%s' set to %b", userRestriction, enabled), + (e) -> onError(e, "Error setting user restriction '%s' to %b", userRestriction, + enabled)); + } + private void lockNow() { // TODO(b/171350084): add flags Log.i(TAG, "lockNow()"); @@ -170,4 +202,15 @@ private void onError(@NonNull Exception e, @NonNull String pattern, @Nullable Ob Log.e(TAG, msg, e); mWriter.printf("%s: %s\n", msg, e); } + + private void print(String name, Collection collection) { + if (collection.isEmpty()) { + mWriter.printf("No %s\n", name); + return; + + } + int size = collection.size(); + mWriter.printf("%d %s:\n", size, name); + collection.forEach((s) -> mWriter.printf(" %s\n", s)); + } } diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java index db703db0..c851133c 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java @@ -1400,7 +1400,7 @@ private void lockNow() { DevicePolicyManagerGateway gateway = mDevicePolicyManagerGateway; if (Util.SDK_INT >= VERSION_CODES.N && isManagedProfileOwner()) { // Always call lock now on the parent for managed profile on N - gateway = getParentProfileDevicePolicyManagerGateway(); + gateway = DevicePolicyManagerGatewayImpl.forParentProfile(getActivity()); } gateway.lockNow((v) -> onSuccessLog("lockNow"), (e) -> onErrorLog("lockNow", e)); } @@ -4071,7 +4071,7 @@ private int validateAffiliatedUserAfterP() { @TargetApi(30) private void factoryResetOrgOwnedDevice() { - getParentProfileDevicePolicyManagerGateway().wipeData(/* flags= */ 0, + DevicePolicyManagerGatewayImpl.forParentProfile(getActivity()).wipeData(/* flags= */ 0, (v) -> onSuccessLog("wipeData"), (e) -> onErrorLog("wipeData", e)); } @@ -4117,12 +4117,6 @@ private void setAutoTimeZoneEnabled(boolean enabled) { mDevicePolicyManager.setAutoTimeZoneEnabled(mAdminComponentName, enabled); } - private DevicePolicyManagerGateway getParentProfileDevicePolicyManagerGateway() { - return new DevicePolicyManagerGatewayImpl( - mDevicePolicyManager.getParentProfileInstance(mAdminComponentName), - mUserManager, mAdminComponentName); - } - private void onSuccessLog(String method) { Log.d(TAG, method + "() succeeded"); } diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsDisplayFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsDisplayFragment.java index b4ac36e7..ede9c5d1 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsDisplayFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsDisplayFragment.java @@ -19,8 +19,6 @@ import static android.os.UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES; import android.app.AlertDialog; -import android.app.admin.DevicePolicyManager; -import android.content.ComponentName; import android.content.Context; import android.os.Build.VERSION_CODES; import android.os.Bundle; @@ -30,6 +28,8 @@ import android.util.Log; import android.widget.Toast; import com.afwsamples.testdpc.DeviceAdminReceiver; +import com.afwsamples.testdpc.DevicePolicyManagerGateway; +import com.afwsamples.testdpc.DevicePolicyManagerGatewayImpl; import com.afwsamples.testdpc.R; import com.afwsamples.testdpc.common.BaseSearchablePolicyPreferenceFragment; import com.afwsamples.testdpc.common.preference.DpcPreferenceBase; @@ -40,9 +40,7 @@ public class UserRestrictionsDisplayFragment extends BaseSearchablePolicyPrefere implements Preference.OnPreferenceChangeListener { private static final String TAG = "UserRestrictions"; - private DevicePolicyManager mDevicePolicyManager; - private UserManager mUserManager; - private ComponentName mAdminComponentName; + private DevicePolicyManagerGateway mDevicePolicyManagerGateway; public static UserRestrictionsDisplayFragment newInstance() { UserRestrictionsDisplayFragment fragment = new UserRestrictionsDisplayFragment(); @@ -51,10 +49,7 @@ public static UserRestrictionsDisplayFragment newInstance() { @Override public void onCreate(Bundle savedInstanceState) { - mDevicePolicyManager = (DevicePolicyManager) getActivity().getSystemService( - Context.DEVICE_POLICY_SERVICE); - mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE); - mAdminComponentName = DeviceAdminReceiver.getComponentName(getActivity()); + mDevicePolicyManagerGateway = new DevicePolicyManagerGatewayImpl(getActivity()); super.onCreate(savedInstanceState); getActivity().getActionBar().setTitle(R.string.user_restrictions_management_title); } @@ -92,11 +87,10 @@ public boolean isAvailable(Context context) { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { String restriction = preference.getKey(); + boolean enabled = newValue.equals(true); try { - if (newValue.equals(true)) { - mDevicePolicyManager.addUserRestriction(mAdminComponentName, restriction); - } else { - mDevicePolicyManager.clearUserRestriction(mAdminComponentName, restriction); + mDevicePolicyManagerGateway.setUserRestriction(restriction, enabled); + if (!enabled) { if (DISALLOW_INSTALL_UNKNOWN_SOURCES.equals(restriction) || UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY.equals( restriction)) { @@ -127,7 +121,7 @@ private void updateAllUserRestrictions() { private void updateUserRestriction(String userRestriction) { DpcSwitchPreference preference = (DpcSwitchPreference) findPreference(userRestriction); - boolean disallowed = mUserManager.hasUserRestriction(userRestriction); + boolean disallowed = mDevicePolicyManagerGateway.hasUserRestriction(userRestriction); preference.setChecked(disallowed); } diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsParentDisplayFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsParentDisplayFragment.java index 0270a3d5..0bce1097 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsParentDisplayFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsParentDisplayFragment.java @@ -1,7 +1,5 @@ package com.afwsamples.testdpc.policy; -import android.app.admin.DevicePolicyManager; -import android.content.ComponentName; import android.content.Context; import android.os.Build.VERSION_CODES; import android.os.Bundle; @@ -12,7 +10,11 @@ import androidx.preference.Preference; import androidx.preference.PreferenceScreen; +import java.util.Set; + import com.afwsamples.testdpc.DeviceAdminReceiver; +import com.afwsamples.testdpc.DevicePolicyManagerGateway; +import com.afwsamples.testdpc.DevicePolicyManagerGatewayImpl; import com.afwsamples.testdpc.R; import com.afwsamples.testdpc.common.BaseSearchablePolicyPreferenceFragment; import com.afwsamples.testdpc.common.Util; @@ -23,17 +25,13 @@ public class UserRestrictionsParentDisplayFragment extends BaseSearchablePolicyP implements Preference.OnPreferenceChangeListener { private static final String TAG = "UserRestrictionsParent"; - private DevicePolicyManager mParentDevicePolicyManager; - private ComponentName mAdminComponentName; + private DevicePolicyManagerGateway mDevicePolicyManagerGateway; @RequiresApi(api = VERSION_CODES.R) @Override public void onCreate(Bundle savedInstanceState) { - DevicePolicyManager mDevicePolicyManager = (DevicePolicyManager) - getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE); - mAdminComponentName = DeviceAdminReceiver.getComponentName(getActivity()); - mParentDevicePolicyManager = mDevicePolicyManager - .getParentProfileInstance(mAdminComponentName); + mDevicePolicyManagerGateway = DevicePolicyManagerGatewayImpl + .forParentProfile(getActivity()); super.onCreate(savedInstanceState); getActivity().getActionBar().setTitle(R.string.user_restrictions_management_title); } @@ -74,12 +72,10 @@ public boolean isAvailable(Context context) { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { String restriction = preference.getKey(); + boolean enabled = newValue.equals(true); + try { - if (newValue.equals(true)) { - mParentDevicePolicyManager.addUserRestriction(mAdminComponentName, restriction); - } else { - mParentDevicePolicyManager.clearUserRestriction(mAdminComponentName, restriction); - } + mDevicePolicyManagerGateway.setUserRestriction(restriction, enabled); updateUserRestriction(restriction); return true; } catch (SecurityException e) { @@ -100,8 +96,8 @@ private void updateAllUserRestrictions() { @RequiresApi(api = VERSION_CODES.R) private void updateUserRestriction(String userRestriction) { DpcSwitchPreference preference = (DpcSwitchPreference) findPreference(userRestriction); - Bundle restrictions = mParentDevicePolicyManager.getUserRestrictions(mAdminComponentName); - preference.setChecked(restrictions.containsKey(userRestriction)); + Set restrictions = mDevicePolicyManagerGateway.getUserRestrictions(); + preference.setChecked(restrictions.contains(userRestriction)); } private void constrainPreferences() { From 5ab834d5058f6097c2505c7ac032831806278f16 Mon Sep 17 00:00:00 2001 From: felipeal Date: Fri, 20 Nov 2020 13:57:25 -0800 Subject: [PATCH 11/75] Added Shell commands for features that fail on unafilliated users. Test: adb shell dumpsys activity service com.afwsamples.testdpc request-bugreport Test: adb shell dumpsys activity service com.afwsamples.testdpc set-network-logging true Test: manual verification using TestDPC UI Change-Id: I1d221a680c8a0a0fcc9ad76962096f90fef9aad1 --- .../testdpc/DevicePolicyManagerGateway.java | 38 ++++++++++++++++++- .../DevicePolicyManagerGatewayImpl.java | 37 ++++++++++++++++++ .../com/afwsamples/testdpc/ShellCommand.java | 32 +++++++++++++++- .../policy/PolicyManagementFragment.java | 22 ++++++----- 4 files changed, 115 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java index 40f7269c..868f1c3c 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java @@ -81,17 +81,32 @@ void setUserRestriction(@NonNull String userRestriction, boolean enabled, void lockNow(@NonNull Consumer onSuccess, @NonNull Consumer onError); /** - * See {@link android.app.admin.DevicePolicyManager#wipeData()}. + * See {@link android.app.admin.DevicePolicyManager#wipeData(int)}. */ void wipeData(int flags, @NonNull Consumer onSuccess, @NonNull Consumer onError); + /** + * See {@link android.app.admin.DevicePolicyManager#requestBugreport(android.content.ComponentName)}. + */ + void requestBugreport(@NonNull Consumer onSuccess, @NonNull Consumer onError); + + /** + * See {@link android.app.admin.DevicePolicyManager#setNetworkLoggingEnabled(android.content.ComponentName, boolean)}. + */ + void setNetworkLogging(boolean enabled, @NonNull Consumer onSuccess, @NonNull Consumer onError); + + /** + * Same as {@link #setNetworkLogging(boolean, Consumer, Consumer)}, but ignoring callbacks. + */ + void setNetworkLogging(boolean enabled); + /** * Used on error callbacks to indicate a {@link android.app.admin.DevicePolicyManager} method * call failed. */ @SuppressWarnings("serial") - public static final class InvalidResultException extends Exception { + public static class InvalidResultException extends Exception { private final String mMethod; private final String mResult; @@ -99,6 +114,7 @@ public static final class InvalidResultException extends Exception { /** * Default constructor. * + * @param result result of the method call. * @param method method name template. * @param args method arguments. */ @@ -113,4 +129,22 @@ public String toString() { return "DPM." + mMethod + " returned " + mResult; } } + + /** + * Used on error callbacks to indicate a {@link android.app.admin.DevicePolicyManager} method + * call that returned {@code false}. + */ + @SuppressWarnings("serial") + public static final class FailedOperationException extends InvalidResultException { + + /** + * Default constructor. + * + * @param method method name template. + * @param args method arguments. + */ + public FailedOperationException(@NonNull String method, @NonNull Object...args) { + super("false", method, args); + } + } } diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java index 56d9d293..85bb8f29 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java @@ -177,6 +177,43 @@ public void wipeData(int flags, Consumer onSuccess, Consumer on } } + @Override + public void requestBugreport(Consumer onSuccess, Consumer onError) { + Log.d(TAG, "requestBugreport("); + + try { + boolean success = mDevicePolicyManager.requestBugreport(mAdminComponentName); + if (success) { + onSuccess.accept(null); + } else { + onError.accept(new FailedOperationException("requestBugreport()")); + } + } catch (Exception e) { + onError.accept(e); + } + } + + @Override + public void setNetworkLogging(boolean enabled, Consumer onSuccess, + Consumer onError) { + Log.d(TAG, "setNetworkLogging(" + enabled + ")"); + + try { + mDevicePolicyManager.setNetworkLoggingEnabled(mAdminComponentName, enabled); + onSuccess.accept(null); + } catch (Exception e) { + onError.accept(e); + } + } + + @Override + public void setNetworkLogging(boolean enabled) { + String message = String.format("setNetworkLogging(%b)", enabled); + setNetworkLogging(enabled, + (v) -> onSuccessLog(message), + (e) -> onErrorLog(message)); + } + @Override public String toString() { return "DevicePolicyManagerGatewayImpl[" + mAdminComponentName + "]"; diff --git a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java index a5c29845..b3f009dd 100644 --- a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java +++ b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java @@ -45,6 +45,8 @@ final class ShellCommand { private static final String CMD_LOCK_NOW = "lock-now"; private static final String ARG_FLAGS = "--flags"; private static final String CMD_WIPE_DATA = "wipe-data"; + private static final String CMD_REQUEST_BUGREPORT = "request-bugreport"; + private static final String CMD_SET_NETWORK_LOGGING = "set-network-logging"; private final PrintWriter mWriter; private final String[] mArgs; @@ -87,6 +89,12 @@ public void run() { case CMD_WIPE_DATA: execute(() -> wipeData()); break; + case CMD_REQUEST_BUGREPORT: + execute(() -> requestBugreport()); + break; + case CMD_SET_NETWORK_LOGGING: + execute(() -> setNetworkLogging()); + break; default: mWriter.printf("Invalid command: %s\n\n", cmd); showUsage(); @@ -100,9 +108,12 @@ private void showUsage() { CMD_CREATE_USER, ARG_FLAGS); mWriter.printf("\t%s - remove the given user\n", CMD_REMOVE_USER); mWriter.printf("\t%s - list the user restrictions\n", CMD_LIST_USER_RESTRICTIONS); - mWriter.printf("\t%s - sets the given user restriction\n", + mWriter.printf("\t%s - set the given user restriction\n", CMD_SET_USER_RESTRICTION); - mWriter.printf("\t%s - locks the device (now! :-)\n", CMD_LOCK_NOW); + mWriter.printf("\t%s - lock the device (now! :-)\n", CMD_LOCK_NOW); + mWriter.printf("\t%s - request a bugreport\n", CMD_REQUEST_BUGREPORT); + mWriter.printf("\t%s - enable / disable network logging\n", + CMD_SET_NETWORK_LOGGING); } private void createUser() { @@ -181,6 +192,23 @@ private void wipeData() { (e) -> onError(e, "Error wiping data")); } + private void requestBugreport() { + Log.i(TAG, "requestBugreport()"); + mDevicePolicyManagerGateway.requestBugreport( + (v) -> onSuccess("Bugreport requested"), + (e) -> onError(e, "Error requesting bugreport")); + } + + private void setNetworkLogging() { + // TODO(b/171350084): check args + boolean enabled = Boolean.parseBoolean(mArgs[1]); + Log.i(TAG, "setNetworkLogging(" + enabled + ")"); + + mDevicePolicyManagerGateway.setNetworkLogging(enabled, + (v) -> onSuccess("Network logging set to %b", enabled), + (e) -> onError(e, "Error setting network logging to %b", enabled)); + } + private void execute(@NonNull Runnable r) { try { r.run(); diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java index c851133c..70059604 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java @@ -100,6 +100,7 @@ import com.afwsamples.testdpc.DevicePolicyManagerGatewayImpl; import com.afwsamples.testdpc.R; import com.afwsamples.testdpc.SetupManagementActivity; +import com.afwsamples.testdpc.DevicePolicyManagerGateway.FailedOperationException; import com.afwsamples.testdpc.common.AccountArrayAdapter; import com.afwsamples.testdpc.common.AppInfoArrayAdapter; import com.afwsamples.testdpc.common.BaseSearchablePolicyPreferenceFragment; @@ -1468,8 +1469,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { reloadEnableSecurityLoggingUi(); return true; case ENABLE_NETWORK_LOGGING: - mDevicePolicyManager.setNetworkLoggingEnabled(mAdminComponentName, - (Boolean) newValue); + mDevicePolicyManagerGateway.setNetworkLogging((Boolean) newValue); reloadEnableNetworkLoggingUi(); return true; case DISABLE_SCREEN_CAPTURE_KEY: @@ -1643,14 +1643,16 @@ private boolean isSecurityLoggingEnabled() { @TargetApi(VERSION_CODES.N) private void requestBugReport() { - try { - if (!mDevicePolicyManager.requestBugreport(mAdminComponentName)) { - showToast(R.string.bugreport_failure_throttled); - } - } catch (SecurityException e) { - Log.i(TAG, "Exception when calling requestBugreport()", e); - showToast(R.string.bugreport_failure_exception); - } + mDevicePolicyManagerGateway.requestBugreport( + (v) -> onSuccessLog("requestBugreport"), + (e) -> { + if (e instanceof FailedOperationException) { + showToast(R.string.bugreport_failure_throttled); + } else { + Log.e(TAG, "Exception when calling requestBugreport()", e); + showToast(R.string.bugreport_failure_exception); + } + }); } @TargetApi(VERSION_CODES.M) From d7627ad44971a6640d49b713b63f35e2e7f50274 Mon Sep 17 00:00:00 2001 From: felipeal Date: Fri, 20 Nov 2020 18:55:08 -0800 Subject: [PATCH 12/75] Added Shell commands for user affiliation. Bug: 171350084 Bug: 173821381 Test: adb shell dumpsys activity service com.afwsamples.testdpc set-affiliation-ids 4 8 15 16 23 42 Test: adb shell dumpsys activity service com.afwsamples.testdpc get-affiliation-ids Test: adb shell dumpsys activity service com.afwsamples.testdpc is-user-affiliated Change-Id: I69c2898714103d4a4c0c46ee9d8fa2ac12f0095d --- .../testdpc/DevicePolicyManagerGateway.java | 16 +++++++ .../DevicePolicyManagerGatewayImpl.java | 20 ++++++++ .../testdpc/SetupManagementFragment.java | 8 ++-- .../com/afwsamples/testdpc/ShellCommand.java | 47 ++++++++++++++++++- .../policy/ManageAffiliationIdsFragment.java | 16 +++---- 5 files changed, 91 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java index 868f1c3c..9570dd37 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java @@ -51,6 +51,22 @@ void removeUser(@NonNull UserHandle userHandle, void removeUser(@NonNull long serialNumber, @NonNull Consumer onSuccess, @NonNull Consumer onError); + /** + * See {@link android.app.admin.DevicePolicyManager#isAffiliatedUser()}. + */ + boolean isAffiliatedUser(); + + /** + * See {@link android.app.admin.DevicePolicyManager#setAffiliationIds(android.content.ComponentName, Set)}. + */ + void setAffiliationIds(@NonNull Set ids); + + /** + * See {@link android.app.admin.DevicePolicyManager#getAffiliationIds(android.content.ComponentName)}. + */ + @NonNull + Set getAffiliationIds(); + /** * See {@link android.app.admin.DevicePolicyManager#getUserRestrictions(android.content.ComponentName)}. */ diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java index 85bb8f29..68b0d49d 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java @@ -112,6 +112,26 @@ public void removeUser(UserHandle userHandle, Consumer onSuccess, } } + @Override + public boolean isAffiliatedUser() { + boolean isIt = mDevicePolicyManager.isAffiliatedUser(); + Log.d(TAG, "isAffiliatedUser(): " + isIt); + return isIt; + } + + @Override + public void setAffiliationIds(Set ids) { + Log.d(TAG, "setAffiliationIds(" + ids + ")"); + mDevicePolicyManager.setAffiliationIds(mAdminComponentName, ids); + } + + @Override + public Set getAffiliationIds() { + Set ids = mDevicePolicyManager.getAffiliationIds(mAdminComponentName); + Log.d(TAG, "getAffiliationIds(): " + ids); + return ids; + } + @Override public Set getUserRestrictions() { Log.d(TAG, "getUserRestrictions()"); diff --git a/app/src/main/java/com/afwsamples/testdpc/SetupManagementFragment.java b/app/src/main/java/com/afwsamples/testdpc/SetupManagementFragment.java index ec8d4a2a..f68a1957 100644 --- a/app/src/main/java/com/afwsamples/testdpc/SetupManagementFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/SetupManagementFragment.java @@ -282,15 +282,13 @@ private void maybePassAffiliationIds(Intent intent, PersistableBundle adminExtra @TargetApi(VERSION_CODES.O) private void passAffiliationIds(Intent intent, PersistableBundle adminExtras) { - ComponentName admin = DeviceAdminReceiver.getComponentName(getActivity()); - DevicePolicyManager dpm = (DevicePolicyManager) - getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE); - Set ids = dpm.getAffiliationIds(admin); + DevicePolicyManagerGateway dpmg = new DevicePolicyManagerGatewayImpl(getActivity()); + Set ids = dpmg.getAffiliationIds(); String affiliationId = null; if (ids.size() == 0) { SecureRandom randomGenerator = new SecureRandom(); affiliationId = Integer.toString(randomGenerator.nextInt(1000000)); - dpm.setAffiliationIds(admin, Collections.singleton(affiliationId)); + dpmg.setAffiliationIds(Collections.singleton(affiliationId)); } else { affiliationId = ids.iterator().next(); } diff --git a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java index b3f009dd..017e1a29 100644 --- a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java +++ b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java @@ -25,11 +25,13 @@ import java.io.PrintWriter; import java.util.Arrays; import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; /** * Provides a CLI (command-line interface) to TestDPC through {@code dumpsys}. * - *

Usage: <@code adb shell dumpsys activity service com.afwsamples.testdpc}. + *

Usage: {@code adb shell dumpsys activity service --user USER_ID com.afwsamples.testdpc CMD}. * */ final class ShellCommand { @@ -41,6 +43,9 @@ final class ShellCommand { private static final String CMD_REMOVE_USER = "remove-user"; private static final String CMD_LIST_USER_RESTRICTIONS = "list-user-restrictions"; private static final String CMD_SET_USER_RESTRICTION = "set-user-restriction"; + private static final String CMD_IS_USER_AFFILIATED = "is-user-affiliated"; + private static final String CMD_SET_AFFILIATION_IDS = "set-affiliation-ids"; + private static final String CMD_GET_AFFILIATION_IDS = "get-affiliation-ids"; private static final String CMD_HELP = "help"; private static final String CMD_LOCK_NOW = "lock-now"; private static final String ARG_FLAGS = "--flags"; @@ -77,6 +82,15 @@ public void run() { case CMD_REMOVE_USER: execute(() -> removeUser()); break; + case CMD_IS_USER_AFFILIATED: + execute(() -> isUserAffiliated()); + break; + case CMD_SET_AFFILIATION_IDS: + execute(() -> setAffiliationIds()); + break; + case CMD_GET_AFFILIATION_IDS: + execute(() -> getAffiliationIds()); + break; case CMD_LIST_USER_RESTRICTIONS: execute(() -> listUserRestrictions()); break; @@ -107,6 +121,12 @@ private void showUsage() { mWriter.printf("\t%s [%s FLAGS] [NAME] - create a user with the optional flags and name\n", CMD_CREATE_USER, ARG_FLAGS); mWriter.printf("\t%s - remove the given user\n", CMD_REMOVE_USER); + mWriter.printf("\t%s - checks if the user is affiliated with the device\n", + CMD_IS_USER_AFFILIATED); + mWriter.printf("\t%s [ID1] [ID2] [IDN] - sets the user affiliation ids (or clear them if " + + "no ids is passed)\n", CMD_SET_AFFILIATION_IDS); + mWriter.printf("\t%s - gets the user affiliation ids\n", + CMD_GET_AFFILIATION_IDS); mWriter.printf("\t%s - list the user restrictions\n", CMD_LIST_USER_RESTRICTIONS); mWriter.printf("\t%s - set the given user restriction\n", CMD_SET_USER_RESTRICTION); @@ -158,6 +178,31 @@ private void removeUser() { (e) -> onError(e, "Error removing user %d", serialNumber)); } + private void getAffiliationIds() { + Set ids = mDevicePolicyManagerGateway.getAffiliationIds(); + if (ids.isEmpty()) { + mWriter.println("no affiliation ids"); + return; + } + mWriter.printf("%d affiliation ids: %s\n", ids.size(), ids); + } + + private void setAffiliationIds() { + Set ids = new LinkedHashSet(mArgs.length - 1); + for (int i = 1; i < mArgs.length; i++) { + ids.add(mArgs[i]); + } + Log.i(TAG, "setAffiliationIds(): ids=" + ids); + mDevicePolicyManagerGateway.setAffiliationIds(ids); + + getAffiliationIds(); + } + + private void isUserAffiliated() { + mWriter.println(mDevicePolicyManagerGateway.isAffiliatedUser()); + } + + private void listUserRestrictions() { Log.i(TAG, "listUserRestrictions()"); diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/ManageAffiliationIdsFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/ManageAffiliationIdsFragment.java index 81be335f..8fb45fb4 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/ManageAffiliationIdsFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/ManageAffiliationIdsFragment.java @@ -17,13 +17,12 @@ package com.afwsamples.testdpc.policy; import android.annotation.TargetApi; -import android.app.admin.DevicePolicyManager; -import android.content.ComponentName; -import android.content.Context; import android.os.Build.VERSION_CODES; import android.os.Bundle; import androidx.collection.ArraySet; import com.afwsamples.testdpc.DeviceAdminReceiver; +import com.afwsamples.testdpc.DevicePolicyManagerGateway; +import com.afwsamples.testdpc.DevicePolicyManagerGatewayImpl; import com.afwsamples.testdpc.R; import java.util.Collection; import java.util.List; @@ -35,8 +34,7 @@ */ public class ManageAffiliationIdsFragment extends BaseStringItemsFragment { - private DevicePolicyManager mDevicePolicyManager; - private ComponentName mAdminComponent; + private DevicePolicyManagerGateway mDevicePolicyManagerGateway; public ManageAffiliationIdsFragment() { super(R.string.manage_affiliation_ids, R.string.enter_affiliation_id, @@ -47,21 +45,19 @@ public ManageAffiliationIdsFragment() { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mDevicePolicyManager = (DevicePolicyManager) getActivity().getSystemService( - Context.DEVICE_POLICY_SERVICE); - mAdminComponent = DeviceAdminReceiver.getComponentName(getActivity()); + mDevicePolicyManagerGateway = new DevicePolicyManagerGatewayImpl(getActivity()); } @TargetApi(VERSION_CODES.O) @Override protected Collection loadItems() { - return mDevicePolicyManager.getAffiliationIds(mAdminComponent); + return mDevicePolicyManagerGateway.getAffiliationIds(); } @TargetApi(VERSION_CODES.O) @Override protected void saveItems(List items) { - mDevicePolicyManager.setAffiliationIds(mAdminComponent, new ArraySet<>(items)); + mDevicePolicyManagerGateway.setAffiliationIds(new ArraySet<>(items)); } } From 6ece8774d720697be32f784a7b55085db34460fb Mon Sep 17 00:00:00 2001 From: felipeal Date: Mon, 23 Nov 2020 13:15:51 -0800 Subject: [PATCH 13/75] Added Shell commands for starting / stopping users Bug: 171350084 Test: adb shell dumpsys activity service com.afwsamples.testdpc switch-user 10 Test: adb shell dumpsys activity service com.afwsamples.testdpc start-user-in-background 10 Test: adb shell dumpsys activity service com.afwsamples.testdpc stop-user 10 Change-Id: I149c5c808f16b74e59627342be557749ee828b69 --- .../testdpc/DevicePolicyManagerGateway.java | 71 ++++++++++++++++--- .../DevicePolicyManagerGatewayImpl.java | 64 ++++++++++++++--- .../com/afwsamples/testdpc/ShellCommand.java | 64 +++++++++++++++-- .../policy/PolicyManagementFragment.java | 71 ++++++++++++------- 4 files changed, 219 insertions(+), 51 deletions(-) diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java index 9570dd37..4fbd3dbe 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java @@ -16,7 +16,7 @@ package com.afwsamples.testdpc; import android.os.UserHandle; - +import android.os.UserManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -37,19 +37,33 @@ public interface DevicePolicyManagerGateway { void createAndManageUser(@Nullable String name, int flags, @NonNull Consumer onSuccess, @NonNull Consumer onError); + /** @see {@link android.os.UserManager#getUserForSerialNumber(long)}.*/ + @Nullable + UserHandle getUserHandle(long serialNumber); + /** * See {@link android.app.admin.DevicePolicyManager#removeUser(android.content.ComponentName, UserHandle)}. */ - void removeUser(@NonNull UserHandle userHandle, - @NonNull Consumer onSuccess, @NonNull Consumer onError); + void removeUser(@NonNull UserHandle userHandle, @NonNull Consumer onSuccess, + @NonNull Consumer onError); /** - * Same as {@link #removeUser(UserHandle, Consumer, Consumer)}, but it uses - * {@link android.os.UserManager#getSerialNumberForUser(UserHandle)} - * to get the {@link UserHandle} associated with the {@code serialNumber}. + * See {@link android.app.admin.DevicePolicyManager#switchUser(android.content.ComponentName, UserHandle)}. */ - void removeUser(@NonNull long serialNumber, - @NonNull Consumer onSuccess, @NonNull Consumer onError); + void switchUser(@NonNull UserHandle userHandle, @NonNull Consumer onSuccess, + @NonNull Consumer onError); + + /** + * See {@link android.app.admin.DevicePolicyManager#startUserInBackground(android.content.ComponentName, UserHandle)}. + */ + void startUserInBackground(@NonNull UserHandle userHandle, @NonNull Consumer onSuccess, + @NonNull Consumer onError); + + /** + * See {@link android.app.admin.DevicePolicyManager#stopUser(android.content.ComponentName, UserHandle)}. + */ + void stopUser(@NonNull UserHandle userHandle, @NonNull Consumer onSuccess, + @NonNull Consumer onError); /** * See {@link android.app.admin.DevicePolicyManager#isAffiliatedUser()}. @@ -163,4 +177,45 @@ public FailedOperationException(@NonNull String method, @NonNull Object...args) super("false", method, args); } } + + /** + * Used on error callbacks to indicate a {@link android.app.admin.DevicePolicyManager} method + * call that returned a user-related error. + */ + @SuppressWarnings("serial") + public static final class FailedUserOperationException extends InvalidResultException { + + /** + * Default constructor. + * + * @param status user-related opeartion status. + * @param method method name template. + * @param args method arguments. + */ + public FailedUserOperationException(int status, @NonNull String method, + @NonNull Object...args) { + super(userStatusToString(status), method, args); + } + + private static String userStatusToString(int status) { + switch (status) { + case UserManager.USER_OPERATION_SUCCESS: + return "SUCCESS"; + case UserManager.USER_OPERATION_ERROR_CURRENT_USER: + return "ERROR_CURRENT_USER"; + case UserManager.USER_OPERATION_ERROR_LOW_STORAGE: + return "ERROR_LOW_STORAGE"; + case UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE: + return "ERROR_MAX_MANAGED_PROFILE"; + case UserManager.USER_OPERATION_ERROR_MAX_RUNNING_USERS: + return "ERROR_MAX_RUNNING_USERS"; + case UserManager.USER_OPERATION_ERROR_MAX_USERS: + return "ERROR_MAX_USERS"; + case UserManager.USER_OPERATION_ERROR_UNKNOWN: + return "ERROR_UNKNOWN"; + default: + return "INVALID_STATUS:" + status; + } + } + } } diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java index 68b0d49d..467dbce2 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java @@ -83,16 +83,8 @@ public void createAndManageUser(String name, int flags, Consumer onS } @Override - public void removeUser(long serialNumber, Consumer onSuccess, - Consumer onError) { - Log.d(TAG, "removeUser(" + serialNumber + ")"); - UserHandle userHandle = mUserManager.getUserForSerialNumber(serialNumber); - if (userHandle == null) { - onError.accept(new InvalidResultException("null", "getUserForSerialNumber(%d)", - serialNumber)); - return; - } - removeUser(userHandle, onSuccess, onError); + public UserHandle getUserHandle(long serialNumber) { + return mUserManager.getUserForSerialNumber(serialNumber); } @Override @@ -112,6 +104,58 @@ public void removeUser(UserHandle userHandle, Consumer onSuccess, } } + @Override + public void switchUser(UserHandle userHandle, Consumer onSuccess, + Consumer onError) { + Log.d(TAG, "switchUser(" + userHandle + ")"); + + try { + boolean success = mDevicePolicyManager.switchUser(mAdminComponentName, userHandle); + if (success) { + onSuccess.accept(null); + } else { + onError.accept(new FailedOperationException("switchUser(%s)", userHandle)); + } + } catch (Exception e) { + onError.accept(e); + } + } + + @Override + public void startUserInBackground(UserHandle userHandle, Consumer onSuccess, + Consumer onError) { + Log.d(TAG, "startUserInBackground(" + userHandle + ")"); + try { + int status = mDevicePolicyManager.startUserInBackground(mAdminComponentName, + userHandle); + if (status == UserManager.USER_OPERATION_SUCCESS) { + onSuccess.accept(status); + } else { + onError.accept(new FailedUserOperationException(status, "startUserInBackground(%s)", + userHandle)); + } + } catch (Exception e) { + onError.accept(e); + } + } + + @Override + public void stopUser(UserHandle userHandle, Consumer onSuccess, + Consumer onError) { + Log.d(TAG, "stopUser(" + userHandle + ")"); + try { + int status = mDevicePolicyManager.stopUser(mAdminComponentName, userHandle); + if (status == UserManager.USER_OPERATION_SUCCESS) { + onSuccess.accept(status); + } else { + onError.accept(new FailedUserOperationException(status, "stopUser(%s)", + userHandle)); + } + } catch (Exception e) { + onError.accept(e); + } + } + @Override public boolean isAffiliatedUser() { boolean isIt = mDevicePolicyManager.isAffiliatedUser(); diff --git a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java index 017e1a29..bf35a6c5 100644 --- a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java +++ b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java @@ -41,6 +41,9 @@ final class ShellCommand { private static final String CMD_CREATE_USER = "create-user"; private static final String CMD_REMOVE_USER = "remove-user"; + private static final String CMD_SWITCH_USER = "switch-user"; + private static final String CMD_START_USER_BG = "start-user-in-background"; + private static final String CMD_STOP_USER = "stop-user"; private static final String CMD_LIST_USER_RESTRICTIONS = "list-user-restrictions"; private static final String CMD_SET_USER_RESTRICTION = "set-user-restriction"; private static final String CMD_IS_USER_AFFILIATED = "is-user-affiliated"; @@ -82,6 +85,15 @@ public void run() { case CMD_REMOVE_USER: execute(() -> removeUser()); break; + case CMD_SWITCH_USER: + execute(() -> switchUser()); + break; + case CMD_START_USER_BG: + execute(() -> startUserInBackground()); + break; + case CMD_STOP_USER: + execute(() -> stopUser()); + break; case CMD_IS_USER_AFFILIATED: execute(() -> isUserAffiliated()); break; @@ -121,6 +133,10 @@ private void showUsage() { mWriter.printf("\t%s [%s FLAGS] [NAME] - create a user with the optional flags and name\n", CMD_CREATE_USER, ARG_FLAGS); mWriter.printf("\t%s - remove the given user\n", CMD_REMOVE_USER); + mWriter.printf("\t%s - switch the given user to foreground\n", + CMD_SWITCH_USER); + mWriter.printf("\t%s - start the given user in the background\n", + CMD_START_USER_BG); mWriter.printf("\t%s - checks if the user is affiliated with the device\n", CMD_IS_USER_AFFILIATED); mWriter.printf("\t%s [ID1] [ID2] [IDN] - sets the user affiliation ids (or clear them if " @@ -169,13 +185,39 @@ private void createUser() { } private void removeUser() { - // TODO(b/171350084): check args - long serialNumber = Long.parseLong(mArgs[1]); - Log.i(TAG, "removeUser(): serialNumber=" + serialNumber); + UserHandle userHandle = getUserHandleArg(1); + if (userHandle == null) return; - mDevicePolicyManagerGateway.removeUser(serialNumber, - (v) -> onSuccess("User %d removed", serialNumber), - (e) -> onError(e, "Error removing user %d", serialNumber)); + mDevicePolicyManagerGateway.removeUser(userHandle, + (v) -> onSuccess("User %s removed", userHandle), + (e) -> onError(e, "Error removing user %s", userHandle)); + } + + private void switchUser() { + UserHandle userHandle = getUserHandleArg(1); + if (userHandle == null) return; + + mDevicePolicyManagerGateway.switchUser(userHandle, + (v) -> onSuccess("User %s switched", userHandle), + (e) -> onError(e, "Error switching user %s", userHandle)); + } + + private void startUserInBackground() { + UserHandle userHandle = getUserHandleArg(1); + if (userHandle == null) return; + + mDevicePolicyManagerGateway.startUserInBackground(userHandle, + (v) -> onSuccess("User %s started in background", userHandle), + (e) -> onError(e, "Error starting user %s in background", userHandle)); + } + + private void stopUser() { + UserHandle userHandle = getUserHandleArg(1); + if (userHandle == null) return; + + mDevicePolicyManagerGateway.stopUser(userHandle, + (v) -> onSuccess("User %s stopped", userHandle), + (e) -> onError(e, "Error stopping user %s", userHandle)); } private void getAffiliationIds() { @@ -286,4 +328,14 @@ private void print(String name, Collection collection) { mWriter.printf("%d %s:\n", size, name); collection.forEach((s) -> mWriter.printf(" %s\n", s)); } + + private UserHandle getUserHandleArg(int index) { + // TODO(b/171350084): check args + long serialNumber = Long.parseLong(mArgs[index]); + UserHandle userHandle = mDevicePolicyManagerGateway.getUserHandle(serialNumber); + if (userHandle == null) { + mWriter.printf("No user handle for serial number %d\n", serialNumber); + } + return userHandle; + } } diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java index 70059604..56f15a55 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java @@ -1645,14 +1645,9 @@ private boolean isSecurityLoggingEnabled() { private void requestBugReport() { mDevicePolicyManagerGateway.requestBugreport( (v) -> onSuccessLog("requestBugreport"), - (e) -> { - if (e instanceof FailedOperationException) { - showToast(R.string.bugreport_failure_throttled); - } else { - Log.e(TAG, "Exception when calling requestBugreport()", e); - showToast(R.string.bugreport_failure_exception); - } - }); + (e) -> onErrorOrFailureShowToast("requestBugreport", + R.string.bugreport_failure_throttled, R.string.bugreport_failure_exception, + e)); } @TargetApi(VERSION_CODES.M) @@ -2179,9 +2174,7 @@ public void onClick(DialogInterface dialogInterface, int i) { long serialNumber = -1; try { serialNumber = Long.parseLong(input.getText().toString()); - mDevicePolicyManagerGateway.removeUser(serialNumber, - (u) -> showToast(R.string.user_removed), - (e) -> showToast(R.string.failed_to_remove_user)); + removeUser(mDevicePolicyManagerGateway.getUserHandle(serialNumber)); } catch (NumberFormatException e) { // Error message is printed in the next line. } @@ -2190,6 +2183,12 @@ public void onClick(DialogInterface dialogInterface, int i) { .show(); } + private void removeUser(UserHandle userHandle) { + mDevicePolicyManagerGateway.removeUser(userHandle, + (u) -> onSuccessShowToast("removeUser()", R.string.user_removed), + (e) -> onErrorShowToast("removeUser()", R.string.failed_to_remove_user, e)); + } + /** * For user removal: * If the device is P or above, shows a prompt for choosing a user to be removed. Otherwise, @@ -2198,11 +2197,7 @@ public void onClick(DialogInterface dialogInterface, int i) { */ private void showRemoveUserPrompt() { if (Util.SDK_INT >= VERSION_CODES.P) { - showChooseUserPrompt(R.string.remove_user, userHandle -> { - mDevicePolicyManagerGateway.removeUser(userHandle, - (u) -> showToast(R.string.user_removed), - (e) -> showToast(R.string.failed_to_remove_user)); - }); + showChooseUserPrompt(R.string.remove_user, (u) -> removeUser(u)); } else { showRemoveUserPromptLegacy(); } @@ -2215,9 +2210,9 @@ private void showRemoveUserPrompt() { @TargetApi(VERSION_CODES.P) private void showSwitchUserPrompt() { showChooseUserPrompt(R.string.switch_user, userHandle -> { - boolean success = - mDevicePolicyManager.switchUser(mAdminComponentName, userHandle); - showToast(success ? R.string.user_switched : R.string.failed_to_switch_user); + mDevicePolicyManagerGateway.switchUser(userHandle, + (v) -> onSuccessShowToast("switchUser", R.string.user_switched), + (e) -> onErrorShowToast("switchUser", R.string.failed_to_switch_user, e)); }); } @@ -2228,10 +2223,11 @@ private void showSwitchUserPrompt() { @TargetApi(VERSION_CODES.P) private void showStartUserInBackgroundPrompt() { showChooseUserPrompt(R.string.start_user_in_background, userHandle -> { - int status = mDevicePolicyManager.startUserInBackground(mAdminComponentName, userHandle); - showToast(status == USER_OPERATION_SUCCESS - ? R.string.user_started_in_background - : R.string.failed_to_start_user_in_background); + mDevicePolicyManagerGateway.startUserInBackground(userHandle, + (v) -> onSuccessShowToast("startUserInBackground", + R.string.user_started_in_background), + (e) -> onErrorShowToast("startUserInBackground", + R.string.failed_to_start_user_in_background, e)); }); } @@ -2242,9 +2238,9 @@ private void showStartUserInBackgroundPrompt() { @TargetApi(VERSION_CODES.P) private void showStopUserPrompt() { showChooseUserPrompt(R.string.stop_user, userHandle -> { - int status = mDevicePolicyManager.stopUser(mAdminComponentName, userHandle); - showToast(status == USER_OPERATION_SUCCESS ? R.string.user_stopped - : R.string.failed_to_stop_user); + mDevicePolicyManagerGateway.startUserInBackground(userHandle, + (v) -> onSuccessShowToast("stopUser", R.string.user_stopped), + (e) -> onErrorShowToast("stopUser", R.string.failed_to_stop_user, e)); }); } @@ -4124,6 +4120,27 @@ private void onSuccessLog(String method) { } private void onErrorLog(String method, Exception e) { - Log.d(TAG, method + "() failed: ", e); + Log.e(TAG, method + "() failed: ", e); + } + + private void onSuccessShowToast(String method, int msgId) { + Log.d(TAG, method + "() succeeded"); + showToast(msgId); + } + + private void onErrorShowToast(String method, int msgId, Exception e) { + Log.e(TAG, method + "() failed: ", e); + showToast(msgId); + } + + private void onErrorOrFailureShowToast(String method, int failureMsgId, int errorMsgId, + Exception e) { + if (e instanceof FailedOperationException) { + Log.e(TAG, method + " returned false"); + showToast(failureMsgId); + } else { + Log.e(TAG, "Exception when calling " + method, e); + showToast(errorMsgId); + } } } From 809b63c5e6b8159972d4d0ad886c0324c642bbcc Mon Sep 17 00:00:00 2001 From: felipeal Date: Wed, 2 Dec 2020 11:15:02 -0800 Subject: [PATCH 14/75] Add flags for lock-now and wipe-data Shell commands. Test: adb shell dumpsys activity service --user 0 com.afwsamples.testdpc lock-now 1 Test: adb shell dumpsys activity service --user 0 com.afwsamples.testdpc wipe-data 7 Bug: 171350084 Bug: 171603586 Bug: 172697975 Change-Id: I37d5f8c59645459e48620047261ffd06d90ac2be --- .../testdpc/DevicePolicyManagerGateway.java | 5 ++++ .../DevicePolicyManagerGatewayImpl.java | 12 ++++++++ .../com/afwsamples/testdpc/ShellCommand.java | 30 ++++++++++++++----- .../policy/PolicyManagementFragment.java | 9 +++--- 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java index 4fbd3dbe..b6cdbf4c 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java @@ -110,6 +110,11 @@ void setUserRestriction(@NonNull String userRestriction, boolean enabled, */ void lockNow(@NonNull Consumer onSuccess, @NonNull Consumer onError); + /** + * See {@link android.app.admin.DevicePolicyManager#lockNow(int)}. + */ + void lockNow(int flags, @NonNull Consumer onSuccess, @NonNull Consumer onError); + /** * See {@link android.app.admin.DevicePolicyManager#wipeData(int)}. */ diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java index 467dbce2..8e0329a0 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java @@ -229,6 +229,18 @@ public void lockNow(Consumer onSuccess, Consumer onError) { } } + @Override + public void lockNow(int flags, Consumer onSuccess, Consumer onError) { + Log.d(TAG, "lockNow(" + flags + ")"); + + try { + mDevicePolicyManager.lockNow(flags); + onSuccess.accept(null); + } catch (Exception e) { + onError.accept(e); + } + } + @Override public void wipeData(int flags, Consumer onSuccess, Consumer onError) { Log.d(TAG, "wipeData(" + flags + ")"); diff --git a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java index bf35a6c5..cb94d9c5 100644 --- a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java +++ b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java @@ -146,7 +146,8 @@ private void showUsage() { mWriter.printf("\t%s - list the user restrictions\n", CMD_LIST_USER_RESTRICTIONS); mWriter.printf("\t%s - set the given user restriction\n", CMD_SET_USER_RESTRICTION); - mWriter.printf("\t%s - lock the device (now! :-)\n", CMD_LOCK_NOW); + mWriter.printf("\t%s [FLAGS]- lock the device (now! :-)\n", CMD_LOCK_NOW); + mWriter.printf("\t%s [FLAGS]- factory reset the device\n", CMD_WIPE_DATA); mWriter.printf("\t%s - request a bugreport\n", CMD_REQUEST_BUGREPORT); mWriter.printf("\t%s - enable / disable network logging\n", CMD_SET_NETWORK_LOGGING); @@ -264,17 +265,24 @@ private void setUserRestriction() { } private void lockNow() { - // TODO(b/171350084): add flags - Log.i(TAG, "lockNow()"); - mDevicePolicyManagerGateway.lockNow( - (v) -> onSuccess("Device locked"), - (e) -> onError(e, "Error locking device")); + Integer flags = getIntArg(/* index= */ 1); + if (flags == null) { + Log.i(TAG, "lockNow()"); + mDevicePolicyManagerGateway.lockNow( + (v) -> onSuccess("Device locked"), + (e) -> onError(e, "Error locking device")); + } else { + Log.i(TAG, "lockNow(" + flags + ")"); + mDevicePolicyManagerGateway.lockNow(flags, + (v) -> onSuccess("Device locked"), + (e) -> onError(e, "Error locking device")); + } } private void wipeData() { - // TODO(b/171350084): add flags + Integer flags = getIntArg(/* index= */ 1); Log.i(TAG, "wipeData()"); - mDevicePolicyManagerGateway.wipeData(/* flags= */ 0, + mDevicePolicyManagerGateway.wipeData(flags == null ? 0 : flags, (v) -> onSuccess("Data wiped"), (e) -> onError(e, "Error wiping data")); } @@ -338,4 +346,10 @@ private UserHandle getUserHandleArg(int index) { } return userHandle; } + + /** Gets an optional {@code int} argument at index {@code index}. */ + @Nullable + private Integer getIntArg(int index) { + return mArgs.length <= index ? null : Integer.parseInt(mArgs[index]); + } } diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java index 56f15a55..3844022d 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java @@ -1429,10 +1429,11 @@ private void showLockNowPrompt() { .setPositiveButton(android.R.string.ok, (d, i) -> { final int flags = evictKeyCheckBox.isChecked() ? DevicePolicyManager.FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY : 0; - final DevicePolicyManager dpm = lockParentCheckBox.isChecked() - ? mDevicePolicyManager.getParentProfileInstance(mAdminComponentName) - : mDevicePolicyManager; - dpm.lockNow(flags); + final DevicePolicyManagerGateway gateway = lockParentCheckBox.isChecked() + ? DevicePolicyManagerGatewayImpl.forParentProfile(getActivity()) + : mDevicePolicyManagerGateway; + gateway.lockNow(flags, (v) -> onSuccessLog("lockNow"), + (e) -> onErrorLog("lockNow", e)); }) .setNegativeButton(android.R.string.cancel, null) .show(); From 6c33b60a18626a7d0db855be5ce99f6e74517d87 Mon Sep 17 00:00:00 2001 From: Eran Messeri Date: Wed, 2 Dec 2020 18:14:56 +0000 Subject: [PATCH 15/75] TestDPC: Support setRequiredPasswordComplexity on parent Support setting the required password complexity on the parent profile DevicePolicyManager instance. Test: Manual, installed TestDPC in PO mode and verified that the complexity can be set (and correctly read) on the parent. Bug: 165573442 Change-Id: I956a4b108dbe98d125b9d59d0a413c1f60a3e165 --- .../policy/PolicyManagementFragment.java | 36 ++++++++++++++++++- app/src/main/res/values/strings.xml | 1 + app/src/main/res/xml/device_policy_header.xml | 5 +++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java index 56f15a55..c2da22bb 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java @@ -283,6 +283,8 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag private static final String PASSWORD_COMPLIANT_KEY = "password_compliant"; private static final String PASSWORD_COMPLEXITY_KEY = "password_complexity"; private static final String REQUIRED_PASSWORD_COMPLEXITY_KEY = "required_password_complexity"; + private static final String REQUIRED_PASSWORD_COMPLEXITY_ON_PARENT_KEY = + "required_password_complexity_on_parent"; private static final String SEPARATE_CHALLENGE_KEY = "separate_challenge"; private static final String DISABLE_CAMERA_KEY = "disable_camera"; private static final String DISABLE_CAMERA_ON_PARENT_KEY = "disable_camera_on_parent"; @@ -393,6 +395,8 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag "set_new_password_with_complexity"; private static final String SET_REQUIRED_PASSWORD_COMPLEXITY = "set_required_password_complexity"; + private static final String SET_REQUIRED_PASSWORD_COMPLEXITY_ON_PARENT = + "set_required_password_complexity_on_parent"; private static final String SET_PROFILE_PARENT_NEW_PASSWORD = "set_profile_parent_new_password"; private static final String BIND_DEVICE_ADMIN_POLICIES = "bind_device_admin_policies"; private static final String CROSS_PROFILE_APPS = "cross_profile_apps"; @@ -780,6 +784,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { onCreateSetNewPasswordWithComplexityPreference(); onCreateSetRequiredPasswordComplexityPreference(); + onCreateSetRequiredPasswordComplexityOnParentPreference(); constrainSpecialCasePreferences(); maybeDisableLockTaskPreferences(); @@ -872,6 +877,13 @@ private void onCreateSetRequiredPasswordComplexityPreference() { requiredComplexityPref.setOnPreferenceChangeListener(this); } + private void onCreateSetRequiredPasswordComplexityOnParentPreference() { + ListPreference requiredParentComplexityPref = + (ListPreference) findPreference(SET_REQUIRED_PASSWORD_COMPLEXITY_ON_PARENT); + addPasswordComplexityListToPreference(requiredParentComplexityPref); + requiredParentComplexityPref.setOnPreferenceChangeListener(this); + } + private void constrainSpecialCasePreferences() { // Reset password can be used in all contexts since N if (Util.SDK_INT >= VERSION_CODES.N) { @@ -1540,6 +1552,10 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { int requiredComplexity = Integer.parseInt((String) newValue); setRequiredPasswordComplexity(requiredComplexity); return true; + case SET_REQUIRED_PASSWORD_COMPLEXITY_ON_PARENT: + int requiredParentComplexity = Integer.parseInt((String) newValue); + setRequiredPasswordComplexityOnParent(requiredParentComplexity); + return true; case APP_FEEDBACK_NOTIFICATIONS: SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(getContext()).edit(); @@ -2453,8 +2469,25 @@ private void loadRequiredPasswordComplexity() { // running older releases and obviates the need for a target sdk check here. @TargetApi(VERSION_CODES.S) private void setRequiredPasswordComplexity(int complexity) { + setRequiredPasswordComplexity(mDevicePolicyManager, complexity); + } + + // NOTE: The setRequiredPasswordComplexity call is gated by a check in device_policy_header.xml, + // where the minSdkVersion for it is specified. That prevents it from being callable on devices + // running older releases and obviates the need for a target sdk check here. + @TargetApi(VERSION_CODES.S) + private void setRequiredPasswordComplexityOnParent(int complexity) { + setRequiredPasswordComplexity( + mDevicePolicyManager.getParentProfileInstance(mAdminComponentName), complexity); + } + + // NOTE: The setRequiredPasswordComplexity call is gated by a check in device_policy_header.xml, + // where the minSdkVersion for it is specified. That prevents it from being callable on devices + // running older releases and obviates the need for a target sdk check here. + @TargetApi(VERSION_CODES.S) + private void setRequiredPasswordComplexity(DevicePolicyManager dpm, int complexity) { try { - ReflectionUtil.invoke(mDevicePolicyManager, "setRequiredPasswordComplexity", + ReflectionUtil.invoke(dpm, "setRequiredPasswordComplexity", new Class[]{Integer.TYPE}, complexity); } catch (ReflectionIsTemporaryException e) { Log.e(TAG, "Error invoking setRequiredPasswordComplexity", e); @@ -2464,6 +2497,7 @@ private void setRequiredPasswordComplexity(int complexity) { loadRequiredPasswordComplexity(); } + @TargetApi(VERSION_CODES.N) private void loadPasswordCompliant() { Preference passwordCompliantPreference = findPreference(PASSWORD_COMPLIANT_KEY); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 67d45943..c3a4a68e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1055,6 +1055,7 @@ Request user to set new password Set new password and specify minimum complexity Set required password complexity + Set required password complexity on parent Request user to set profile parent new password diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml index 4a51f5cd..3571203e 100644 --- a/app/src/main/res/xml/device_policy_header.xml +++ b/app/src/main/res/xml/device_policy_header.xml @@ -409,6 +409,11 @@ android:title="@string/set_required_password_complexity" testdpc:admin="deviceOwner|profileOwner" testdpc:minSdkVersion="S"/> + Date: Tue, 8 Dec 2020 16:38:26 -0800 Subject: [PATCH 16/75] Added Shell command to reboot Bug: 171350084 Test: adb shell dumpsys activity service com.afwsamples.testdpc reboot Change-Id: I8263035b1fa0898aa2b3348fc70e0808373f660b (cherry picked from commit 3e0df40c0af151fd7be4835c339ec5bedea7a917) --- .../testdpc/DevicePolicyManagerGateway.java | 5 +++++ .../testdpc/DevicePolicyManagerGatewayImpl.java | 12 ++++++++++++ .../java/com/afwsamples/testdpc/ShellCommand.java | 14 +++++++++++++- .../testdpc/policy/PolicyManagementFragment.java | 3 ++- 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java index b6cdbf4c..a09ab356 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java @@ -115,6 +115,11 @@ void setUserRestriction(@NonNull String userRestriction, boolean enabled, */ void lockNow(int flags, @NonNull Consumer onSuccess, @NonNull Consumer onError); + /** + * See {@link android.app.admin.DevicePolicyManager#reboot()}. + */ + void reboot(@NonNull Consumer onSuccess, @NonNull Consumer onError); + /** * See {@link android.app.admin.DevicePolicyManager#wipeData(int)}. */ diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java index 8e0329a0..8399d45e 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java @@ -241,6 +241,18 @@ public void lockNow(int flags, Consumer onSuccess, Consumer onE } } + @Override + public void reboot(Consumer onSuccess, Consumer onError) { + Log.d(TAG, "reboot()"); + + try { + mDevicePolicyManager.reboot(mAdminComponentName); + onSuccess.accept(null); + } catch (Exception e) { + onError.accept(e); + } + } + @Override public void wipeData(int flags, Consumer onSuccess, Consumer onError) { Log.d(TAG, "wipeData(" + flags + ")"); diff --git a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java index cb94d9c5..616ed7ab 100644 --- a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java +++ b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java @@ -51,10 +51,11 @@ final class ShellCommand { private static final String CMD_GET_AFFILIATION_IDS = "get-affiliation-ids"; private static final String CMD_HELP = "help"; private static final String CMD_LOCK_NOW = "lock-now"; - private static final String ARG_FLAGS = "--flags"; + private static final String CMD_REBOOT = "reboot"; private static final String CMD_WIPE_DATA = "wipe-data"; private static final String CMD_REQUEST_BUGREPORT = "request-bugreport"; private static final String CMD_SET_NETWORK_LOGGING = "set-network-logging"; + private static final String ARG_FLAGS = "--flags"; private final PrintWriter mWriter; private final String[] mArgs; @@ -112,6 +113,9 @@ public void run() { case CMD_LOCK_NOW: execute(() -> lockNow()); break; + case CMD_REBOOT: + execute(() -> reboot()); + break; case CMD_WIPE_DATA: execute(() -> wipeData()); break; @@ -147,6 +151,7 @@ private void showUsage() { mWriter.printf("\t%s - set the given user restriction\n", CMD_SET_USER_RESTRICTION); mWriter.printf("\t%s [FLAGS]- lock the device (now! :-)\n", CMD_LOCK_NOW); + mWriter.printf("\t%s - reboot the device\n", CMD_REBOOT); mWriter.printf("\t%s [FLAGS]- factory reset the device\n", CMD_WIPE_DATA); mWriter.printf("\t%s - request a bugreport\n", CMD_REQUEST_BUGREPORT); mWriter.printf("\t%s - enable / disable network logging\n", @@ -279,6 +284,13 @@ private void lockNow() { } } + private void reboot() { + Log.i(TAG, "reboot()"); + mDevicePolicyManagerGateway.reboot( + (v) -> onSuccess("Device rebooted"), + (e) -> onError(e, "Error rebooting device")); + } + private void wipeData() { Integer flags = getIntArg(/* index= */ 1); Log.i(TAG, "wipeData()"); diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java index 89f346e8..e9716b0a 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java @@ -3854,7 +3854,8 @@ private void reboot() { showToast(R.string.reboot_error_msg); return; } - mDevicePolicyManager.reboot(mAdminComponentName); + mDevicePolicyManagerGateway.reboot((v) -> onSuccessLog("reboot"), + (e) -> onErrorLog("reboot", e)); } private void showSetupManagement() { From 2e076a0e97979e887620b55451de27e649edb493 Mon Sep 17 00:00:00 2001 From: felipeal Date: Wed, 16 Dec 2020 17:03:36 -0800 Subject: [PATCH 17/75] Added Shell commands to get / set organization name. Bug: 171350084 Bug: 175828371 Test: adb shell "dumpsys activity service --user 0 com.afwsamples.testdpc set-organization-name 'D.H.A.R.M.A Initiative'" Test: adb shell dumpsys activity service --user 0 com.afwsamples.testdpc set-organization-name # to reset Test: adb shell dumpsys activity service --user 0 com.afwsamples.testdpc get-organization-name Change-Id: Ib4c1b42427965689926752fd7aa1be204a186cc5 --- .../testdpc/DevicePolicyManagerGateway.java | 10 +++++++ .../DevicePolicyManagerGatewayImpl.java | 18 ++++++++++++ .../com/afwsamples/testdpc/ShellCommand.java | 29 +++++++++++++++++++ .../policy/PolicyManagementFragment.java | 4 ++- 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java index a09ab356..29b86383 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java @@ -141,6 +141,16 @@ void wipeData(int flags, @NonNull Consumer onSuccess, */ void setNetworkLogging(boolean enabled); + /** + * See {@link android.app.admin.DevicePolicyManager#setOrganizationName(android.content.ComponentName, name)}. + */ + void setOrganizationName(@Nullable CharSequence title, @NonNull Consumer onSuccess, @NonNull Consumer onError); + + /** + * See {@link android.app.admin.DevicePolicyManager#getOrganizationName(android.content.ComponentName)}. + */ + @Nullable CharSequence getOrganizationName(); + /** * Used on error callbacks to indicate a {@link android.app.admin.DevicePolicyManager} method * call failed. diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java index 8399d45e..2c8c2521 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java @@ -302,6 +302,24 @@ public void setNetworkLogging(boolean enabled) { (e) -> onErrorLog(message)); } + @Override + public void setOrganizationName(CharSequence title, Consumer onSuccess, + Consumer onError) { + Log.d(TAG, "setOrganizationName(" + title + ")"); + + try { + mDevicePolicyManager.setOrganizationName(mAdminComponentName, title); + onSuccess.accept(null); + } catch (Exception e) { + onError.accept(e); + } + } + + @Override + public CharSequence getOrganizationName() { + return mDevicePolicyManager.getOrganizationName(mAdminComponentName); + } + @Override public String toString() { return "DevicePolicyManagerGatewayImpl[" + mAdminComponentName + "]"; diff --git a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java index 616ed7ab..359ac945 100644 --- a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java +++ b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java @@ -55,6 +55,8 @@ final class ShellCommand { private static final String CMD_WIPE_DATA = "wipe-data"; private static final String CMD_REQUEST_BUGREPORT = "request-bugreport"; private static final String CMD_SET_NETWORK_LOGGING = "set-network-logging"; + private static final String CMD_SET_ORGANIZATION_NAME = "set-organization-name"; + private static final String CMD_GET_ORGANIZATION_NAME = "get-organization-name"; private static final String ARG_FLAGS = "--flags"; private final PrintWriter mWriter; @@ -125,6 +127,12 @@ public void run() { case CMD_SET_NETWORK_LOGGING: execute(() -> setNetworkLogging()); break; + case CMD_SET_ORGANIZATION_NAME: + execute(() -> setOrganizationName()); + break; + case CMD_GET_ORGANIZATION_NAME: + execute(() -> getOrganizationName()); + break; default: mWriter.printf("Invalid command: %s\n\n", cmd); showUsage(); @@ -156,6 +164,9 @@ private void showUsage() { mWriter.printf("\t%s - request a bugreport\n", CMD_REQUEST_BUGREPORT); mWriter.printf("\t%s - enable / disable network logging\n", CMD_SET_NETWORK_LOGGING); + mWriter.printf("\t%s [NAME] - set the organization name; use it without a name to reset\n", + CMD_SET_ORGANIZATION_NAME); + mWriter.printf("\t%s - get the organization name\n", CMD_GET_ORGANIZATION_NAME); } private void createUser() { @@ -316,6 +327,24 @@ private void setNetworkLogging() { (e) -> onError(e, "Error setting network logging to %b", enabled)); } + private void setOrganizationName() { + String title = mArgs.length > 1 ? mArgs[1] : null; + Log.i(TAG, "setOrganizationName(" + title + ")"); + + mDevicePolicyManagerGateway.setOrganizationName(title, + (v) -> onSuccess("Organization name set to %s", title), + (e) -> onError(e, "Error setting Organization name to %s", title)); + } + + private void getOrganizationName() { + CharSequence title = mDevicePolicyManagerGateway.getOrganizationName(); + if (title == null) { + mWriter.println("Not set"); + return; + } + mWriter.println(title); + } + private void execute(@NonNull Runnable r) { try { r.run(); diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java index e9716b0a..1f0c20f3 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java @@ -1530,7 +1530,9 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { reloadSetAutoTimeZoneUi(); return true; case SET_DEVICE_ORGANIZATION_NAME_KEY: - mDevicePolicyManager.setOrganizationName(mAdminComponentName, (String) newValue); + mDevicePolicyManagerGateway.setOrganizationName((String) newValue, + (v) -> onSuccessLog("setOrganizationName"), + (e) -> onErrorLog("setOrganizationName", e)); mSetDeviceOrganizationNamePreference.setSummary((String) newValue); return true; case ENABLE_LOGOUT_KEY: From 977512d84dcb3435c28360278777a495f12c5d77 Mon Sep 17 00:00:00 2001 From: Rubin Xu Date: Tue, 22 Dec 2020 17:03:41 +0000 Subject: [PATCH 18/75] Upgrade TestDPC SDK to S Bug: 152203329 Test: ./gradlew assemble Change-Id: I0d70939d36efdff1d94d64af675fdef7b7f30059 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6eaa3fb7..e0dedcec 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,13 +13,13 @@ ext { } android { - compileSdkVersion 'android-R' + compileSdkVersion 'android-S' buildToolsVersion "28.0.0" defaultConfig { applicationId "com.afwsamples.testdpc" minSdkVersion 21 - targetSdkVersion 29 + targetSdkVersion 30 versionCode versionMajor * 1000 + versionMinor * 100 + versionBuild versionName "${versionMajor}.${versionMinor}.${versionBuild}" multiDexEnabled true From d637e57edf72e379334b131d64a3c352924009d9 Mon Sep 17 00:00:00 2001 From: felipeal Date: Mon, 21 Dec 2020 16:37:56 -0800 Subject: [PATCH 19/75] Added Shell command to set the user icons. The icons must be located under the user's sdcard/Pictures/UserIcons directory; notice that starting on Android R, Shell cannot access other user's data, so this CL also added a ContentProvider that can be used to manage this files. For example, to set the icon for a managed user with id 10, you'd need to run 2 commands: $ adb shell content write --user 10 --uri content://com.afwsamples.testdpc.usericoncontentprovider/icon.png < ~/tmp/icon.png $ adb shell dumpsys activity service --user 10 com.afwsamples.testdpc set-user-icon icon.png Test: see above Bug: 171350084 Bug: 175214581 Bug: 175213385 Change-Id: I94eeb3ce9a58c0c464322ee2cb4105f1fe2fedaa --- app/src/main/AndroidManifest.xml | 7 + .../testdpc/DevicePolicyManagerGateway.java | 6 + .../DevicePolicyManagerGatewayImpl.java | 12 + .../com/afwsamples/testdpc/ShellCommand.java | 54 +++ .../testdpc/UserIconContentProvider.java | 337 ++++++++++++++++++ 5 files changed, 416 insertions(+) create mode 100644 app/src/main/java/com/afwsamples/testdpc/UserIconContentProvider.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8734a85f..e0b11c4e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -184,6 +184,13 @@ android:resource="@xml/filepaths" /> + + + diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java index 29b86383..254c2e50 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java @@ -15,6 +15,7 @@ */ package com.afwsamples.testdpc; +import android.graphics.Bitmap; import android.os.UserHandle; import android.os.UserManager; import androidx.annotation.NonNull; @@ -37,6 +38,11 @@ public interface DevicePolicyManagerGateway { void createAndManageUser(@Nullable String name, int flags, @NonNull Consumer onSuccess, @NonNull Consumer onError); + /** + * See {@link android.app.admin.DevicePolicyManager#setUserIcon(android.content.ComponentName, android.graphics.Bitmap)}. + */ + void setUserIcon(@NonNull Bitmap icon, @NonNull Consumer onSuccess, @NonNull Consumer onError); + /** @see {@link android.os.UserManager#getUserForSerialNumber(long)}.*/ @Nullable UserHandle getUserHandle(long serialNumber); diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java index 2c8c2521..e1326839 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java @@ -19,6 +19,7 @@ import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; +import android.graphics.Bitmap; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; @@ -82,6 +83,17 @@ public void createAndManageUser(String name, int flags, Consumer onS } } + @Override + public void setUserIcon(Bitmap icon, Consumer onSuccess, Consumer onError) { + Log.d(TAG, "setUserIcon(" + icon + ")"); + try { + mDevicePolicyManager.setUserIcon(mAdminComponentName, icon); + onSuccess.accept(null); + } catch (Exception e) { + onError.accept(e); + } + } + @Override public UserHandle getUserHandle(long serialNumber) { return mUserManager.getUserForSerialNumber(serialNumber); diff --git a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java index 359ac945..97e63b66 100644 --- a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java +++ b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java @@ -16,12 +16,16 @@ package com.afwsamples.testdpc; import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Environment; import android.os.UserHandle; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.io.File; import java.io.PrintWriter; import java.util.Arrays; import java.util.Collection; @@ -40,6 +44,7 @@ final class ShellCommand { private static final String TAG = "TestDPCShellCommand"; private static final String CMD_CREATE_USER = "create-user"; + private static final String CMD_SET_USER_ICON = "set-user-icon"; private static final String CMD_REMOVE_USER = "remove-user"; private static final String CMD_SWITCH_USER = "switch-user"; private static final String CMD_START_USER_BG = "start-user-in-background"; @@ -59,12 +64,14 @@ final class ShellCommand { private static final String CMD_GET_ORGANIZATION_NAME = "get-organization-name"; private static final String ARG_FLAGS = "--flags"; + private final Context mContext; private final PrintWriter mWriter; private final String[] mArgs; private final DevicePolicyManagerGateway mDevicePolicyManagerGateway; public ShellCommand(@NonNull Context context, @NonNull PrintWriter writer, @Nullable String[] args) { + mContext = context; mWriter = writer; mArgs = args; mDevicePolicyManagerGateway = new DevicePolicyManagerGatewayImpl(context); @@ -85,6 +92,9 @@ public void run() { case CMD_CREATE_USER: execute(() -> createUser()); break; + case CMD_SET_USER_ICON: + execute(() -> setUserIcon()); + break; case CMD_REMOVE_USER: execute(() -> removeUser()); break; @@ -144,6 +154,17 @@ private void showUsage() { mWriter.printf("\t%s - show this help\n", CMD_HELP); mWriter.printf("\t%s [%s FLAGS] [NAME] - create a user with the optional flags and name\n", CMD_CREATE_USER, ARG_FLAGS); + File setIconRootDir = UserIconContentProvider.getStorageDirectory(mContext); + mWriter.printf("\t%s - sets the user icon using the bitmap located at the given " + + "file,\n" + + "\t\twhich must be located in the user's `%s` directory.\n" + + "\t\tFor user 0, you can use `adb push` to push a local file to that directory\n" + + "\t\t(%s),\n" + + "\t\tbut for other users you need to switch to that user and use its content " + + "provider \n" + + "\t\t(for example, `adb shell content write --user 10 --uri \n" + + "\t\tcontent://%s/icon.png < /tmp/icon.png`)\n", CMD_SET_USER_ICON, + setIconRootDir.getName(), setIconRootDir, UserIconContentProvider.AUTHORITY); mWriter.printf("\t%s - remove the given user\n", CMD_REMOVE_USER); mWriter.printf("\t%s - switch the given user to foreground\n", CMD_SWITCH_USER); @@ -201,6 +222,31 @@ private void createUser() { (e) -> onError(e, "Error creating user %s", name)); } + private void setUserIcon() { + if (!hasExactlyNumberOfArguments(2)) return; + + String name = mArgs[1]; + Log.i(TAG, "setUserIcon(): name=" + name); + + File file = UserIconContentProvider.getFile(mContext, name); + + if (!file.isFile()) { + mWriter.printf("Could not open file %s\n", name); + return; + } + + String absolutePath = file.getAbsolutePath(); + Log.i(TAG, "setUserIcon(): path=" + absolutePath); + Bitmap icon = BitmapFactory.decodeFile(absolutePath, /* bmOptions= */ null); + if (icon == null) { + mWriter.printf("Could not create bitmap from file %s\n", absolutePath); + return; + } + mDevicePolicyManagerGateway.setUserIcon(icon, + (v) -> onSuccess("User icon created from file %s", absolutePath), + (e) -> onError(e, "Error creating user icon from file %s", absolutePath)); + } + private void removeUser() { UserHandle userHandle = getUserHandleArg(1); if (userHandle == null) return; @@ -393,4 +439,12 @@ private UserHandle getUserHandleArg(int index) { private Integer getIntArg(int index) { return mArgs.length <= index ? null : Integer.parseInt(mArgs[index]); } + + private boolean hasExactlyNumberOfArguments(int number) { + if (mArgs.length != number) { + mWriter.printf("Must have exactly %d arguments: %s\n", number, Arrays.toString(mArgs)); + return false; + } + return true; + } } diff --git a/app/src/main/java/com/afwsamples/testdpc/UserIconContentProvider.java b/app/src/main/java/com/afwsamples/testdpc/UserIconContentProvider.java new file mode 100644 index 00000000..06cae563 --- /dev/null +++ b/app/src/main/java/com/afwsamples/testdpc/UserIconContentProvider.java @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.afwsamples.testdpc; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.os.Environment; +import android.os.ParcelFileDescriptor; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import android.util.Log; +import android.webkit.MimeTypeMap; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; + +/** + * Content Provider used to upload user icons through {@code adb shell content}. + * + *

It stores the files in the user's {@link Environment#DIRECTORY_PICTURES} directory, under the + * sub-directory {@link #USER_ICONS_DIR}. + * + *

Note: it's based on {@code ManagedFileContentProvider} from {@code tools/tradefederation}. + */ +public final class UserIconContentProvider extends ContentProvider { + + private static final String TAG = UserIconContentProvider.class.getSimpleName(); + private static final String USER_ICONS_DIR = "UserIcons"; + + private static final String COLUMN_NAME = "name"; + private static final String COLUMN_ABSOLUTE_PATH = "absolute_path"; + private static final String COLUMN_DIRECTORY = "is_directory"; + private static final String COLUMN_MIME_TYPE = "mime_type"; + private static final String COLUMN_METADATA = "metadata"; + + private static final String[] COLUMNS = + new String[] { + COLUMN_NAME, + COLUMN_ABSOLUTE_PATH, + COLUMN_DIRECTORY, + COLUMN_MIME_TYPE, + COLUMN_METADATA + }; + + private static final MimeTypeMap MIME_MAP = MimeTypeMap.getSingleton(); + + static final String AUTHORITY = "com.afwsamples.testdpc.usericoncontentprovider"; + + private final Map mFileTracker = new HashMap<>(); + + @Override + public boolean onCreate() { + return true; + } + + /** + * Use a content URI with absolute device path embedded to get information about a file or a + * directory on the device. + * + * @param uri A content uri that contains the path to the desired file/directory. + * @param projection - not supported. + * @param selection - not supported. + * @param selectionArgs - not supported. + * @param sortOrder - not supported. + * @return A {@link Cursor} containing the results of the query. Cursor contains a single row + * for files and for directories it returns one row for each {@link File} returned by {@link + * File#listFiles()}. + */ + @Nullable + @Override + public Cursor query( + @NonNull Uri uri, + @Nullable String[] projection, + @Nullable String selection, + @Nullable String[] selectionArgs, + @Nullable String sortOrder) { + File file = getFileForUri(uri); + Log.v(TAG, "Query: " + uri); + if ("/".equals(file.getAbsolutePath())) { + // Querying the root will list all the known file (inserted) + MatrixCursor cursor = new MatrixCursor(COLUMNS, mFileTracker.size()); + for (Map.Entry path : mFileTracker.entrySet()) { + Log.v(TAG, "Adding path " + path); + String metadata = path.getValue().getAsString(COLUMN_METADATA); + cursor.addRow(getRow(COLUMNS, getFileForUri(path.getKey()), metadata)); + } + return cursor; + } + + if (!file.exists()) { + Log.e(TAG, "Query - File from uri: '" + uri + "' doesn't exists"); + return null; + } + + if (!file.isDirectory()) { + // Just return the information about the file itself. + MatrixCursor cursor = new MatrixCursor(COLUMNS, 1); + cursor.addRow(getRow(COLUMNS, file, /* metadata= */ null)); + return cursor; + } + + // Otherwise return the content of the directory - similar to doing ls command. + File[] files = file.listFiles(); + sortFilesByAbsolutePath(files); + MatrixCursor cursor = new MatrixCursor(COLUMNS, files.length + 1); + for (File child : files) { + cursor.addRow(getRow(COLUMNS, child, /* metadata= */ null)); + } + return cursor; + } + + @Nullable + @Override + public String getType(@NonNull Uri uri) { + return getType(getFileForUri(uri)); + } + + @Nullable + @Override + public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) { + String extra = ""; + File file = getFileForUri(uri); + Log.v(TAG, "insert(): uri=" + uri + ", file=" + file); + if (!file.exists()) { + Log.e(TAG, "Insert - File from uri: '" + uri + "' doesn't exist"); + return null; + } + if (mFileTracker.get(uri) != null) { + Log.e(TAG, "Insert - File from uri: '" + uri + "' already exists, ignoring"); + return null; + } + mFileTracker.put(uri, contentValues); + return uri; + } + + @Override + public int delete(@NonNull Uri uri, @Nullable String selection, + @Nullable String[] selectionArgs) { + Log.v(TAG, "delete(): uri=" + uri); + // Stop Tracking the File of directory if it was tracked and delete it from the disk + mFileTracker.remove(uri); + File file = getFileForUri(uri); + int num = recursiveDelete(file); + return num; + } + + @Override + public int update( + @NonNull Uri uri, + @Nullable ContentValues values, + @Nullable String selection, + @Nullable String[] selectionArgs) { + File file = getFileForUri(uri); + if (!file.exists()) { + Log.e(TAG, "Update - File from uri: '" + uri +"' doesn't exist"); + return 0; + } + if (mFileTracker.get(uri) == null) { + Log.e(TAG, "Update - File from uri: '" + uri +"' isn't tracked yet, use insert"); + return 0; + } + mFileTracker.put(uri, values); + return 1; + } + + @Override + public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) + throws FileNotFoundException { + File file = getFileForUri(uri); + int fileMode = modeToMode(mode); + Log.v(TAG, "openFile(): uri=" + uri + ", mode=" + mode + "(" + fileMode + ")"); + + if ((fileMode & ParcelFileDescriptor.MODE_CREATE) == ParcelFileDescriptor.MODE_CREATE) { + Log.v(TAG, "Creating file " + file); + // If the file is being created, create all its parent directories that don't already + // exist. + File parentFile = file.getParentFile(); + if (!parentFile.exists()) { + Log.v(TAG, "Creating parents for " + file); + boolean created = parentFile.mkdirs(); + if (!created) { + throw new FileNotFoundException("Could not created parent dirs for " + file); + } + } + if (!mFileTracker.containsKey(uri)) { + // Track the file, if not already tracked. + mFileTracker.put(uri, new ContentValues()); + } + } + ParcelFileDescriptor fd = ParcelFileDescriptor.open(file, fileMode); + Log.v(TAG, "Returning FD " + fd.getFd() + " for " + file.getAbsoluteFile()); + return fd; + } + + private Object[] getRow(String[] columns, File file, String metadata) { + Object[] values = new Object[columns.length]; + for (int i = 0; i < columns.length; i++) { + values[i] = getColumnValue(columns[i], file, metadata); + } + return values; + } + + private Object getColumnValue(String columnName, File file, String metadata) { + Object value = null; + if (COLUMN_NAME.equals(columnName)) { + value = file.getName(); + } else if (COLUMN_ABSOLUTE_PATH.equals(columnName)) { + value = file.getAbsolutePath(); + } else if (COLUMN_DIRECTORY.equals(columnName)) { + value = file.isDirectory(); + } else if (COLUMN_METADATA.equals(columnName)) { + value = metadata; + } else if (COLUMN_MIME_TYPE.equals(columnName)) { + value = file.isDirectory() ? null : getType(file); + } + return value; + } + + private String getType(@NonNull File file) { + int lastDot = file.getName().lastIndexOf('.'); + if (lastDot >= 0) { + String extension = file.getName().substring(lastDot + 1); + String mime = MIME_MAP.getMimeTypeFromExtension(extension); + if (mime != null) { + return mime; + } + } + + return "application/octet-stream"; + } + + /** + * Gets the file for the given name. + * + *

Note: if the name contains path separators, they'll be ignored and just the file name will + * be used. + */ + static File getFile(@NonNull Context context, @NonNull String name) { + File fullFile = new File(name); + String baseFile = fullFile.getName(); + File file = new File(getStorageDirectory(context), baseFile); + Log.v(TAG, "getFile(" + name + "): returning " + file); + return file; + } + + /** + * Gets the directory where the user icons are stored. + */ + static File getStorageDirectory(@NonNull Context context) { + return new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), USER_ICONS_DIR); + } + + private File getFileForUri(@NonNull Uri uri) { + String uriPath = uri.getPath(); + + File file = getFile(getContext(), uriPath); + Log.v(TAG, "getFileForUri(" + uri + "): returning " + file); + return file; + } + + /** Copied from FileProvider.java. */ + private static int modeToMode(String mode) { + int modeBits; + if ("r".equals(mode)) { + modeBits = ParcelFileDescriptor.MODE_READ_ONLY; + } else if ("w".equals(mode) || "wt".equals(mode)) { + modeBits = + ParcelFileDescriptor.MODE_WRITE_ONLY + | ParcelFileDescriptor.MODE_CREATE + | ParcelFileDescriptor.MODE_TRUNCATE; + } else if ("wa".equals(mode)) { + modeBits = + ParcelFileDescriptor.MODE_WRITE_ONLY + | ParcelFileDescriptor.MODE_CREATE + | ParcelFileDescriptor.MODE_APPEND; + } else if ("rw".equals(mode)) { + modeBits = ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_CREATE; + } else if ("rwt".equals(mode)) { + modeBits = + ParcelFileDescriptor.MODE_READ_WRITE + | ParcelFileDescriptor.MODE_CREATE + | ParcelFileDescriptor.MODE_TRUNCATE; + } else { + throw new IllegalArgumentException("Invalid mode: " + mode); + } + return modeBits; + } + + /** + * Recursively delete given file or directory and all its contents. + * + * @param rootDir the directory or file to be deleted; can be null + * @return The number of deleted files. + */ + private int recursiveDelete(@Nullable File rootDir) { + Log.v(TAG, "recursiveDelete(): rootDir=" + rootDir); + if (rootDir == null) return 0; + int count = 0; + if (rootDir.isDirectory()) { + File[] childFiles = rootDir.listFiles(); + if (childFiles != null) { + for (File child : childFiles) { + count += recursiveDelete(child); + } + } + } + rootDir.delete(); + return ++count; + } + + private static void sortFilesByAbsolutePath(@NonNull File[] files) { + Arrays.sort(files,(f1, f2) -> f1.getAbsolutePath().compareTo(f2.getAbsolutePath())); + } +} From ccdf71448916f1797a8f162ab45428790c9071d1 Mon Sep 17 00:00:00 2001 From: Alex Johnston Date: Wed, 28 Oct 2020 12:13:50 +0000 Subject: [PATCH 20/75] TestDPC to test credential management app Test: Manual testing Bug: 165641221 Change-Id: If3520360e36bfaa85441f908fbac536626d6f06a --- .../policy/PolicyManagementFragment.java | 83 +++++++++++++++++-- app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/device_policy_header.xml | 5 ++ 3 files changed, 84 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java index 1f0c20f3..583f836f 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java @@ -58,6 +58,7 @@ import android.provider.MediaStore; import android.provider.Settings; import android.provider.Settings.Secure; +import android.security.AppUriAuthenticationPolicy; import android.security.KeyChain; import android.security.KeyChainAliasCallback; import android.service.notification.NotificationListenerService; @@ -81,7 +82,6 @@ import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.Toast; - import androidx.annotation.RequiresApi; import androidx.annotation.StringRes; import androidx.core.content.FileProvider; @@ -90,17 +90,16 @@ import androidx.preference.Preference; import androidx.preference.PreferenceManager; import androidx.preference.SwitchPreference; - import com.afwsamples.testdpc.AddAccountActivity; import com.afwsamples.testdpc.BuildConfig; import com.afwsamples.testdpc.CrossProfileAppsFragment; import com.afwsamples.testdpc.CrossProfileAppsWhitelistFragment; import com.afwsamples.testdpc.DeviceAdminReceiver; import com.afwsamples.testdpc.DevicePolicyManagerGateway; +import com.afwsamples.testdpc.DevicePolicyManagerGateway.FailedOperationException; import com.afwsamples.testdpc.DevicePolicyManagerGatewayImpl; import com.afwsamples.testdpc.R; import com.afwsamples.testdpc.SetupManagementActivity; -import com.afwsamples.testdpc.DevicePolicyManagerGateway.FailedOperationException; import com.afwsamples.testdpc.common.AccountArrayAdapter; import com.afwsamples.testdpc.common.AppInfoArrayAdapter; import com.afwsamples.testdpc.common.BaseSearchablePolicyPreferenceFragment; @@ -145,10 +144,8 @@ import com.afwsamples.testdpc.profilepolicy.permission.ManageAppPermissionsFragment; import com.afwsamples.testdpc.transferownership.PickTransferComponentFragment; import com.afwsamples.testdpc.util.MainThreadExecutor; - import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -260,6 +257,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag private static final int CAPTURE_IMAGE_REQUEST_CODE = 7691; private static final int CAPTURE_VIDEO_REQUEST_CODE = 7692; private static final int INSTALL_APK_PACKAGE_REQUEST_CODE = 7693; + private static final int REQUEST_MANAGE_CREDENTIALS_REQUEST_CODE = 7694; public static final String X509_CERT_TYPE = "X.509"; public static final String TAG = "PolicyManagement"; @@ -313,6 +311,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag private static final String REMOVE_ACCOUNT_KEY = "remove_account"; private static final String HIDE_APPS_KEY = "hide_apps"; private static final String HIDE_APPS_PARENT_KEY = "hide_apps_parent"; + private static final String REQUEST_MANAGE_CREDENTIALS_KEY = "request_manage_credentials"; private static final String INSTALL_CA_CERTIFICATE_KEY = "install_ca_certificate"; private static final String INSTALL_KEY_CERTIFICATE_KEY = "install_key_certificate"; private static final String INSTALL_NONMARKET_APPS_KEY @@ -521,7 +520,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag @Override public void onCreate(Bundle savedInstanceState) { - if (isDelegatedApp()) { + if (isDelegatedApp() || isCredentialManagerApp()) { mAdminComponentName = null; } else { mAdminComponentName = DeviceAdminReceiver.getComponentName(getActivity()); @@ -691,6 +690,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { findPreference(DISABLE_METERED_DATA_KEY).setOnPreferenceClickListener(this); findPreference(GENERIC_DELEGATION_KEY).setOnPreferenceClickListener(this); findPreference(APP_RESTRICTIONS_MANAGING_PACKAGE_KEY).setOnPreferenceClickListener(this); + findPreference(REQUEST_MANAGE_CREDENTIALS_KEY).setOnPreferenceClickListener(this); findPreference(INSTALL_KEY_CERTIFICATE_KEY).setOnPreferenceClickListener(this); findPreference(GENERATE_KEY_CERTIFICATE_KEY).setOnPreferenceClickListener(this); findPreference(REMOVE_KEY_CERTIFICATE_KEY).setOnPreferenceClickListener(this); @@ -916,6 +916,17 @@ private boolean isDelegatedApp() { return !dpm.getDelegatedScopes(null, getActivity().getPackageName()).isEmpty(); } + private boolean isCredentialManagerApp() { + //TODO: replace with more precise API + if (Util.SDK_INT < VERSION_CODES.S) { + return false; + } + DevicePolicyManager dpm = getActivity().getSystemService(DevicePolicyManager.class); + final String packageName = getActivity().getPackageName(); + return !dpm.isDeviceOwnerApp(packageName) && + !dpm.isProfileOwnerApp(packageName); + } + @Override public boolean isAvailable(Context context) { return true; @@ -1143,6 +1154,9 @@ public void onPositiveButtonClicked(String[] lockTaskArray) { case MANAGE_APP_PERMISSIONS_KEY: showFragment(new ManageAppPermissionsFragment()); return true; + case REQUEST_MANAGE_CREDENTIALS_KEY: + showConfigurePolicyAndManageCredentialsPrompt(); + return true; case INSTALL_KEY_CERTIFICATE_KEY: Util.showFileViewer(this, INSTALL_KEY_CERTIFICATE_REQUEST_CODE); @@ -2739,6 +2753,63 @@ public void onClick(DialogInterface dialog, int which) { .show(); } + private void showConfigurePolicyAndManageCredentialsPrompt() { + if (getActivity() == null || getActivity().isFinishing()) { + return; + } + final String appUriPolicyName = "appUriPolicy"; + final String defaultPolicy = + "com.android.chrome#client.badssl.com:443#testAlias\n" + + "com.android.chrome#prod.idrix.eu/secure#testAlias\n" + + "de.blinkt.openvpn#192.168.0.1#vpnAlias"; + LinearLayout inputContainer = (LinearLayout) getActivity().getLayoutInflater() + .inflate(R.layout.simple_edittext, null); + final EditText editText = (EditText) inputContainer.findViewById(R.id.input); + editText.setSingleLine(false); + editText.setHint(defaultPolicy); + editText.setText(PreferenceManager.getDefaultSharedPreferences(getActivity()).getString( + appUriPolicyName, defaultPolicy)); + + new AlertDialog.Builder(getActivity()) + .setTitle(getString(R.string.request_manage_credentials)) + .setView(inputContainer) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String policy = editText.getText().toString(); + if (TextUtils.isEmpty(policy)) policy = defaultPolicy; + try { + requestToManageCredentials(policy); + SharedPreferences.Editor editor = + PreferenceManager.getDefaultSharedPreferences(getActivity()).edit(); + editor.putString(appUriPolicyName, policy); + editor.commit(); + } finally { + dialog.dismiss(); + } + } + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + + private void requestToManageCredentials(String policyStr) { + AppUriAuthenticationPolicy.Builder builder = new AppUriAuthenticationPolicy.Builder(); + String[] policies = policyStr.split("\n"); + for (int i = 0; i < policies.length; i++) { + String[] segments = policies[i].split("#"); + if (segments.length != 3) { + showToast(String.format( + getString(R.string.invalid_app_uri_policy), policies[i])); + return; + } + builder.addAppAndUriMapping(segments[0], + new Uri.Builder().authority(segments[1]).build(), segments[2]); + } + startActivityForResult(KeyChain.createManageCredentialsIntent(builder.build()), + REQUEST_MANAGE_CREDENTIALS_REQUEST_CODE); + } + /** * Imports a certificate to the managed profile. If the provided password failed to decrypt the * given certificate, shows a try again prompt. Otherwise, shows a prompt for the certificate diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c3a4a68e..bdbdcae7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -514,6 +514,7 @@ Certificate management + Request to manage credentials Install a private/public key pair Remove a private/public key pair The key pair is removed. @@ -564,6 +565,7 @@ StrongBox-related options Use StrongBox Use Individual Attestation Certificate + Invalid policy string: %1$s diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml index 3571203e..d04c606d 100644 --- a/app/src/main/res/xml/device_policy_header.xml +++ b/app/src/main/res/xml/device_policy_header.xml @@ -257,6 +257,11 @@ + Date: Thu, 31 Dec 2020 11:51:54 +0000 Subject: [PATCH 21/75] TestDPC: Support Enrollment-Specific ID TestDPC support for ESID consists of two parts: * A new dialog for setting Organization ID. * Displaying the generated ESID. Test: Manual, provision a work profile and set an Organization ID. Bug: 168627890 Change-Id: I4f54fd711e1e1aca3416d83240884a5b35bc2183 --- .../policy/PolicyManagementFragment.java | 65 ++++++++++++++++++- app/src/main/res/values/strings.xml | 4 ++ app/src/main/res/xml/device_policy_header.xml | 10 +++ 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java index 1f0c20f3..befb6b33 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java @@ -148,7 +148,6 @@ import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -419,6 +418,8 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag private static final String SUSPEND_PERSONAL_APPS_KEY = "suspend_personal_apps"; private static final String PROFILE_MAX_TIME_OFF_KEY = "profile_max_time_off"; private static final String COMMON_CRITERIA_MODE_KEY = "common_criteria_mode"; + private static final String SET_ORGANIZATION_ID_KEY = "set_organization_id"; + private static final String ENROLLMENT_SPECIFIC_ID_KEY = "enrollment_specific_id"; private static final String BATTERY_PLUGGED_ANY = Integer.toString( BatteryManager.BATTERY_PLUGGED_AC | @@ -746,6 +747,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { findPreference(CROSS_PROFILE_CALENDAR_KEY).setOnPreferenceClickListener(this); findPreference(FACTORY_RESET_ORG_OWNED_DEVICE).setOnPreferenceClickListener(this); findPreference(SET_FACTORY_RESET_PROTECTION_POLICY_KEY).setOnPreferenceClickListener(this); + findPreference(SET_ORGANIZATION_ID_KEY).setOnPreferenceClickListener(this); DpcPreference bindDeviceAdminPreference = (DpcPreference) findPreference(BIND_DEVICE_ADMIN_POLICIES); @@ -791,6 +793,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { loadAppFeedbackNotifications(); loadAppStatus(); loadSecurityPatch(); + loadEnrollmentSpecificId(); loadIsEphemeralUserUi(); reloadCameraDisableUi(); reloadScreenCaptureDisableUi(); @@ -1323,6 +1326,9 @@ public void onPositiveButtonClicked(String[] lockTaskArray) { case SET_FACTORY_RESET_PROTECTION_POLICY_KEY: showFragment(new FactoryResetProtectionPolicyFragment()); return true; + case SET_ORGANIZATION_ID_KEY: + showSetOrganizationIdDialog(); + return true; } return false; } @@ -2399,6 +2405,21 @@ private void loadSecurityPatch() { securityPatchPreference.setSummary(display); } + @TargetApi(VERSION_CODES.S) + private void loadEnrollmentSpecificId() { + Preference enrollmentSpecificIdPreference = findPreference(ENROLLMENT_SPECIFIC_ID_KEY); + String esid = ""; + try { + //TODO: Call directly when the S SDK is available. + esid = (String) ReflectionUtil.invoke(mDevicePolicyManager, "getEnrollmentSpecificId"); + } catch (ReflectionIsTemporaryException e) { + Log.e(TAG, "Error invoking getEnterpriseSpecificId", e); + esid = "Error"; + } + + enrollmentSpecificIdPreference.setSummary(esid); + } + @TargetApi(VERSION_CODES.P) private void loadSeparateChallenge() { final Preference separateChallengePreference = findPreference(SEPARATE_CHALLENGE_KEY); @@ -4056,6 +4077,48 @@ private void showSetProfileNameDialog() { .show(); } + /** + * Shows a dialog that asks the user to set an organization ID + */ + @TargetApi(VERSION_CODES.S) + private void showSetOrganizationIdDialog() { + if (getActivity() == null || getActivity().isFinishing()) { + return; + } + + final View dialogView = getActivity().getLayoutInflater().inflate( + R.layout.simple_edittext, null); + final EditText organizationIdTextEdit = (EditText) dialogView.findViewById( + R.id.input); + organizationIdTextEdit.setText(""); + + new AlertDialog.Builder(getActivity()) + .setTitle(R.string.set_organization_id) + .setView(dialogView) + .setPositiveButton(android.R.string.ok, (dialogInterface, i) -> { + final String organizationId = organizationIdTextEdit.getText().toString(); + if (organizationId.isEmpty()) { + showToast(R.string.organization_id_empty); + return; + } + setOrganizationId(organizationId); + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + + private void setOrganizationId(String organizationId) { + try { + //TODO: Call directly when the S SDK is available. + ReflectionUtil.invoke(mDevicePolicyManager, "setOrganizationId", organizationId); + } catch (ReflectionIsTemporaryException e) { + Log.e(TAG, "Error invoking setOrganizationId", e); + showToast("Error setting organization ID"); + } + + loadEnrollmentSpecificId(); + } + private void chooseAccount() { if (getActivity() == null || getActivity().isFinishing()) { return; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c3a4a68e..f5bfcdb3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1173,4 +1173,8 @@ This screen will be shown if the user sets device up with a managed account, and we believe that the user may want to set up a managed profile rather than a managed device.[CHAR LIMIT=100] --> or + Set Organization Identifier + Organization Identifier must not be empty. + Enrollment-specific ID + diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml index 3571203e..e92fb886 100644 --- a/app/src/main/res/xml/device_policy_header.xml +++ b/app/src/main/res/xml/device_policy_header.xml @@ -28,6 +28,12 @@ android:title="@string/security_patch" testdpc:admin="any" testdpc:minSdkVersion="M" /> + + From 80d0662aaf35d3977621193c3af5863a031c7de5 Mon Sep 17 00:00:00 2001 From: felipeal Date: Wed, 13 Jan 2021 19:37:45 -0800 Subject: [PATCH 22/75] Added [s|g]et-user-control-disabled-packages Shell commands. Test: adb shell dumpsys activity service --user 0 com.afwsamples.testdpc set-user-control-disabled-packages dude sweet Test: adb shell dumpsys activity service --user 0 com.afwsamples.testdpc get-user-control-disabled-packages Bug: 171350084 Change-Id: Icc7db59611c497f5c578713e6b91b0f06c0ae4cf (cherry picked from commit 835be726e2f050fc4ff660ea247fb5745e39cca0) --- .../testdpc/DevicePolicyManagerGateway.java | 13 ++++++- .../DevicePolicyManagerGatewayImpl.java | 19 +++++++++ .../com/afwsamples/testdpc/ShellCommand.java | 39 +++++++++++++++++-- 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java index 254c2e50..8d6b87f6 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java @@ -21,6 +21,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.util.List; import java.util.Set; import java.util.function.Consumer; @@ -148,7 +149,7 @@ void wipeData(int flags, @NonNull Consumer onSuccess, void setNetworkLogging(boolean enabled); /** - * See {@link android.app.admin.DevicePolicyManager#setOrganizationName(android.content.ComponentName, name)}. + * See {@link android.app.admin.DevicePolicyManager#setOrganizationName(android.content.ComponentName, String)}. */ void setOrganizationName(@Nullable CharSequence title, @NonNull Consumer onSuccess, @NonNull Consumer onError); @@ -157,6 +158,16 @@ void wipeData(int flags, @NonNull Consumer onSuccess, */ @Nullable CharSequence getOrganizationName(); + /** + * See {@link android.app.admin.DevicePolicyManager#setUserControlDisabledPackages(android.content.ComponentName, List)}. + */ + void setUserControlDisabledPackages(@Nullable List packages, @NonNull Consumer onSuccess, @NonNull Consumer onError); + + /** + * See {@link android.app.admin.DevicePolicyManager#getUserControlDisabledPackages(android.content.ComponentName)}. + */ + @NonNull List getUserControlDisabledPackages(); + /** * Used on error callbacks to indicate a {@link android.app.admin.DevicePolicyManager} method * call failed. diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java index e1326839..0cd822b4 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java @@ -28,6 +28,7 @@ import androidx.annotation.NonNull; import java.util.Arrays; +import java.util.List; import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -332,6 +333,24 @@ public CharSequence getOrganizationName() { return mDevicePolicyManager.getOrganizationName(mAdminComponentName); } + @Override + public void setUserControlDisabledPackages(List packages, Consumer onSuccess, + Consumer onError) { + Log.d(TAG, "setUserControlDisabledPackages(" + packages + ")"); + + try { + mDevicePolicyManager.setUserControlDisabledPackages(mAdminComponentName, packages); + onSuccess.accept(null); + } catch (Exception e) { + onError.accept(e); + } + } + + @Override + public List getUserControlDisabledPackages() { + return mDevicePolicyManager.getUserControlDisabledPackages(mAdminComponentName); + } + @Override public String toString() { return "DevicePolicyManagerGatewayImpl[" + mAdminComponentName + "]"; diff --git a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java index 97e63b66..f9a20d3f 100644 --- a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java +++ b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java @@ -27,9 +27,11 @@ import java.io.File; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; /** @@ -39,8 +41,6 @@ * */ final class ShellCommand { - // TODO(b/171350084): add unit tests - private static final String TAG = "TestDPCShellCommand"; private static final String CMD_CREATE_USER = "create-user"; @@ -62,6 +62,11 @@ final class ShellCommand { private static final String CMD_SET_NETWORK_LOGGING = "set-network-logging"; private static final String CMD_SET_ORGANIZATION_NAME = "set-organization-name"; private static final String CMD_GET_ORGANIZATION_NAME = "get-organization-name"; + private static final String CMD_SET_USER_CONTROL_DISABLED_PACKAGES = + "set-user-control-disabled-packages"; + private static final String CMD_GET_USER_CONTROL_DISABLED_PACKAGES = + "get-user-control-disabled-packages"; + private static final String ARG_FLAGS = "--flags"; private final Context mContext; @@ -143,6 +148,12 @@ public void run() { case CMD_GET_ORGANIZATION_NAME: execute(() -> getOrganizationName()); break; + case CMD_SET_USER_CONTROL_DISABLED_PACKAGES: + execute(() -> setUserControlDisabledPackages()); + break; + case CMD_GET_USER_CONTROL_DISABLED_PACKAGES: + execute(() -> getUserControlDisabledPackages()); + break; default: mWriter.printf("Invalid command: %s\n\n", cmd); showUsage(); @@ -188,6 +199,11 @@ private void showUsage() { mWriter.printf("\t%s [NAME] - set the organization name; use it without a name to reset\n", CMD_SET_ORGANIZATION_NAME); mWriter.printf("\t%s - get the organization name\n", CMD_GET_ORGANIZATION_NAME); + mWriter.printf("\t%s [PKG1] [PKG2] [PKGN] - sets the packages that the user cannot force " + + "stop or clear data. Use no args to reset it.\n", + CMD_SET_USER_CONTROL_DISABLED_PACKAGES); + mWriter.printf("\t%s - gets the packages that the user cannot force stop or " + + "clear data\n", CMD_GET_USER_CONTROL_DISABLED_PACKAGES); } private void createUser() { @@ -293,7 +309,7 @@ private void getAffiliationIds() { } private void setAffiliationIds() { - Set ids = new LinkedHashSet(mArgs.length - 1); + Set ids = new LinkedHashSet<>(mArgs.length - 1); for (int i = 1; i < mArgs.length; i++) { ids.add(mArgs[i]); } @@ -391,6 +407,23 @@ private void getOrganizationName() { mWriter.println(title); } + private void setUserControlDisabledPackages() { + List pkgs = new ArrayList<>(mArgs.length - 1); + for (int i = 1; i < mArgs.length; i++) { + pkgs.add(mArgs[i]); + } + Log.i(TAG, "setUserControlDisabledPackages(" + pkgs + ")"); + + mDevicePolicyManagerGateway.setUserControlDisabledPackages(pkgs, + (v) -> onSuccess("User-control disabled packages set to %s", pkgs), + (e) -> onError(e, "Error setting User-control disabled packages to %s", pkgs)); + } + + private void getUserControlDisabledPackages() { + List pkgs = mDevicePolicyManagerGateway.getUserControlDisabledPackages(); + pkgs.forEach((p) -> mWriter.println(p)); + } + private void execute(@NonNull Runnable r) { try { r.run(); From c5123e676461834d3e8c6ea0cfd0e737701aada5 Mon Sep 17 00:00:00 2001 From: Rubin Xu Date: Wed, 6 Jan 2021 11:36:03 +0000 Subject: [PATCH 23/75] Supports APIs related to new work challange enrolment flow * isActivePasswordSufficientForDeviceRequirement * EXTRA_DEVICE_PASSWORD_REQUIREMENT_ONLY Bug: 169832516 Test: manual Change-Id: I05514357ff3727e94465b3a9e7a8cf594ea82d6b --- .../testdpc/policy/PolicyManagementFragment.java | 16 +++++++++++++++- app/src/main/res/values/strings.xml | 3 ++- app/src/main/res/xml/device_policy_header.xml | 5 +++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java index 1f0c20f3..51f715e3 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java @@ -398,6 +398,8 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag private static final String SET_REQUIRED_PASSWORD_COMPLEXITY_ON_PARENT = "set_required_password_complexity_on_parent"; private static final String SET_PROFILE_PARENT_NEW_PASSWORD = "set_profile_parent_new_password"; + private static final String SET_PROFILE_PARENT_NEW_PASSWORD_DEVICE_REQUIREMENT = + "set_profile_parent_new_password_device_requirement"; private static final String BIND_DEVICE_ADMIN_POLICIES = "bind_device_admin_policies"; private static final String CROSS_PROFILE_APPS = "cross_profile_apps"; private static final String CROSS_PROFILE_APPS_WHITELIST = "cross_profile_apps_whitelist"; @@ -727,6 +729,8 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { findPreference(SET_LONG_SUPPORT_MESSAGE_KEY).setOnPreferenceClickListener(this); findPreference(SET_NEW_PASSWORD).setOnPreferenceClickListener(this); findPreference(SET_PROFILE_PARENT_NEW_PASSWORD).setOnPreferenceClickListener(this); + findPreference(SET_PROFILE_PARENT_NEW_PASSWORD_DEVICE_REQUIREMENT) + .setOnPreferenceClickListener(this); findPreference(CROSS_PROFILE_APPS).setOnPreferenceClickListener(this); findPreference(CROSS_PROFILE_APPS_WHITELIST).setOnPreferenceClickListener(this); @@ -1268,6 +1272,10 @@ public void onPositiveButtonClicked(String[] lockTaskArray) { startActivity( new Intent(DevicePolicyManager.ACTION_SET_NEW_PARENT_PROFILE_PASSWORD)); return true; + case SET_PROFILE_PARENT_NEW_PASSWORD_DEVICE_REQUIREMENT: + startActivity(new Intent(DevicePolicyManager.ACTION_SET_NEW_PARENT_PROFILE_PASSWORD) + .putExtra("android.app.extra.DEVICE_PASSWORD_REQUIREMENT_ONLY", true)); + return true; case BIND_DEVICE_ADMIN_POLICIES: showFragment(new BindDeviceAdminFragment()); return true; @@ -2514,8 +2522,14 @@ private void loadPasswordCompliant() { DevicePolicyManager parentDpm = mDevicePolicyManager.getParentProfileInstance(mAdminComponentName); boolean parentCompliant = parentDpm.isActivePasswordSufficient(); + final String deviceCompliant; + if (Util.SDK_INT < VERSION_CODES.S) { + deviceCompliant = "N/A"; + } else { + deviceCompliant = Boolean.toString(parentDpm.isActivePasswordSufficientForDeviceRequirement()); + } summary = String.format(getString(R.string.password_compliant_profile_summary), - Boolean.toString(parentCompliant), Boolean.toString(compliant)); + Boolean.toString(parentCompliant), deviceCompliant, Boolean.toString(compliant)); } else { summary = String.format(getString(R.string.password_compliant_summary), Boolean.toString(compliant)); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c3a4a68e..bc0ab56e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -963,7 +963,7 @@ No limit set on failed password attempts. Active password compliant - Personal: %1$s, Work: %2$s + Personal: %1$s (device req: %2$s), Work: %3$s Compliant %1$s @@ -1057,6 +1057,7 @@ Set required password complexity Set required password complexity on parent Request user to set profile parent new password + Request user to set profile parent new password (device requirement only) Bind to profile owner diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml index 3571203e..11dc9de0 100644 --- a/app/src/main/res/xml/device_policy_header.xml +++ b/app/src/main/res/xml/device_policy_header.xml @@ -419,6 +419,11 @@ android:title="@string/request_to_set_profile_parent_new_password" testdpc:minSdkVersion="N" testdpc:user="managedProfile" /> + From 7a0dca97c26adf4d5a4af8bbbba3b4e4d8915eb1 Mon Sep 17 00:00:00 2001 From: Alex Johnston Date: Mon, 23 Nov 2020 14:44:07 +0000 Subject: [PATCH 24/75] TestDPC disable IME on the personal profile Background: * On organization-owned devices with managed profiles, the work admin needs to be able to restrict IMEs on the personal side. * They should be able to restrict IMEs without having visibility on the personal side. This is done by introducing the ability for the admin to set the permitted IMEs on the parent profile. Changes: * Add new preference to modify permitted input methods on the parent profile Manual test steps: * Set up organization-owned device and download IME apps on the personal profile * Download TestDPC * Select prefence Set input methods on parent * Select allow only system IMEs and check Settings to see all non-system IMEs are disabled. * Select allow all IME apps and check Settings to see no IME apps are disabled. Bug: 170459562 Test: Manual testing Change-Id: Iab37320ba9e612c330734c4cc1561294fab165bd --- .../testdpc/DevicePolicyManagerGateway.java | 14 ++++++ .../DevicePolicyManagerGatewayImpl.java | 27 ++++++++-- .../com/afwsamples/testdpc/ShellCommand.java | 17 ++++++- .../policy/PolicyManagementFragment.java | 36 ++++++++++++++ .../permitted_input_methods_on_parent.xml | 49 +++++++++++++++++++ app/src/main/res/values/strings.xml | 6 +++ app/src/main/res/xml/device_policy_header.xml | 5 ++ 7 files changed, 149 insertions(+), 5 deletions(-) create mode 100644 app/src/main/res/layout/permitted_input_methods_on_parent.xml diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java index 8d6b87f6..1cd22521 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGateway.java @@ -15,6 +15,7 @@ */ package com.afwsamples.testdpc; +import android.content.ComponentName; import android.graphics.Bitmap; import android.os.UserHandle; import android.os.UserManager; @@ -168,6 +169,19 @@ void wipeData(int flags, @NonNull Consumer onSuccess, */ @NonNull List getUserControlDisabledPackages(); + /* + * See {@link android.app.admin.DevicePolicyManager#setPermittedInputMethods( + * android.content.ComponentName, List)}. + */ + boolean setPermittedInputMethods(List packageNames, @NonNull Consumer onSuccess, + @NonNull Consumer onError); + + /** + * See {@link android.app.admin.DevicePolicyManager#setPermittedInputMethods( + * android.content.ComponentName, List)}. + */ + boolean setPermittedInputMethods(List packageNames); + /** * Used on error callbacks to indicate a {@link android.app.admin.DevicePolicyManager} method * call failed. diff --git a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java index 0cd822b4..3049e971 100644 --- a/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java +++ b/app/src/main/java/com/afwsamples/testdpc/DevicePolicyManagerGatewayImpl.java @@ -24,10 +24,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.Log; - import androidx.annotation.NonNull; - -import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.function.Consumer; @@ -335,7 +332,7 @@ public CharSequence getOrganizationName() { @Override public void setUserControlDisabledPackages(List packages, Consumer onSuccess, - Consumer onError) { + Consumer onError) { Log.d(TAG, "setUserControlDisabledPackages(" + packages + ")"); try { @@ -351,6 +348,28 @@ public List getUserControlDisabledPackages() { return mDevicePolicyManager.getUserControlDisabledPackages(mAdminComponentName); } + public boolean setPermittedInputMethods(List packageNames) { + String inputMethods = packageNames != null ? String.join(",", packageNames) : ""; + String message = "setPermittedInputMethods(" + inputMethods + ")"; + return setPermittedInputMethods(packageNames, + (v) -> onSuccessLog(message), + (e) -> onErrorLog(message)); + } + + @Override + public boolean setPermittedInputMethods(List packageNames, Consumer onSuccess, + Consumer onError) { + boolean result = false; + try { + result = mDevicePolicyManager + .setPermittedInputMethods(mAdminComponentName, packageNames); + onSuccess.accept(null); + } catch (Exception e) { + onError.accept(e); + } + return result; + } + @Override public String toString() { return "DevicePolicyManagerGatewayImpl[" + mAdminComponentName + "]"; diff --git a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java index f9a20d3f..e4e5950e 100644 --- a/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java +++ b/app/src/main/java/com/afwsamples/testdpc/ShellCommand.java @@ -27,6 +27,7 @@ import java.io.File; import java.io.PrintWriter; +import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -66,7 +67,8 @@ final class ShellCommand { "set-user-control-disabled-packages"; private static final String CMD_GET_USER_CONTROL_DISABLED_PACKAGES = "get-user-control-disabled-packages"; - + private static final String CMD_SET_PERMITTED_INPUT_METHODS_PARENT = + "set-permitted-input-methods-parent"; private static final String ARG_FLAGS = "--flags"; private final Context mContext; @@ -154,6 +156,9 @@ public void run() { case CMD_GET_USER_CONTROL_DISABLED_PACKAGES: execute(() -> getUserControlDisabledPackages()); break; + case CMD_SET_PERMITTED_INPUT_METHODS_PARENT: + execute(() -> setPermittedInputMethodsOnParent()); + break; default: mWriter.printf("Invalid command: %s\n\n", cmd); showUsage(); @@ -424,6 +429,16 @@ private void getUserControlDisabledPackages() { pkgs.forEach((p) -> mWriter.println(p)); } + private void setPermittedInputMethodsOnParent() { + List inputMethods = new ArrayList(mArgs.length - 1); + for (int i = 1; i < mArgs.length; i++) { + inputMethods.add(mArgs[i]); + } + DevicePolicyManagerGateway parentDpmGateway = + DevicePolicyManagerGatewayImpl.forParentProfile(mContext); + parentDpmGateway.setPermittedInputMethods(inputMethods); + } + private void execute(@NonNull Runnable r) { try { r.run(); diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java index befb6b33..e7707f53 100644 --- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java +++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java @@ -74,6 +74,7 @@ import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.ArrayAdapter; +import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; import android.widget.LinearLayout; @@ -359,6 +360,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag private static final String SET_DISABLE_ACCOUNT_MANAGEMENT_KEY = "set_disable_account_management"; private static final String SET_INPUT_METHODS_KEY = "set_input_methods"; + private static final String SET_INPUT_METHODS_ON_PARENT_KEY = "set_input_methods_on_parent"; private static final String SET_NOTIFICATION_LISTENERS_KEY = "set_notification_listeners"; private static final String SET_NOTIFICATION_LISTENERS_TEXT_KEY = "set_notification_listeners_text"; private static final String SET_LONG_SUPPORT_MESSAGE_KEY = "set_long_support_message"; @@ -658,6 +660,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { : R.string.requires_network_logs); findPreference(SET_ACCESSIBILITY_SERVICES_KEY).setOnPreferenceClickListener(this); findPreference(SET_INPUT_METHODS_KEY).setOnPreferenceClickListener(this); + findPreference(SET_INPUT_METHODS_ON_PARENT_KEY).setOnPreferenceClickListener(this); findPreference(SET_NOTIFICATION_LISTENERS_KEY).setOnPreferenceClickListener(this); findPreference(SET_NOTIFICATION_LISTENERS_TEXT_KEY).setOnPreferenceClickListener(this); findPreference(SET_DISABLE_ACCOUNT_MANAGEMENT_KEY).setOnPreferenceClickListener(this); @@ -1029,6 +1032,9 @@ public void onPositiveButtonClicked(String[] lockTaskArray) { mGetInputMethodsTask = new GetInputMethodsTask(); mGetInputMethodsTask.execute(); return true; + case SET_INPUT_METHODS_ON_PARENT_KEY: + setPermittedInputMethodsOnParent(); + return true; case SET_NOTIFICATION_LISTENERS_KEY: // Avoid starting the same task twice. if (mGetNotificationListenersTask != null @@ -3678,6 +3684,36 @@ protected void setPermittedComponentsList(List permittedInputMethods) { } } + @RequiresApi(api = VERSION_CODES.S) + private void setPermittedInputMethodsOnParent() { + if (getActivity() == null || getActivity().isFinishing()) { + return; + } + DevicePolicyManagerGateway parentDpmGateway = + DevicePolicyManagerGatewayImpl.forParentProfile(getActivity()); + View view = getActivity().getLayoutInflater() + .inflate(R.layout.permitted_input_methods_on_parent, null); + + Button allInputMethodsButton = view.findViewById(R.id.all_input_methods_button); + allInputMethodsButton.setOnClickListener(v -> { + boolean result = parentDpmGateway.setPermittedInputMethods(null); + showToast(result + ? R.string.all_input_methods_on_parent + : R.string.add_input_method_on_parent_fail); + }); + Button systemInputMethodsButton = view.findViewById(R.id.system_input_methods_button); + systemInputMethodsButton.setOnClickListener(v -> { + boolean result = parentDpmGateway.setPermittedInputMethods(new ArrayList<>()); + showToast(result + ? R.string.system_input_methods_on_parent + : R.string.add_input_method_on_parent_fail); + }); + + new AlertDialog.Builder(getActivity()) + .setView(view) + .show(); + } + @TargetApi(VERSION_CODES.O) private void setNotificationWhitelistEditBox() { if (getActivity() == null || getActivity().isFinishing()) { diff --git a/app/src/main/res/layout/permitted_input_methods_on_parent.xml b/app/src/main/res/layout/permitted_input_methods_on_parent.xml new file mode 100644 index 00000000..a7cc8e7b --- /dev/null +++ b/app/src/main/res/layout/permitted_input_methods_on_parent.xml @@ -0,0 +1,49 @@ + + + + + + + +