Loading core/java/android/app/admin/DevicePolicyManager.java +17 −8 Original line number Diff line number Diff line Loading @@ -18595,18 +18595,27 @@ public class DevicePolicyManager { @Nullable T value) { throwIfParentInstance("setPolicy"); if (mService != null) { // TODO(b/434655549): Implement as a generic handler. if (id.equals(PolicyIdentifier.SCREEN_CAPTURE_DISABLED)) { if (value == null) return; // No way to clear the policy. // TODO(b/434615264): Actually use the scope here. setScreenCaptureDisabled(null, (Boolean) value); } else { throw new IllegalArgumentException("Unhandled policy " + id); try { mService.setPolicy(mContext.getPackageName(), id.getId(), scope, policyValueToTransport(value)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } @Nullable private static PolicyValueTransport policyValueToTransport(@Nullable Object value) { return switch (value) { case null -> null; case Integer i -> PolicyValueTransport.integerField(i); case Boolean b -> PolicyValueTransport.booleanField(b); default -> throw new IllegalArgumentException( "Type of policy is not supported: " + value + "(" + value.getClass().getName() + ")"); }; } /** * Template free version of setPolicy for booleans. * core/java/android/app/admin/IDevicePolicyManager.aidl +3 −0 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ import android.telephony.data.ApnSetting; import com.android.internal.infra.AndroidFuture; import android.app.admin.DevicePolicyState; import android.app.admin.EnforcingAdmin; import android.app.admin.PolicyValueTransport; import java.util.List; Loading Loading @@ -654,4 +655,6 @@ interface IDevicePolicyManager { void setAppFunctionsPolicy(String callerPackageName, int policy); int getAppFunctionsPolicy(String callerPackageName, int userId); void setPolicy(in String callerPackageName, in String policy, in int scope, in PolicyValueTransport value); } core/java/android/app/admin/PolicyValueTransport.aidl 0 → 100644 +28 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 android.app.admin; /** * Internal IPC to send a policy value over the wire. * Currently only supports a limited set of built-in types. * * {@hide} */ union PolicyValueTransport { int integerField; boolean booleanField; } No newline at end of file services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +75 −0 Original line number Diff line number Diff line Loading @@ -146,6 +146,8 @@ import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANT import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED; import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_EXPLICITLY; import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT; import static android.app.admin.DevicePolicyManager.POLICY_SCOPE_DEVICE; import static android.app.admin.DevicePolicyManager.POLICY_SCOPE_USER; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OFF; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; Loading Loading @@ -328,9 +330,11 @@ import android.app.admin.ParcelableGranteeMap; import android.app.admin.ParcelableResource; import android.app.admin.PasswordMetrics; import android.app.admin.PasswordPolicy; import android.app.admin.PolicyIdentifier; import android.app.admin.PolicyKey; import android.app.admin.PolicySizeVerifier; import android.app.admin.PolicyValue; import android.app.admin.PolicyValueTransport; import android.app.admin.PreferentialNetworkServiceConfig; import android.app.admin.SecurityLog; import android.app.admin.SecurityLog.SecurityEvent; Loading Loading @@ -24762,4 +24766,75 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return Binder.withCleanCallingIdentity(() -> getHeadlessDeviceOwnerModeForDeviceOwner()); } private void setScreenCaptureDisabled(CallerIdentity caller, int scope, Boolean disabled) { if (scope != POLICY_SCOPE_DEVICE && scope != POLICY_SCOPE_USER) { throw new IllegalArgumentException("Invalid scope " + scope); } mPermissions.enforce(MANAGE_DEVICE_POLICY_SCREEN_CAPTURE, caller); EnforcingAdmin admin = getEnforcingAdmin(caller); if (scope == POLICY_SCOPE_DEVICE && !isDefaultDeviceOwner(caller) && !isProfileOwnerOfOrganizationOwnedDevice(caller)) { throw new SecurityException( "Caller must be a device owner or profile owner of an organization-owned " + "managed profile to be able to set the policy with " + "POLICY_SCOPE_DEVICE."); } // Clearing is done by false. if (disabled == null) { disabled = false; } switch (scope) { case POLICY_SCOPE_DEVICE: if (disabled) { mDevicePolicyEngine.setGlobalPolicy(PolicyDefinition.SCREEN_CAPTURE_DISABLED, admin, new BooleanPolicyValue(disabled)); } else { mDevicePolicyEngine.removeGlobalPolicy(PolicyDefinition.SCREEN_CAPTURE_DISABLED, admin); } break; case POLICY_SCOPE_USER: int userId = caller.getUserId(); if (disabled) { mDevicePolicyEngine.setLocalPolicy(PolicyDefinition.SCREEN_CAPTURE_DISABLED, admin, new BooleanPolicyValue(disabled), userId); } else { mDevicePolicyEngine.removeLocalPolicy(PolicyDefinition.SCREEN_CAPTURE_DISABLED, admin, userId); } break; default: throw new IllegalArgumentException( "SCREEN_CAPTURE_DISABLED only supports POLICY_SCOPE_DEVICE and " + "POLICY_SCOPE_USER"); } } @Override public void setPolicy(String callerPackageName, String id, int scope, PolicyValueTransport value) { if (!mHasFeature) { return; } CallerIdentity caller = getCallerIdentity(callerPackageName); Binder.withCleanCallingIdentity(() -> { if (id.equals(PolicyIdentifier.SCREEN_CAPTURE_DISABLED.getId())) { if (value.getTag() != PolicyValueTransport.Tag.booleanField) { throw new IllegalArgumentException( "SCREEN_CAPTURE_DISABLED requires a Boolean value"); } setScreenCaptureDisabled(caller, scope, value.getBooleanField()); } else { throw new IllegalArgumentException("Unhandled policy " + id); } }); } } Loading
core/java/android/app/admin/DevicePolicyManager.java +17 −8 Original line number Diff line number Diff line Loading @@ -18595,18 +18595,27 @@ public class DevicePolicyManager { @Nullable T value) { throwIfParentInstance("setPolicy"); if (mService != null) { // TODO(b/434655549): Implement as a generic handler. if (id.equals(PolicyIdentifier.SCREEN_CAPTURE_DISABLED)) { if (value == null) return; // No way to clear the policy. // TODO(b/434615264): Actually use the scope here. setScreenCaptureDisabled(null, (Boolean) value); } else { throw new IllegalArgumentException("Unhandled policy " + id); try { mService.setPolicy(mContext.getPackageName(), id.getId(), scope, policyValueToTransport(value)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } @Nullable private static PolicyValueTransport policyValueToTransport(@Nullable Object value) { return switch (value) { case null -> null; case Integer i -> PolicyValueTransport.integerField(i); case Boolean b -> PolicyValueTransport.booleanField(b); default -> throw new IllegalArgumentException( "Type of policy is not supported: " + value + "(" + value.getClass().getName() + ")"); }; } /** * Template free version of setPolicy for booleans. *
core/java/android/app/admin/IDevicePolicyManager.aidl +3 −0 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ import android.telephony.data.ApnSetting; import com.android.internal.infra.AndroidFuture; import android.app.admin.DevicePolicyState; import android.app.admin.EnforcingAdmin; import android.app.admin.PolicyValueTransport; import java.util.List; Loading Loading @@ -654,4 +655,6 @@ interface IDevicePolicyManager { void setAppFunctionsPolicy(String callerPackageName, int policy); int getAppFunctionsPolicy(String callerPackageName, int userId); void setPolicy(in String callerPackageName, in String policy, in int scope, in PolicyValueTransport value); }
core/java/android/app/admin/PolicyValueTransport.aidl 0 → 100644 +28 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 android.app.admin; /** * Internal IPC to send a policy value over the wire. * Currently only supports a limited set of built-in types. * * {@hide} */ union PolicyValueTransport { int integerField; boolean booleanField; } No newline at end of file
services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +75 −0 Original line number Diff line number Diff line Loading @@ -146,6 +146,8 @@ import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANT import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED; import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_EXPLICITLY; import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT; import static android.app.admin.DevicePolicyManager.POLICY_SCOPE_DEVICE; import static android.app.admin.DevicePolicyManager.POLICY_SCOPE_USER; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OFF; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; Loading Loading @@ -328,9 +330,11 @@ import android.app.admin.ParcelableGranteeMap; import android.app.admin.ParcelableResource; import android.app.admin.PasswordMetrics; import android.app.admin.PasswordPolicy; import android.app.admin.PolicyIdentifier; import android.app.admin.PolicyKey; import android.app.admin.PolicySizeVerifier; import android.app.admin.PolicyValue; import android.app.admin.PolicyValueTransport; import android.app.admin.PreferentialNetworkServiceConfig; import android.app.admin.SecurityLog; import android.app.admin.SecurityLog.SecurityEvent; Loading Loading @@ -24762,4 +24766,75 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return Binder.withCleanCallingIdentity(() -> getHeadlessDeviceOwnerModeForDeviceOwner()); } private void setScreenCaptureDisabled(CallerIdentity caller, int scope, Boolean disabled) { if (scope != POLICY_SCOPE_DEVICE && scope != POLICY_SCOPE_USER) { throw new IllegalArgumentException("Invalid scope " + scope); } mPermissions.enforce(MANAGE_DEVICE_POLICY_SCREEN_CAPTURE, caller); EnforcingAdmin admin = getEnforcingAdmin(caller); if (scope == POLICY_SCOPE_DEVICE && !isDefaultDeviceOwner(caller) && !isProfileOwnerOfOrganizationOwnedDevice(caller)) { throw new SecurityException( "Caller must be a device owner or profile owner of an organization-owned " + "managed profile to be able to set the policy with " + "POLICY_SCOPE_DEVICE."); } // Clearing is done by false. if (disabled == null) { disabled = false; } switch (scope) { case POLICY_SCOPE_DEVICE: if (disabled) { mDevicePolicyEngine.setGlobalPolicy(PolicyDefinition.SCREEN_CAPTURE_DISABLED, admin, new BooleanPolicyValue(disabled)); } else { mDevicePolicyEngine.removeGlobalPolicy(PolicyDefinition.SCREEN_CAPTURE_DISABLED, admin); } break; case POLICY_SCOPE_USER: int userId = caller.getUserId(); if (disabled) { mDevicePolicyEngine.setLocalPolicy(PolicyDefinition.SCREEN_CAPTURE_DISABLED, admin, new BooleanPolicyValue(disabled), userId); } else { mDevicePolicyEngine.removeLocalPolicy(PolicyDefinition.SCREEN_CAPTURE_DISABLED, admin, userId); } break; default: throw new IllegalArgumentException( "SCREEN_CAPTURE_DISABLED only supports POLICY_SCOPE_DEVICE and " + "POLICY_SCOPE_USER"); } } @Override public void setPolicy(String callerPackageName, String id, int scope, PolicyValueTransport value) { if (!mHasFeature) { return; } CallerIdentity caller = getCallerIdentity(callerPackageName); Binder.withCleanCallingIdentity(() -> { if (id.equals(PolicyIdentifier.SCREEN_CAPTURE_DISABLED.getId())) { if (value.getTag() != PolicyValueTransport.Tag.booleanField) { throw new IllegalArgumentException( "SCREEN_CAPTURE_DISABLED requires a Boolean value"); } setScreenCaptureDisabled(caller, scope, value.getBooleanField()); } else { throw new IllegalArgumentException("Unhandled policy " + id); } }); } }