diff options
138 files changed, 6660 insertions, 1493 deletions
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp index 56d91b2e4bc5..d8f693cdedcd 100644 --- a/ProtoLibraries.bp +++ b/ProtoLibraries.bp @@ -97,6 +97,8 @@ java_library_host { ], type: "full", }, + // b/267831518: Pin tradefed and dependencies to Java 11. + java_version: "11", // Protos have lots of MissingOverride and similar. errorprone: { enabled: false, diff --git a/core/api/current.txt b/core/api/current.txt index fcb3a14d3cec..971852edbc6d 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -7708,7 +7708,7 @@ package android.app.admin { method @RequiresPermission(value=android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, conditional=true) public int getNearbyAppStreamingPolicy(); method @RequiresPermission(value=android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, conditional=true) public int getNearbyNotificationStreamingPolicy(); method @Deprecated @ColorInt public int getOrganizationColor(@NonNull android.content.ComponentName); - method @Nullable public CharSequence getOrganizationName(@NonNull android.content.ComponentName); + method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY) public CharSequence getOrganizationName(@Nullable android.content.ComponentName); method public java.util.List<android.telephony.data.ApnSetting> getOverrideApns(@NonNull android.content.ComponentName); method @NonNull public android.app.admin.DevicePolicyManager getParentProfileInstance(@NonNull android.content.ComponentName); method @RequiresPermission(android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY) public int getPasswordComplexity(); @@ -7863,7 +7863,7 @@ package android.app.admin { method public void setNetworkLoggingEnabled(@Nullable android.content.ComponentName, boolean); method @Deprecated public void setOrganizationColor(@NonNull android.content.ComponentName, int); method public void setOrganizationId(@NonNull String); - method public void setOrganizationName(@NonNull android.content.ComponentName, @Nullable CharSequence); + method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY) public void setOrganizationName(@Nullable android.content.ComponentName, @Nullable CharSequence); method public void setOverrideApnsEnabled(@NonNull android.content.ComponentName, boolean); method @NonNull public String[] setPackagesSuspended(@NonNull android.content.ComponentName, @NonNull String[], boolean); method public void setPasswordExpirationTimeout(@NonNull android.content.ComponentName, long); @@ -8190,6 +8190,7 @@ package android.app.admin { method public int getResultCode(); field public static final int RESULT_FAILURE_CONFLICTING_ADMIN_POLICY = 1; // 0x1 field public static final int RESULT_FAILURE_UNKNOWN = -1; // 0xffffffff + field public static final int RESULT_POLICY_CLEARED = 2; // 0x2 field public static final int RESULT_SUCCESS = 0; // 0x0 } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 58299fd0113e..489b1eed77ff 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1197,6 +1197,15 @@ package android.app { package android.app.admin { + public abstract class Authority implements android.os.Parcelable { + method public int describeContents(); + } + + public final class DeviceAdminAuthority extends android.app.admin.Authority { + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DeviceAdminAuthority> CREATOR; + } + public final class DevicePolicyDrawableResource implements android.os.Parcelable { ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, @NonNull String, @NonNull String, @NonNull String, @DrawableRes int); ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, @NonNull String, @NonNull String, @DrawableRes int); @@ -1228,6 +1237,7 @@ package android.app.admin { method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getDeviceOwnerNameOnAnyUser(); method @Nullable public CharSequence getDeviceOwnerOrganizationName(); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getDeviceOwnerUser(); + method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.app.admin.DevicePolicyState getDevicePolicyState(); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedAccessibilityServices(int); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser(); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public java.util.List<android.os.UserHandle> getPolicyManagedProfiles(@NonNull android.os.UserHandle); @@ -1355,6 +1365,14 @@ package android.app.admin { method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void setStrings(@NonNull java.util.Set<android.app.admin.DevicePolicyStringResource>); } + public final class DevicePolicyState implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.Map<android.os.UserHandle,java.util.Map<android.app.admin.PolicyKey,android.app.admin.PolicyState<?>>> getPoliciesForAllUsers(); + method @NonNull public java.util.Map<android.app.admin.PolicyKey,android.app.admin.PolicyState<?>> getPoliciesForUser(@NonNull android.os.UserHandle); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DevicePolicyState> CREATOR; + } + public final class DevicePolicyStringResource implements android.os.Parcelable { ctor public DevicePolicyStringResource(@NonNull android.content.Context, @NonNull String, @StringRes int); method public int describeContents(); @@ -1364,6 +1382,20 @@ package android.app.admin { field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DevicePolicyStringResource> CREATOR; } + public final class DpcAuthority extends android.app.admin.Authority { + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DpcAuthority> CREATOR; + } + + public final class EnforcingAdmin implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.app.admin.Authority getAuthority(); + method @NonNull public String getPackageName(); + method @NonNull public android.os.UserHandle getUserHandle(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.EnforcingAdmin> CREATOR; + } + public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable { method public boolean canDeviceOwnerGrantSensorsPermissions(); method public int describeContents(); @@ -1391,6 +1423,21 @@ package android.app.admin { method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setTimeZone(@Nullable String); } + public final class IntentFilterPolicyKey extends android.app.admin.PolicyKey { + method public int describeContents(); + method @NonNull public android.content.IntentFilter getIntentFilter(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.IntentFilterPolicyKey> CREATOR; + } + + public final class LockTaskPolicy implements android.os.Parcelable { + method public int describeContents(); + method public int getFlags(); + method @NonNull public java.util.Set<java.lang.String> getPackages(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.LockTaskPolicy> CREATOR; + } + public final class ManagedProfileProvisioningParams implements android.os.Parcelable { method public int describeContents(); method @Nullable public android.accounts.Account getAccountToMigrate(); @@ -1416,6 +1463,39 @@ package android.app.admin { method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setProfileName(@Nullable String); } + public final class NoArgsPolicyKey extends android.app.admin.PolicyKey { + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.NoArgsPolicyKey> CREATOR; + } + + public final class PackagePermissionPolicyKey extends android.app.admin.PolicyKey { + method public int describeContents(); + method @NonNull public String getPackageName(); + method @NonNull public String getPermissionName(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.PackagePermissionPolicyKey> CREATOR; + } + + public final class PackagePolicyKey extends android.app.admin.PolicyKey { + method public int describeContents(); + method @NonNull public String getPackageName(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.PackagePolicyKey> CREATOR; + } + + public abstract class PolicyKey implements android.os.Parcelable { + method @NonNull public String getIdentifier(); + } + + public final class PolicyState<V> implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public V getCurrentResolvedPolicy(); + method @NonNull public java.util.LinkedHashMap<android.app.admin.EnforcingAdmin,V> getPoliciesSetByAdmins(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.PolicyState<?>> CREATOR; + } + public class ProvisioningException extends android.util.AndroidException { ctor public ProvisioningException(@NonNull Exception, int); ctor public ProvisioningException(@NonNull Exception, int, @Nullable String); @@ -1430,6 +1510,12 @@ package android.app.admin { field public static final int ERROR_UNKNOWN = 0; // 0x0 } + public final class RoleAuthority extends android.app.admin.Authority { + method @NonNull public java.util.Set<java.lang.String> getRoles(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.RoleAuthority> CREATOR; + } + public class SecurityLog { method @RequiresPermission(android.Manifest.permission.WRITE_SECURITY_LOG) public static int writeEvent(int, @NonNull java.lang.Object...); } @@ -1444,6 +1530,11 @@ package android.app.admin { method public int getType(); } + public final class UnknownAuthority extends android.app.admin.Authority { + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.UnknownAuthority> CREATOR; + } + } package android.app.ambientcontext { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 51e90d28abdc..4f2058d85d1f 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -544,6 +544,7 @@ package android.app.admin { method public void setDeviceOwnerType(@NonNull android.content.ComponentName, int); method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public void setNextOperationSafety(int, int); method @RequiresPermission(anyOf={android.Manifest.permission.MARK_DEVICE_ORGANIZATION_OWNED, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}, conditional=true) public void setProfileOwnerOnOrganizationOwnedDevice(@NonNull android.content.ComponentName, boolean); + method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean triggerDevicePolicyEngineMigration(boolean); field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED"; field public static final String ACTION_DEVICE_POLICY_CONSTANTS_CHANGED = "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED"; field public static final int DEVICE_OWNER_TYPE_DEFAULT = 0; // 0x0 @@ -592,10 +593,49 @@ package android.app.admin { field @Deprecated public static final int STATUS_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe } + public final class FlagUnion extends android.app.admin.ResolutionMechanism<java.lang.Integer> { + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FlagUnion> CREATOR; + } + + public final class MostRecent<V> extends android.app.admin.ResolutionMechanism<V> { + ctor public MostRecent(); + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.MostRecent> CREATOR; + } + + public final class MostRestrictive<V> extends android.app.admin.ResolutionMechanism<V> { + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.MostRestrictive<?>> CREATOR; + } + + public final class PolicyState<V> implements android.os.Parcelable { + method @NonNull public android.app.admin.ResolutionMechanism<V> getResolutionMechanism(); + } + + public abstract class ResolutionMechanism<V> implements android.os.Parcelable { + } + public static final class SecurityLog.SecurityEvent implements android.os.Parcelable { ctor public SecurityLog.SecurityEvent(long, byte[]); } + public final class StringSetUnion extends android.app.admin.ResolutionMechanism<java.util.Set<java.lang.String>> { + ctor public StringSetUnion(); + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.StringSetUnion> CREATOR; + } + + public final class TopPriority<V> extends android.app.admin.ResolutionMechanism<V> { + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.TopPriority<?>> CREATOR; + } + public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable { ctor public UnsafeStateException(int, int); method public int getOperation(); @@ -919,7 +959,7 @@ package android.content.pm { method public boolean isQuietModeEnabled(); method public boolean isRestricted(); method public boolean supportsSwitchTo(); - method public boolean supportsSwitchToByUser(); + method @Deprecated public boolean supportsSwitchToByUser(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.UserInfo> CREATOR; field public static final int FLAG_ADMIN = 2; // 0x2 diff --git a/core/java/android/app/admin/Authority.java b/core/java/android/app/admin/Authority.java new file mode 100644 index 000000000000..52f79cf7f319 --- /dev/null +++ b/core/java/android/app/admin/Authority.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 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; + +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.os.Parcelable; + +/** + * Abstract class used to identify the authority of the {@link EnforcingAdmin}. + * + * @hide + */ +// This is ok as the constructor is package-protected and all subclasses have implemented +// Parcelable. +@SuppressLint({"ParcelNotFinal", "ParcelCreator"}) +@SystemApi +public abstract class Authority implements Parcelable { + + /** + * @hide + */ + protected Authority() {} + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + return o != null && getClass() == o.getClass(); + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/app/admin/BooleanPolicyValue.java b/core/java/android/app/admin/BooleanPolicyValue.java new file mode 100644 index 000000000000..aa6f4a4b33ba --- /dev/null +++ b/core/java/android/app/admin/BooleanPolicyValue.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2023 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; + +import java.util.Objects; + +/** + * @hide + */ +public final class BooleanPolicyValue extends PolicyValue<Boolean> { + + public BooleanPolicyValue(boolean value) { + super(value); + } + + private BooleanPolicyValue(Parcel source) { + this(source.readBoolean()); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BooleanPolicyValue other = (BooleanPolicyValue) o; + return Objects.equals(getValue(), other.getValue()); + } + + @Override + public int hashCode() { + return Objects.hash(getValue()); + } + + @Override + public String toString() { + return "BooleanPolicyValue { mValue= " + getValue() + " }"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeBoolean(getValue()); + } + + @NonNull + public static final Creator<BooleanPolicyValue> CREATOR = + new Creator<BooleanPolicyValue>() { + @Override + public BooleanPolicyValue createFromParcel(Parcel source) { + return new BooleanPolicyValue(source); + } + + @Override + public BooleanPolicyValue[] newArray(int size) { + return new BooleanPolicyValue[size]; + } + }; +} diff --git a/core/java/android/app/admin/BundlePolicyValue.java b/core/java/android/app/admin/BundlePolicyValue.java new file mode 100644 index 000000000000..f9653a4e85b0 --- /dev/null +++ b/core/java/android/app/admin/BundlePolicyValue.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2023 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.os.Parcel; + +import java.util.Objects; + +/** + * @hide + */ +public final class BundlePolicyValue extends PolicyValue<Bundle> { + + public BundlePolicyValue(Bundle value) { + super(value); + } + + private BundlePolicyValue(Parcel source) { + this(source.readBundle()); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BundlePolicyValue other = (BundlePolicyValue) o; + return Objects.equals(getValue(), other.getValue()); + } + + @Override + public int hashCode() { + return Objects.hash(getValue()); + } + + @Override + public String toString() { + return "BundlePolicyValue { mValue= " + getValue() + " }"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeBundle(getValue()); + } + + @NonNull + public static final Creator<BundlePolicyValue> CREATOR = + new Creator<BundlePolicyValue>() { + @Override + public BundlePolicyValue createFromParcel(Parcel source) { + return new BundlePolicyValue(source); + } + + @Override + public BundlePolicyValue[] newArray(int size) { + return new BundlePolicyValue[size]; + } + }; +} diff --git a/core/java/android/app/admin/ComponentNamePolicyValue.java b/core/java/android/app/admin/ComponentNamePolicyValue.java new file mode 100644 index 000000000000..635e5826e0c5 --- /dev/null +++ b/core/java/android/app/admin/ComponentNamePolicyValue.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2023 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.os.Parcel; + +import java.util.Objects; + +/** + * @hide + */ +public final class ComponentNamePolicyValue extends PolicyValue<ComponentName> { + + public ComponentNamePolicyValue(@NonNull ComponentName value) { + super(value); + } + + private ComponentNamePolicyValue(Parcel source) { + this((ComponentName) source.readParcelable(ComponentName.class.getClassLoader())); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ComponentNamePolicyValue other = (ComponentNamePolicyValue) o; + return Objects.equals(getValue(), other.getValue()); + } + + @Override + public int hashCode() { + return Objects.hash(getValue()); + } + + @Override + public String toString() { + return "ComponentNamePolicyValue { mValue= " + getValue() + " }"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(getValue(), flags); + } + + @NonNull + public static final Creator<ComponentNamePolicyValue> CREATOR = + new Creator<ComponentNamePolicyValue>() { + @Override + public ComponentNamePolicyValue createFromParcel(Parcel source) { + return new ComponentNamePolicyValue(source); + } + + @Override + public ComponentNamePolicyValue[] newArray(int size) { + return new ComponentNamePolicyValue[size]; + } + }; +} diff --git a/core/java/android/app/admin/DeviceAdminAuthority.java b/core/java/android/app/admin/DeviceAdminAuthority.java new file mode 100644 index 000000000000..5d1ff1104b06 --- /dev/null +++ b/core/java/android/app/admin/DeviceAdminAuthority.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; + +/** + * Class used to identify that authority of the {@link EnforcingAdmin} setting a policy is a non-DPC + * device admin. + * + * @hide + */ +@SystemApi +public final class DeviceAdminAuthority extends Authority { + + /** + * @hide + */ + public static final DeviceAdminAuthority DEVICE_ADMIN_AUTHORITY = new DeviceAdminAuthority(); + + private DeviceAdminAuthority() {} + + @Override + public String toString() { + return "DeviceAdminAuthority {}"; + } + + @Override + public boolean equals(@Nullable Object o) { + return super.equals(o); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) {} + + @NonNull + public static final Creator<DeviceAdminAuthority> CREATOR = + new Creator<DeviceAdminAuthority>() { + @Override + public DeviceAdminAuthority createFromParcel(Parcel source) { + return new DeviceAdminAuthority(); + } + + @Override + public DeviceAdminAuthority[] newArray(int size) { + return new DeviceAdminAuthority[size]; + } + }; +} diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 3fe63d8af662..de62d740b182 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -18,6 +18,7 @@ package android.app.admin; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY; import static android.Manifest.permission.QUERY_ADMIN_POLICY; import static android.Manifest.permission.SET_TIME; import static android.Manifest.permission.SET_TIME_ZONE; @@ -4020,18 +4021,8 @@ public class DevicePolicyManager { /** * @hide */ - public static final String PERMISSION_GRANT_POLICY_KEY = "permissionGrant"; + public static final String PERMISSION_GRANT_POLICY = "permissionGrant"; - // TODO: Expose this as SystemAPI once we add the query API - /** - * @hide - */ - public static String PERMISSION_GRANT_POLICY( - @NonNull String packageName, @NonNull String permission) { - Objects.requireNonNull(packageName); - Objects.requireNonNull(permission); - return PERMISSION_GRANT_POLICY_KEY + "_" + packageName + "_" + permission; - } // TODO: Expose this as SystemAPI once we add the query API /** @@ -4060,6 +4051,12 @@ public class DevicePolicyManager { */ public static final String PACKAGE_UNINSTALL_BLOCKED_POLICY = "packageUninstallBlocked"; + // TODO: Expose this as SystemAPI once we add the query API + /** + * @hide + */ + public static final String APPLICATION_RESTRICTIONS_POLICY = "applicationRestrictions"; + /** * This object is a single place to tack on invalidation and disable calls. All * binder caches in this class derive from this Config, so all can be invalidated or @@ -8488,7 +8485,8 @@ public class DevicePolicyManager { * higher, to set whether auto time is required. If auto time is required, no user will be able * set the date and time and network date and time will be used. * <p> - * Note: if auto time is required the user can still manually set the time zone. + * Note: If auto time is required the user can still manually set the time zone. Staring from + * Android 11, if auto time is required, the user cannot manually set the time zone. * <p> * The calling device admin must be a device owner, or alternatively a profile owner from * Android 8.0 (API level 26) or higher. If it is not, a security exception will be thrown. @@ -13485,18 +13483,24 @@ public class DevicePolicyManager { } /** - * Called by the device owner (since API 26) or profile owner (since API 24) to set the name of - * the organization under management. + * Called by the device owner (since API 26) or profile owner (since API 24) or, starting from + * Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, + * holders of the permission + * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY} to set the + * name of the organization under management. * - * <p>If the organization name needs to be localized, it is the responsibility of the {@link - * DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast and set - * a new version of this string accordingly. + * <p>If the organization name needs to be localized, it is the responsibility of the caller + * to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast and set a new version of this + * string accordingly. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with or can be + * {@code null} if accessing with a permission without association with a DeviceAdminReceiver. * @param title The organization name or {@code null} to clear a previously set name. - * @throws SecurityException if {@code admin} is not a device or profile owner. + * @throws SecurityException if {@code admin} is not a device or profile owner or holder of the + * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY}. */ - public void setOrganizationName(@NonNull ComponentName admin, @Nullable CharSequence title) { + @RequiresPermission(MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY) + public void setOrganizationName(@Nullable ComponentName admin, @Nullable CharSequence title) { throwIfParentInstance("setOrganizationName"); try { mService.setOrganizationName(admin, title); @@ -13506,14 +13510,21 @@ public class DevicePolicyManager { } /** - * Called by a profile owner of a managed profile to retrieve the name of the organization under - * management. + * Called by the device owner (since API 26) or profile owner (since API 24) or, starting from + * Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, + * holders of the permission + * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY + * to retrieve the name of the organization under management. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with or can be + * {@code null} if accessing with a permission without association with a DeviceAdminReceiver. * @return The organization name or {@code null} if none is set. - * @throws SecurityException if {@code admin} is not a profile owner. + * @throws SecurityException if {@code admin} if {@code admin} is not a device or profile + * owner or holder of the + * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY}. */ - public @Nullable CharSequence getOrganizationName(@NonNull ComponentName admin) { + @RequiresPermission(MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY) + public @Nullable CharSequence getOrganizationName(@Nullable ComponentName admin) { throwIfParentInstance("getOrganizationName"); try { return mService.getOrganizationName(admin); @@ -16299,4 +16310,47 @@ public class DevicePolicyManager { } return false; } + + /** + * Returns a {@link DevicePolicyState} object containing information about the current state + * of device policies (e.g. values set by different admins, info about the enforcing admins, + * resolved policy, etc). + * + * @hide + */ + @SystemApi + @NonNull + @RequiresPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + public DevicePolicyState getDevicePolicyState() { + if (mService != null) { + try { + return mService.getDevicePolicyState(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return null; + } + + /** + * Triggers the data migration of device policies for existing DPCs to the Device Policy Engine. + * If {@code forceMigration} is set to {@code true} it skips the prerequisite checks before + * triggering the migration. + * + * <p>Returns {@code true} if migration was completed successfully, {@code false} otherwise. + * + * @hide + */ + @TestApi + @RequiresPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + public boolean triggerDevicePolicyEngineMigration(boolean forceMigration) { + if (mService != null) { + try { + return mService.triggerDevicePolicyEngineMigration(forceMigration); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } } diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index 1b5c1966decc..807661582e99 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -21,9 +21,11 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.Intent; +import android.os.Bundle; import android.os.UserHandle; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -309,4 +311,10 @@ public abstract class DevicePolicyManagerInternal { * Returns whether the application exemptions feature flag is enabled. */ public abstract boolean isApplicationExemptionsFlagEnabled(); + + /** + * Returns the application restrictions set by each admin for the given {@code packageName}. + */ + public abstract Map<String, Bundle> getApplicationRestrictionsPerAdmin( + String packageName, int userId); } diff --git a/core/java/android/app/admin/DevicePolicyState.aidl b/core/java/android/app/admin/DevicePolicyState.aidl new file mode 100644 index 000000000000..0eac33f6829e --- /dev/null +++ b/core/java/android/app/admin/DevicePolicyState.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2023 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; + +parcelable DevicePolicyState;
\ No newline at end of file diff --git a/core/java/android/app/admin/DevicePolicyState.java b/core/java/android/app/admin/DevicePolicyState.java new file mode 100644 index 000000000000..ee33b00312d5 --- /dev/null +++ b/core/java/android/app/admin/DevicePolicyState.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2023 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; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.UserHandle; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * A class containing information about the current state of device policies (e.g. values set by + * different admins, info about the enforcing admins, resolved policy, etc). + * + * @hide + */ +@SystemApi +public final class DevicePolicyState implements Parcelable { + private final Map<UserHandle, Map<PolicyKey, PolicyState<?>>> mPolicies; + + /** + * @hide + */ + public DevicePolicyState(Map<UserHandle, Map<PolicyKey, PolicyState<?>>> policies) { + mPolicies = Objects.requireNonNull(policies); + } + + private DevicePolicyState(Parcel source) { + mPolicies = new HashMap<>(); + int usersSize = source.readInt(); + for (int i = 0; i < usersSize; i++) { + UserHandle userHandle = UserHandle.of(source.readInt()); + mPolicies.put(userHandle, new HashMap<>()); + int policiesSize = source.readInt(); + for (int j = 0; j < policiesSize; j++) { + PolicyKey policyKey = + source.readParcelable(PolicyKey.class.getClassLoader()); + PolicyState<?> policyState = + source.readParcelable(PolicyState.class.getClassLoader()); + mPolicies.get(userHandle).put(policyKey, policyState); + } + } + } + + /** + * Returns a {@link Map} of current policies for each {@link UserHandle}, note that users + * that do not have any policies set will not be included in the returned map. + * + * <p> If the device has global policies affecting all users, it will be returned under + * {@link UserHandle#ALL}. + */ + @NonNull + public Map<UserHandle, Map<PolicyKey, PolicyState<?>>> getPoliciesForAllUsers() { + return mPolicies; + } + + /** + * Returns a {@link Map} of current policies for the provided {@code user}, use + * {@link UserHandle#ALL} to get global policies affecting all users on the device. + */ + @NonNull + public Map<PolicyKey, PolicyState<?>> getPoliciesForUser(@NonNull UserHandle user) { + return mPolicies.containsKey(user) ? mPolicies.get(user) : new HashMap<>(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mPolicies.size()); + for (UserHandle user : mPolicies.keySet()) { + dest.writeInt(user.getIdentifier()); + dest.writeInt(mPolicies.get(user).size()); + for (PolicyKey key : mPolicies.get(user).keySet()) { + dest.writeParcelable(key, flags); + dest.writeParcelable(mPolicies.get(user).get(key), flags); + } + } + } + + public static final @NonNull Parcelable.Creator<DevicePolicyState> CREATOR = + new Parcelable.Creator<DevicePolicyState>() { + @Override + public DevicePolicyState createFromParcel(Parcel source) { + return new DevicePolicyState(source); + } + + @Override + public DevicePolicyState[] newArray(int size) { + return new DevicePolicyState[size]; + } + }; +} diff --git a/core/java/android/app/admin/DpcAuthority.java b/core/java/android/app/admin/DpcAuthority.java new file mode 100644 index 000000000000..72c16bc2c52c --- /dev/null +++ b/core/java/android/app/admin/DpcAuthority.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; + +/** + * Class used to identify that authority of the {@link EnforcingAdmin} setting a policy is a DPC or + * an app acting as a DPC (e.g. delegate apps). + * + * @hide + */ +@SystemApi +public final class DpcAuthority extends Authority { + + /** + * @hide + */ + public static final DpcAuthority DPC_AUTHORITY = new DpcAuthority(); + + private DpcAuthority() {} + + @Override + public String toString() { + return "DpcAuthority {}"; + } + + @Override + public boolean equals(@Nullable Object o) { + return super.equals(o); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) {} + + @NonNull + public static final Creator<DpcAuthority> CREATOR = + new Creator<DpcAuthority>() { + @Override + public DpcAuthority createFromParcel(Parcel source) { + return new DpcAuthority(); + } + + @Override + public DpcAuthority[] newArray(int size) { + return new DpcAuthority[size]; + } + }; +} diff --git a/core/java/android/app/admin/EnforcingAdmin.java b/core/java/android/app/admin/EnforcingAdmin.java new file mode 100644 index 000000000000..178646739c5e --- /dev/null +++ b/core/java/android/app/admin/EnforcingAdmin.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2023 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.UserHandle; + +import java.util.Objects; + +/** + * Class containing info about the admin enforcing a certain policy, e.g. its {@code packageName} + * and {@link Authority}. + * + * @hide + */ +@SystemApi +public final class EnforcingAdmin implements Parcelable { + private final String mPackageName; + private final Authority mAuthority; + private final UserHandle mUserHandle; + + /** + * @hide + */ + public EnforcingAdmin( + @NonNull String packageName, @NonNull Authority authority, + @NonNull UserHandle userHandle) { + mPackageName = Objects.requireNonNull(packageName); + mAuthority = Objects.requireNonNull(authority); + mUserHandle = Objects.requireNonNull(userHandle); + } + + private EnforcingAdmin(Parcel source) { + mPackageName = Objects.requireNonNull(source.readString()); + mUserHandle = new UserHandle(source.readInt()); + mAuthority = Objects.requireNonNull( + source.readParcelable(Authority.class.getClassLoader())); + } + + /** + * Returns the {@link Authority} on which the admin is acting on, e.g. DPC, DeviceAdmin, etc. + */ + @NonNull + public Authority getAuthority() { + return mAuthority; + } + + /** + * Returns the package name of the admin. + */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + /** + * Returns the {@link UserHandle} on which the admin is installed on. + */ + @NonNull + public UserHandle getUserHandle() { + return mUserHandle; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + EnforcingAdmin other = (EnforcingAdmin) o; + return Objects.equals(mPackageName, other.mPackageName) + && Objects.equals(mAuthority, other.mAuthority) + && Objects.equals(mUserHandle, other.mUserHandle); + } + + @Override + public int hashCode() { + return Objects.hash(mPackageName, mAuthority, mUserHandle); + } + + @Override + public String toString() { + return "EnforcingAdmin { mPackageName= " + mPackageName + ", mAuthority= " + mAuthority + + ", mUserHandle= " + mUserHandle + " }"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mPackageName); + dest.writeInt(mUserHandle.getIdentifier()); + dest.writeParcelable(mAuthority, flags); + } + + @NonNull + public static final Parcelable.Creator<EnforcingAdmin> CREATOR = + new Parcelable.Creator<EnforcingAdmin>() { + @Override + public EnforcingAdmin createFromParcel(Parcel source) { + return new EnforcingAdmin(source); + } + + @Override + public EnforcingAdmin[] newArray(int size) { + return new EnforcingAdmin[size]; + } + }; +} diff --git a/core/java/android/app/admin/FlagUnion.java b/core/java/android/app/admin/FlagUnion.java new file mode 100644 index 000000000000..be924d886d04 --- /dev/null +++ b/core/java/android/app/admin/FlagUnion.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2022 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Class to identify a union resolution mechanism for flag policies, it's used to resolve the + * enforced policy when being set by multiple admins (see + * {@link PolicyState#getResolutionMechanism()}). + * + * @hide + */ +@TestApi +public final class FlagUnion extends ResolutionMechanism<Integer> { + + /** + * @hide + */ + public static final FlagUnion FLAG_UNION = new FlagUnion(); + + private FlagUnion(){}; + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + return o != null && getClass() == o.getClass(); + } + + @Override + public int hashCode() { + return Objects.hash(this); + } + + @Override + public String toString() { + return "FlagUnion {}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) {} + + @NonNull + public static final Parcelable.Creator<FlagUnion> CREATOR = + new Parcelable.Creator<FlagUnion>() { + @Override + public FlagUnion createFromParcel(Parcel source) { + return new FlagUnion(); + } + + @Override + public FlagUnion[] newArray(int size) { + return new FlagUnion[size]; + } + }; + +} diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 1ed39c51cd5b..c86852a9c615 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -53,6 +53,7 @@ import android.security.keymaster.KeymasterCertificateChain; import android.security.keystore.ParcelableKeyGenParameterSpec; import android.telephony.data.ApnSetting; import com.android.internal.infra.AndroidFuture; +import android.app.admin.DevicePolicyState; import java.util.List; @@ -591,4 +592,8 @@ interface IDevicePolicyManager { void setManagedSubscriptionsPolicy(in ManagedSubscriptionsPolicy policy); ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy(); + + DevicePolicyState getDevicePolicyState(); + + boolean triggerDevicePolicyEngineMigration(boolean forceMigration); } diff --git a/core/java/android/app/admin/IntegerPolicyValue.java b/core/java/android/app/admin/IntegerPolicyValue.java new file mode 100644 index 000000000000..6fa6180463f5 --- /dev/null +++ b/core/java/android/app/admin/IntegerPolicyValue.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2023 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; + +import java.util.Objects; + +/** + * @hide + */ +public final class IntegerPolicyValue extends PolicyValue<Integer> { + + public IntegerPolicyValue(int value) { + super(value); + } + + private IntegerPolicyValue(Parcel source) { + this(source.readInt()); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + IntegerPolicyValue other = (IntegerPolicyValue) o; + return Objects.equals(getValue(), other.getValue()); + } + + @Override + public int hashCode() { + return Objects.hash(getValue()); + } + + @Override + public String toString() { + return "IntegerPolicyValue { mValue= " + getValue() + " }"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(getValue()); + } + + @NonNull + public static final Creator<IntegerPolicyValue> CREATOR = + new Creator<IntegerPolicyValue>() { + @Override + public IntegerPolicyValue createFromParcel(Parcel source) { + return new IntegerPolicyValue(source); + } + + @Override + public IntegerPolicyValue[] newArray(int size) { + return new IntegerPolicyValue[size]; + } + }; +} diff --git a/core/java/android/app/admin/IntentFilterPolicyKey.java b/core/java/android/app/admin/IntentFilterPolicyKey.java new file mode 100644 index 000000000000..d7eb10197ced --- /dev/null +++ b/core/java/android/app/admin/IntentFilterPolicyKey.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2023 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; + +import static android.app.admin.PolicyUpdatesReceiver.EXTRA_INTENT_FILTER; +import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_BUNDLE_KEY; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.IntentFilter; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.Objects; + +/** + * Class used to identify a policy that relates to a certain {@link IntentFilter} + * (e.g. {@link DevicePolicyManager#addPersistentPreferredActivity}). + * + * @hide + */ +@SystemApi +public final class IntentFilterPolicyKey extends PolicyKey { + private final IntentFilter mFilter; + + /** + * @hide + */ + public IntentFilterPolicyKey(@NonNull String identifier, @NonNull IntentFilter filter) { + super(identifier); + mFilter = Objects.requireNonNull(filter); + } + + /** + * @hide + */ + public IntentFilterPolicyKey(@NonNull String identifier) { + super(identifier); + mFilter = null; + } + + private IntentFilterPolicyKey(Parcel source) { + super(source.readString()); + mFilter = source.readTypedObject(IntentFilter.CREATOR); + } + + /** + * Returns the {@link IntentFilter} this policy relates to. + */ + @NonNull + public IntentFilter getIntentFilter() { + return mFilter; + } + + /** + * @hide + */ + @Override + public void saveToXml(TypedXmlSerializer serializer) throws IOException { + serializer.attribute(/* namespace= */ null, ATTR_POLICY_IDENTIFIER, getIdentifier()); + mFilter.writeToXml(serializer); + } + + /** + * @hide + */ + @Override + public IntentFilterPolicyKey readFromXml(TypedXmlPullParser parser) + throws XmlPullParserException, IOException { + String identifier = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_IDENTIFIER); + IntentFilter filter = new IntentFilter(); + filter.readFromXml(parser); + return new IntentFilterPolicyKey(identifier, filter); + } + + /** + * @hide + */ + @Override + public void writeToBundle(Bundle bundle) { + super.writeToBundle(bundle); + Bundle extraPolicyParams = new Bundle(); + extraPolicyParams.putParcelable(EXTRA_INTENT_FILTER, mFilter); + bundle.putBundle(EXTRA_POLICY_BUNDLE_KEY, extraPolicyParams); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + IntentFilterPolicyKey other = (IntentFilterPolicyKey) o; + return Objects.equals(getIdentifier(), other.getIdentifier()) + && IntentFilter.filterEquals(mFilter, other.mFilter); + } + + @Override + public int hashCode() { + return Objects.hash(getIdentifier(), mFilter); + } + + @Override + public String toString() { + return "IntentFilterPolicyKey{mKey= " + getIdentifier() + "; mFilter= " + mFilter + "}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(getIdentifier()); + mFilter.writeToParcel(dest, flags); + } + + @NonNull + public static final Parcelable.Creator<IntentFilterPolicyKey> CREATOR = + new Parcelable.Creator<IntentFilterPolicyKey>() { + @Override + public IntentFilterPolicyKey createFromParcel(Parcel source) { + return new IntentFilterPolicyKey(source); + } + + @Override + public IntentFilterPolicyKey[] newArray(int size) { + return new IntentFilterPolicyKey[size]; + } + }; +} diff --git a/core/java/android/app/admin/LockTaskPolicy.java b/core/java/android/app/admin/LockTaskPolicy.java new file mode 100644 index 000000000000..28757df41f5e --- /dev/null +++ b/core/java/android/app/admin/LockTaskPolicy.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2023 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * Class to represent a lock task policy, this is a combination of lock task packages (see + * {@link DevicePolicyManager#setLockTaskPackages}) and lock task features (see + * {@link DevicePolicyManager#setLockTaskFeatures}. + * + * @hide + */ +@SystemApi +public final class LockTaskPolicy extends PolicyValue<LockTaskPolicy> { + /** + * @hide + */ + public static final int DEFAULT_LOCK_TASK_FLAG = + DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS; + + private Set<String> mPackages = new HashSet<>(); + private int mFlags = DEFAULT_LOCK_TASK_FLAG; + + /** + * Returns the list of packages allowed to start the lock task mode. + */ + @NonNull + public Set<String> getPackages() { + return mPackages; + } + + /** + * Returns which system features are enabled for LockTask mode. + */ + public @DevicePolicyManager.LockTaskFeature int getFlags() { + return mFlags; + } + + // Overriding to hide + /** + * @hide + */ + @Override + @NonNull + public LockTaskPolicy getValue() { + return super.getValue(); + } + + /** + * @hide + */ + public LockTaskPolicy(@NonNull Set<String> packages) { + Objects.requireNonNull(packages); + mPackages.addAll(packages); + } + + /** + * @hide + */ + public LockTaskPolicy(@NonNull Set<String> packages, int flags) { + Objects.requireNonNull(packages); + mPackages = new HashSet<>(packages); + mFlags = flags; + setValue(this); + } + + private LockTaskPolicy(Parcel source) { + String[] packages = Objects.requireNonNull(source.readStringArray()); + mPackages = new HashSet<>(Arrays.stream(packages).toList()); + mFlags = source.readInt(); + } + + /** + * @hide + */ + public LockTaskPolicy(LockTaskPolicy policy) { + mPackages = new HashSet<>(policy.mPackages); + mFlags = policy.mFlags; + } + + /** + * @hide + */ + public void setPackages(@NonNull Set<String> packages) { + Objects.requireNonNull(packages); + mPackages = new HashSet<>(packages); + } + + /** + * @hide + */ + public void setFlags(int flags) { + mFlags = flags; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LockTaskPolicy other = (LockTaskPolicy) o; + return Objects.equals(mPackages, other.mPackages) + && mFlags == other.mFlags; + } + + @Override + public int hashCode() { + return Objects.hash(mPackages, mFlags); + } + + @Override + public String toString() { + return "LockTaskPolicy {mPackages= " + String.join(", ", mPackages) + "; mFlags= " + + mFlags + " }"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeArray(mPackages.toArray(new String[0])); + dest.writeInt(mFlags); + } + + @NonNull + public static final Parcelable.Creator<LockTaskPolicy> CREATOR = + new Parcelable.Creator<LockTaskPolicy>() { + @Override + public LockTaskPolicy createFromParcel(Parcel source) { + return new LockTaskPolicy(source); + } + + @Override + public LockTaskPolicy[] newArray(int size) { + return new LockTaskPolicy[size]; + } + }; +} diff --git a/core/java/android/app/admin/MostRecent.java b/core/java/android/app/admin/MostRecent.java new file mode 100644 index 000000000000..ac165718947f --- /dev/null +++ b/core/java/android/app/admin/MostRecent.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2022 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; + + +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Class to identify a most-recent-setter wins resolution mechanism that is used to resolve the + * enforced policy when being set by multiple admins (see + * {@link PolicyState#getResolutionMechanism()}). + * + * @hide + */ +@TestApi +public final class MostRecent<V> extends ResolutionMechanism<V> { + + @Override + public String toString() { + return "MostRecent {}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) {} + + @NonNull + public static final Parcelable.Creator<MostRecent> CREATOR = + new Parcelable.Creator<MostRecent>() { + @Override + public MostRecent createFromParcel(Parcel source) { + return new MostRecent(); + } + + @Override + public MostRecent[] newArray(int size) { + return new MostRecent[size]; + } + }; +} diff --git a/core/java/android/app/admin/MostRestrictive.java b/core/java/android/app/admin/MostRestrictive.java new file mode 100644 index 000000000000..adb4744d9d64 --- /dev/null +++ b/core/java/android/app/admin/MostRestrictive.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2022 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Class to identify a most restrictive resolution mechanism that is used to resolve the enforced + * policy when being set by multiple admins (see {@link PolicyState#getResolutionMechanism()}). + * + * @hide + */ +@TestApi +public final class MostRestrictive<V> extends ResolutionMechanism<V> { + + private final List<PolicyValue<V>> mMostToLeastRestrictive; + + /** + * @hide + */ + public MostRestrictive(@NonNull List<PolicyValue<V>> mostToLeastRestrictive) { + mMostToLeastRestrictive = new ArrayList<>(mostToLeastRestrictive); + } + + /** + * Returns an ordered list of most to least restrictive values for a certain policy. + */ + List<V> getMostToLeastRestrictiveValues() { + return mMostToLeastRestrictive.stream().map(PolicyValue::getValue).toList(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MostRestrictive other = (MostRestrictive) o; + return Objects.equals(mMostToLeastRestrictive, other.mMostToLeastRestrictive); + } + + @Override + public int hashCode() { + return Objects.hash(mMostToLeastRestrictive); + } + + /** + * @hide + */ + public MostRestrictive(Parcel source) { + mMostToLeastRestrictive = new ArrayList<>(); + int size = source.readInt(); + for (int i = 0; i < size; i++) { + mMostToLeastRestrictive.add(source.readParcelable(PolicyValue.class.getClassLoader())); + } + } + + @Override + public String toString() { + return "MostRestrictive { mMostToLeastRestrictive= " + mMostToLeastRestrictive + " }"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mMostToLeastRestrictive.size()); + for (PolicyValue<V> entry : mMostToLeastRestrictive) { + dest.writeParcelable(entry, flags); + } + } + + @NonNull + public static final Parcelable.Creator<MostRestrictive<?>> CREATOR = + new Parcelable.Creator<MostRestrictive<?>>() { + @Override + public MostRestrictive<?> createFromParcel(Parcel source) { + return new MostRestrictive<>(source); + } + + @Override + public MostRestrictive<?>[] newArray(int size) { + return new MostRestrictive[size]; + } + }; +} diff --git a/core/java/android/app/admin/NoArgsPolicyKey.java b/core/java/android/app/admin/NoArgsPolicyKey.java new file mode 100644 index 000000000000..57b67d5cab5d --- /dev/null +++ b/core/java/android/app/admin/NoArgsPolicyKey.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 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; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Default implementation for {@link PolicyKey} used to identify a policy that doesn't require any + * additional arguments to be represented. + * + * @hide + */ +@SystemApi +public final class NoArgsPolicyKey extends PolicyKey { + + /** + * @hide + */ + public NoArgsPolicyKey(@NonNull String identifier) { + super(identifier); + } + + private NoArgsPolicyKey(Parcel source) { + this(source.readString()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(getIdentifier()); + } + + @NonNull + public static final Parcelable.Creator<NoArgsPolicyKey> CREATOR = + new Parcelable.Creator<NoArgsPolicyKey>() { + @Override + public NoArgsPolicyKey createFromParcel(Parcel source) { + return new NoArgsPolicyKey(source); + } + + @Override + public NoArgsPolicyKey[] newArray(int size) { + return new NoArgsPolicyKey[size]; + } + }; + + @Override + public String toString() { + return "DefaultPolicyKey " + getIdentifier(); + } +} diff --git a/core/java/android/app/admin/PackagePermissionPolicyKey.java b/core/java/android/app/admin/PackagePermissionPolicyKey.java new file mode 100644 index 000000000000..4aa2e38b591b --- /dev/null +++ b/core/java/android/app/admin/PackagePermissionPolicyKey.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2023 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; + +import static android.app.admin.PolicyUpdatesReceiver.EXTRA_PACKAGE_NAME; +import static android.app.admin.PolicyUpdatesReceiver.EXTRA_PERMISSION_NAME; +import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_BUNDLE_KEY; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.Objects; + +/** + * Class used to identify a policy that relates to a certain package and permission + * (e.g. {@link DevicePolicyManager#setPermissionGrantState}). + * + * @hide + */ +@SystemApi +public final class PackagePermissionPolicyKey extends PolicyKey { + private static final String ATTR_PACKAGE_NAME = "package-name"; + private static final String ATTR_PERMISSION_NAME = "permission-name"; + + private final String mPackageName; + private final String mPermissionName; + + /** + * @hide + */ + public PackagePermissionPolicyKey(@NonNull String identifier, @NonNull String packageName, + @NonNull String permissionName) { + super(identifier); + mPackageName = Objects.requireNonNull((packageName)); + mPermissionName = Objects.requireNonNull((permissionName)); + } + + /** + * @hide + */ + public PackagePermissionPolicyKey(@NonNull String identifier) { + super(identifier); + mPackageName = null; + mPermissionName = null; + } + + private PackagePermissionPolicyKey(Parcel source) { + super(source.readString()); + mPackageName = source.readString(); + mPermissionName = source.readString(); + } + + /** + * Returns the package name this policy relates to. + */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + /** + * Returns the permission name this policy relates to. + */ + @NonNull + public String getPermissionName() { + return mPermissionName; + } + + /** + * @hide + */ + @Override + public void saveToXml(TypedXmlSerializer serializer) throws IOException { + serializer.attribute(/* namespace= */ null, ATTR_POLICY_IDENTIFIER, getIdentifier()); + serializer.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, mPackageName); + serializer.attribute(/* namespace= */ null, ATTR_PERMISSION_NAME, mPermissionName); + } + + /** + * @hide + */ + @Override + public PackagePermissionPolicyKey readFromXml(TypedXmlPullParser parser) + throws XmlPullParserException, IOException { + String identifier = parser.getAttributeValue( + /* namespace= */ null, ATTR_POLICY_IDENTIFIER); + String packageName = parser.getAttributeValue(/* namespace= */ null, ATTR_PACKAGE_NAME); + String permissionName = parser.getAttributeValue( + /* namespace= */ null, ATTR_PERMISSION_NAME); + return new PackagePermissionPolicyKey(identifier, packageName, permissionName); + } + + /** + * @hide + */ + @Override + public void writeToBundle(Bundle bundle) { + super.writeToBundle(bundle); + Bundle extraPolicyParams = new Bundle(); + extraPolicyParams.putString(EXTRA_PACKAGE_NAME, mPackageName); + extraPolicyParams.putString(EXTRA_PERMISSION_NAME, mPermissionName); + bundle.putBundle(EXTRA_POLICY_BUNDLE_KEY, extraPolicyParams); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PackagePermissionPolicyKey other = (PackagePermissionPolicyKey) o; + return Objects.equals(getIdentifier(), other.getIdentifier()) + && Objects.equals(mPackageName, other.mPackageName) + && Objects.equals(mPermissionName, other.mPermissionName); + } + + @Override + public int hashCode() { + return Objects.hash(getIdentifier(), mPackageName, mPermissionName); + } + + @Override + public String toString() { + return "PackagePermissionPolicyKey{mIdentifier= " + getIdentifier() + "; mPackageName= " + + mPackageName + "; mPermissionName= " + mPermissionName + "}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(getIdentifier()); + dest.writeString(mPackageName); + dest.writeString(mPermissionName); + } + + @NonNull + public static final Parcelable.Creator<PackagePermissionPolicyKey> CREATOR = + new Parcelable.Creator<PackagePermissionPolicyKey>() { + @Override + public PackagePermissionPolicyKey createFromParcel(Parcel source) { + return new PackagePermissionPolicyKey(source); + } + + @Override + public PackagePermissionPolicyKey[] newArray(int size) { + return new PackagePermissionPolicyKey[size]; + } + }; +} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PackageSpecificPolicyKey.java b/core/java/android/app/admin/PackagePolicyKey.java index 1665830f4762..346997060f53 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PackageSpecificPolicyKey.java +++ b/core/java/android/app/admin/PackagePolicyKey.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2023 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. @@ -14,13 +14,17 @@ * limitations under the License. */ -package com.android.server.devicepolicy; +package android.app.admin; import static android.app.admin.PolicyUpdatesReceiver.EXTRA_PACKAGE_NAME; import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_BUNDLE_KEY; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -31,49 +35,72 @@ import java.io.IOException; import java.util.Objects; /** - * Class used to identify a policy that relates to a certain package in the policy engine's data - * structure. + * Class used to identify a policy that relates to a certain package + * (e.g. {@link DevicePolicyManager#setUninstallBlocked}). + * + * @hide */ -final class PackageSpecificPolicyKey extends PolicyKey { - private static final String ATTR_POLICY_KEY = "policy-key"; +@SystemApi +public final class PackagePolicyKey extends PolicyKey { private static final String ATTR_PACKAGE_NAME = "package-name"; - private static final String ATTR_PERMISSION_NAME = "permission-name"; private final String mPackageName; - PackageSpecificPolicyKey(String key, String packageName) { + /** + * @hide + */ + public PackagePolicyKey(@NonNull String key, @NonNull String packageName) { super(key); mPackageName = Objects.requireNonNull((packageName)); } - PackageSpecificPolicyKey(String key) { + private PackagePolicyKey(Parcel source) { + super(source.readString()); + mPackageName = source.readString(); + } + + /** + * @hide + */ + public PackagePolicyKey(String key) { super(key); mPackageName = null; } - @Nullable - String getPackageName() { + /** + * Returns the package name this policy relates to. + */ + @NonNull + public String getPackageName() { return mPackageName; } + /** + * @hide + */ @Override - void saveToXml(TypedXmlSerializer serializer) throws IOException { - serializer.attribute(/* namespace= */ null, ATTR_POLICY_KEY, mKey); + public void saveToXml(TypedXmlSerializer serializer) throws IOException { + serializer.attribute(/* namespace= */ null, ATTR_POLICY_IDENTIFIER, getIdentifier()); serializer.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, mPackageName); } + /** + * @hide + */ @Override - PackageSpecificPolicyKey readFromXml(TypedXmlPullParser parser) + public PackagePolicyKey readFromXml(TypedXmlPullParser parser) throws XmlPullParserException, IOException { - String policyKey = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_KEY); + String policyKey = parser.getAttributeValue(/* namespace= */ null, + ATTR_POLICY_IDENTIFIER); String packageName = parser.getAttributeValue(/* namespace= */ null, ATTR_PACKAGE_NAME); - String permissionName = parser.getAttributeValue( - /* namespace= */ null, ATTR_PERMISSION_NAME); - return new PackageSpecificPolicyKey(policyKey, packageName); + return new PackagePolicyKey(policyKey, packageName); } + /** + * @hide + */ @Override - void writeToBundle(Bundle bundle) { + public void writeToBundle(Bundle bundle) { super.writeToBundle(bundle); Bundle extraPolicyParams = new Bundle(); extraPolicyParams.putString(EXTRA_PACKAGE_NAME, mPackageName); @@ -84,18 +111,44 @@ final class PackageSpecificPolicyKey extends PolicyKey { public boolean equals(@Nullable Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - PackageSpecificPolicyKey other = (PackageSpecificPolicyKey) o; - return Objects.equals(mKey, other.mKey) + PackagePolicyKey other = (PackagePolicyKey) o; + return Objects.equals(getIdentifier(), other.getIdentifier()) && Objects.equals(mPackageName, other.mPackageName); } @Override public int hashCode() { - return Objects.hash(mKey, mPackageName); + return Objects.hash(getIdentifier(), mPackageName); } @Override public String toString() { - return "mPolicyKey= " + mKey + "; mPackageName= " + mPackageName; + return "PackagePolicyKey{mPolicyKey= " + getIdentifier() + + "; mPackageName= " + mPackageName + "}"; + } + + @Override + public int describeContents() { + return 0; } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(getIdentifier()); + dest.writeString(mPackageName); + } + + @NonNull + public static final Parcelable.Creator<PackagePolicyKey> CREATOR = + new Parcelable.Creator<PackagePolicyKey>() { + @Override + public PackagePolicyKey createFromParcel(Parcel source) { + return new PackagePolicyKey(source); + } + + @Override + public PackagePolicyKey[] newArray(int size) { + return new PackagePolicyKey[size]; + } + }; } diff --git a/core/java/android/app/admin/PolicyKey.java b/core/java/android/app/admin/PolicyKey.java new file mode 100644 index 000000000000..a35f6341d868 --- /dev/null +++ b/core/java/android/app/admin/PolicyKey.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2023 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; + +import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_KEY; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.os.Bundle; +import android.os.Parcelable; + +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.Objects; + +/** + * Abstract class used to identify a policy returned from + * {@link DevicePolicyManager#getDevicePolicyState()}. + * + * @hide + */ +// This is ok as the constructor is hidden and all subclasses have implemented +// Parcelable. +@SuppressLint({"ParcelNotFinal", "ParcelCreator"}) +@SystemApi +public abstract class PolicyKey implements Parcelable { + /** + * @hide + */ + static final String ATTR_POLICY_IDENTIFIER = "policy-identifier"; + + private final String mIdentifier; + + /** + * @hide + */ + protected PolicyKey(@NonNull String identifier) { + mIdentifier = Objects.requireNonNull(identifier); + } + + /** + * Returns the string identifier for this policy. + */ + @NonNull + public String getIdentifier() { + return mIdentifier; + } + + /** + * @hide + */ + public boolean hasSameIdentifierAs(PolicyKey other) { + if (other == null) { + return false; + } + return mIdentifier.equals(other.mIdentifier); + } + + /** + * @hide + */ + public static PolicyKey readGenericPolicyKeyFromXml(TypedXmlPullParser parser) { + String identifier = parser.getAttributeValue( + /* namespace= */ null, ATTR_POLICY_IDENTIFIER); + return new NoArgsPolicyKey(identifier); + } + + /** + * @hide + */ + public void saveToXml(TypedXmlSerializer serializer) throws IOException { + serializer.attribute(/* namespace= */ null, ATTR_POLICY_IDENTIFIER, mIdentifier); + } + + /** + * @hide + */ + public PolicyKey readFromXml(TypedXmlPullParser parser) + throws XmlPullParserException, IOException { + // No need to read anything + return this; + } + + /** + * @hide + */ + public void writeToBundle(Bundle bundle) { + bundle.putString(EXTRA_POLICY_KEY, mIdentifier); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PolicyKey other = (PolicyKey) o; + return Objects.equals(mIdentifier, other.mIdentifier); + } + + @Override + public int hashCode() { + return Objects.hash(mIdentifier); + } +} diff --git a/core/java/android/app/admin/PolicyState.java b/core/java/android/app/admin/PolicyState.java new file mode 100644 index 000000000000..da71bb11fb13 --- /dev/null +++ b/core/java/android/app/admin/PolicyState.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2022 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.LinkedHashMap; +import java.util.Objects; + +/** + * Class containing the state of a certain policy (e.g. all values set by different admins, + * current resolved policy, etc). + * + * <p>Note that the value returned from {@link #getCurrentResolvedPolicy()} might not match any + * of the values in {@link #getPoliciesSetByAdmins()} as some policies might be affected by a + * conflicting global policy set on the device (retrieved using + * {@link DevicePolicyState#getPoliciesForUser} with {@link android.os.UserHandle#ALL}. + * + * @hide + */ +@SystemApi +public final class PolicyState<V> implements Parcelable { + private final LinkedHashMap<EnforcingAdmin, PolicyValue<V>> mPoliciesSetByAdmins = + new LinkedHashMap<>(); + private PolicyValue<V> mCurrentResolvedPolicy; + private ResolutionMechanism<V> mResolutionMechanism; + + /** + * @hide + */ + public PolicyState( + @NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<V>> policiesSetByAdmins, + PolicyValue<V> currentEnforcedPolicy, + @NonNull ResolutionMechanism<V> resolutionMechanism) { + Objects.requireNonNull(policiesSetByAdmins); + Objects.requireNonNull(resolutionMechanism); + + mPoliciesSetByAdmins.putAll(policiesSetByAdmins); + mCurrentResolvedPolicy = currentEnforcedPolicy; + mResolutionMechanism = resolutionMechanism; + } + + private PolicyState(Parcel source) { + int size = source.readInt(); + for (int i = 0; i < size; i++) { + EnforcingAdmin admin = source.readParcelable(EnforcingAdmin.class.getClassLoader()); + PolicyValue<V> policyValue = source.readParcelable(PolicyValue.class.getClassLoader()); + mPoliciesSetByAdmins.put(admin, policyValue); + } + mCurrentResolvedPolicy = source.readParcelable((PolicyValue.class.getClassLoader())); + mResolutionMechanism = source.readParcelable(ResolutionMechanism.class.getClassLoader()); + } + + /** + * Returns all values set by admins for this policy + */ + @NonNull + public LinkedHashMap<EnforcingAdmin, V> getPoliciesSetByAdmins() { + LinkedHashMap<EnforcingAdmin, V> policies = new LinkedHashMap<>(); + for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) { + policies.put(admin, mPoliciesSetByAdmins.get(admin).getValue()); + } + return policies; + } + + /** + * Returns the current resolved policy value. + */ + @Nullable + public V getCurrentResolvedPolicy() { + return mCurrentResolvedPolicy.getValue(); + } + + /** + * Returns the resolution mechanism used to resolve the enforced policy when the policy has + * been set by multiple enforcing admins {@link EnforcingAdmin}. + * + * @hide + */ + @TestApi + @NonNull + public ResolutionMechanism<V> getResolutionMechanism() { + return mResolutionMechanism; + } + + @Override + public String toString() { + return "PolicyState { mPoliciesSetByAdmins= " + + mPoliciesSetByAdmins + ", mCurrentResolvedPolicy= " + mCurrentResolvedPolicy + + ", mResolutionMechanism= " + mResolutionMechanism + " }"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mPoliciesSetByAdmins.size()); + for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) { + dest.writeParcelable(admin, flags); + dest.writeParcelable(mPoliciesSetByAdmins.get(admin), flags); + } + dest.writeParcelable(mCurrentResolvedPolicy, flags); + dest.writeParcelable(mResolutionMechanism, flags); + } + + @NonNull + public static final Parcelable.Creator<PolicyState<?>> CREATOR = + new Parcelable.Creator<PolicyState<?>>() { + @Override + public PolicyState<?> createFromParcel(Parcel source) { + return new PolicyState<>(source); + } + + @Override + public PolicyState<?>[] newArray(int size) { + return new PolicyState[size]; + } + }; +} diff --git a/core/java/android/app/admin/PolicyUpdateResult.java b/core/java/android/app/admin/PolicyUpdateResult.java index 9e13e000fa5f..a6d0ebf93035 100644 --- a/core/java/android/app/admin/PolicyUpdateResult.java +++ b/core/java/android/app/admin/PolicyUpdateResult.java @@ -48,6 +48,14 @@ public final class PolicyUpdateResult { public static final int RESULT_FAILURE_CONFLICTING_ADMIN_POLICY = 1; /** + * Result code to indicate that the policy set by the admin has been successfully cleared, + * admins will no longer receive policy updates for this policy after this point. + * + * <p>Note that the policy can still be enforced by some other admin. + */ + public static final int RESULT_POLICY_CLEARED = 2; + + /** * Reason codes for {@link #getResultCode()}. * * @hide @@ -56,7 +64,8 @@ public final class PolicyUpdateResult { @IntDef(flag = true, prefix = { "RESULT_" }, value = { RESULT_FAILURE_UNKNOWN, RESULT_SUCCESS, - RESULT_FAILURE_CONFLICTING_ADMIN_POLICY + RESULT_FAILURE_CONFLICTING_ADMIN_POLICY, + RESULT_POLICY_CLEARED }) public @interface ResultCode {} diff --git a/core/java/android/app/admin/PolicyValue.java b/core/java/android/app/admin/PolicyValue.java new file mode 100644 index 000000000000..b0a884baf8a6 --- /dev/null +++ b/core/java/android/app/admin/PolicyValue.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 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; + +import android.annotation.NonNull; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Wrapper class to ensure that all policy values stored in the policy engine are parcelable. + * + * @hide + */ +public abstract class PolicyValue<V> implements Parcelable { + private V mValue; + + public PolicyValue(V value) { + mValue = Objects.requireNonNull(value); + } + + PolicyValue() {} + + @NonNull + public V getValue() { + return mValue; + } + + void setValue(V value) { + mValue = value; + } +} diff --git a/core/java/android/app/admin/ResolutionMechanism.java b/core/java/android/app/admin/ResolutionMechanism.java new file mode 100644 index 000000000000..a7c684202a45 --- /dev/null +++ b/core/java/android/app/admin/ResolutionMechanism.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 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; + +import android.annotation.SuppressLint; +import android.annotation.TestApi; +import android.os.Parcelable; + +/** + * Abstract class to identify a resolution mechanism that is used to resolve the enforced + * policy when being set by multiple admins (see {@link PolicyState#getResolutionMechanism()}). + * + * @hide + */ +// This is ok as the constructor is hidden and all subclasses have implemented +// Parcelable. +@SuppressLint({"ParcelNotFinal", "ParcelCreator"}) +@TestApi +public abstract class ResolutionMechanism<V> implements Parcelable { + /** + * @hide + */ + ResolutionMechanism(){} +} diff --git a/core/java/android/app/admin/RoleAuthority.java b/core/java/android/app/admin/RoleAuthority.java new file mode 100644 index 000000000000..7fdd1188f65f --- /dev/null +++ b/core/java/android/app/admin/RoleAuthority.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2023 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * Class used to identify the authority of the {@link EnforcingAdmin} based on a certain role/roles + * it holds. + * + * @hide + */ +@SystemApi +public final class RoleAuthority extends Authority { + private final Set<String> mRoles; + + /** + * @hide + */ + public RoleAuthority(@NonNull Set<String> roles) { + mRoles = new HashSet<>(Objects.requireNonNull(roles)); + } + + private RoleAuthority(Parcel source) { + String[] roles = source.readStringArray(); + mRoles = roles == null ? new HashSet<>() : new HashSet<>(Arrays.stream(roles).toList()); + } + + /** + * Returns the list of roles held by the associated admin. + */ + @NonNull + public Set<String> getRoles() { + return mRoles; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeArray(mRoles.toArray()); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RoleAuthority other = (RoleAuthority) o; + return Objects.equals(mRoles, other.mRoles); + } + + @Override + public int hashCode() { + return Objects.hash(mRoles); + } + + @Override + public String toString() { + return "RoleAuthority { mRoles= " + mRoles + " }"; + } + + @NonNull + public static final Parcelable.Creator<RoleAuthority> CREATOR = + new Parcelable.Creator<RoleAuthority>() { + @Override + public RoleAuthority createFromParcel(Parcel source) { + return new RoleAuthority(source); + } + + @Override + public RoleAuthority[] newArray(int size) { + return new RoleAuthority[size]; + } + }; +} diff --git a/core/java/android/app/admin/StringPolicyValue.java b/core/java/android/app/admin/StringPolicyValue.java new file mode 100644 index 000000000000..14b6dab2899e --- /dev/null +++ b/core/java/android/app/admin/StringPolicyValue.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2023 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; + +import java.util.Objects; + +/** + * @hide + */ +public final class StringPolicyValue extends PolicyValue<String> { + + public StringPolicyValue(@NonNull String value) { + super(value); + } + + private StringPolicyValue(Parcel source) { + super(source.readString()); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + StringPolicyValue other = (StringPolicyValue) o; + return Objects.equals(getValue(), other.getValue()); + } + + @Override + public int hashCode() { + return Objects.hash(getValue()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "StringPolicyValue { " + getValue() + " }"; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(getValue()); + } + + @NonNull + public static final Creator<StringPolicyValue> CREATOR = new Creator<StringPolicyValue>() { + @Override + public StringPolicyValue createFromParcel(Parcel source) { + return new StringPolicyValue(source); + } + + @Override + public StringPolicyValue[] newArray(int size) { + return new StringPolicyValue[size]; + } + }; +} diff --git a/core/java/android/app/admin/StringSetPolicyValue.java b/core/java/android/app/admin/StringSetPolicyValue.java new file mode 100644 index 000000000000..cbfc60464467 --- /dev/null +++ b/core/java/android/app/admin/StringSetPolicyValue.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2023 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * @hide + */ +public final class StringSetPolicyValue extends PolicyValue<Set<String>> { + + public StringSetPolicyValue(@NonNull Set<String> value) { + super(value); + } + + public StringSetPolicyValue(Parcel source) { + this(readValues(source)); + } + + private static Set<String> readValues(Parcel source) { + Set<String> values = new HashSet<>(); + int size = source.readInt(); + for (int i = 0; i < size; i++) { + values.add(source.readString()); + } + return values; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + StringSetPolicyValue other = (StringSetPolicyValue) o; + return Objects.equals(getValue(), other.getValue()); + } + + @Override + public int hashCode() { + return Objects.hash(getValue()); + } + + @Override + public String toString() { + return "StringSetPolicyValue { " + getValue() + " }"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(getValue().size()); + for (String entry : getValue()) { + dest.writeString(entry); + } + } + + @NonNull + public static final Creator<StringSetPolicyValue> CREATOR = + new Creator<StringSetPolicyValue>() { + @Override + public StringSetPolicyValue createFromParcel(Parcel source) { + return new StringSetPolicyValue(source); + } + + @Override + public StringSetPolicyValue[] newArray(int size) { + return new StringSetPolicyValue[size]; + } + }; +} diff --git a/core/java/android/app/admin/StringSetUnion.java b/core/java/android/app/admin/StringSetUnion.java new file mode 100644 index 000000000000..730e6a23e382 --- /dev/null +++ b/core/java/android/app/admin/StringSetUnion.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022 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; + +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Set; + +/** + * Class to identify a union resolution mechanism for {@code Set<String>} policies, it's used to + * resolve the enforced policy when being set by multiple admins (see + * {@link PolicyState#getResolutionMechanism()}). + * + * @hide + */ +@TestApi +public final class StringSetUnion extends ResolutionMechanism<Set<String>> { + + @Override + public String toString() { + return "StringSetUnion {}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) {} + + @NonNull + public static final Parcelable.Creator<StringSetUnion> CREATOR = + new Parcelable.Creator<StringSetUnion>() { + @Override + public StringSetUnion createFromParcel(Parcel source) { + return new StringSetUnion(); + } + + @Override + public StringSetUnion[] newArray(int size) { + return new StringSetUnion[size]; + } + }; +} diff --git a/core/java/android/app/admin/TopPriority.java b/core/java/android/app/admin/TopPriority.java new file mode 100644 index 000000000000..e712274820d2 --- /dev/null +++ b/core/java/android/app/admin/TopPriority.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2022 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; + +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * Class to identify a top priority resolution mechanism that is used to resolve the enforced + * policy when being set by multiple admins (see {@link PolicyState#getResolutionMechanism()}). + * + * <p>Priorities are defined based on the calling admin's {@link Authority}. + * + * @hide + */ +@TestApi +public final class TopPriority<V> extends ResolutionMechanism<V> { + + private final List<String> mHighestToLowestPriorityAuthorities; + + /** + * @hide + */ + public TopPriority(@NonNull List<String> highestToLowestPriorityAuthorities) { + mHighestToLowestPriorityAuthorities = Objects.requireNonNull( + highestToLowestPriorityAuthorities); + } + + /** + * Returns an ordered list of authorities from highest priority to lowest priority for a + * certain policy. + */ + @NonNull + List<String> getHighestToLowestPriorityAuthorities() { + return mHighestToLowestPriorityAuthorities; + } + + @Override + public String toString() { + return "TopPriority { mHighestToLowestPriorityAuthorities= " + + mHighestToLowestPriorityAuthorities + " }"; + } + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStringArray(mHighestToLowestPriorityAuthorities.toArray(new String[0])); + } + + @NonNull + public static final Parcelable.Creator<TopPriority<?>> CREATOR = + new Parcelable.Creator<TopPriority<?>>() { + @Override + public TopPriority<?> createFromParcel(Parcel source) { + String[] highestToLowestPriorityAuthorities = source.readStringArray(); + return new TopPriority<>( + Arrays.stream(highestToLowestPriorityAuthorities).toList()); + } + + @Override + public TopPriority<?>[] newArray(int size) { + return new TopPriority[size]; + } + }; + +} diff --git a/core/java/android/app/admin/UnknownAuthority.java b/core/java/android/app/admin/UnknownAuthority.java new file mode 100644 index 000000000000..4492b96895da --- /dev/null +++ b/core/java/android/app/admin/UnknownAuthority.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; + +/** + * Class used to identify a default value for the authority of the {@link EnforcingAdmin} setting + * a policy, meaning it is not one of the other known subclasses of {@link Authority}, this would be + * the case for example for a system component setting the policy. + * + * @hide + */ +@SystemApi +public final class UnknownAuthority extends Authority { + + /** + * @hide + */ + public static final UnknownAuthority UNKNOWN_AUTHORITY = new UnknownAuthority(); + + private UnknownAuthority() {} + + @Override + public String toString() { + return "DefaultAuthority {}"; + } + + @Override + public boolean equals(@Nullable Object o) { + return super.equals(o); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) {} + + @NonNull + public static final Creator<UnknownAuthority> CREATOR = + new Creator<UnknownAuthority>() { + @Override + public UnknownAuthority createFromParcel(Parcel source) { + return new UnknownAuthority(); + } + + @Override + public UnknownAuthority[] newArray(int size) { + return new UnknownAuthority[size]; + } + }; +} diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index 611ad880499b..d4d323e83276 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -3039,4 +3039,81 @@ public class IntentFilter implements Parcelable { ArrayList<String> list = getHostsList(); return list.toArray(new String[list.size()]); } + + /** + * @hide + */ + public static boolean filterEquals(IntentFilter f1, IntentFilter f2) { + int s1 = f1.countActions(); + int s2 = f2.countActions(); + if (s1 != s2) { + return false; + } + for (int i=0; i<s1; i++) { + if (!f2.hasAction(f1.getAction(i))) { + return false; + } + } + s1 = f1.countCategories(); + s2 = f2.countCategories(); + if (s1 != s2) { + return false; + } + for (int i=0; i<s1; i++) { + if (!f2.hasCategory(f1.getCategory(i))) { + return false; + } + } + s1 = f1.countDataTypes(); + s2 = f2.countDataTypes(); + if (s1 != s2) { + return false; + } + for (int i=0; i<s1; i++) { + if (!f2.hasExactDataType(f1.getDataType(i))) { + return false; + } + } + s1 = f1.countDataSchemes(); + s2 = f2.countDataSchemes(); + if (s1 != s2) { + return false; + } + for (int i=0; i<s1; i++) { + if (!f2.hasDataScheme(f1.getDataScheme(i))) { + return false; + } + } + s1 = f1.countDataAuthorities(); + s2 = f2.countDataAuthorities(); + if (s1 != s2) { + return false; + } + for (int i=0; i<s1; i++) { + if (!f2.hasDataAuthority(f1.getDataAuthority(i))) { + return false; + } + } + s1 = f1.countDataPaths(); + s2 = f2.countDataPaths(); + if (s1 != s2) { + return false; + } + for (int i=0; i<s1; i++) { + if (!f2.hasDataPath(f1.getDataPath(i))) { + return false; + } + } + s1 = f1.countDataSchemeSpecificParts(); + s2 = f2.countDataSchemeSpecificParts(); + if (s1 != s2) { + return false; + } + for (int i=0; i<s1; i++) { + if (!f2.hasDataSchemeSpecificPart(f1.getDataSchemeSpecificPart(i))) { + return false; + } + } + return true; + } } diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index e38cb65f991f..6386f75ae2f3 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.annotation.TestApi; import android.annotation.UserIdInt; import android.compat.annotation.UnsupportedAppUsage; +import android.content.res.Resources; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; @@ -418,16 +419,25 @@ public class UserInfo implements Parcelable { // Don't support switching to pre-created users until they become "real" users. return false; } - return !isProfile(); + return isFull() || canSwitchToHeadlessSystemUser(); + } + + /** + * @return true if user is of type {@link UserManager#USER_TYPE_SYSTEM_HEADLESS} and + * {@link com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser} is true. + */ + private boolean canSwitchToHeadlessSystemUser() { + return UserManager.USER_TYPE_SYSTEM_HEADLESS.equals(userType) && Resources.getSystem() + .getBoolean(com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser); } /** * @return true if this user can be switched to by end user through UI. + * @deprecated Use {@link UserInfo#supportsSwitchTo} instead. */ + @Deprecated public boolean supportsSwitchToByUser() { - // Hide the system user when it does not represent a human user. - boolean hideSystemUser = UserManager.isHeadlessSystemUserMode(); - return (!hideSystemUser || id != UserHandle.USER_SYSTEM) && supportsSwitchTo(); + return supportsSwitchTo(); } // TODO(b/142482943): Make this logic more specific and customizable. (canHaveProfile(userType)) @@ -436,11 +446,7 @@ public class UserInfo implements Parcelable { if (isProfile() || isGuest() || isRestricted()) { return false; } - if (UserManager.isHeadlessSystemUserMode()) { - return id != UserHandle.USER_SYSTEM; - } else { - return id == UserHandle.USER_SYSTEM; - } + return isMain(); } // TODO(b/142482943): Get rid of this (after removing it from all tests) if feasible. diff --git a/core/java/android/os/ExternalVibration.java b/core/java/android/os/ExternalVibration.java index db5bc7024e0b..fb115b3c2aa3 100644 --- a/core/java/android/os/ExternalVibration.java +++ b/core/java/android/os/ExternalVibration.java @@ -104,6 +104,10 @@ public class ExternalVibration implements Parcelable { return mAttrs; } + public IBinder getToken() { + return mToken; + } + public VibrationAttributes getVibrationAttributes() { return new VibrationAttributes.Builder(mAttrs).build(); } diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index 810bd636de07..93d508292c7f 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -45,6 +45,7 @@ import android.system.Os; import android.system.OsConstants; import android.system.StructStat; import android.util.Log; +import android.util.Slog; import dalvik.system.CloseGuard; import dalvik.system.VMRuntime; @@ -329,6 +330,14 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { } private static FileDescriptor openInternal(File file, int mode) throws FileNotFoundException { + if ((mode & MODE_WRITE_ONLY) != 0 && (mode & MODE_APPEND) == 0 + && (mode & MODE_TRUNCATE) == 0 && ((mode & MODE_READ_ONLY) == 0) + && file != null && file.exists()) { + Slog.wtfQuiet(TAG, "ParcelFileDescriptor.open is called with w without t or a or r, " + + "which will have a different behavior beginning in Android Q." + + "\nMode: " + mode + "\nFilename: " + file.getPath()); + } + final int flags = FileUtils.translateModePfdToPosix(mode) | ifAtLeastQ(O_CLOEXEC); int realMode = S_IRWXU | S_IRWXG; diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index 0fa5e3e3b323..d943bf9ac872 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -18,6 +18,7 @@ package android.service.autofill; import static android.view.autofill.Helper.sDebug; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -33,6 +34,8 @@ import android.widget.RemoteViews; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Objects; import java.util.regex.Pattern; @@ -112,6 +115,55 @@ import java.util.regex.Pattern; * </ol> */ public final class Dataset implements Parcelable { + /** + * This dataset is picked because of unknown reason. + * @hide + */ + public static final int PICK_REASON_UNKNOWN = 0; + /** + * This dataset is picked because of autofill provider detection was chosen. + * @hide + */ + public static final int PICK_REASON_AUTOFILL_PROVIDER_DETECTION = 1; + /** + * This dataset is picked because of PCC detection was chosen. + * @hide + */ + public static final int PICK_REASON_PCC_DETECTION = 2; + /** + * This dataset is picked because of Framework detection was chosen. + * @hide + */ + public static final int PICK_REASON_FRAMEWORK_DETECTION = 3; + /** + * This dataset is picked because of Autofill Provider being a fallback. + * @hide + */ + public static final int PICK_REASON_AUTOFILL_PROVIDER_FALLBACK = 4; + /** + * This dataset is picked because of PCC detection being a fallback. + * @hide + */ + public static final int PICK_REASON_PCC_DETECTION_FALLBACK = 5; + /** + * This dataset is picked because of Framework detection being a fallback. + * @hide + */ + public static final int PICK_REASON_FRAMEWORK_FALLBACK = 6; + + @IntDef(prefix = { "PICK_REASON_" }, value = { + PICK_REASON_UNKNOWN, + PICK_REASON_AUTOFILL_PROVIDER_DETECTION, + PICK_REASON_PCC_DETECTION, + PICK_REASON_FRAMEWORK_DETECTION, + PICK_REASON_AUTOFILL_PROVIDER_FALLBACK, + PICK_REASON_PCC_DETECTION_FALLBACK, + PICK_REASON_FRAMEWORK_FALLBACK, + }) + @Retention(RetentionPolicy.SOURCE) + @interface DatasetEligibleReason{} + + private @DatasetEligibleReason int mEligibleReason; private final ArrayList<AutofillId> mFieldIds; private final ArrayList<AutofillValue> mFieldValues; @@ -130,6 +182,67 @@ public final class Dataset implements Parcelable { private final IntentSender mAuthentication; @Nullable String mId; + /** + * Constructor to copy the dataset, but replaces the AutofillId with the given input. + * Useful to modify the field type, and provide autofillId. + * @hide + */ + public Dataset( + ArrayList<AutofillId> fieldIds, + ArrayList<AutofillValue> fieldValues, + ArrayList<RemoteViews> fieldPresentations, + ArrayList<RemoteViews> fieldDialogPresentations, + ArrayList<InlinePresentation> fieldInlinePresentations, + ArrayList<InlinePresentation> fieldInlineTooltipPresentations, + ArrayList<DatasetFieldFilter> fieldFilters, + ArrayList<String> autofillDatatypes, + ClipData fieldContent, + RemoteViews presentation, + RemoteViews dialogPresentation, + @Nullable InlinePresentation inlinePresentation, + @Nullable InlinePresentation inlineTooltipPresentation, + @Nullable String id, + IntentSender authentication) { + mFieldIds = fieldIds; + mFieldValues = fieldValues; + mFieldPresentations = fieldPresentations; + mFieldDialogPresentations = fieldDialogPresentations; + mFieldInlinePresentations = fieldInlinePresentations; + mFieldInlineTooltipPresentations = fieldInlineTooltipPresentations; + mAutofillDatatypes = autofillDatatypes; + mFieldFilters = fieldFilters; + mFieldContent = fieldContent; + mPresentation = presentation; + mDialogPresentation = dialogPresentation; + mInlinePresentation = inlinePresentation; + mInlineTooltipPresentation = inlineTooltipPresentation; + mAuthentication = authentication; + mId = id; + } + + /** + * Constructor to copy the dataset, but replaces the AutofillId with the given input. + * Useful to modify the field type, and provide autofillId. + * @hide + */ + public Dataset(Dataset dataset, ArrayList<AutofillId> ids) { + mFieldIds = ids; + mFieldValues = dataset.mFieldValues; + mFieldPresentations = dataset.mFieldPresentations; + mFieldDialogPresentations = dataset.mFieldDialogPresentations; + mFieldInlinePresentations = dataset.mFieldInlinePresentations; + mFieldInlineTooltipPresentations = dataset.mFieldInlineTooltipPresentations; + mFieldFilters = dataset.mFieldFilters; + mFieldContent = dataset.mFieldContent; + mPresentation = dataset.mPresentation; + mDialogPresentation = dataset.mDialogPresentation; + mInlinePresentation = dataset.mInlinePresentation; + mInlineTooltipPresentation = dataset.mInlineTooltipPresentation; + mAuthentication = dataset.mAuthentication; + mId = dataset.mId; + mAutofillDatatypes = dataset.mAutofillDatatypes; + } + private Dataset(Builder builder) { mFieldIds = builder.mFieldIds; mFieldValues = builder.mFieldValues; @@ -292,6 +405,22 @@ public final class Dataset implements Parcelable { } /** + * Sets the reason as to why this dataset is eligible + * @hide + */ + public void setEligibleReasonReason(@DatasetEligibleReason int eligibleReason) { + this.mEligibleReason = eligibleReason; + } + + /** + * Get the reason as to why this dataset is eligible. + * @hide + */ + public @DatasetEligibleReason int getEligibleReason() { + return mEligibleReason; + } + + /** * A builder for {@link Dataset} objects. You must provide at least * one value for a field or set an authentication intent. */ @@ -1147,6 +1276,7 @@ public final class Dataset implements Parcelable { parcel.writeParcelable(mFieldContent, flags); parcel.writeParcelable(mAuthentication, flags); parcel.writeString(mId); + parcel.writeInt(mEligibleReason); } public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() { @@ -1181,6 +1311,7 @@ public final class Dataset implements Parcelable { final IntentSender authentication = parcel.readParcelable(null, android.content.IntentSender.class); final String datasetId = parcel.readString(); + final int eligibleReason = parcel.readInt(); // Always go through the builder to ensure the data ingested by // the system obeys the contract of the builder to avoid attacks @@ -1243,7 +1374,9 @@ public final class Dataset implements Parcelable { } builder.setAuthentication(authentication); builder.setId(datasetId); - return builder.build(); + Dataset dataset = builder.build(); + dataset.mEligibleReason = eligibleReason; + return dataset; } @Override diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index fa7ace3bbe0d..c962bf1954bf 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -117,6 +117,80 @@ public final class FillResponse implements Parcelable { private final boolean mShowSaveDialogIcon; private final @Nullable FieldClassification[] mDetectedFieldTypes; + /** + * Creates a shollow copy of the provided FillResponse. + * + * @hide + */ + public static FillResponse shallowCopy(FillResponse r, List<Dataset> datasets) { + return new FillResponse( + (datasets != null) ? new ParceledListSlice<>(datasets) : null, + r.mSaveInfo, + r.mClientState, + r.mPresentation, + r.mInlinePresentation, + r.mInlineTooltipPresentation, + r.mDialogPresentation, + r.mDialogHeader, + r.mHeader, + r.mFooter, + r.mAuthentication, + r.mAuthenticationIds, + r.mIgnoredIds, + r.mFillDialogTriggerIds, + r.mDisableDuration, + r.mFieldClassificationIds, + r.mFlags, + r.mRequestId, + r.mUserData, + r.mCancelIds, + r.mSupportsInlineSuggestions, + r.mIconResourceId, + r.mServiceDisplayNameResourceId, + r.mShowFillDialogIcon, + r.mShowSaveDialogIcon, + r.mDetectedFieldTypes); + } + + private FillResponse(ParceledListSlice<Dataset> datasets, SaveInfo saveInfo, Bundle clientState, + RemoteViews presentation, InlinePresentation inlinePresentation, + InlinePresentation inlineTooltipPresentation, RemoteViews dialogPresentation, + RemoteViews dialogHeader, RemoteViews header, RemoteViews footer, + IntentSender authentication, AutofillId[] authenticationIds, AutofillId[] ignoredIds, + AutofillId[] fillDialogTriggerIds, long disableDuration, + AutofillId[] fieldClassificationIds, int flags, int requestId, UserData userData, + int[] cancelIds, boolean supportsInlineSuggestions, int iconResourceId, + int serviceDisplayNameResourceId, boolean showFillDialogIcon, + boolean showSaveDialogIcon, + FieldClassification[] detectedFieldTypes) { + mDatasets = datasets; + mSaveInfo = saveInfo; + mClientState = clientState; + mPresentation = presentation; + mInlinePresentation = inlinePresentation; + mInlineTooltipPresentation = inlineTooltipPresentation; + mDialogPresentation = dialogPresentation; + mDialogHeader = dialogHeader; + mHeader = header; + mFooter = footer; + mAuthentication = authentication; + mAuthenticationIds = authenticationIds; + mIgnoredIds = ignoredIds; + mFillDialogTriggerIds = fillDialogTriggerIds; + mDisableDuration = disableDuration; + mFieldClassificationIds = fieldClassificationIds; + mFlags = flags; + mRequestId = requestId; + mUserData = userData; + mCancelIds = cancelIds; + mSupportsInlineSuggestions = supportsInlineSuggestions; + mIconResourceId = iconResourceId; + mServiceDisplayNameResourceId = serviceDisplayNameResourceId; + mShowFillDialogIcon = showFillDialogIcon; + mShowSaveDialogIcon = showSaveDialogIcon; + mDetectedFieldTypes = detectedFieldTypes; + } + private FillResponse(@NonNull Builder builder) { mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null; mSaveInfo = builder.mSaveInfo; @@ -674,6 +748,15 @@ public final class FillResponse implements Parcelable { } /** + * @hide + */ + @NonNull + public Builder setDatasets(ArrayList<Dataset> dataset) { + mDatasets = dataset; + return this; + } + + /** * Sets the {@link SaveInfo} associated with this response. * * @return This builder. diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 7da141bc392a..8e0941144b51 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -173,6 +173,7 @@ public final class SurfaceControl implements Parcelable { boolean isTrustedOverlay); private static native void nativeSetDropInputMode( long transactionObj, long nativeObject, int flags); + private static native void nativeSurfaceFlushJankData(long nativeSurfaceObject); private static native boolean nativeClearContentFrameStats(long nativeObject); private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats); private static native boolean nativeClearAnimationFrameStats(); @@ -3885,6 +3886,15 @@ public final class SurfaceControl implements Parcelable { } /** + * Sends a flush jank data transaction for the given surface. + * @hide + */ + public static void sendSurfaceFlushJankData(SurfaceControl sc) { + sc.checkNotReleased(); + nativeSurfaceFlushJankData(sc.mNativeObject); + } + + /** * @hide */ public void sanitize() { diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index c8c910d98bbb..7d1dc7660871 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -28,7 +28,6 @@ import android.content.res.XmlResourceParser; import android.os.SystemClock; import android.util.AttributeSet; import android.util.Xml; -import android.view.InflateException; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -138,9 +137,16 @@ public class AnimationUtils { try { parser = context.getResources().getAnimation(id); return createAnimationFromXml(context, parser); - } catch (XmlPullParserException | IOException | InflateException ex) { - throw new NotFoundException( - "Can't load animation resource ID #0x" + Integer.toHexString(id), ex); + } catch (XmlPullParserException ex) { + NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(ex); + throw rnf; + } catch (IOException ex) { + NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(ex); + throw rnf; } finally { if (parser != null) parser.close(); } @@ -153,9 +159,8 @@ public class AnimationUtils { } @UnsupportedAppUsage - private static Animation createAnimationFromXml( - Context c, XmlPullParser parser, AnimationSet parent, AttributeSet attrs) - throws XmlPullParserException, IOException, InflateException { + private static Animation createAnimationFromXml(Context c, XmlPullParser parser, + AnimationSet parent, AttributeSet attrs) throws XmlPullParserException, IOException { Animation anim = null; @@ -163,8 +168,8 @@ public class AnimationUtils { int type; int depth = parser.getDepth(); - while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) - && type != XmlPullParser.END_DOCUMENT) { + while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) + && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; @@ -188,7 +193,7 @@ public class AnimationUtils { } else if (name.equals("extend")) { anim = new ExtendAnimation(c, attrs); } else { - throw new InflateException("Unknown animation name: " + parser.getName()); + throw new RuntimeException("Unknown animation name: " + parser.getName()); } if (parent != null) { @@ -215,24 +220,29 @@ public class AnimationUtils { try { parser = context.getResources().getAnimation(id); return createLayoutAnimationFromXml(context, parser); - } catch (XmlPullParserException | IOException | InflateException ex) { - throw new NotFoundException( - "Can't load animation resource ID #0x" + Integer.toHexString(id), ex); + } catch (XmlPullParserException ex) { + NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(ex); + throw rnf; + } catch (IOException ex) { + NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(ex); + throw rnf; } finally { if (parser != null) parser.close(); } } - private static LayoutAnimationController createLayoutAnimationFromXml( - Context c, XmlPullParser parser) - throws XmlPullParserException, IOException, InflateException { + private static LayoutAnimationController createLayoutAnimationFromXml(Context c, + XmlPullParser parser) throws XmlPullParserException, IOException { return createLayoutAnimationFromXml(c, parser, Xml.asAttributeSet(parser)); } - private static LayoutAnimationController createLayoutAnimationFromXml( - Context c, XmlPullParser parser, AttributeSet attrs) - throws XmlPullParserException, IOException, InflateException { + private static LayoutAnimationController createLayoutAnimationFromXml(Context c, + XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException { LayoutAnimationController controller = null; @@ -253,7 +263,7 @@ public class AnimationUtils { } else if ("gridLayoutAnimation".equals(name)) { controller = new GridLayoutAnimationController(c, attrs); } else { - throw new InflateException("Unknown layout animation name: " + name); + throw new RuntimeException("Unknown layout animation name: " + name); } } @@ -332,9 +342,16 @@ public class AnimationUtils { try { parser = context.getResources().getAnimation(id); return createInterpolatorFromXml(context.getResources(), context.getTheme(), parser); - } catch (XmlPullParserException | IOException | InflateException ex) { - throw new NotFoundException( - "Can't load animation resource ID #0x" + Integer.toHexString(id), ex); + } catch (XmlPullParserException ex) { + NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(ex); + throw rnf; + } catch (IOException ex) { + NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(ex); + throw rnf; } finally { if (parser != null) parser.close(); } @@ -355,20 +372,25 @@ public class AnimationUtils { try { parser = res.getAnimation(id); return createInterpolatorFromXml(res, theme, parser); - } catch (XmlPullParserException | IOException | InflateException ex) { - throw new NotFoundException( - "Can't load animation resource ID #0x" + Integer.toHexString(id), ex); + } catch (XmlPullParserException ex) { + NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(ex); + throw rnf; + } catch (IOException ex) { + NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(ex); + throw rnf; } finally { - if (parser != null) { + if (parser != null) parser.close(); - } } } - private static Interpolator createInterpolatorFromXml( - Resources res, Theme theme, XmlPullParser parser) - throws XmlPullParserException, IOException, InflateException { + private static Interpolator createInterpolatorFromXml(Resources res, Theme theme, XmlPullParser parser) + throws XmlPullParserException, IOException { BaseInterpolator interpolator = null; @@ -408,7 +430,7 @@ public class AnimationUtils { } else if (name.equals("pathInterpolator")) { interpolator = new PathInterpolator(res, theme, attrs); } else { - throw new InflateException("Unknown interpolator name: " + parser.getName()); + throw new RuntimeException("Unknown interpolator name: " + parser.getName()); } } return interpolator; diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java index 864ba92ca887..6d78e60e9358 100644 --- a/core/java/android/view/autofill/AutofillFeatureFlags.java +++ b/core/java/android/view/autofill/AutofillFeatureFlags.java @@ -82,12 +82,6 @@ public class AutofillFeatureFlags { "autofill_dialog_enabled"; /** - * Indicates that PCC Autofill detection feature is enabled or not. - */ - public static final String DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS = - "pcc_classification_hints"; - - /** * Sets the autofill hints allowed list for the fields that can trigger the fill dialog * feature at Activity starting. * @@ -190,6 +184,12 @@ public class AutofillFeatureFlags { */ public static final String DEVICE_CONFIG_PREFER_PROVIDER_OVER_PCC = "prefer_provider_over_pcc"; + /** + * Indicates the Autofill Hints that would be requested by the service from the Autofill + * Provider. + */ + public static final String DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS = + "pcc_classification_hints"; /** * Use data from secondary source if primary not present . @@ -212,11 +212,9 @@ public class AutofillFeatureFlags { "autofill_inline_tooltip_first_show_delay"; private static final String DIALOG_HINTS_DELIMITER = ":"; - private static final String PCC_HINTS_DELIMITER = ","; private static final boolean DEFAULT_HAS_FILL_DIALOG_UI_FEATURE = false; private static final String DEFAULT_FILL_DIALOG_ENABLED_HINTS = ""; - private static final String DEFAULT_PCC_FEATURE_PROVIDER_HINTS = ""; // CREDENTIAL MANAGER DEFAULTS @@ -250,25 +248,6 @@ public class AutofillFeatureFlags { } /** - * The list of datatypes that is supported by framework - * detection. - * - * @hide - */ - public static String[] getTypeHintsForProvider() { - final String typeHints = DeviceConfig.getString( - DeviceConfig.NAMESPACE_AUTOFILL, - DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS, - DEFAULT_PCC_FEATURE_PROVIDER_HINTS); - if (TextUtils.isEmpty(typeHints)) { - return new String[0]; - } - - return ArrayUtils.filter(typeHints.split(PCC_HINTS_DELIMITER), String[]::new, - (str) -> !TextUtils.isEmpty(str)); - } - - /** * Gets fill dialog enabled hints. * * @hide diff --git a/core/java/com/android/internal/expresslog/Histogram.java b/core/java/com/android/internal/expresslog/Histogram.java index fe950c4c2932..db70cacbe6ed 100644 --- a/core/java/com/android/internal/expresslog/Histogram.java +++ b/core/java/com/android/internal/expresslog/Histogram.java @@ -20,7 +20,7 @@ import android.annotation.NonNull; import com.android.internal.util.FrameworkStatsLog; -/** CounterHistogram encapsulates StatsD write API calls */ +/** Histogram encapsulates StatsD write API calls */ public final class Histogram { private final long mMetricIdHash; @@ -42,10 +42,10 @@ public final class Histogram { /*count*/ 1, binIndex); } - /** Used by CounterHistogram to map data sample to corresponding bin */ + /** Used by Histogram to map data sample to corresponding bin */ public interface BinOptions { /** - * Returns bins count to be used by counter histogram + * Returns bins count to be used by a histogram * * @return bins count used to initialize Options, including overflow & underflow bins * @hide @@ -62,7 +62,7 @@ public final class Histogram { int getBinForSample(float sample); } - /** Used by CounterHistogram to map data sample to corresponding bin for on uniform bins */ + /** Used by Histogram to map data sample to corresponding bin for uniform bins */ public static final class UniformOptions implements BinOptions { private final int mBinCount; diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index e4195d29c1ee..3226669ee750 100644 --- a/core/java/com/android/internal/jank/FrameTracker.java +++ b/core/java/com/android/internal/jank/FrameTracker.java @@ -68,6 +68,9 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener private static final int MAX_LENGTH_EVENT_DESC = 20; + private static final int MAX_FLUSH_ATTEMPTS = 3; + private static final int FLUSH_DELAY_MILLISECOND = 60; + static final int REASON_END_UNKNOWN = -1; static final int REASON_END_NORMAL = 0; static final int REASON_END_SURFACE_DESTROYED = 1; @@ -358,11 +361,35 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener // will remove it when all the frame metrics in this duration are called back. // See onFrameMetricsAvailable for the logic of removing the observer. // Waiting at most 10 seconds for all callbacks to finish. - mWaitForFinishTimedOut = () -> { - Log.e(TAG, "force finish cuj because of time out:" + mSession.getName()); - finish(); + mWaitForFinishTimedOut = new Runnable() { + private int mFlushAttempts = 0; + + @Override + public void run() { + if (mWaitForFinishTimedOut == null || mMetricsFinalized) { + return; + } + + // Send a flush jank data transaction. + if (mSurfaceControl != null && mSurfaceControl.isValid()) { + SurfaceControl.Transaction.sendSurfaceFlushJankData(mSurfaceControl); + } + + long delay; + if (mFlushAttempts < MAX_FLUSH_ATTEMPTS) { + delay = FLUSH_DELAY_MILLISECOND; + mFlushAttempts++; + } else { + mWaitForFinishTimedOut = () -> { + Log.e(TAG, "force finish cuj, time out: " + mSession.getName()); + finish(); + }; + delay = TimeUnit.SECONDS.toMillis(10); + } + getHandler().postDelayed(mWaitForFinishTimedOut, delay); + } }; - getHandler().postDelayed(mWaitForFinishTimedOut, TimeUnit.SECONDS.toMillis(10)); + getHandler().postDelayed(mWaitForFinishTimedOut, FLUSH_DELAY_MILLISECOND); notifyCujEvent(ACTION_SESSION_END); return true; } @@ -537,11 +564,12 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener @UiThread private void finish() { + if (mMetricsFinalized || mCancelled) return; + mMetricsFinalized = true; + getHandler().removeCallbacks(mWaitForFinishTimedOut); mWaitForFinishTimedOut = null; - if (mMetricsFinalized || mCancelled) return; markEvent("FT#finish#" + mJankInfos.size()); - mMetricsFinalized = true; // The tracing has been ended, remove the observer, see if need to trigger perfetto. removeObservers(); diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 97a0f50e760d..5ca71b89f483 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -951,6 +951,11 @@ static void nativeSetDropInputMode(JNIEnv* env, jclass clazz, jlong transactionO transaction->setDropInputMode(ctrl, static_cast<gui::DropInputMode>(mode)); } +static void nativeSurfaceFlushJankData(JNIEnv* env, jclass clazz, jlong nativeObject) { + SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); + SurfaceComposerClient::Transaction::sendSurfaceFlushJankDataTransaction(ctrl); +} + static void nativeSanitize(JNIEnv* env, jclass clazz, jlong transactionObj) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); transaction->sanitize(); @@ -2246,6 +2251,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeGetLayerId }, {"nativeSetDropInputMode", "(JJI)V", (void*)nativeSetDropInputMode }, + {"nativeSurfaceFlushJankData", "(J)V", + (void*)nativeSurfaceFlushJankData }, {"nativeAddTransactionCommittedListener", "(JLandroid/view/SurfaceControl$TransactionCommittedListener;)V", (void*) nativeAddTransactionCommittedListener }, {"nativeSetTrustedPresentationCallback", "(JJJLandroid/view/SurfaceControl$TrustedPresentationThresholds;)V", diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index a1e18a70fdf2..c75ee93b6c39 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2781,6 +2781,10 @@ it can't be deleted or downgraded to non-admin status. --> <bool name="config_isMainUserPermanentAdmin">false</bool> + <!-- Whether switch to headless system user is allowed. If allowed, + headless system user can run in the foreground even though it is not a full user. --> + <bool name="config_canSwitchToHeadlessSystemUser">false</bool> + <!-- Whether UI for multi user should be shown --> <bool name="config_enableMultiUserUI">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 5ccc2482ff48..6fceec93b307 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -355,6 +355,7 @@ <java-symbol type="bool" name="config_speed_up_audio_on_mt_calls" /> <java-symbol type="bool" name="config_useFixedVolume" /> <java-symbol type="bool" name="config_isMainUserPermanentAdmin"/> + <java-symbol type="bool" name="config_canSwitchToHeadlessSystemUser"/> <java-symbol type="bool" name="config_enableMultiUserUI"/> <java-symbol type="bool" name="config_enableMultipleAdmins"/> <java-symbol type="bool" name="config_enableNewAutoSelectNetworkUI"/> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 53f47475d2f3..0d74e125e68a 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -4417,6 +4417,12 @@ "group": "WM_DEBUG_TASKS", "at": "com\/android\/server\/wm\/RootWindowContainer.java" }, + "2043434284": { + "message": "setWallpaperShowWhenLocked: non-existent wallpaper token: %s", + "level": "WARN", + "group": "WM_ERROR", + "at": "com\/android\/server\/wm\/WindowManagerService.java" + }, "2045641491": { "message": "Checking %d opening apps (frozen=%b timeout=%b)...", "level": "VERBOSE", diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java index e91987dab972..ac13f96585b6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java @@ -16,6 +16,7 @@ package com.android.wm.shell.kidsmode; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; @@ -68,7 +69,7 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { private static final String TAG = "KidsModeTaskOrganizer"; private static final int[] CONTROLLED_ACTIVITY_TYPES = - {ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD}; + {ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_HOME}; private static final int[] CONTROLLED_WINDOWING_MODES = {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED}; @@ -93,6 +94,8 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { private KidsModeSettingsObserver mKidsModeSettingsObserver; private boolean mEnabled; + private ActivityManager.RunningTaskInfo mHomeTask; + private final BroadcastReceiver mUserSwitchIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -219,6 +222,13 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { } super.onTaskAppeared(taskInfo, leash); + // Only allow home to draw under system bars. + if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) { + final WindowContainerTransaction wct = getWindowContainerTransaction(); + wct.setBounds(taskInfo.token, new Rect(0, 0, mDisplayWidth, mDisplayHeight)); + mSyncQueue.queue(wct); + mHomeTask = taskInfo; + } mSyncQueue.runInSync(t -> { // Reset several properties back to fullscreen (PiP, for example, leaves all these // properties in a bad state). @@ -291,6 +301,13 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { } mLaunchRootTask = null; mLaunchRootLeash = null; + if (mHomeTask != null && mHomeTask.token != null) { + final WindowContainerToken homeToken = mHomeTask.token; + final WindowContainerTransaction wct = getWindowContainerTransaction(); + wct.setBounds(homeToken, null); + mSyncQueue.queue(wct); + } + mHomeTask = null; unregisterOrganizer(); } @@ -320,7 +337,7 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { final SurfaceControl rootLeash = mLaunchRootLeash; mSyncQueue.runInSync(t -> { t.setPosition(rootLeash, taskBounds.left, taskBounds.top); - t.setWindowCrop(rootLeash, taskBounds.width(), taskBounds.height()); + t.setWindowCrop(rootLeash, mDisplayWidth, mDisplayHeight); }); } } @@ -351,7 +368,7 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { final SurfaceControl finalLeash = mLaunchRootLeash; mSyncQueue.runInSync(t -> { t.setPosition(finalLeash, taskBounds.left, taskBounds.top); - t.setWindowCrop(finalLeash, taskBounds.width(), taskBounds.height()); + t.setWindowCrop(finalLeash, mDisplayWidth, mDisplayHeight); }); } diff --git a/packages/SettingsLib/Spa/gallery/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle index 7868aff4c189..416a403258bc 100644 --- a/packages/SettingsLib/Spa/gallery/build.gradle +++ b/packages/SettingsLib/Spa/gallery/build.gradle @@ -42,11 +42,11 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '11' freeCompilerArgs = ["-Xjvm-default=all"] } buildFeatures { diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle index e03561566402..6f1b41c69ff0 100644 --- a/packages/SettingsLib/Spa/spa/build.gradle +++ b/packages/SettingsLib/Spa/spa/build.gradle @@ -48,11 +48,11 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '11' freeCompilerArgs = ["-Xjvm-default=all"] } buildFeatures { diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle index b898a4fb89ef..536829e00b65 100644 --- a/packages/SettingsLib/Spa/testutils/build.gradle +++ b/packages/SettingsLib/Spa/testutils/build.gradle @@ -38,11 +38,11 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '11' freeCompilerArgs = ["-Xjvm-default=all"] } buildFeatures { diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java index 8b68a09bbf65..e0588ee9cfeb 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java @@ -53,6 +53,12 @@ public class BatteryStatus { public final int maxChargingWattage; public final boolean present; + public static BatteryStatus create(Context context) { + final Intent batteryChangedIntent = BatteryUtils.getBatteryIntent(context); + return batteryChangedIntent == null + ? null : new BatteryStatus(batteryChangedIntent); + } + public BatteryStatus(int status, int level, int plugged, int health, int maxChargingWattage, boolean present) { this.status = status; @@ -87,11 +93,7 @@ public class BatteryStatus { } } - /** - * Determine whether the device is plugged in (USB, power, wireless or dock). - * - * @return true if the device is plugged in. - */ + /** Determine whether the device is plugged. */ public boolean isPluggedIn() { return plugged == BatteryManager.BATTERY_PLUGGED_AC || plugged == BatteryManager.BATTERY_PLUGGED_USB @@ -99,30 +101,19 @@ public class BatteryStatus { || plugged == BatteryManager.BATTERY_PLUGGED_DOCK; } - /** - * Determine whether the device is plugged in (USB, power). - * - * @return true if the device is plugged in wired (as opposed to wireless) - */ + /** Determine whether the device is plugged in (USB, power). */ public boolean isPluggedInWired() { return plugged == BatteryManager.BATTERY_PLUGGED_AC || plugged == BatteryManager.BATTERY_PLUGGED_USB; } /** - * Determine whether the device is plugged in wireless. - * - * @return true if the device is plugged in wireless - */ + * Determine whether the device is plugged in wireless. */ public boolean isPluggedInWireless() { return plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS; } - /** - * Determine whether the device is plugged in dock. - * - * @return true if the device is plugged in dock - */ + /** Determine whether the device is plugged in dock. */ public boolean isPluggedInDock() { return plugged == BatteryManager.BATTERY_PLUGGED_DOCK; } @@ -131,36 +122,22 @@ public class BatteryStatus { * Whether or not the device is charged. Note that some devices never return 100% for * battery level, so this allows either battery level or status to determine if the * battery is charged. - * - * @return true if the device is charged */ public boolean isCharged() { return isCharged(status, level); } - /** - * Whether battery is low and needs to be charged. - * - * @return true if battery is low - */ + /** Whether battery is low and needs to be charged. */ public boolean isBatteryLow() { return level < LOW_BATTERY_THRESHOLD; } - /** - * Whether battery is overheated. - * - * @return true if battery is overheated - */ + /** Whether battery is overheated. */ public boolean isOverheated() { return health == BATTERY_HEALTH_OVERHEAT; } - /** - * Return current chargin speed is fast, slow or normal. - * - * @return the charing speed - */ + /** Return current chargin speed is fast, slow or normal. */ public final int getChargingSpeed(Context context) { final int slowThreshold = context.getResources().getInteger( R.integer.config_chargingSlowlyThreshold); diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java new file mode 100644 index 000000000000..ad9886e1b0a1 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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.android.settingslib.fuelgauge; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +public final class BatteryUtils { + + /** Gets the latest sticky battery intent from the Android system. */ + public static Intent getBatteryIntent(Context context) { + return context.registerReceiver( + /*receiver=*/ null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + } +} diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index bf110194cb8c..17203570ad55 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2561,7 +2561,10 @@ <string name="media_output_broadcast_update_error">Can\u2019t save. Try again.</string> <!-- The error message when Broadcast name/code update failed and can't change again[CHAR LIMIT=60] --> <string name="media_output_broadcast_last_update_error">Can\u2019t save.</string> - + <!-- The hint message when Broadcast code is less than 4 characters [CHAR LIMIT=60] --> + <string name="media_output_broadcast_code_hint_no_less_than_min">Use at least 4 characters</string> + <!-- The hint message when Broadcast code is more than 16 characters [CHAR LIMIT=60] --> + <string name="media_output_broadcast_code_hint_no_more_than_max">Use fewer than 16 characters</string> <!-- Label for clip data when copying the build number off QS [CHAR LIMIT=NONE]--> <string name="build_number_clip_data_label">Build number</string> @@ -2843,7 +2846,7 @@ </string> <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]--> - <string name="log_access_confirmation_learn_more_url" translatable="false">https://support.google.com/android?p=system_logs#topic=7313011</string> + <string name="log_access_confirmation_learn_more_url" translatable="false">https://support.google.com/android/answer/12986432</string> <string name="log_access_confirmation_learn_more">Learn more</string> <string name="log_access_confirmation_learn_more_at">Learn more at <xliff:g id="url" example="g.co/android/devicelogs">%s</xliff:g></string> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index f491232790c1..06e7d80d01b9 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -261,6 +261,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @VisibleForTesting public static final int BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED = -1; public static final int BIOMETRIC_HELP_FACE_NOT_RECOGNIZED = -2; + public static final int BIOMETRIC_HELP_FACE_NOT_AVAILABLE = -3; /** * If no cancel signal has been received after this amount of time, set the biometric running @@ -2949,9 +2950,26 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // This would need to be updated for multi-sensor devices final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty() && mFaceSensorProperties.get(0).supportsFaceDetection; - if (!isUnlockingWithBiometricAllowed(FACE) && supportsFaceDetection) { - mLogger.v("startListeningForFace - detect"); - mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId); + if (!isUnlockingWithBiometricAllowed(FACE)) { + final boolean udfpsFingerprintAuthRunning = isUdfpsSupported() + && isFingerprintDetectionRunning(); + if (supportsFaceDetection && !udfpsFingerprintAuthRunning) { + // Run face detection. (If a face is detected, show the bouncer.) + mLogger.v("startListeningForFace - detect"); + mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId); + } else { + // Don't run face detection. Instead, inform the user + // face auth is unavailable and how to proceed. + // (ie: "Use fingerprint instead" or "Swipe up to open") + mLogger.v("Ignoring \"startListeningForFace - detect\". " + + "Informing user face isn't available."); + mFaceAuthenticationCallback.onAuthenticationHelp( + BIOMETRIC_HELP_FACE_NOT_AVAILABLE, + mContext.getResources().getString( + R.string.keyguard_face_unlock_unavailable) + ); + return; + } } else { mLogger.v("startListeningForFace - authenticate"); final boolean isBypassEnabled = mKeyguardBypassController != null diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java index 35baf0131b9d..12d6b7ccf5cd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java @@ -20,6 +20,8 @@ import android.app.AlertDialog; import android.content.Context; import android.graphics.Bitmap; import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; import android.text.method.HideReturnsTransformationMethod; import android.text.method.PasswordTransformationMethod; import android.util.Log; @@ -64,11 +66,51 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { private String mCurrentBroadcastName; private String mCurrentBroadcastCode; private boolean mIsStopbyUpdateBroadcastCode = false; + private TextWatcher mTextWatcher = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // Do nothing + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + // Do nothing + } + + @Override + public void afterTextChanged(Editable s) { + if (mAlertDialog == null || mBroadcastErrorMessage == null) { + return; + } + boolean breakBroadcastCodeRuleTextLengthLessThanMin = + s.length() > 0 && s.length() < BROADCAST_CODE_MIN_LENGTH; + boolean breakBroadcastCodeRuleTextLengthMoreThanMax = + s.length() > BROADCAST_CODE_MAX_LENGTH; + boolean breakRule = breakBroadcastCodeRuleTextLengthLessThanMin + || breakBroadcastCodeRuleTextLengthMoreThanMax; + + if (breakBroadcastCodeRuleTextLengthLessThanMin) { + mBroadcastErrorMessage.setText( + R.string.media_output_broadcast_code_hint_no_less_than_min); + } else if (breakBroadcastCodeRuleTextLengthMoreThanMax) { + mBroadcastErrorMessage.setText( + R.string.media_output_broadcast_code_hint_no_more_than_max); + } + + mBroadcastErrorMessage.setVisibility(breakRule ? View.VISIBLE : View.INVISIBLE); + Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE); + if (positiveBtn != null) { + positiveBtn.setEnabled(breakRule ? false : true); + } + } + }; static final int METADATA_BROADCAST_NAME = 0; static final int METADATA_BROADCAST_CODE = 1; private static final int MAX_BROADCAST_INFO_UPDATE = 3; + private static final int BROADCAST_CODE_MAX_LENGTH = 16; + private static final int BROADCAST_CODE_MIN_LENGTH = 4; MediaOutputBroadcastDialog(Context context, boolean aboveStatusbar, BroadcastSender broadcastSender, MediaOutputController mediaOutputController) { @@ -219,6 +261,9 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { R.layout.media_output_broadcast_update_dialog, null); final EditText editText = layout.requireViewById(R.id.broadcast_edit_text); editText.setText(editString); + if (isBroadcastCode) { + editText.addTextChangedListener(mTextWatcher); + } mBroadcastErrorMessage = layout.requireViewById(R.id.broadcast_error_message); mAlertDialog = new Builder(mContext) .setTitle(isBroadcastCode ? R.string.media_output_broadcast_code diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index dff79c0df1fa..250900e3e931 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -25,6 +25,7 @@ import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; import static android.view.View.GONE; import static android.view.View.VISIBLE; +import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE; import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED; import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser; @@ -1096,18 +1097,23 @@ public class KeyguardIndicationController { } } + final boolean faceAuthUnavailable = biometricSourceType == FACE + && msgId == BIOMETRIC_HELP_FACE_NOT_AVAILABLE; + // TODO(b/141025588): refactor to reduce repetition of code/comments // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the // check of whether non-strong biometric is allowed if (!mKeyguardUpdateMonitor - .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)) { + .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */) + && !faceAuthUnavailable) { return; } final boolean faceAuthSoftError = biometricSourceType == FACE - && msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; + && msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED + && msgId != BIOMETRIC_HELP_FACE_NOT_AVAILABLE; final boolean faceAuthFailed = biometricSourceType == FACE && msgId == BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; // ran through matcher & failed final boolean fpAuthFailed = biometricSourceType == FINGERPRINT @@ -1150,6 +1156,13 @@ public class KeyguardIndicationController { getTrustGrantedIndication(), mContext.getString(R.string.keyguard_unlock) ); + } else if (faceAuthUnavailable) { + showBiometricMessage( + helpString, + isUnlockWithFingerprintPossible + ? mContext.getString(R.string.keyguard_suggest_fingerprint) + : mContext.getString(R.string.keyguard_unlock) + ); } else { showBiometricMessage(helpString); } diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt index b22af3b5056c..3f54aebf0263 100644 --- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt +++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt @@ -25,6 +25,7 @@ import android.os.Handler import android.util.ArrayMap import android.util.Log import android.view.InputDevice +import com.android.internal.logging.UiEventLogger import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.flags.FeatureFlags @@ -47,6 +48,7 @@ constructor( @Background private val handler: Handler, @Background private val executor: Executor, private val featureFlags: FeatureFlags, + private val uiEventLogger: UiEventLogger, ) : InputManager.InputDeviceListener, InputManager.InputDeviceBatteryListener, @@ -186,6 +188,7 @@ constructor( } private fun onStylusBluetoothConnected(btAddress: String) { + uiEventLogger.log(StylusUiEvent.BLUETOOTH_STYLUS_CONNECTED) val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return try { bluetoothAdapter.addOnMetadataChangedListener(device, executor, this) @@ -195,6 +198,7 @@ constructor( } private fun onStylusBluetoothDisconnected(btAddress: String) { + uiEventLogger.log(StylusUiEvent.BLUETOOTH_STYLUS_DISCONNECTED) val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return try { bluetoothAdapter.removeOnMetadataChangedListener(device, this) diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUiEvent.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUiEvent.kt index 577292f539b9..99da4ce9ab68 100644 --- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUiEvent.kt +++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUiEvent.kt @@ -29,7 +29,9 @@ enum class StylusUiEvent(private val _id: Int) : UiEventLogger.UiEventEnum { @UiEvent(doc = "UIEvent for Toast shown when stylus started charging") STYLUS_STARTED_CHARGING(1302), @UiEvent(doc = "UIEvent for Toast shown when stylus stopped charging") - STYLUS_STOPPED_CHARGING(1303); + STYLUS_STOPPED_CHARGING(1303), + @UiEvent(doc = "UIEvent for bluetooth stylus connected") BLUETOOTH_STYLUS_CONNECTED(1304), + @UiEvent(doc = "UIEvent for bluetooth stylus disconnected") BLUETOOTH_STYLUS_DISCONNECTED(1305); override fun getId() = _id } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index ed9b5cf7e885..e601779c37bd 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -28,6 +28,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOM import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED; +import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE; import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING; import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT; import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT; @@ -241,7 +242,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Mock private FingerprintInteractiveToAuthProvider mInteractiveToAuthProvider; - + private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties; private final int mCurrentUserId = 100; private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0); @@ -280,9 +281,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { when(mFaceSensorProperties.get(anyInt())).thenReturn( createFaceSensorProperties(/* supportsFaceDetection = */ false)); - when(mFingerprintManager.isHardwareDetected()).thenReturn(true); - when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true); - when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(List.of( + mFingerprintSensorProperties = List.of( new FingerprintSensorPropertiesInternal(1 /* sensorId */, FingerprintSensorProperties.STRENGTH_STRONG, 1 /* maxEnrollmentsPerUser */, @@ -291,7 +290,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { "1.01" /* firmwareVersion */, "00000001" /* serialNumber */, "" /* softwareVersion */)), FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, - false /* resetLockoutRequiresHAT */))); + false /* resetLockoutRequiresHAT */)); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true); + when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn( + mFingerprintSensorProperties); when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true); when(mUserManager.isPrimaryUser()).thenReturn(true); when(mStrongAuthTracker.getStub()).thenReturn(mock(IStrongAuthTracker.Stub.class)); @@ -769,12 +772,43 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void nofaceDetect_whenStrongAuthRequiredAndBypassUdfpsSupportedAndFpRunning() { + // GIVEN mocked keyguardUpdateMonitorCallback + KeyguardUpdateMonitorCallback keyguardUpdateMonitorCallback = + mock(KeyguardUpdateMonitorCallback.class); + mKeyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback); + + // GIVEN bypass is enabled, face detection is supported + lockscreenBypassIsAllowed(); + supportsFaceDetection(); + keyguardIsVisible(); + + // GIVEN udfps is supported and strong auth required for weak biometrics (face) only + givenUdfpsSupported(); + strongAuthRequiredForWeakBiometricOnly(); // this allows fingerprint to run but not face + + // WHEN the device wakes up + mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); + mTestableLooper.processAllMessages(); + + // THEN face detect and authenticate are NOT triggered + verify(mFaceManager, never()).detectFace(any(), any(), anyInt()); + verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(), + anyBoolean()); + + // THEN biometric help message sent to callback + verify(keyguardUpdateMonitorCallback).onBiometricHelp( + eq(BIOMETRIC_HELP_FACE_NOT_AVAILABLE), anyString(), eq(BiometricSourceType.FACE)); + } + + @Test public void faceDetect_whenStrongAuthRequiredAndBypass() { // GIVEN bypass is enabled, face detection is supported and strong auth is required lockscreenBypassIsAllowed(); supportsFaceDetection(); strongAuthRequiredEncrypted(); keyguardIsVisible(); + // fingerprint is NOT running, UDFPS is NOT supported // WHEN the device wakes up mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); @@ -2465,6 +2499,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); } + private void strongAuthRequiredForWeakBiometricOnly() { + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(true))).thenReturn(true); + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(false))).thenReturn(false); + } + private void strongAuthNotRequired() { when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser())) .thenReturn(0); @@ -2519,6 +2558,12 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTestableLooper.processAllMessages(); } + private void givenUdfpsSupported() { + Assert.assertFalse(mFingerprintSensorProperties.isEmpty()); + when(mAuthController.getUdfpsProps()).thenReturn(mFingerprintSensorProperties); + Assert.assertTrue(mKeyguardUpdateMonitor.isUdfpsSupported()); + } + private void setBroadcastReceiverPendingResult(BroadcastReceiver receiver) { BroadcastReceiver.PendingResult pendingResult = new BroadcastReceiver.PendingResult(Activity.RESULT_OK, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 1ac7eaa303c5..3b4cc7ccdfd5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -23,6 +23,7 @@ import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_T import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT; import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT; +import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE; import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT; @@ -631,6 +632,33 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { } @Test + public void onBiometricHelp_coEx_faceUnavailable() { + createController(); + + // GIVEN unlocking with fingerprint is possible + when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt())) + .thenReturn(true); + + String message = "A message"; + mController.setVisible(true); + + // WHEN there's a face unavailable message + mController.getKeyguardCallback().onBiometricHelp( + BIOMETRIC_HELP_FACE_NOT_AVAILABLE, + message, + BiometricSourceType.FACE); + + // THEN show sequential messages such as: 'face unlock unavailable' and + // 'try fingerprint instead' + verifyIndicationMessage( + INDICATION_TYPE_BIOMETRIC_MESSAGE, + message); + verifyIndicationMessage( + INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, + mContext.getString(R.string.keyguard_suggest_fingerprint)); + } + + @Test public void onBiometricHelp_coEx_fpFailure_faceAlreadyUnlocked() { createController(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt index a08e00223381..56203d9ab3ba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt @@ -23,6 +23,7 @@ import android.os.Handler import android.testing.AndroidTestingRunner import android.view.InputDevice import androidx.test.filters.SmallTest +import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags @@ -53,8 +54,11 @@ class StylusManagerTest : SysuiTestCase() { @Mock lateinit var bluetoothAdapter: BluetoothAdapter @Mock lateinit var bluetoothDevice: BluetoothDevice @Mock lateinit var handler: Handler + @Mock lateinit var featureFlags: FeatureFlags + @Mock lateinit var uiEventLogger: UiEventLogger + @Mock lateinit var stylusCallback: StylusManager.StylusCallback @Mock lateinit var otherStylusCallback: StylusManager.StylusCallback @@ -75,7 +79,15 @@ class StylusManagerTest : SysuiTestCase() { } stylusManager = - StylusManager(mContext, inputManager, bluetoothAdapter, handler, EXECUTOR, featureFlags) + StylusManager( + mContext, + inputManager, + bluetoothAdapter, + handler, + EXECUTOR, + featureFlags, + uiEventLogger + ) whenever(otherDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(false) whenever(stylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true) @@ -104,7 +116,15 @@ class StylusManagerTest : SysuiTestCase() { @Test fun startListener_hasNotStarted_registersInputDeviceListener() { stylusManager = - StylusManager(mContext, inputManager, bluetoothAdapter, handler, EXECUTOR, featureFlags) + StylusManager( + mContext, + inputManager, + bluetoothAdapter, + handler, + EXECUTOR, + featureFlags, + uiEventLogger + ) stylusManager.startListener() @@ -121,7 +141,15 @@ class StylusManagerTest : SysuiTestCase() { @Test fun onInputDeviceAdded_hasNotStarted_doesNothing() { stylusManager = - StylusManager(mContext, inputManager, bluetoothAdapter, handler, EXECUTOR, featureFlags) + StylusManager( + mContext, + inputManager, + bluetoothAdapter, + handler, + EXECUTOR, + featureFlags, + uiEventLogger + ) stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID) @@ -203,7 +231,15 @@ class StylusManagerTest : SysuiTestCase() { @Test fun onInputDeviceChanged_hasNotStarted_doesNothing() { stylusManager = - StylusManager(mContext, inputManager, bluetoothAdapter, handler, EXECUTOR, featureFlags) + StylusManager( + mContext, + inputManager, + bluetoothAdapter, + handler, + EXECUTOR, + featureFlags, + uiEventLogger + ) stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID) @@ -268,7 +304,15 @@ class StylusManagerTest : SysuiTestCase() { @Test fun onInputDeviceRemoved_hasNotStarted_doesNothing() { stylusManager = - StylusManager(mContext, inputManager, bluetoothAdapter, handler, EXECUTOR, featureFlags) + StylusManager( + mContext, + inputManager, + bluetoothAdapter, + handler, + EXECUTOR, + featureFlags, + uiEventLogger + ) stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID) stylusManager.onInputDeviceRemoved(STYLUS_DEVICE_ID) @@ -337,6 +381,13 @@ class StylusManagerTest : SysuiTestCase() { } @Test + fun onStylusBluetoothConnected_logsEvent() { + stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) + + verify(uiEventLogger, times(1)).log(StylusUiEvent.BLUETOOTH_STYLUS_CONNECTED) + } + + @Test fun onStylusBluetoothDisconnected_unregistersMetadataListener() { stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) @@ -346,6 +397,15 @@ class StylusManagerTest : SysuiTestCase() { } @Test + fun onStylusBluetoothDisconnected_logsEvent() { + stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) + + stylusManager.onInputDeviceRemoved(BT_STYLUS_DEVICE_ID) + + verify(uiEventLogger, times(1)).log(StylusUiEvent.BLUETOOTH_STYLUS_DISCONNECTED) + } + + @Test fun onMetadataChanged_multipleRegisteredBatteryCallbacks_executesAll() { stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) stylusManager.registerBatteryCallback(otherStylusBatteryCallback) diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index b991a026faa1..5a7fbc57ba11 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -150,6 +150,12 @@ public final class AutofillManagerService @NonNull final FrameworkResourcesServiceNameResolver mAugmentedAutofillResolver; + /** + * Object used to set the name of the field classification service. + */ + @NonNull + final FrameworkResourcesServiceNameResolver mFieldClassificationResolver; + private final AutoFillUI mUi; private final LocalLog mRequestsHistory = new LocalLog(20); @@ -245,6 +251,15 @@ public final class AutofillManagerService mAugmentedAutofillResolver.setOnTemporaryServiceNameChangedCallback( (u, s, t) -> onAugmentedServiceNameChanged(u, s, t)); + mFieldClassificationResolver = new FrameworkResourcesServiceNameResolver(getContext(), + com.android.internal.R.string.config_defaultFieldClassificationService); + if (sVerbose) { + Slog.v(TAG, "Resolving FieldClassificationService to serviceName: " + + mFieldClassificationResolver.readServiceName(0)); + } + mFieldClassificationResolver.setOnTemporaryServiceNameChangedCallback( + (u, s, t) -> onFieldClassificationServiceNameChanged(u, s, t)); + if (mSupportedSmartSuggestionModes != AutofillManager.FLAG_SMART_SUGGESTION_OFF) { final List<UserInfo> users = getSupportedUsers(); for (int i = 0; i < users.size(); i++) { @@ -358,6 +373,20 @@ public final class AutofillManagerService } } + private void onFieldClassificationServiceNameChanged( + @UserIdInt int userId, @Nullable String serviceName, boolean isTemporary) { + synchronized (mLock) { + final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); + if (service == null) { + // If we cannot get the service from the services cache, it will call + // updateRemoteAugmentedAutofillService() finally. Skip call this update again. + getServiceForUserLocked(userId); + } else { + service.updateRemoteFieldClassificationService(); + } + } + } + @Override // from AbstractMasterSystemService protected AutofillManagerServiceImpl newServiceLocked(@UserIdInt int resolvedUserId, boolean disabled) { diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index cc29109d85c5..76e6974e14a6 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -162,6 +162,17 @@ final class AutofillManagerServiceImpl private long mLastPrune = 0; /** + * Reference to the {@link RemoteFieldClassificationService}, is set on demand. + */ + @GuardedBy("mLock") + @Nullable + private RemoteFieldClassificationService mRemoteFieldClassificationService; + + @GuardedBy("mLock") + @Nullable + private ServiceInfo mRemoteFieldClassificationServiceInfo; + + /** * Reference to the {@link RemoteAugmentedAutofillService}, is set on demand. */ @GuardedBy("mLock") @@ -1051,10 +1062,11 @@ final class AutofillManagerServiceImpl } pw.print(prefix); pw.print("Default component: "); pw.println(getContext() .getString(R.string.config_defaultAutofillService)); + pw.println(); - pw.print(prefix); pw.println("mAugmentedAutofillNamer: "); - pw.print(prefix2); mMaster.mAugmentedAutofillResolver.dumpShort(pw, mUserId); pw.println(); - + pw.print(prefix); pw.println("mAugmentedAutofillName: "); + pw.print(prefix2); mMaster.mAugmentedAutofillResolver.dumpShort(pw, mUserId); + pw.println(); if (mRemoteAugmentedAutofillService != null) { pw.print(prefix); pw.println("RemoteAugmentedAutofillService: "); mRemoteAugmentedAutofillService.dump(prefix2, pw); @@ -1063,6 +1075,27 @@ final class AutofillManagerServiceImpl pw.print(prefix); pw.print("RemoteAugmentedAutofillServiceInfo: "); pw.println(mRemoteAugmentedAutofillServiceInfo); } + pw.println(); + + pw.print(prefix); pw.println("mFieldClassificationService for system detection"); + pw.print(prefix2); pw.print("Default component: "); pw.println(getContext() + .getString(R.string.config_defaultFieldClassificationService)); + pw.print(prefix2); mMaster.mFieldClassificationResolver.dumpShort(pw, mUserId); + pw.println(); + + if (mRemoteFieldClassificationService != null) { + pw.print(prefix); pw.println("RemoteFieldClassificationService: "); + mRemoteFieldClassificationService.dump(prefix2, pw); + } else { + pw.print(prefix); pw.println("mRemoteFieldClassificationService: null"); + } + if (mRemoteFieldClassificationServiceInfo != null) { + pw.print(prefix); pw.print("RemoteFieldClassificationServiceInfo: "); + pw.println(mRemoteFieldClassificationServiceInfo); + } else { + pw.print(prefix); pw.println("mRemoteFieldClassificationServiceInfo: null"); + } + pw.println(); pw.print(prefix); pw.print("Field classification enabled: "); pw.println(isFieldClassificationEnabledLocked()); @@ -1629,6 +1662,95 @@ final class AutofillManagerServiceImpl } } + @GuardedBy("mLock") + @Nullable RemoteFieldClassificationService getRemoteFieldClassificationServiceLocked() { + if (mRemoteFieldClassificationService == null) { + final String serviceName = mMaster.mFieldClassificationResolver.getServiceName(mUserId); + if (serviceName == null) { + if (mMaster.verbose) { + Slog.v(TAG, "getRemoteFieldClassificationServiceLocked(): not set"); + } + return null; + } + if (sVerbose) { + Slog.v(TAG, "getRemoteFieldClassificationServiceLocked serviceName: " + + serviceName); + } + boolean sTemporaryFieldDetectionService = + mMaster.mFieldClassificationResolver.isTemporary(mUserId); + final Pair<ServiceInfo, ComponentName> pair = RemoteFieldClassificationService + .getComponentName(serviceName, mUserId, sTemporaryFieldDetectionService); + if (pair == null) { + Slog.w(TAG, "RemoteFieldClassificationService.getComponentName returned null " + + "with serviceName: " + serviceName); + return null; + } + + mRemoteFieldClassificationServiceInfo = pair.first; + final ComponentName componentName = pair.second; + if (sVerbose) { + Slog.v(TAG, "getRemoteFieldClassificationServiceLocked(): " + componentName); + } + final int serviceUid = mRemoteFieldClassificationServiceInfo.applicationInfo.uid; + mRemoteFieldClassificationService = new RemoteFieldClassificationService(getContext(), + componentName, serviceUid, mUserId); + } + + return mRemoteFieldClassificationService; + } + + @GuardedBy("mLock") + @Nullable RemoteFieldClassificationService + getRemoteFieldClassificationServiceIfCreatedLocked() { + return mRemoteFieldClassificationService; + } + + /** + * Called when the {@link AutofillManagerService#mAugmentedAutofillResolver} + * changed (among other places). + */ + void updateRemoteFieldClassificationService() { + synchronized (mLock) { + if (mRemoteFieldClassificationService != null) { + if (sVerbose) { + Slog.v(TAG, "updateRemoteFieldClassificationService(): " + + "destroying old remote service"); + } + mRemoteFieldClassificationService.unbind(); + + mRemoteFieldClassificationService = null; + mRemoteFieldClassificationServiceInfo = null; + } + + final boolean available = isFieldClassificationServiceAvailableLocked(); + if (sVerbose) Slog.v(TAG, "updateRemoteFieldClassificationService(): " + available); + + if (available) { + mRemoteFieldClassificationService = getRemoteFieldClassificationServiceLocked(); + } + } + } + + private boolean isFieldClassificationServiceAvailableLocked() { + if (mMaster.verbose) { + Slog.v(TAG, "isAugmentedAutofillService(): " + + "setupCompleted=" + isSetupCompletedLocked() + + ", disabled=" + isDisabledByUserRestrictionsLocked() + + ", augmentedService=" + + mMaster.mAugmentedAutofillResolver.getServiceName(mUserId)); + } + if (!isSetupCompletedLocked() || isDisabledByUserRestrictionsLocked() + || mMaster.mAugmentedAutofillResolver.getServiceName(mUserId) == null) { + return false; + } + return true; + } + + boolean isRemoteClassificationServiceForUserLocked(int callingUid) { + return mRemoteFieldClassificationServiceInfo != null + && mRemoteFieldClassificationServiceInfo.applicationInfo.uid == callingUid; + } + @Override public String toString() { return "AutofillManagerServiceImpl: [userId=" + mUserId diff --git a/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java b/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java new file mode 100644 index 000000000000..99a2291016e9 --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java @@ -0,0 +1,177 @@ +/* + * 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.android.server.autofill; + +import static com.android.server.autofill.Helper.sDebug; +import static com.android.server.autofill.Helper.sVerbose; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.AppGlobals; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; +import android.os.ICancellationSignal; +import android.os.RemoteException; +import android.service.assist.classification.FieldClassificationRequest; +import android.service.assist.classification.FieldClassificationResponse; +import android.service.assist.classification.FieldClassificationService; +import android.service.assist.classification.IFieldClassificationCallback; +import android.service.assist.classification.IFieldClassificationService; +import android.util.Log; +import android.util.Pair; +import android.util.Slog; + +import com.android.internal.infra.AbstractRemoteService; +import com.android.internal.infra.ServiceConnector; + +/** + * Class responsible for connection with the Remote {@link FieldClassificationService}. + * This class is instantiated when {@link AutofillManagerServiceImpl} is established. + * The connection is supposed to be bounded forever, as such, this class persists beyond + * Autofill {@link Session}'s lifecycle. As such, it can't contain information relevant to Session. + * This design is completely different from {@link RemoteFillService}. + */ +final class RemoteFieldClassificationService + extends ServiceConnector.Impl<IFieldClassificationService> { + + private static final String TAG = + "Autofill" + RemoteFieldClassificationService.class.getSimpleName(); + + // Bind forever. + private static final long TIMEOUT_IDLE_UNBIND_MS = + AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS; + private final ComponentName mComponentName; + + public interface FieldClassificationServiceCallbacks { + void onClassificationRequestSuccess(@NonNull FieldClassificationResponse response); + void onClassificationRequestFailure(int requestId, @Nullable CharSequence message); + void onClassificationRequestTimeout(int requestId); + void onServiceDied(@NonNull RemoteFieldClassificationService service); + } + + RemoteFieldClassificationService(Context context, ComponentName serviceName, + int serviceUid, int userId) { + super(context, + // TODO(b/266379948): Update service + new Intent(FieldClassificationService.SERVICE_INTERFACE).setComponent(serviceName), + /* bindingFlags= */ 0, userId, IFieldClassificationService.Stub::asInterface); + mComponentName = serviceName; + if (sDebug) { + Slog.d(TAG, "About to connect to serviceName: " + serviceName); + } + // Bind right away. + connect(); + } + + @Nullable + static Pair<ServiceInfo, ComponentName> getComponentName(@NonNull String serviceName, + @UserIdInt int userId, boolean isTemporary) { + int flags = PackageManager.GET_META_DATA; + if (!isTemporary) { + flags |= PackageManager.MATCH_SYSTEM_ONLY; + } + + final ComponentName serviceComponent; + ServiceInfo serviceInfo = null; + try { + serviceComponent = ComponentName.unflattenFromString(serviceName); + serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, flags, + userId); + if (serviceInfo == null) { + Slog.e(TAG, "Bad service name for flags " + flags + ": " + serviceName); + return null; + } + } catch (Exception e) { + Slog.e(TAG, "Error getting service info for '" + serviceName + "': " + e); + return null; + } + return new Pair<>(serviceInfo, serviceComponent); + } + + public ComponentName getComponentName() { + return mComponentName; + } + + @Override // from ServiceConnector.Impl + protected void onServiceConnectionStatusChanged(IFieldClassificationService service, + boolean connected) { + try { + if (connected) { + service.onConnected(false, false); + } else { + service.onDisconnected(); + } + } catch (Exception e) { + Slog.w(TAG, + "Exception calling onServiceConnectionStatusChanged(" + connected + "): ", e); + } + } + + @Override // from AbstractRemoteService + protected long getAutoDisconnectTimeoutMs() { + return TIMEOUT_IDLE_UNBIND_MS; + } + + public void onFieldClassificationRequest(@NonNull FieldClassificationRequest request, + FieldClassificationServiceCallbacks fieldClassificationServiceCallbacks) { + + if (sVerbose) { + Slog.v(TAG, "onFieldClassificationRequest request:" + request); + } + + run( + (s) -> + s.onFieldClassificationRequest( + request, + new IFieldClassificationCallback.Stub() { + @Override + public void onCancellable(ICancellationSignal cancellation) { + if (sDebug) { + Log.d(TAG, "onCancellable"); + } + } + + @Override + public void onSuccess(FieldClassificationResponse response) { + if (sDebug) { + Log.d(TAG, "onSuccess Response: " + response); + } + fieldClassificationServiceCallbacks + .onClassificationRequestSuccess(response); + } + + @Override + public void onFailure() { + if (sDebug) { + Log.d(TAG, "onFailure"); + } + } + + @Override + public boolean isCompleted() throws RemoteException { + return false; + } + + @Override + public void cancel() throws RemoteException {} + })); + } +} diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java index 94872b09cd36..4688658bf1c3 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java @@ -135,6 +135,9 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { } public void onFillRequest(@NonNull FillRequest request) { + if (sVerbose) { + Slog.v(TAG, "onFillRequest:" + request); + } AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>(); AtomicReference<CompletableFuture<FillResponse>> futureRef = new AtomicReference<>(); diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index c5c92885406f..b55f16bc9ae4 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -88,6 +88,8 @@ import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.SystemClock; +import android.service.assist.classification.FieldClassificationRequest; +import android.service.assist.classification.FieldClassificationResponse; import android.service.autofill.AutofillFieldClassificationService.Scores; import android.service.autofill.AutofillService; import android.service.autofill.CompositeUserData; @@ -125,6 +127,7 @@ import android.view.autofill.AutofillValue; import android.view.autofill.IAutoFillManagerClient; import android.view.autofill.IAutofillWindowPresenter; import android.view.inputmethod.InlineSuggestionsRequest; +import android.widget.RemoteViews; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; @@ -146,6 +149,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Function; @@ -163,7 +167,8 @@ import java.util.function.Function; * until the user authenticates or it times out. */ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener, - AutoFillUI.AutoFillUiCallback, ValueFinder { + AutoFillUI.AutoFillUiCallback, ValueFinder, + RemoteFieldClassificationService.FieldClassificationServiceCallbacks { private static final String TAG = "AutofillSession"; private static final String ACTION_DELAYED_FILL = @@ -185,6 +190,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private static AtomicInteger sIdCounter = new AtomicInteger(2); + private static AtomicInteger sIdCounterForPcc = new AtomicInteger(2); + @GuardedBy("mLock") private @SessionState int mSessionState = STATE_UNKNOWN; @@ -395,6 +402,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Nullable private ClientSuggestionsSession mClientSuggestionsSession; + private final ClassificationState mClassificationState = new ClassificationState(); + // TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a // new one per Session. private final BroadcastReceiver mDelayedFillBroadcastReceiver = @@ -729,24 +738,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * Assist Data Receiver for PCC */ private final class PccAssistDataReceiverImpl extends IAssistDataReceiver.Stub { - // TODO: Uncomment lines below after field classification service definition merged - // @GuardedBy("mLock") - // private FieldClassificationRequest mPendingFieldClassifitacionRequest; - // @GuardedBy("mLock") - // private FieldClassificationRequest mLastFieldClassifitacionRequest; @GuardedBy("mLock") void maybeRequestFieldClassificationFromServiceLocked() { - // TODO: Uncomment lines below after field classification service definition merged - // if (mPendingFieldClassifitacionRequest == null) { - // return; - // } - // mLastFieldClassifitacionRequest = mPendingFieldClassifitacionRequest; - // - // mRemoteFieldClassificationService.onFieldClassificationRequest( - // mPendingFieldClassifitacionRequest); - // - // mPendingFieldClassifitacionRequest = null; + if (mClassificationState.mPendingFieldClassificationRequest == null) { + Log.w(TAG, "Received AssistData without pending classification request"); + return; + } + + RemoteFieldClassificationService remoteFieldClassificationService = + mService.getRemoteFieldClassificationServiceLocked(); + if (remoteFieldClassificationService != null) { + remoteFieldClassificationService.onFieldClassificationRequest( + mClassificationState.mPendingFieldClassificationRequest, Session.this); + } + mClassificationState.onFieldClassificationRequestSent(); } @Override @@ -793,12 +799,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState ids.get(i).setSessionId(Session.this.id); } - // TODO: Uncomment lines below after field classification service definition merged - // FieldClassificationRequest request = new FieldClassificationRequest(structure); - // - // mPendingFieldClassifitacionRequest = request; - // - // maybeRequestFieldClassificationFromServiceLocked(); + mClassificationState.onAssistStructureReceived(structure); + + maybeRequestFieldClassificationFromServiceLocked(); } } @@ -1177,9 +1180,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState int requestId; // TODO(b/158623971): Update this to prevent possible overflow do { - requestId = sIdCounter.getAndIncrement(); + requestId = sIdCounterForPcc.getAndIncrement(); } while (requestId == INVALID_REQUEST_ID); + if (sVerbose) { + Slog.v(TAG, "request id is " + requestId + ", requesting assist structure for pcc"); + } // Call requestAutofilLData try { final Bundle receiverExtras = new Bundle(); @@ -1187,8 +1193,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final long identity = Binder.clearCallingIdentity(); try { if (!ActivityTaskManager.getService().requestAutofillData(mPccAssistReceiver, - receiverExtras, mActivityToken, flags)) { - Slog.w(TAG, "failed to request autofill data for pcc: " + mActivityToken); + receiverExtras, mActivityToken, flags)) { + Slog.w(TAG, "failed to request autofill data for " + mActivityToken); } } finally { Binder.restoreCallingIdentity(identity); @@ -1386,6 +1392,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } + // TODO: Check if this is required. We can still present datasets to the user even if + // traditional field classification is disabled. fieldClassificationIds = response.getFieldClassificationIds(); if (!mSessionFlags.mClientSuggestionsEnabled && fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) { @@ -1470,11 +1478,231 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } + // TODO(b/266379948): Ideally wait for PCC request to finish for a while more + // (say 100ms) before proceeding further on. + synchronized (mLock) { + response = getEffectiveFillResponse(response); processResponseLocked(response, null, requestFlags); } } + private FillResponse getEffectiveFillResponse(FillResponse response) { + // TODO(b/266379948): label dataset source + if (!mService.getMaster().isPccClassificationEnabled()) return response; + synchronized (mLock) { + if (mClassificationState.mState != ClassificationState.STATE_RESPONSE + || mClassificationState.mLastFieldClassificationResponse == null) { + return response; + } + if (!mClassificationState.processResponse()) return response; + } + boolean preferAutofillProvider = mService.getMaster().preferProviderOverPcc(); + boolean shouldUseFallback = mService.getMaster().shouldUsePccFallback(); + if (preferAutofillProvider && !shouldUseFallback) { + return response; + } + + DatasetComputationContainer autofillProviderContainer = new DatasetComputationContainer(); + DatasetComputationContainer detectionPccContainer = new DatasetComputationContainer(); + + computeDatasetsForProviderAndUpdateContainer(response, autofillProviderContainer); + computeDatasetsForPccAndUpdateContainer(response, detectionPccContainer); + + DatasetComputationContainer resultContainer; + if (preferAutofillProvider) { + resultContainer = autofillProviderContainer; + if (shouldUseFallback) { + // add PCC datasets that are not detected by provider. + addFallbackDatasets(autofillProviderContainer, detectionPccContainer); + } + } else { + resultContainer = detectionPccContainer; + if (shouldUseFallback) { + // add Provider's datasets that are not detected by PCC. + addFallbackDatasets(detectionPccContainer, autofillProviderContainer); + } + } + // Create FillResponse with effectiveDatasets, and all the rest value from the original + // response. + return FillResponse.shallowCopy(response, new ArrayList<>(resultContainer.mDatasets)); + } + + /** + * A private class to hold & compute datasets to be shown + */ + private static class DatasetComputationContainer { + // List of all autofill ids that have a corresponding datasets + Set<AutofillId> mAutofillIds = new ArraySet<>(); + // Set of datasets. Kept separately, to be able to be used directly for composing + // FillResponse. + Set<Dataset> mDatasets = new ArraySet<>(); + ArrayMap<AutofillId, Set<Dataset>> mAutofillIdToDatasetMap = new ArrayMap<>(); + } + + // Adds fallback datasets to the first container. + // This function will destruct and modify c2 container. + private void addFallbackDatasets( + DatasetComputationContainer c1, DatasetComputationContainer c2) { + for (AutofillId id : c2.mAutofillIds) { + if (!c1.mAutofillIds.contains(id)) { + + // Since c2 could be modified in a previous iteration, it's possible that all + // datasets corresponding to it have been evaluated, and it's map no longer has + // any more datasets left. Early return in this case. + if (c2.mAutofillIdToDatasetMap.get(id).isEmpty()) return; + + // For AutofillId id, do the following + // 1. Add all the datasets corresponding to it to c1's dataset, and update c1 + // properly. + // 2. All the datasets that were added should be removed from the other autofill + // ids that were in this dataset. This prevents us from revisiting those datasets. + // Although we are using Sets, and that'd avoid re-adding them, using this logic + // for now to keep safe. TODO(b/266379948): Revisit this logic. + + Set<Dataset> datasets = c2.mAutofillIdToDatasetMap.get(id); + Set<Dataset> copyDatasets = new ArraySet<>(datasets); + c1.mAutofillIds.add(id); + c1.mAutofillIdToDatasetMap.put(id, copyDatasets); + c1.mDatasets.addAll(copyDatasets); + + for (Dataset dataset : datasets) { + for (AutofillId currentId : dataset.getFieldIds()) { + if (currentId.equals(id)) continue; + // For this id, we need to remove the dataset from it's map. + c2.mAutofillIdToDatasetMap.get(currentId).remove(dataset); + } + } + } + } + } + + private void computeDatasetsForProviderAndUpdateContainer( + FillResponse response, DatasetComputationContainer container) { + List<Dataset> datasets = response.getDatasets(); + if (datasets == null) return; + ArrayMap<AutofillId, Set<Dataset>> autofillIdToDatasetMap = new ArrayMap<>(); + Set<Dataset> eligibleDatasets = new ArraySet<>(); + Set<AutofillId> eligibleAutofillIds = new ArraySet<>(); + for (Dataset dataset : response.getDatasets()) { + if (dataset.getFieldIds() == null) continue; + if (dataset.getAutofillDatatypes() != null + && dataset.getAutofillDatatypes().size() > 0) { + continue; + } + eligibleDatasets.add(dataset); + for (AutofillId id : dataset.getFieldIds()) { + eligibleAutofillIds.add(id); + Set<Dataset> datasetForIds = autofillIdToDatasetMap.get(id); + if (datasetForIds == null) { + datasetForIds = new ArraySet<>(); + } + datasetForIds.add(dataset); + autofillIdToDatasetMap.put(id, datasetForIds); + } + } + container.mAutofillIdToDatasetMap = autofillIdToDatasetMap; + container.mDatasets = eligibleDatasets; + container.mAutofillIds = eligibleAutofillIds; + } + + private void computeDatasetsForPccAndUpdateContainer( + FillResponse response, DatasetComputationContainer container) { + List<Dataset> datasets = response.getDatasets(); + if (datasets == null) return; + + synchronized (mLock) { + ArrayMap<String, Set<AutofillId>> hintsToAutofillIdMap = + mClassificationState.mHintsToAutofillIdMap; + + ArrayMap<String, Set<AutofillId>> groupHintsToAutofillIdMap = + mClassificationState.mGroupHintsToAutofillIdMap; + + ArrayMap<AutofillId, Set<Dataset>> map = new ArrayMap<>(); + + Set<Dataset> eligibleDatasets = new ArraySet<>(); + Set<AutofillId> eligibleAutofillIds = new ArraySet<>(); + + for (int i = 0; i < datasets.size(); i++) { + Dataset dataset = datasets.get(i); + if (dataset.getAutofillDatatypes() == null) continue; + if (dataset.getFieldIds() != null && dataset.getFieldIds().size() > 0) continue; + + ArrayList<AutofillId> fieldIds = new ArrayList<>(); + ArrayList<AutofillValue> fieldValues = new ArrayList<>(); + ArrayList<RemoteViews> fieldPresentations = new ArrayList<>(); + ArrayList<RemoteViews> fieldDialogPresentations = new ArrayList<>(); + ArrayList<InlinePresentation> fieldInlinePresentations = new ArrayList<>(); + ArrayList<InlinePresentation> fieldInlineTooltipPresentations = new ArrayList<>(); + ArrayList<Dataset.DatasetFieldFilter> fieldFilters = new ArrayList<>(); + + for (int j = 0; j < dataset.getAutofillDatatypes().size(); j++) { + String hint = dataset.getAutofillDatatypes().get(j); + + if (hintsToAutofillIdMap.containsKey(hint)) { + ArrayList<AutofillId> tempIds = + new ArrayList<>(hintsToAutofillIdMap.get(hint)); + + for (AutofillId autofillId : tempIds) { + eligibleAutofillIds.add(autofillId); + // For each of the field, copy over values. + fieldIds.add(autofillId); + fieldValues.add(dataset.getFieldValues().get(j)); + // TODO(b/266379948): might need to make it more efficient by not + // copying over value if it didn't exist. This would require creating + // a getter for the presentations arraylist. + fieldPresentations.add(dataset.getFieldPresentation(j)); + fieldDialogPresentations.add(dataset.getFieldDialogPresentation(j)); + fieldInlinePresentations.add(dataset.getFieldInlinePresentation(j)); + fieldInlineTooltipPresentations.add( + dataset.getFieldInlineTooltipPresentation(j)); + fieldFilters.add(dataset.getFilter(j)); + } + + Dataset newDataset = + new Dataset( + fieldIds, + fieldValues, + fieldPresentations, + fieldDialogPresentations, + fieldInlinePresentations, + fieldInlineTooltipPresentations, + fieldFilters, + new ArrayList<>(), + dataset.getFieldContent(), + null, + null, + null, + null, + dataset.getId(), + dataset.getAuthentication()); + eligibleDatasets.add(newDataset); + + // Associate this dataset with all the ids that are represented with it. + Set<Dataset> newDatasets; + for (AutofillId autofillId : tempIds) { + if (map.containsKey(autofillId)) { + newDatasets = map.get(autofillId); + } else { + newDatasets = new ArraySet<>(); + } + newDatasets.add(newDataset); + map.put(autofillId, newDatasets); + } + } + // TODO(b/266379948): handle the case: + // groupHintsToAutofillIdMap.containsKey(hint)) + // but the autofill id not being applicable to other hints. + // TODO(b/266379948): also handle the case where there could be more types in + // the dataset, provided by the provider, however, they aren't applicable. + } + } + container.mAutofillIds = eligibleAutofillIds; + container.mDatasets = eligibleDatasets; + container.mAutofillIdToDatasetMap = map; + } + } + @GuardedBy("mLock") private void processNullResponseOrFallbackLocked(int requestId, int flags) { if (!mSessionFlags.mClientSuggestionsEnabled) { @@ -4579,6 +4807,189 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } + /** + * Class maintaining the state of the requests to + * {@link android.service.assist.classification.FieldClassificationService}. + */ + private static final class ClassificationState { + + /** + * Initial state indicating that the request for classification hasn't been triggered yet. + */ + private static final int STATE_INITIAL = 1; + /** + * Assist request has been triggered, but awaiting response. + */ + private static final int STATE_PENDING_ASSIST_REQUEST = 2; + /** + * Classification request has been triggered, but awaiting response. + */ + private static final int STATE_PENDING_REQUEST = 3; + /** + * Classification response has been received. + */ + private static final int STATE_RESPONSE = 4; + /** + * Classification state has been invalidated, and the last response may no longer be valid. + * This could occur due to various reasons like views changing their layouts, becoming + * visible or invisible, thereby rendering previous response potentially inaccurate or + * incomplete. + */ + private static final int STATE_INVALIDATED = 5; + + @IntDef(prefix = { "STATE_" }, value = { + STATE_INITIAL, + STATE_PENDING_ASSIST_REQUEST, + STATE_PENDING_REQUEST, + STATE_RESPONSE, + STATE_INVALIDATED + }) + @Retention(RetentionPolicy.SOURCE) + @interface ClassificationRequestState{} + + @GuardedBy("mLock") + private @ClassificationRequestState int mState = STATE_INITIAL; + + @GuardedBy("mLock") + private FieldClassificationRequest mPendingFieldClassificationRequest; + + @GuardedBy("mLock") + private FieldClassificationResponse mLastFieldClassificationResponse; + + @GuardedBy("mLock") + private ArrayMap<AutofillId, Set<String>> mClassificationHintsMap; + + @GuardedBy("mLock") + private ArrayMap<AutofillId, Set<String>> mClassificationGroupHintsMap; + + @GuardedBy("mLock") + private ArrayMap<AutofillId, Set<String>> mClassificationCombinedHintsMap; + + /** + * Typically, there would be a 1:1 mapping. However, in certain cases, we may have a hint + * being applicable to many types. An example of this being new/change password forms, + * where you need to confirm the passward twice. + */ + @GuardedBy("mLock") + private ArrayMap<String, Set<AutofillId>> mHintsToAutofillIdMap; + + /** + * Group hints are expected to have a 1:many mapping. For example, different credit card + * fields (creditCardNumber, expiry, cvv) will all map to the same group hints. + */ + @GuardedBy("mLock") + private ArrayMap<String, Set<AutofillId>> mGroupHintsToAutofillIdMap; + + @GuardedBy("mLock") + private String stateToString() { + switch (mState) { + case STATE_INITIAL: + return "STATE_INITIAL"; + case STATE_PENDING_ASSIST_REQUEST: + return "STATE_PENDING_ASSIST_REQUEST"; + case STATE_PENDING_REQUEST: + return "STATE_PENDING_REQUEST"; + case STATE_RESPONSE: + return "STATE_RESPONSE"; + case STATE_INVALIDATED: + return "STATE_INVALIDATED"; + default: + return "UNKNOWN_CLASSIFICATION_STATE_" + mState; + } + } + + /** + * Process the response received. + * @return true if the response was processed, false otherwise. If there wasn't any + * response, yet this function was called, it would return false. + */ + @GuardedBy("mLock") + private boolean processResponse() { + if (mClassificationHintsMap != null && !mClassificationHintsMap.isEmpty()) { + // Already processed, so return + return true; + } + + FieldClassificationResponse response = mLastFieldClassificationResponse; + if (response == null) return false; + + mClassificationHintsMap = new ArrayMap<>(); + mClassificationGroupHintsMap = new ArrayMap<>(); + mHintsToAutofillIdMap = new ArrayMap<>(); + mGroupHintsToAutofillIdMap = new ArrayMap<>(); + Set<android.service.assist.classification.FieldClassification> classifications = + response.getClassifications(); + + for (android.service.assist.classification.FieldClassification classification : + classifications) { + AutofillId id = classification.getAutofillId(); + Set<String> hintDetections = classification.getHints(); + Set<String> groupHintsDetections = classification.getGroupHints(); + ArraySet<String> combinedHints = new ArraySet<>(hintDetections); + mClassificationHintsMap.put(id, hintDetections); + if (groupHintsDetections != null) { + mClassificationGroupHintsMap.put(id, groupHintsDetections); + combinedHints.addAll(groupHintsDetections); + } + mClassificationCombinedHintsMap.put(id, combinedHints); + + processDetections(hintDetections, id, mHintsToAutofillIdMap); + processDetections(groupHintsDetections, id, mGroupHintsToAutofillIdMap); + } + return true; + } + + @GuardedBy("mLock") + private static void processDetections(Set<String> detections, AutofillId id, + ArrayMap<String, Set<AutofillId>> currentMap) { + for (String detection : detections) { + Set<AutofillId> autofillIds; + if (currentMap.containsKey(detection)) { + autofillIds = currentMap.get(detection); + } else { + autofillIds = new ArraySet<>(); + } + autofillIds.add(id); + currentMap.put(detection, autofillIds); + } + } + + @GuardedBy("mLock") + private void invalidateState() { + mState = STATE_INVALIDATED; + } + + @GuardedBy("mLock") + private void updatePendingAssistData() { + mState = STATE_PENDING_ASSIST_REQUEST; + } + + @GuardedBy("mLock") + private void updatePendingRequest() { + mState = STATE_PENDING_REQUEST; + } + + @GuardedBy("mLock") + private void updateResponseReceived(FieldClassificationResponse response) { + mState = STATE_RESPONSE; + mLastFieldClassificationResponse = response; + mPendingFieldClassificationRequest = null; + processResponse(); + } + + @GuardedBy("mLock") + private void onAssistStructureReceived(AssistStructure structure) { + mState = STATE_PENDING_REQUEST; + mPendingFieldClassificationRequest = new FieldClassificationRequest(structure); + } + + @GuardedBy("mLock") + private void onFieldClassificationRequestSent() { + mState = STATE_PENDING_REQUEST; + mPendingFieldClassificationRequest = null; + } + } + @Override public String toString() { return "Session: [id=" + id + ", component=" + mComponentName @@ -5088,4 +5499,28 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState ServiceInfo serviceInfo = mService.getServiceInfo(); return serviceInfo == null ? Process.INVALID_UID : serviceInfo.applicationInfo.uid; } + + // DetectionServiceCallbacks + public void onClassificationRequestSuccess(@Nullable FieldClassificationResponse response) { + mClassificationState.updateResponseReceived(response); + } + + public void onClassificationRequestFailure(int requestId, @Nullable CharSequence message) { + + } + + public void onClassificationRequestTimeout(int requestId) { + + } + + @Override + public void onServiceDied(@NonNull RemoteFieldClassificationService service) { + Slog.w(TAG, "removing session because service died"); + synchronized (mLock) { + // TODO(b/266379948) + // forceRemoveFromServiceLocked(); + } + } + // DetectionServiceCallbacks end + } diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java index a1adc6f4aff3..2a46d862b991 100644 --- a/services/core/java/com/android/server/IntentResolver.java +++ b/services/core/java/com/android/server/IntentResolver.java @@ -77,80 +77,6 @@ public abstract class IntentResolver<F, R extends Object> { } } - public static boolean filterEquals(IntentFilter f1, IntentFilter f2) { - int s1 = f1.countActions(); - int s2 = f2.countActions(); - if (s1 != s2) { - return false; - } - for (int i=0; i<s1; i++) { - if (!f2.hasAction(f1.getAction(i))) { - return false; - } - } - s1 = f1.countCategories(); - s2 = f2.countCategories(); - if (s1 != s2) { - return false; - } - for (int i=0; i<s1; i++) { - if (!f2.hasCategory(f1.getCategory(i))) { - return false; - } - } - s1 = f1.countDataTypes(); - s2 = f2.countDataTypes(); - if (s1 != s2) { - return false; - } - for (int i=0; i<s1; i++) { - if (!f2.hasExactDataType(f1.getDataType(i))) { - return false; - } - } - s1 = f1.countDataSchemes(); - s2 = f2.countDataSchemes(); - if (s1 != s2) { - return false; - } - for (int i=0; i<s1; i++) { - if (!f2.hasDataScheme(f1.getDataScheme(i))) { - return false; - } - } - s1 = f1.countDataAuthorities(); - s2 = f2.countDataAuthorities(); - if (s1 != s2) { - return false; - } - for (int i=0; i<s1; i++) { - if (!f2.hasDataAuthority(f1.getDataAuthority(i))) { - return false; - } - } - s1 = f1.countDataPaths(); - s2 = f2.countDataPaths(); - if (s1 != s2) { - return false; - } - for (int i=0; i<s1; i++) { - if (!f2.hasDataPath(f1.getDataPath(i))) { - return false; - } - } - s1 = f1.countDataSchemeSpecificParts(); - s2 = f2.countDataSchemeSpecificParts(); - if (s1 != s2) { - return false; - } - for (int i=0; i<s1; i++) { - if (!f2.hasDataSchemeSpecificPart(f1.getDataSchemeSpecificPart(i))) { - return false; - } - } - return true; - } - /** * Returns whether an intent matches the IntentFilter with a pre-resolved type. */ @@ -200,7 +126,7 @@ public abstract class IntentResolver<F, R extends Object> { if (cur == null) { break; } - if (filterEquals(getIntentFilter(cur), matching)) { + if (IntentFilter.filterEquals(getIntentFilter(cur), matching)) { if (res == null) { res = new ArrayList<>(); } @@ -225,7 +151,7 @@ public abstract class IntentResolver<F, R extends Object> { } else { ArrayList<F> res = null; for (F cur : mFilters) { - if (filterEquals(getIntentFilter(cur), matching)) { + if (IntentFilter.filterEquals(getIntentFilter(cur), matching)) { if (res == null) { res = new ArrayList<>(); } diff --git a/services/core/java/com/android/server/am/ReceiverList.java b/services/core/java/com/android/server/am/ReceiverList.java index f3d8ba1564fa..7d2dab65958f 100644 --- a/services/core/java/com/android/server/am/ReceiverList.java +++ b/services/core/java/com/android/server/am/ReceiverList.java @@ -25,8 +25,6 @@ import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.proto.ProtoOutputStream; -import com.android.server.IntentResolver; - import java.io.PrintWriter; import java.util.ArrayList; @@ -74,7 +72,7 @@ final class ReceiverList extends ArrayList<BroadcastFilter> final int N = size(); for (int i = 0; i < N; i++) { final BroadcastFilter f = get(i); - if (IntentResolver.filterEquals(f, filter)) { + if (IntentFilter.filterEquals(f, filter)) { return true; } } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index c5b611db48e1..b0a14ab142c7 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -1986,10 +1986,6 @@ class UserController implements Handler.Callback { Slogf.w(TAG, "Cannot switch to User #" + targetUserId + ": not supported"); return false; } - if (targetUserInfo.isProfile()) { - Slogf.w(TAG, "Cannot switch to User #" + targetUserId + ": not a full user"); - return false; - } if (FactoryResetter.isFactoryResetting()) { Slogf.w(TAG, "Cannot switch to User #" + targetUserId + ": factory reset in progress"); return false; diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index a107f33b5244..ac9400d0d96f 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -1345,6 +1345,23 @@ public class DisplayDeviceConfig { } /** + * @return Default refresh rate while the device has high brightness mode enabled for HDR. + */ + public int getDefaultRefreshRateInHbmHdr() { + return mContext.getResources().getInteger( + R.integer.config_defaultRefreshRateInHbmHdr); + } + + /** + * @return Default refresh rate while the device has high brightness mode enabled because of + * high lux. + */ + public int getDefaultRefreshRateInHbmSunlight() { + return mContext.getResources().getInteger( + R.integer.config_defaultRefreshRateInHbmSunlight); + } + + /** * @return Default refresh rate in the higher blocking zone of the associated display */ public int getDefaultHighBlockingZoneRefreshRate() { diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index 91ef167dfc3a..0a4b5b0e5c94 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -123,6 +123,10 @@ public class DisplayModeDirector { private final DeviceConfigInterface mDeviceConfig; private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings; + @GuardedBy("mLock") + @Nullable + private DisplayDeviceConfig mDefaultDisplayDeviceConfig; + // A map from the display ID to the collection of votes and their priority. The latter takes // the form of another map from the priority to the vote itself so that each priority is // guaranteed to have exactly one vote, which is also easily and efficiently replaceable. @@ -163,6 +167,7 @@ public class DisplayModeDirector { mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings(); mSettingsObserver = new SettingsObserver(context, handler); mBrightnessObserver = new BrightnessObserver(context, handler, injector); + mDefaultDisplayDeviceConfig = null; mUdfpsObserver = new UdfpsObserver(); final BallotBox ballotBox = (displayId, priority, vote) -> { synchronized (mLock) { @@ -701,11 +706,15 @@ public class DisplayModeDirector { * @param displayDeviceConfig configurations relating to the underlying display device. */ public void defaultDisplayDeviceUpdated(DisplayDeviceConfig displayDeviceConfig) { - mSettingsObserver.setRefreshRates(displayDeviceConfig, - /* attemptLoadingFromDeviceConfig= */ true); - mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig, - /* attemptLoadingFromDeviceConfig= */ true); - mBrightnessObserver.reloadLightSensor(displayDeviceConfig); + synchronized (mLock) { + mDefaultDisplayDeviceConfig = displayDeviceConfig; + mSettingsObserver.setRefreshRates(displayDeviceConfig, + /* attemptLoadingFromDeviceConfig= */ true); + mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig, + /* attemptLoadingFromDeviceConfig= */ true); + mBrightnessObserver.reloadLightSensor(displayDeviceConfig); + mHbmObserver.setupHdrRefreshRates(displayDeviceConfig); + } } /** @@ -2766,7 +2775,7 @@ public class DisplayModeDirector { * HBM that are associated with that display. Restrictions are retrieved from * DisplayManagerInternal but originate in the display-device-config file. */ - public static class HbmObserver implements DisplayManager.DisplayListener { + public class HbmObserver implements DisplayManager.DisplayListener { private final BallotBox mBallotBox; private final Handler mHandler; private final SparseIntArray mHbmMode = new SparseIntArray(); @@ -2786,10 +2795,24 @@ public class DisplayModeDirector { mDeviceConfigDisplaySettings = displaySettings; } - public void observe() { - mRefreshRateInHbmSunlight = mDeviceConfigDisplaySettings.getRefreshRateInHbmSunlight(); - mRefreshRateInHbmHdr = mDeviceConfigDisplaySettings.getRefreshRateInHbmHdr(); + /** + * Sets up the refresh rate to be used when HDR is enabled + */ + public void setupHdrRefreshRates(DisplayDeviceConfig displayDeviceConfig) { + mRefreshRateInHbmHdr = mDeviceConfigDisplaySettings + .getRefreshRateInHbmHdr(displayDeviceConfig); + mRefreshRateInHbmSunlight = mDeviceConfigDisplaySettings + .getRefreshRateInHbmSunlight(displayDeviceConfig); + } + /** + * Sets up the HDR refresh rates, and starts observing for the changes in the display that + * might impact it + */ + public void observe() { + synchronized (mLock) { + setupHdrRefreshRates(mDefaultDisplayDeviceConfig); + } mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); mInjector.registerDisplayListener(this, mHandler, DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS @@ -3021,26 +3044,33 @@ public class DisplayModeDirector { -1); } - public int getRefreshRateInHbmSunlight() { - final int defaultRefreshRateInHbmSunlight = - mContext.getResources().getInteger( - R.integer.config_defaultRefreshRateInHbmSunlight); - - final int refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, - defaultRefreshRateInHbmSunlight); - + public int getRefreshRateInHbmHdr(DisplayDeviceConfig displayDeviceConfig) { + int refreshRate = + (displayDeviceConfig == null) ? mContext.getResources().getInteger( + R.integer.config_defaultRefreshRateInHbmHdr) + : displayDeviceConfig.getDefaultRefreshRateInHbmHdr(); + try { + refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR, + refreshRate); + } catch (NullPointerException e) { + // Do Nothing + } return refreshRate; } - public int getRefreshRateInHbmHdr() { - final int defaultRefreshRateInHbmHdr = - mContext.getResources().getInteger(R.integer.config_defaultRefreshRateInHbmHdr); - - final int refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR, - defaultRefreshRateInHbmHdr); - + public int getRefreshRateInHbmSunlight(DisplayDeviceConfig displayDeviceConfig) { + int refreshRate = + (displayDeviceConfig == null) ? mContext.getResources() + .getInteger(R.integer.config_defaultRefreshRateInHbmSunlight) + : displayDeviceConfig.getDefaultRefreshRateInHbmSunlight(); + try { + refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, + refreshRate); + } catch (NullPointerException e) { + // Do Nothing + } return refreshRate; } @@ -3090,14 +3120,18 @@ public class DisplayModeDirector { 0).sendToTarget(); } - final int refreshRateInHbmSunlight = getRefreshRateInHbmSunlight(); - mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED, - refreshRateInHbmSunlight, 0) + synchronized (mLock) { + final int refreshRateInHbmSunlight = + getRefreshRateInHbmSunlight(mDefaultDisplayDeviceConfig); + mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED, + refreshRateInHbmSunlight, 0) .sendToTarget(); - final int refreshRateInHbmHdr = getRefreshRateInHbmHdr(); - mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED, refreshRateInHbmHdr, 0) + final int refreshRateInHbmHdr = + getRefreshRateInHbmHdr(mDefaultDisplayDeviceConfig); + mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED, refreshRateInHbmHdr, 0) .sendToTarget(); + } } private int[] getIntArrayProperty(String prop) { diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index 6d1af3bf96f6..e21ec7243958 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -627,11 +627,14 @@ final class Constants { }) @interface HpdSignalType {} - static final String DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE = "soundbar_mode"; + static final String DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE = "enable_soundbar_mode"; static final String DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX = "enable_earc_tx"; + static final String DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX = + "transition_arc_to_earc_tx"; @StringDef({ DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE, - DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX + DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX, + DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX }) @interface FeatureFlag {} diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 740d2a3c2b6e..3563938a9c3c 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -458,6 +458,9 @@ public class HdmiControlService extends SystemService { private boolean mEarcTxFeatureFlagEnabled = false; @ServiceThreadOnly + private boolean mTransitionFromArcToEarcTxEnabled = false; + + @ServiceThreadOnly private int mActivePortId = Constants.INVALID_PORT_ID; // Set to true while the input change by MHL is allowed. @@ -675,6 +678,8 @@ public class HdmiControlService extends SystemService { Constants.DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE, false); mEarcTxFeatureFlagEnabled = mDeviceConfig.getBoolean( Constants.DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX, false); + mTransitionFromArcToEarcTxEnabled = mDeviceConfig.getBoolean( + Constants.DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX, false); synchronized (mLock) { mEarcEnabled = (mHdmiCecConfig.getIntValue( @@ -886,6 +891,16 @@ public class HdmiControlService extends SystemService { ? SOUNDBAR_MODE_ENABLED : SOUNDBAR_MODE_DISABLED); } }, mServiceThreadExecutor); + + mDeviceConfig.addOnPropertiesChangedListener(getContext().getMainExecutor(), + new DeviceConfig.OnPropertiesChangedListener() { + @Override + public void onPropertiesChanged(DeviceConfig.Properties properties) { + mTransitionFromArcToEarcTxEnabled = properties.getBoolean( + Constants.DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX, + false); + } + }); } /** Returns true if the device screen is off */ boolean isScreenOff() { @@ -1618,6 +1633,11 @@ public class HdmiControlService extends SystemService { } void enableAudioReturnChannel(int portId, boolean enabled) { + if (!mTransitionFromArcToEarcTxEnabled && enabled && mEarcController != null) { + // If the feature flag is set to false, prevent eARC from establishing if ARC is already + // established. + setEarcEnabledInHal(false, false); + } mCecController.enableAudioReturnChannel(portId, enabled); } diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java index de37080d0255..d8bfa592ea95 100644 --- a/services/core/java/com/android/server/pm/DexOptHelper.java +++ b/services/core/java/com/android/server/pm/DexOptHelper.java @@ -40,8 +40,10 @@ import static dalvik.system.DexFile.isProfileGuidedCompilerFilter; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppGlobals; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ResolveInfo; import android.content.pm.SharedLibraryInfo; import android.content.pm.dex.ArtManager; @@ -1019,7 +1021,15 @@ public final class DexOptHelper { pm.getDexOptHelper().new DexoptDoneHandler()); LocalManagerRegistry.addManager(ArtManagerLocal.class, artManager); - artManager.scheduleBackgroundDexoptJob(); + // Schedule the background job when boot is complete. This decouples us from when + // JobSchedulerService is initialized. + systemContext.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + context.unregisterReceiver(this); + artManager.scheduleBackgroundDexoptJob(); + } + }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); } /** diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 97ee3c5b3f74..382be45fd9df 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -189,7 +189,6 @@ import com.android.modules.utils.TypedXmlSerializer; import com.android.permission.persistence.RuntimePermissionsPersistence; import com.android.server.EventLogTags; import com.android.server.FgThread; -import com.android.server.IntentResolver; import com.android.server.LocalManagerRegistry; import com.android.server.LocalServices; import com.android.server.LockGuard; @@ -4775,7 +4774,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService ArraySet<CrossProfileIntentFilter> set = new ArraySet<>(resolver.filterSet()); for (CrossProfileIntentFilter filter : set) { - if (IntentResolver.filterEquals(filter.mFilter, intentFilter) + if (IntentFilter.filterEquals(filter.mFilter, intentFilter) && filter.getOwnerPackage().equals(ownerPackage) && filter.getTargetUserId() == targetUserId && filter.getFlags() == flags) { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 7e7205d84493..6541b40cc724 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -98,7 +98,6 @@ import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.permission.persistence.RuntimePermissionsPersistence; import com.android.permission.persistence.RuntimePermissionsState; -import com.android.server.IntentResolver; import com.android.server.LocalServices; import com.android.server.backup.PreferredActivityBackupHelper; import com.android.server.pm.Installer.InstallerException; @@ -6307,7 +6306,7 @@ public final class Settings implements Watchable, Snappable { boolean changed = false; while (it.hasNext()) { PersistentPreferredActivity ppa = it.next(); - if (IntentResolver.filterEquals(ppa.getIntentFilter(), filter)) { + if (IntentFilter.filterEquals(ppa.getIntentFilter(), filter)) { ppir.removeFilter(ppa); changed = true; break; diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java index be9053055fe3..90b6f95f8740 100644 --- a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java +++ b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java @@ -126,7 +126,7 @@ abstract class AbstractVibratorStep extends Step { "Turning off vibrator " + getVibratorId()); } controller.off(); - getVibration().stats().reportVibratorOff(); + getVibration().stats.reportVibratorOff(); mPendingVibratorOffDeadline = 0; } @@ -136,7 +136,7 @@ abstract class AbstractVibratorStep extends Step { "Amplitude changed on vibrator " + getVibratorId() + " to " + amplitude); } controller.setAmplitude(amplitude); - getVibration().stats().reportSetAmplitude(); + getVibration().stats.reportSetAmplitude(); } /** @@ -170,7 +170,7 @@ abstract class AbstractVibratorStep extends Step { // Count the loops that were played. int loopSize = effectSize - repeatIndex; int loopSegmentsPlayed = nextSegmentIndex - repeatIndex; - getVibration().stats().reportRepetition(loopSegmentsPlayed / loopSize); + getVibration().stats.reportRepetition(loopSegmentsPlayed / loopSize); nextSegmentIndex = repeatIndex + ((nextSegmentIndex - effectSize) % loopSize); } Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect, diff --git a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java index 545ec5bcff03..940bd08eee4b 100644 --- a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java +++ b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java @@ -73,7 +73,7 @@ final class ComposePrimitivesVibratorStep extends AbstractVibratorStep { primitives.toArray(new PrimitiveSegment[primitives.size()]); long vibratorOnResult = controller.on(primitivesArray, getVibration().id); handleVibratorOnResult(vibratorOnResult); - getVibration().stats().reportComposePrimitives(vibratorOnResult, primitivesArray); + getVibration().stats.reportComposePrimitives(vibratorOnResult, primitivesArray); // The next start and off times will be calculated from mVibratorOnResult. return nextSteps(/* segmentsPlayed= */ primitives.size()); diff --git a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java index 8bfa2c3cd082..7100ffd54175 100644 --- a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java +++ b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java @@ -72,7 +72,7 @@ final class ComposePwleVibratorStep extends AbstractVibratorStep { RampSegment[] pwlesArray = pwles.toArray(new RampSegment[pwles.size()]); long vibratorOnResult = controller.on(pwlesArray, getVibration().id); handleVibratorOnResult(vibratorOnResult); - getVibration().stats().reportComposePwle(vibratorOnResult, pwlesArray); + getVibration().stats.reportComposePwle(vibratorOnResult, pwlesArray); // The next start and off times will be calculated from mVibratorOnResult. return nextSteps(/* segmentsPlayed= */ pwles.size()); diff --git a/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java index bbbca024214f..c9683d9f69ed 100644 --- a/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java +++ b/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java @@ -51,7 +51,8 @@ final class FinishSequentialEffectStep extends Step { Slog.d(VibrationThread.TAG, "FinishSequentialEffectStep for effect #" + startedStep.currentIndex); } - conductor.vibratorManagerHooks.noteVibratorOff(conductor.getVibration().uid); + conductor.vibratorManagerHooks.noteVibratorOff( + conductor.getVibration().callerInfo.uid); Step nextStep = startedStep.nextStep(); return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST : Arrays.asList(nextStep); @@ -68,6 +69,7 @@ final class FinishSequentialEffectStep extends Step { @Override public void cancelImmediately() { - conductor.vibratorManagerHooks.noteVibratorOff(conductor.getVibration().uid); + conductor.vibratorManagerHooks.noteVibratorOff( + conductor.getVibration().callerInfo.uid); } } diff --git a/services/core/java/com/android/server/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java index 53f04d7209ce..87f189aed592 100644 --- a/services/core/java/com/android/server/vibrator/HalVibration.java +++ b/services/core/java/com/android/server/vibrator/HalVibration.java @@ -16,6 +16,7 @@ package com.android.server.vibrator; +import android.annotation.NonNull; import android.annotation.Nullable; import android.os.CombinedVibration; import android.os.IBinder; @@ -35,13 +36,6 @@ import java.util.function.Function; */ final class HalVibration extends Vibration { - public final VibrationAttributes attrs; - public final long id; - public final int uid; - public final int displayId; - public final String opPkg; - public final String reason; - public final IBinder token; public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>(); /** The actual effect to be played. */ @@ -58,29 +52,15 @@ final class HalVibration extends Vibration { /** Vibration status. */ private Vibration.Status mStatus; - /** Vibration runtime stats. */ - private final VibrationStats mStats = new VibrationStats(); - /** A {@link CountDownLatch} to enable waiting for completion. */ private final CountDownLatch mCompletionLatch = new CountDownLatch(1); - HalVibration(IBinder token, int id, CombinedVibration effect, - VibrationAttributes attrs, int uid, int displayId, String opPkg, String reason) { - this.token = token; + HalVibration(@NonNull IBinder token, CombinedVibration effect, @NonNull CallerInfo callerInfo) { + super(token, callerInfo); this.mEffect = effect; - this.id = id; - this.attrs = attrs; - this.uid = uid; - this.displayId = displayId; - this.opPkg = opPkg; - this.reason = reason; mStatus = Vibration.Status.RUNNING; } - VibrationStats stats() { - return mStats; - } - /** * Set the {@link Status} of this vibration and reports the current system time as this * vibration end time, for debugging purposes. @@ -94,7 +74,7 @@ final class HalVibration extends Vibration { return; } mStatus = info.status; - mStats.reportEnded(info.endedByUid, info.endedByUsage); + stats.reportEnded(info.endedBy); mCompletionLatch.countDown(); } @@ -190,8 +170,8 @@ final class HalVibration extends Vibration { * Return {@link Vibration.DebugInfo} with read-only debug information about this vibration. */ public Vibration.DebugInfo getDebugInfo() { - return new Vibration.DebugInfo(mStatus, mStats, mEffect, mOriginalEffect, /* scale= */ 0, - attrs, uid, displayId, opPkg, reason); + return new Vibration.DebugInfo(mStatus, stats, mEffect, mOriginalEffect, /* scale= */ 0, + callerInfo); } /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */ @@ -200,7 +180,8 @@ final class HalVibration extends Vibration { ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE; return new VibrationStats.StatsInfo( - uid, vibrationType, attrs.getUsage(), mStatus, mStats, completionUptimeMillis); + callerInfo.uid, vibrationType, callerInfo.attrs.getUsage(), mStatus, + stats, completionUptimeMillis); } /** @@ -212,8 +193,9 @@ final class HalVibration extends Vibration { * pipeline very short vibrations together, regardless of the flag. */ public boolean canPipelineWith(HalVibration vib) { - return uid == vib.uid && attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT) - && vib.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT) + return callerInfo.uid == vib.callerInfo.uid && callerInfo.attrs.isFlagSet( + VibrationAttributes.FLAG_PIPELINED_EFFECT) + && vib.callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT) && !isRepeating(); } } diff --git a/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java b/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java index 21ba87445478..4e58b9a35243 100644 --- a/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java +++ b/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java @@ -21,7 +21,6 @@ import android.content.Context; import android.hardware.input.InputManager; import android.os.CombinedVibration; import android.os.Handler; -import android.os.VibrationAttributes; import android.os.VibratorManager; import android.util.SparseArray; import android.view.InputDevice; @@ -94,11 +93,11 @@ final class InputDeviceDelegate implements InputManager.InputDeviceListener { * * @return {@link #isAvailable()} */ - public boolean vibrateIfAvailable(int uid, String opPkg, CombinedVibration effect, - String reason, VibrationAttributes attrs) { + public boolean vibrateIfAvailable(Vibration.CallerInfo callerInfo, CombinedVibration effect) { synchronized (mLock) { for (int i = 0; i < mInputDeviceVibrators.size(); i++) { - mInputDeviceVibrators.valueAt(i).vibrate(uid, opPkg, effect, reason, attrs); + mInputDeviceVibrators.valueAt(i).vibrate(callerInfo.uid, callerInfo.opPkg, effect, + callerInfo.reason, callerInfo.attrs); } return mInputDeviceVibrators.size() > 0; } diff --git a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java index d91bafa7c8c2..8094e7c5c58e 100644 --- a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java +++ b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java @@ -64,7 +64,7 @@ final class PerformPrebakedVibratorStep extends AbstractVibratorStep { VibrationEffect fallback = getVibration().getFallback(prebaked.getEffectId()); long vibratorOnResult = controller.on(prebaked, getVibration().id); handleVibratorOnResult(vibratorOnResult); - getVibration().stats().reportPerformEffect(vibratorOnResult, prebaked); + getVibration().stats.reportPerformEffect(vibratorOnResult, prebaked); if (vibratorOnResult == 0 && prebaked.shouldFallback() && (fallback instanceof VibrationEffect.Composed)) { diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java index 1672470f1f1a..cce1ef4db381 100644 --- a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java +++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java @@ -161,7 +161,7 @@ final class SetAmplitudeVibratorStep extends AbstractVibratorStep { } long vibratorOnResult = controller.on(duration, getVibration().id); handleVibratorOnResult(vibratorOnResult); - getVibration().stats().reportVibratorOn(vibratorOnResult); + getVibration().stats.reportVibratorOn(vibratorOnResult); return vibratorOnResult; } diff --git a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java index fd1a3acd4cd7..15c60a3ded62 100644 --- a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java +++ b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java @@ -93,8 +93,8 @@ final class StartSequentialEffectStep extends Step { } mVibratorsOnMaxDuration = startVibrating(effectMapping, nextSteps); - conductor.vibratorManagerHooks.noteVibratorOn(conductor.getVibration().uid, - mVibratorsOnMaxDuration); + conductor.vibratorManagerHooks.noteVibratorOn( + conductor.getVibration().callerInfo.uid, mVibratorsOnMaxDuration); } finally { if (mVibratorsOnMaxDuration >= 0) { // It least one vibrator was started then add a finish step to wait for all @@ -211,7 +211,8 @@ final class StartSequentialEffectStep extends Step { // Check if sync was prepared and if any step was accepted by a vibrator, // otherwise there is nothing to trigger here. if (hasPrepared && !hasFailed && maxDuration > 0) { - hasTriggered = conductor.vibratorManagerHooks.triggerSyncedVibration(getVibration().id); + hasTriggered = conductor.vibratorManagerHooks.triggerSyncedVibration( + getVibration().id); hasFailed &= hasTriggered; } diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index 5667c72db5dd..1cc0a4ff4b75 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -19,6 +19,7 @@ package com.android.server.vibrator; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.CombinedVibration; +import android.os.IBinder; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.vibrator.PrebakedSegment; @@ -31,6 +32,7 @@ import android.util.proto.ProtoOutputStream; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; /** * The base class for all vibrations. @@ -38,6 +40,13 @@ import java.util.Objects; class Vibration { private static final SimpleDateFormat DEBUG_DATE_FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); + // Used to generate globally unique vibration ids. + private static final AtomicInteger sNextVibrationId = new AtomicInteger(1); // 0 = no callback + + public final long id; + public final CallerInfo callerInfo; + public final VibrationStats stats = new VibrationStats(); + public final IBinder callerToken; /** Vibration status with reference to values from vibratormanagerservice.proto for logging. */ enum Status { @@ -80,24 +89,82 @@ class Vibration { } } + Vibration(@NonNull IBinder token, @NonNull CallerInfo callerInfo) { + Objects.requireNonNull(token); + Objects.requireNonNull(callerInfo); + this.id = sNextVibrationId.getAndIncrement(); + this.callerToken = token; + this.callerInfo = callerInfo; + } + + /** + * Holds lightweight immutable info on the process that triggered the vibration. This data + * could potentially be kept in memory for a long time for bugreport dumpsys operations. + * + * Since CallerInfo can be kept in memory for a long time, it shouldn't hold any references to + * potentially expensive or resource-linked objects, such as {@link IBinder}. + */ + static final class CallerInfo { + public final VibrationAttributes attrs; + public final int uid; + public final int displayId; + public final String opPkg; + public final String reason; + + CallerInfo(@NonNull VibrationAttributes attrs, int uid, int displayId, + String opPkg, String reason) { + Objects.requireNonNull(attrs); + this.attrs = attrs; + this.uid = uid; + this.displayId = displayId; + this.opPkg = opPkg; + this.reason = reason; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof CallerInfo)) return false; + CallerInfo that = (CallerInfo) o; + return Objects.equals(attrs, that.attrs) + && uid == that.uid + && displayId == that.displayId + && Objects.equals(opPkg, that.opPkg) + && Objects.equals(reason, that.reason); + } + + @Override + public int hashCode() { + return Objects.hash(attrs, uid, displayId, opPkg, reason); + } + + @Override + public String toString() { + return "CallerInfo{" + + " attrs=" + attrs + + ", uid=" + uid + + ", displayId=" + displayId + + ", opPkg=" + opPkg + + ", reason=" + reason + + '}'; + } + } + /** Immutable info passed as a signal to end a vibration. */ static final class EndInfo { /** The {@link Status} to be set to the vibration when it ends with this info. */ @NonNull public final Status status; - /** The UID that triggered the vibration that ended this, or -1 if undefined. */ - public final int endedByUid; - /** The VibrationAttributes.USAGE_* of the vibration that ended this, or -1 if undefined. */ - public final int endedByUsage; + /** Info about the process that ended the vibration. */ + public final CallerInfo endedBy; EndInfo(@NonNull Vibration.Status status) { - this(status, /* endedByUid= */ -1, /* endedByUsage= */ -1); + this(status, null); } - EndInfo(@NonNull Vibration.Status status, int endedByUid, int endedByUsage) { + EndInfo(@NonNull Vibration.Status status, @Nullable CallerInfo endedBy) { this.status = status; - this.endedByUid = endedByUid; - this.endedByUsage = endedByUsage; + this.endedBy = endedBy; } @Override @@ -105,27 +172,31 @@ class Vibration { if (this == o) return true; if (!(o instanceof EndInfo)) return false; EndInfo that = (EndInfo) o; - return endedByUid == that.endedByUid - && endedByUsage == that.endedByUsage + return Objects.equals(endedBy, that.endedBy) && status == that.status; } @Override public int hashCode() { - return Objects.hash(status, endedByUid, endedByUsage); + return Objects.hash(status, endedBy); } @Override public String toString() { return "EndInfo{" + "status=" + status - + ", endedByUid=" + endedByUid - + ", endedByUsage=" + endedByUsage + + ", endedBy=" + endedBy + '}'; } } - /** Debug information about vibrations. */ + /** + * Holds lightweight debug information about the vibration that could potentially be kept in + * memory for a long time for bugreport dumpsys operations. + * + * Since DebugInfo can be kept in memory for a long time, it shouldn't hold any references to + * potentially expensive or resource-linked objects, such as {@link IBinder}. + */ static final class DebugInfo { private final long mCreateTime; private final long mStartTime; @@ -134,16 +205,13 @@ class Vibration { private final CombinedVibration mEffect; private final CombinedVibration mOriginalEffect; private final float mScale; - private final VibrationAttributes mAttrs; - private final int mUid; - private final int mDisplayId; - private final String mOpPkg; - private final String mReason; + private final CallerInfo mCallerInfo; private final Status mStatus; DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration effect, - @Nullable CombinedVibration originalEffect, float scale, VibrationAttributes attrs, - int uid, int displayId, String opPkg, String reason) { + @Nullable CombinedVibration originalEffect, float scale, + @NonNull CallerInfo callerInfo) { + Objects.requireNonNull(callerInfo); mCreateTime = stats.getCreateTimeDebug(); mStartTime = stats.getStartTimeDebug(); mEndTime = stats.getEndTimeDebug(); @@ -151,11 +219,7 @@ class Vibration { mEffect = effect; mOriginalEffect = originalEffect; mScale = scale; - mAttrs = attrs; - mUid = uid; - mDisplayId = displayId; - mOpPkg = opPkg; - mReason = reason; + mCallerInfo = callerInfo; mStatus = status; } @@ -179,16 +243,8 @@ class Vibration { .append(mOriginalEffect) .append(", scale: ") .append(String.format("%.2f", mScale)) - .append(", attrs: ") - .append(mAttrs) - .append(", uid: ") - .append(mUid) - .append(", displayId: ") - .append(mDisplayId) - .append(", opPkg: ") - .append(mOpPkg) - .append(", reason: ") - .append(mReason) + .append(", callerInfo: ") + .append(mCallerInfo) .toString(); } @@ -201,9 +257,10 @@ class Vibration { proto.write(VibrationProto.STATUS, mStatus.ordinal()); final long attrsToken = proto.start(VibrationProto.ATTRIBUTES); - proto.write(VibrationAttributesProto.USAGE, mAttrs.getUsage()); - proto.write(VibrationAttributesProto.AUDIO_USAGE, mAttrs.getAudioUsage()); - proto.write(VibrationAttributesProto.FLAGS, mAttrs.getFlags()); + final VibrationAttributes attrs = mCallerInfo.attrs; + proto.write(VibrationAttributesProto.USAGE, attrs.getUsage()); + proto.write(VibrationAttributesProto.AUDIO_USAGE, attrs.getAudioUsage()); + proto.write(VibrationAttributesProto.FLAGS, attrs.getFlags()); proto.end(attrsToken); if (mEffect != null) { diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java index d944a3b913d6..8a7d607c1e60 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -27,6 +27,7 @@ import static android.os.VibrationAttributes.USAGE_RINGTONE; import static android.os.VibrationAttributes.USAGE_TOUCH; import static android.os.VibrationAttributes.USAGE_UNKNOWN; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.IUidObserver; @@ -375,15 +376,15 @@ final class VibrationSettings { * null otherwise. */ @Nullable - public Vibration.Status shouldIgnoreVibration(int uid, int displayId, - VibrationAttributes attrs) { - final int usage = attrs.getUsage(); + public Vibration.Status shouldIgnoreVibration(@NonNull Vibration.CallerInfo callerInfo) { + final int usage = callerInfo.attrs.getUsage(); synchronized (mLock) { - if (!mUidObserver.isUidForeground(uid) + if (!mUidObserver.isUidForeground(callerInfo.uid) && !BACKGROUND_PROCESS_USAGE_ALLOWLIST.contains(usage)) { return Vibration.Status.IGNORED_BACKGROUND; } - if (mVirtualDeviceListener.isAppOrDisplayOnAnyVirtualDevice(uid, displayId)) { + if (mVirtualDeviceListener.isAppOrDisplayOnAnyVirtualDevice(callerInfo.uid, + callerInfo.displayId)) { return Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE; } @@ -391,7 +392,8 @@ final class VibrationSettings { return Vibration.Status.IGNORED_FOR_POWER; } - if (!attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)) { + if (!callerInfo.attrs.isFlagSet( + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)) { if (!mVibrateOn && (VIBRATE_ON_DISABLED_USAGE_ALLOWED != usage)) { return Vibration.Status.IGNORED_FOR_SETTINGS; } @@ -401,7 +403,7 @@ final class VibrationSettings { } } - if (!attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) { + if (!callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) { if (!shouldVibrateForRingerModeLocked(usage)) { return Vibration.Status.IGNORED_FOR_RINGER_MODE; } @@ -420,8 +422,8 @@ final class VibrationSettings { * * @return true if the vibration should be cancelled when the screen goes off, false otherwise. */ - public boolean shouldCancelVibrationOnScreenOff(int uid, String opPkg, - @VibrationAttributes.Usage int usage, long vibrationStartUptimeMillis) { + public boolean shouldCancelVibrationOnScreenOff(@NonNull Vibration.CallerInfo callerInfo, + long vibrationStartUptimeMillis) { PowerManagerInternal pm; synchronized (mLock) { pm = mPowerManagerInternal; @@ -442,12 +444,13 @@ final class VibrationSettings { return false; } } - if (!SYSTEM_VIBRATION_SCREEN_OFF_USAGE_ALLOWLIST.contains(usage)) { + if (!SYSTEM_VIBRATION_SCREEN_OFF_USAGE_ALLOWLIST.contains(callerInfo.attrs.getUsage())) { // Usages not allowed even for system vibrations should always be cancelled. return true; } // Only allow vibrations from System packages to continue vibrating when the screen goes off - return uid != Process.SYSTEM_UID && uid != 0 && !mSystemUiPackage.equals(opPkg); + return callerInfo.uid != Process.SYSTEM_UID && callerInfo.uid != 0 + && !mSystemUiPackage.equals(callerInfo.opPkg); } /** diff --git a/services/core/java/com/android/server/vibrator/VibrationStats.java b/services/core/java/com/android/server/vibrator/VibrationStats.java index 931be1d5d711..2d003513bee1 100644 --- a/services/core/java/com/android/server/vibrator/VibrationStats.java +++ b/services/core/java/com/android/server/vibrator/VibrationStats.java @@ -16,6 +16,8 @@ package com.android.server.vibrator; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.SystemClock; import android.os.vibrator.PrebakedSegment; import android.os.vibrator.PrimitiveSegment; @@ -159,15 +161,18 @@ final class VibrationStats { * @return true if the status was accepted. This method will only accept given values if * the end timestamp was never set. */ - boolean reportEnded(int endedByUid, int endedByUsage) { + boolean reportEnded(@Nullable Vibration.CallerInfo endedBy) { if (hasEnded()) { // Vibration already ended, keep first ending stats set and ignore this one. return false; } - mEndedByUid = endedByUid; - mEndedByUsage = endedByUsage; + if (endedBy != null) { + mEndedByUid = endedBy.uid; + mEndedByUsage = endedBy.attrs.getUsage(); + } mEndUptimeMillis = SystemClock.uptimeMillis(); mEndTimeDebug = System.currentTimeMillis(); + return true; } @@ -177,9 +182,9 @@ final class VibrationStats { * <p>This method will only accept the first value as the one that was interrupted by this * vibration, and will ignore all successive calls. */ - void reportInterruptedAnotherVibration(int interruptedUsage) { + void reportInterruptedAnotherVibration(@NonNull Vibration.CallerInfo callerInfo) { if (mInterruptedUsage < 0) { - mInterruptedUsage = interruptedUsage; + mInterruptedUsage = callerInfo.attrs.getUsage(); } } diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java index 11cab4b83fb8..4202afb738d0 100644 --- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java +++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java @@ -157,7 +157,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { mNextSteps.offer(new StartSequentialEffectStep(this, sequentialEffect)); // Vibration will start playing in the Vibrator, following the effect timings and delays. // Report current time as the vibration start time, for debugging. - mVibration.stats().reportStarted(); + mVibration.stats.reportStarted(); } public HalVibration getVibration() { diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java index d2dc43c68e5d..cfb4c74fcbfc 100644 --- a/services/core/java/com/android/server/vibrator/VibrationThread.java +++ b/services/core/java/com/android/server/vibrator/VibrationThread.java @@ -161,7 +161,8 @@ final class VibrationThread extends Thread { // for this thread. // No point doing this in finally, as if there's an exception, this thread will die // and be unusable anyway. - mVibratorManagerHooks.onVibrationThreadReleased(mExecutingConductor.getVibration().id); + mVibratorManagerHooks.onVibrationThreadReleased( + mExecutingConductor.getVibration().id); synchronized (mLock) { mLock.notifyAll(); } @@ -230,7 +231,8 @@ final class VibrationThread extends Thread { /** Runs the VibrationThread ensuring that the wake lock is acquired and released. */ private void runCurrentVibrationWithWakeLock() { - WorkSource workSource = new WorkSource(mExecutingConductor.getVibration().uid); + WorkSource workSource = new WorkSource( + mExecutingConductor.getVibration().callerInfo.uid); mWakeLock.setWorkSource(workSource); mWakeLock.acquire(); try { @@ -251,7 +253,7 @@ final class VibrationThread extends Thread { * Called from within runWithWakeLock. */ private void runCurrentVibrationWithWakeLockAndDeathLink() { - IBinder vibrationBinderToken = mExecutingConductor.getVibration().token; + IBinder vibrationBinderToken = mExecutingConductor.getVibration().callerToken; try { vibrationBinderToken.linkToDeath(mExecutingConductor, 0); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index c87332d2ff36..5756414a303d 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -77,7 +77,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Function; @@ -123,9 +122,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } - // Used to generate globally unique vibration ids. - private final AtomicInteger mNextVibrationId = new AtomicInteger(1); // 0 = no callback - private final Object mLock = new Object(); private final Context mContext; private final PowerManager.WakeLock mWakeLock; @@ -367,8 +363,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // missing on individual vibrators. return false; } - AlwaysOnVibration alwaysOnVibration = new AlwaysOnVibration( - alwaysOnId, uid, opPkg, attrs, effects); + AlwaysOnVibration alwaysOnVibration = new AlwaysOnVibration(alwaysOnId, + new Vibration.CallerInfo(attrs, uid, Display.DEFAULT_DISPLAY, opPkg, + null), effects); mAlwaysOnEffects.put(alwaysOnId, alwaysOnVibration); updateAlwaysOnLocked(alwaysOnVibration); } @@ -408,8 +405,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } attrs = fixupVibrationAttributes(attrs, effect); // Create Vibration.Stats as close to the received request as possible, for tracking. - HalVibration vib = new HalVibration(token, mNextVibrationId.getAndIncrement(), effect, - attrs, uid, displayId, opPkg, reason); + HalVibration vib = new HalVibration(token, effect, + new Vibration.CallerInfo(attrs, uid, displayId, opPkg, reason)); fillVibrationFallbacks(vib, effect); if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) { @@ -422,27 +419,23 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (DEBUG) { Slog.d(TAG, "Starting vibrate for vibration " + vib.id); } - int ignoredByUid = -1; - int ignoredByUsage = -1; - Vibration.Status status = null; + Vibration.CallerInfo ignoredByCallerInfo = null; + Vibration.Status status; // Check if user settings or DnD is set to ignore this vibration. - status = shouldIgnoreVibrationLocked(vib.uid, vib.displayId, vib.opPkg, vib.attrs); + status = shouldIgnoreVibrationLocked(vib.callerInfo); // Check if something has external control, assume it's more important. if ((status == null) && (mCurrentExternalVibration != null)) { status = Vibration.Status.IGNORED_FOR_EXTERNAL; - ignoredByUid = mCurrentExternalVibration.externalVibration.getUid(); - ignoredByUsage = mCurrentExternalVibration.externalVibration - .getVibrationAttributes().getUsage(); + ignoredByCallerInfo = mCurrentExternalVibration.callerInfo; } // Check if ongoing vibration is more important than this vibration. if (status == null) { status = shouldIgnoreVibrationForOngoingLocked(vib); if (status != null) { - ignoredByUid = mCurrentVibration.getVibration().uid; - ignoredByUsage = mCurrentVibration.getVibration().attrs.getUsage(); + ignoredByCallerInfo = mCurrentVibration.getVibration().callerInfo; } } @@ -460,12 +453,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { Slog.d(TAG, "Pipelining vibration " + vib.id); } } else { - vib.stats().reportInterruptedAnotherVibration( - mCurrentVibration.getVibration().attrs.getUsage()); + vib.stats.reportInterruptedAnotherVibration( + mCurrentVibration.getVibration().callerInfo); mCurrentVibration.notifyCancelled( - new Vibration.EndInfo( - Vibration.Status.CANCELLED_SUPERSEDED, vib.uid, - vib.attrs.getUsage()), + new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED, + vib.callerInfo), /* immediate= */ false); } } @@ -478,7 +470,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // Ignored or failed to start the vibration, end it and report metrics right away. if (status != Vibration.Status.RUNNING) { endVibrationLocked(vib, - new Vibration.EndInfo(status, ignoredByUid, ignoredByUsage), + new Vibration.EndInfo(status, ignoredByCallerInfo), /* shouldWriteStats= */ true); } return vib; @@ -641,8 +633,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } HalVibration vib = mCurrentVibration.getVibration(); - Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked( - vib.uid, vib.displayId, vib.opPkg, vib.attrs); + Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib.callerInfo); if (inputDevicesChanged || (ignoreStatus != null)) { if (DEBUG) { @@ -671,10 +662,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (vibrator == null) { continue; } - Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked( - vib.uid, Display.DEFAULT_DISPLAY, vib.opPkg, vib.attrs); + Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib.callerInfo); if (ignoreStatus == null) { - effect = mVibrationScaler.scale(effect, vib.attrs.getUsage()); + effect = mVibrationScaler.scale(effect, vib.callerInfo.attrs.getUsage()); } else { // Vibration should not run, use null effect to remove registered effect. effect = null; @@ -687,9 +677,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private Vibration.Status startVibrationLocked(HalVibration vib) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked"); try { - vib.updateEffects(effect -> mVibrationScaler.scale(effect, vib.attrs.getUsage())); + vib.updateEffects( + effect -> mVibrationScaler.scale(effect, vib.callerInfo.attrs.getUsage())); boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable( - vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs); + vib.callerInfo, vib.getEffect()); if (inputDevicesAvailable) { return Vibration.Status.FORWARDED_TO_INPUT_DEVICES; } @@ -704,8 +695,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // Note that we don't consider pipelining here, because new pipelined ones should // replace pending non-executing pipelined ones anyway. clearNextVibrationLocked( - new Vibration.EndInfo(Vibration.Status.IGNORED_SUPERSEDED, - vib.uid, vib.attrs.getUsage())); + new Vibration.EndInfo(Vibration.Status.IGNORED_SUPERSEDED, vib.callerInfo)); mNextVibration = conductor; return Vibration.Status.RUNNING; } finally { @@ -718,7 +708,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationThreadLocked"); try { HalVibration vib = conductor.getVibration(); - int mode = startAppOpModeLocked(vib.uid, vib.opPkg, vib.attrs); + int mode = startAppOpModeLocked(vib.callerInfo); switch (mode) { case AppOpsManager.MODE_ALLOWED: Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); @@ -731,7 +721,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } return Vibration.Status.RUNNING; case AppOpsManager.MODE_ERRORED: - Slog.w(TAG, "Start AppOpsManager operation errored for uid " + vib.uid); + Slog.w(TAG, "Start AppOpsManager operation errored for uid " + + vib.callerInfo.uid); return Vibration.Status.IGNORED_ERROR_APP_OPS; default: return Vibration.Status.IGNORED_APP_OPS; @@ -745,7 +736,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private void endVibrationLocked(HalVibration vib, Vibration.EndInfo vibrationEndInfo, boolean shouldWriteStats) { vib.end(vibrationEndInfo); - logVibrationStatus(vib.uid, vib.attrs, vibrationEndInfo.status); + logVibrationStatus(vib.callerInfo.uid, vib.callerInfo.attrs, + vibrationEndInfo.status); mVibratorManagerRecords.record(vib); if (shouldWriteStats) { mFrameworkStatsLogger.writeVibrationReportedAsync( @@ -817,12 +809,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { try { HalVibration vib = mCurrentVibration.getVibration(); if (DEBUG) { - Slog.d(TAG, "Reporting vibration " + vib.id + " finished with " + vibrationEndInfo); + Slog.d(TAG, "Reporting vibration " + vib.id + " finished with " + + vibrationEndInfo); } // DO NOT write metrics at this point, wait for the VibrationThread to report the // vibration was released, after all cleanup. The metrics will be reported then. endVibrationLocked(vib, vibrationEndInfo, /* shouldWriteStats= */ false); - finishAppOpModeLocked(vib.uid, vib.opPkg); + finishAppOpModeLocked(vib.callerInfo); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } @@ -830,7 +823,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private void onSyncedVibrationComplete(long vibrationId) { synchronized (mLock) { - if (mCurrentVibration != null && mCurrentVibration.getVibration().id == vibrationId) { + if (mCurrentVibration != null + && mCurrentVibration.getVibration().id == vibrationId) { if (DEBUG) { Slog.d(TAG, "Synced vibration " + vibrationId + " complete, notifying thread"); } @@ -841,7 +835,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private void onVibrationComplete(int vibratorId, long vibrationId) { synchronized (mLock) { - if (mCurrentVibration != null && mCurrentVibration.getVibration().id == vibrationId) { + if (mCurrentVibration != null + && mCurrentVibration.getVibration().id == vibrationId) { if (DEBUG) { Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId + " complete, notifying thread"); @@ -871,8 +866,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return null; } - int currentUsage = currentVibration.attrs.getUsage(); - int newUsage = vib.attrs.getUsage(); + int currentUsage = currentVibration.callerInfo.attrs.getUsage(); + int newUsage = vib.callerInfo.attrs.getUsage(); if (getVibrationImportance(currentUsage) > getVibrationImportance(newUsage)) { // Current vibration has higher importance than this one and should not be cancelled. return Vibration.Status.IGNORED_FOR_HIGHER_IMPORTANCE; @@ -916,15 +911,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { */ @GuardedBy("mLock") @Nullable - private Vibration.Status shouldIgnoreVibrationLocked(int uid, int displayId, String opPkg, - VibrationAttributes attrs) { - Vibration.Status statusFromSettings = mVibrationSettings.shouldIgnoreVibration(uid, - displayId, attrs); + private Vibration.Status shouldIgnoreVibrationLocked(Vibration.CallerInfo callerInfo) { + Vibration.Status statusFromSettings = mVibrationSettings.shouldIgnoreVibration(callerInfo); if (statusFromSettings != null) { return statusFromSettings; } - int mode = checkAppOpModeLocked(uid, opPkg, attrs); + int mode = checkAppOpModeLocked(callerInfo); if (mode != AppOpsManager.MODE_ALLOWED) { if (mode == AppOpsManager.MODE_ERRORED) { // We might be getting calls from within system_server, so we don't actually @@ -948,7 +941,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { * started with the same token can be cancelled with it. */ private boolean shouldCancelVibration(HalVibration vib, int usageFilter, IBinder token) { - return (vib.token == token) && shouldCancelVibration(vib.attrs, usageFilter); + return (vib.callerToken == token) && shouldCancelVibration(vib.callerInfo.attrs, + usageFilter); } /** @@ -973,24 +967,25 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { * {@code attrs}. This will return one of the AppOpsManager.MODE_*. */ @GuardedBy("mLock") - private int checkAppOpModeLocked(int uid, String opPkg, VibrationAttributes attrs) { + private int checkAppOpModeLocked(Vibration.CallerInfo callerInfo) { int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE, - attrs.getAudioUsage(), uid, opPkg); - int fixedMode = fixupAppOpModeLocked(mode, attrs); + callerInfo.attrs.getAudioUsage(), callerInfo.uid, callerInfo.opPkg); + int fixedMode = fixupAppOpModeLocked(mode, callerInfo.attrs); if (mode != fixedMode && fixedMode == AppOpsManager.MODE_ALLOWED) { // If we're just ignoring the vibration op then this is set by DND and we should ignore // if we're asked to bypass. AppOps won't be able to record this operation, so make // sure we at least note it in the logs for debugging. - Slog.d(TAG, "Bypassing DND for vibrate from uid " + uid); + Slog.d(TAG, "Bypassing DND for vibrate from uid " + callerInfo.uid); } return fixedMode; } /** Start an operation in {@link AppOpsManager}, if allowed. */ @GuardedBy("mLock") - private int startAppOpModeLocked(int uid, String opPkg, VibrationAttributes attrs) { + private int startAppOpModeLocked(Vibration.CallerInfo callerInfo) { return fixupAppOpModeLocked( - mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, opPkg), attrs); + mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, callerInfo.uid, callerInfo.opPkg), + callerInfo.attrs); } /** @@ -998,8 +993,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { * operation with same uid was previously started. */ @GuardedBy("mLock") - private void finishAppOpModeLocked(int uid, String opPkg) { - mAppOps.finishOp(AppOpsManager.OP_VIBRATE, uid, opPkg); + private void finishAppOpModeLocked(Vibration.CallerInfo callerInfo) { + mAppOps.finishOp(AppOpsManager.OP_VIBRATE, callerInfo.uid, callerInfo.opPkg); } /** @@ -1186,8 +1181,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return false; } HalVibration vib = conductor.getVibration(); - return mVibrationSettings.shouldCancelVibrationOnScreenOff( - vib.uid, vib.opPkg, vib.attrs.getUsage(), vib.stats().getCreateUptimeMillis()); + return mVibrationSettings.shouldCancelVibrationOnScreenOff(vib.callerInfo, + vib.stats.getCreateUptimeMillis()); } @GuardedBy("mLock") @@ -1385,31 +1380,33 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { */ private static final class AlwaysOnVibration { public final int alwaysOnId; - public final int uid; - public final String opPkg; - public final VibrationAttributes attrs; + public final Vibration.CallerInfo callerInfo; public final SparseArray<PrebakedSegment> effects; - AlwaysOnVibration(int alwaysOnId, int uid, String opPkg, VibrationAttributes attrs, + AlwaysOnVibration(int alwaysOnId, Vibration.CallerInfo callerInfo, SparseArray<PrebakedSegment> effects) { this.alwaysOnId = alwaysOnId; - this.uid = uid; - this.opPkg = opPkg; - this.attrs = attrs; + this.callerInfo = callerInfo; this.effects = effects; } } /** Holder for a {@link ExternalVibration}. */ - private final class ExternalVibrationHolder implements IBinder.DeathRecipient { + private final class ExternalVibrationHolder extends Vibration implements + IBinder.DeathRecipient { public final ExternalVibration externalVibration; - public final VibrationStats stats = new VibrationStats(); public int scale; private Vibration.Status mStatus; private ExternalVibrationHolder(ExternalVibration externalVibration) { + super(externalVibration.getToken(), new Vibration.CallerInfo( + externalVibration.getVibrationAttributes(), externalVibration.getUid(), + // TODO(b/243604888): propagating displayID from IExternalVibration instead of + // using INVALID_DISPLAY for all external vibrations. + Display.INVALID_DISPLAY, + externalVibration.getPackage(), null)); this.externalVibration = externalVibration; this.scale = IExternalVibratorService.SCALE_NONE; mStatus = Vibration.Status.RUNNING; @@ -1437,14 +1434,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return; } mStatus = info.status; - stats.reportEnded(info.endedByUid, info.endedByUsage); + stats.reportEnded(info.endedBy); if (stats.hasStarted()) { // External vibration doesn't have feedback from total time the vibrator was playing // with non-zero amplitude, so we use the duration between start and end times of // the vibration as the time the vibrator was ON, since the haptic channels are // open for this duration and can receive vibration waveform data. - stats.reportVibratorOn(stats.getEndUptimeMillis() - stats.getStartUptimeMillis()); + stats.reportVibratorOn( + stats.getEndUptimeMillis() - stats.getStartUptimeMillis()); } } @@ -1462,13 +1460,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } public Vibration.DebugInfo getDebugInfo() { - return new Vibration.DebugInfo( - mStatus, stats, /* effect= */ null, /* originalEffect= */ null, scale, - externalVibration.getVibrationAttributes(), externalVibration.getUid(), - // TODO(b/243604888): propagating displayID from IExternalVibration instead of - // using INVALID_DISPLAY for all external vibrations. - Display.INVALID_DISPLAY, - externalVibration.getPackage(), /* reason= */ null); + return new Vibration.DebugInfo(mStatus, stats, /* effect= */ null, + /* originalEffect= */ null, scale, callerInfo); } public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) { @@ -1538,7 +1531,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } synchronized void record(HalVibration vib) { - int usage = vib.attrs.getUsage(); + int usage = vib.callerInfo.attrs.getUsage(); if (!mPreviousVibrations.contains(usage)) { mPreviousVibrations.put(usage, new LinkedList<>()); } @@ -1679,8 +1672,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { synchronized (mLock) { // TODO(b/243604888): propagating displayID from IExternalVibration instead of // using INVALID_DISPLAY for all external vibrations. - Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked( - vib.getUid(), Display.INVALID_DISPLAY, vib.getPackage(), attrs); + Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vibHolder.callerInfo); if (ignoreStatus != null) { vibHolder.scale = IExternalVibratorService.SCALE_MUTE; // Failed to start the vibration, end it and report metrics right away. @@ -1698,13 +1690,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // vibration that may be playing and ready the vibrator for external control. if (mCurrentVibration != null) { vibHolder.stats.reportInterruptedAnotherVibration( - mCurrentVibration.getVibration().attrs.getUsage()); + mCurrentVibration.getVibration().callerInfo); clearNextVibrationLocked( new Vibration.EndInfo(Vibration.Status.IGNORED_FOR_EXTERNAL, - vib.getUid(), attrs.getUsage())); + vibHolder.callerInfo)); mCurrentVibration.notifyCancelled( new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED, - vib.getUid(), attrs.getUsage()), + vibHolder.callerInfo), /* immediate= */ true); waitForCompletion = true; } @@ -1720,11 +1712,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { alreadyUnderExternalControl = true; mCurrentExternalVibration.mute(); vibHolder.stats.reportInterruptedAnotherVibration( - mCurrentExternalVibration.externalVibration - .getVibrationAttributes().getUsage()); + mCurrentExternalVibration.callerInfo); endExternalVibrateLocked( new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED, - vib.getUid(), attrs.getUsage()), + vibHolder.callerInfo), /* continueExternalControl= */ true); } mCurrentExternalVibration = vibHolder; diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 2b9364c2f692..f092172b24ea 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -5115,6 +5115,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return mDeferHidingClient; } + boolean canAffectSystemUiFlags() { + return task != null && task.canAffectSystemUiFlags() && isVisible() + && !inPinnedWindowingMode(); + } + @Override boolean isVisible() { // If the activity isn't hidden then it is considered visible and there is no need to check diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 71bb99cbf515..59bba2331c6e 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -2010,17 +2010,15 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return mainWin.getAttrs().rotationAnimation; } - /** Applies the new configuration and returns {@code true} if there is a display change. */ - boolean applyDisplayChangeIfNeeded() { - boolean changed = false; + /** Applies the new configuration for the changed displays. */ + void applyDisplayChangeIfNeeded() { for (int i = mParticipants.size() - 1; i >= 0; --i) { final WindowContainer<?> wc = mParticipants.valueAt(i); final DisplayContent dc = wc.asDisplayContent(); if (dc == null || !mChanges.get(dc).hasChanged()) continue; dc.sendNewConfiguration(); - changed = true; + setReady(dc, true); } - return changed; } boolean getLegacyIsReady() { diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index 6b2bf599d5ea..8708f73980c6 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -41,6 +41,8 @@ class WallpaperWindowToken extends WindowToken { private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperWindowToken" : TAG_WM; + private boolean mShowWhenLocked = false; + WallpaperWindowToken(WindowManagerService service, IBinder token, boolean explicit, DisplayContent dc, boolean ownerCanManageAppTokens) { this(service, token, explicit, dc, ownerCanManageAppTokens, null /* options */); @@ -65,6 +67,29 @@ class WallpaperWindowToken extends WindowToken { mDisplayContent.mWallpaperController.removeWallpaperToken(this); } + /** + * Controls whether this wallpaper shows underneath the keyguard or is hidden and only + * revealed once keyguard is dismissed. + */ + void setShowWhenLocked(boolean showWhenLocked) { + if (showWhenLocked == mShowWhenLocked) { + return; + } + mShowWhenLocked = showWhenLocked; + + // Move the window token to the front (private) or back (showWhenLocked). This is possible + // because the DisplayArea underneath TaskDisplayArea only contains TYPE_WALLPAPER windows. + final int position = showWhenLocked ? POSITION_BOTTOM : POSITION_TOP; + + // Note: Moving all the way to the front or back breaks ordering based on addition times. + // We should never have more than one non-animating token of each type. + getParent().positionChildAt(position, this /* child */, false /*includingParents */); + } + + boolean canShowWhenLocked() { + return mShowWhenLocked; + } + void sendWindowWallpaperCommand( String action, int x, int y, int z, Bundle extras, boolean sync) { for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) { diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 1282acbc9e5a..2f3a70eb0e2d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -657,6 +657,17 @@ public abstract class WindowManagerInternal { public abstract int getWindowOwnerUserId(IBinder windowToken); /** + * Control visilibility of a {@link WallpaperWindowToken} {@code} binder on the lock screen. + * + * <p>This will also affect its Z-ordering as {@code showWhenLocked} wallpaper tokens are + * arranged underneath non-{@code showWhenLocked} wallpaper tokens. + * + * @param windowToken wallpaper token previously added via {@link #addWindowToken} + * @param showWhenLocked whether {@param token} can continue to be shown on the lock screen. + */ + public abstract void setWallpaperShowWhenLocked(IBinder windowToken, boolean showWhenLocked); + + /** * Returns {@code true} if a Window owned by {@code uid} has focus. */ public abstract boolean isUidFocused(int uid); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index ad819820ceec..22fddec71326 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -8045,6 +8045,19 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void setWallpaperShowWhenLocked(IBinder binder, boolean showWhenLocked) { + synchronized (mGlobalLock) { + final WindowToken token = mRoot.getWindowToken(binder); + if (token == null || token.asWallpaperToken() == null) { + ProtoLog.w(WM_ERROR, + "setWallpaperShowWhenLocked: non-existent wallpaper token: %s", binder); + return; + } + token.asWallpaperToken().setShowWhenLocked(showWhenLocked); + } + } + + @Override public boolean isUidFocused(int uid) { synchronized (mGlobalLock) { for (int i = mRoot.getChildCount() - 1; i >= 0; i--) { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 3faf9e005c5e..957d99adea3e 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -494,8 +494,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mService.deferWindowLayout(); mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */); try { - if (transition != null && transition.applyDisplayChangeIfNeeded()) { - effects |= TRANSACT_EFFECTS_LIFECYCLE; + if (transition != null) { + transition.applyDisplayChangeIfNeeded(); } final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps(); final int hopSize = hops.size(); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index e429db924df5..69e84c76ac3b 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2067,12 +2067,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final boolean exiting = mAnimatingExit || mDestroying; return shown && !exiting; } else { - final Task task = getTask(); - final boolean canFromTask = task != null && task.canAffectSystemUiFlags(); - return canFromTask && mActivityRecord.isVisible() - // Do not let snapshot window control the bar + return mActivityRecord.canAffectSystemUiFlags() + // Do not let snapshot window control the bar && (mAttrs.type != TYPE_APPLICATION_STARTING - || !(mStartingData instanceof SnapshotStartingData)); + || !(mStartingData instanceof SnapshotStartingData)); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java index 9cb7533e43ac..474df98cb1e6 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java @@ -18,6 +18,8 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.admin.BooleanPolicyValue; +import android.app.admin.PolicyKey; import android.util.Log; import com.android.modules.utils.TypedXmlPullParser; @@ -31,7 +33,8 @@ import java.util.Objects; final class BooleanPolicySerializer extends PolicySerializer<Boolean> { @Override - void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull Boolean value) + void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeName, + @NonNull Boolean value) throws IOException { Objects.requireNonNull(value); serializer.attributeBoolean(/* namespace= */ null, attributeName, value); @@ -39,9 +42,10 @@ final class BooleanPolicySerializer extends PolicySerializer<Boolean> { @Nullable @Override - Boolean readFromXml(TypedXmlPullParser parser, String attributeName) { + BooleanPolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) { try { - return parser.getAttributeBoolean(/* namespace= */ null, attributeName); + return new BooleanPolicyValue( + parser.getAttributeBoolean(/* namespace= */ null, attributeName)); } catch (XmlPullParserException e) { Log.e(DevicePolicyEngine.TAG, "Error parsing Boolean policy value", e); return null; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java new file mode 100644 index 000000000000..c79aac722bd7 --- /dev/null +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2022 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.android.server.devicepolicy; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.admin.BundlePolicyValue; +import android.app.admin.PackagePolicyKey; +import android.app.admin.PolicyKey; +import android.os.Bundle; +import android.os.Environment; +import android.os.Parcelable; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.Xml; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.XmlUtils; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import libcore.io.IoUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Objects; + +// TODO(b/266704763): clean this up and stop creating separate files for each value, the code here +// is copied from UserManagerService, however it doesn't currently handle setting different +// restrictions for the same package in different users, it also will not remove the files for +// outdated restrictions, this will all get fixed when we save it as part of the policies file +// rather than in its own files. +final class BundlePolicySerializer extends PolicySerializer<Bundle> { + + private static final String RESTRICTIONS_FILE_PREFIX = "AppRestrictions_"; + private static final String XML_SUFFIX = ".xml"; + + private static final String TAG_RESTRICTIONS = "restrictions"; + private static final String TAG_ENTRY = "entry"; + private static final String TAG_VALUE = "value"; + private static final String ATTR_KEY = "key"; + private static final String ATTR_VALUE_TYPE = "type"; + private static final String ATTR_MULTIPLE = "m"; + + private static final String ATTR_TYPE_STRING_ARRAY = "sa"; + private static final String ATTR_TYPE_STRING = "s"; + private static final String ATTR_TYPE_BOOLEAN = "b"; + private static final String ATTR_TYPE_INTEGER = "i"; + private static final String ATTR_TYPE_BUNDLE = "B"; + private static final String ATTR_TYPE_BUNDLE_ARRAY = "BA"; + + @Override + void saveToXml(@NonNull PolicyKey policyKey, TypedXmlSerializer serializer, + String attributeName, @NonNull Bundle value) throws IOException { + Objects.requireNonNull(value); + Objects.requireNonNull(policyKey); + if (!(policyKey instanceof PackagePolicyKey)) { + throw new IllegalArgumentException("policyKey is not of type " + + "PackagePolicyKey"); + } + String packageName = ((PackagePolicyKey) policyKey).getPackageName(); + String fileName = packageToRestrictionsFileName(packageName, value); + writeApplicationRestrictionsLAr(fileName, value); + serializer.attribute(/* namespace= */ null, attributeName, fileName); + } + + @Nullable + @Override + BundlePolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) { + String fileName = parser.getAttributeValue(/* namespace= */ null, attributeName); + + return new BundlePolicyValue(readApplicationRestrictions(fileName)); + } + + private static String packageToRestrictionsFileName(String packageName, Bundle restrictions) { + return RESTRICTIONS_FILE_PREFIX + packageName + Objects.hash(restrictions) + XML_SUFFIX; + } + + @GuardedBy("mAppRestrictionsLock") + private static Bundle readApplicationRestrictions(String fileName) { + AtomicFile restrictionsFile = + new AtomicFile(new File(Environment.getDataSystemDirectory(), fileName)); + return readApplicationRestrictions(restrictionsFile); + } + + @VisibleForTesting + @GuardedBy("mAppRestrictionsLock") + static Bundle readApplicationRestrictions(AtomicFile restrictionsFile) { + final Bundle restrictions = new Bundle(); + final ArrayList<String> values = new ArrayList<>(); + if (!restrictionsFile.getBaseFile().exists()) { + return restrictions; + } + + FileInputStream fis = null; + try { + fis = restrictionsFile.openRead(); + final TypedXmlPullParser parser = Xml.resolvePullParser(fis); + XmlUtils.nextElement(parser); + if (parser.getEventType() != XmlPullParser.START_TAG) { + Slog.e(DevicePolicyEngine.TAG, "Unable to read restrictions file " + + restrictionsFile.getBaseFile()); + return restrictions; + } + while (parser.next() != XmlPullParser.END_DOCUMENT) { + readEntry(restrictions, values, parser); + } + } catch (IOException | XmlPullParserException e) { + Slog.w(DevicePolicyEngine.TAG, "Error parsing " + restrictionsFile.getBaseFile(), e); + } finally { + IoUtils.closeQuietly(fis); + } + return restrictions; + } + + private static void readEntry(Bundle restrictions, ArrayList<String> values, + TypedXmlPullParser parser) throws XmlPullParserException, IOException { + int type = parser.getEventType(); + if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_ENTRY)) { + String key = parser.getAttributeValue(null, ATTR_KEY); + String valType = parser.getAttributeValue(null, ATTR_VALUE_TYPE); + int count = parser.getAttributeInt(null, ATTR_MULTIPLE, -1); + if (count != -1) { + values.clear(); + while (count > 0 && (type = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (type == XmlPullParser.START_TAG + && parser.getName().equals(TAG_VALUE)) { + values.add(parser.nextText().trim()); + count--; + } + } + String [] valueStrings = new String[values.size()]; + values.toArray(valueStrings); + restrictions.putStringArray(key, valueStrings); + } else if (ATTR_TYPE_BUNDLE.equals(valType)) { + restrictions.putBundle(key, readBundleEntry(parser, values)); + } else if (ATTR_TYPE_BUNDLE_ARRAY.equals(valType)) { + final int outerDepth = parser.getDepth(); + ArrayList<Bundle> bundleList = new ArrayList<>(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + Bundle childBundle = readBundleEntry(parser, values); + bundleList.add(childBundle); + } + restrictions.putParcelableArray(key, + bundleList.toArray(new Bundle[bundleList.size()])); + } else { + String value = parser.nextText().trim(); + if (ATTR_TYPE_BOOLEAN.equals(valType)) { + restrictions.putBoolean(key, Boolean.parseBoolean(value)); + } else if (ATTR_TYPE_INTEGER.equals(valType)) { + restrictions.putInt(key, Integer.parseInt(value)); + } else { + restrictions.putString(key, value); + } + } + } + } + + private static Bundle readBundleEntry(TypedXmlPullParser parser, ArrayList<String> values) + throws IOException, XmlPullParserException { + Bundle childBundle = new Bundle(); + int outerDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + readEntry(childBundle, values, parser); + } + return childBundle; + } + + private static void writeApplicationRestrictionsLAr(String fileName, Bundle restrictions) { + AtomicFile restrictionsFile = new AtomicFile( + new File(Environment.getDataSystemDirectory(), fileName)); + writeApplicationRestrictionsLAr(restrictions, restrictionsFile); + } + + static void writeApplicationRestrictionsLAr(Bundle restrictions, AtomicFile restrictionsFile) { + FileOutputStream fos = null; + try { + fos = restrictionsFile.startWrite(); + final TypedXmlSerializer serializer = Xml.resolveSerializer(fos); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + + serializer.startTag(null, TAG_RESTRICTIONS); + writeBundle(restrictions, serializer); + serializer.endTag(null, TAG_RESTRICTIONS); + + serializer.endDocument(); + restrictionsFile.finishWrite(fos); + } catch (Exception e) { + restrictionsFile.failWrite(fos); + Slog.e(DevicePolicyEngine.TAG, "Error writing application restrictions list", e); + } + } + + private static void writeBundle(Bundle restrictions, TypedXmlSerializer serializer) + throws IOException { + for (String key : restrictions.keySet()) { + Object value = restrictions.get(key); + serializer.startTag(null, TAG_ENTRY); + serializer.attribute(null, ATTR_KEY, key); + + if (value instanceof Boolean) { + serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BOOLEAN); + serializer.text(value.toString()); + } else if (value instanceof Integer) { + serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_INTEGER); + serializer.text(value.toString()); + } else if (value == null || value instanceof String) { + serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING); + serializer.text(value != null ? (String) value : ""); + } else if (value instanceof Bundle) { + serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BUNDLE); + writeBundle((Bundle) value, serializer); + } else if (value instanceof Parcelable[]) { + serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BUNDLE_ARRAY); + Parcelable[] array = (Parcelable[]) value; + for (Parcelable parcelable : array) { + if (!(parcelable instanceof Bundle)) { + throw new IllegalArgumentException("bundle-array can only hold Bundles"); + } + serializer.startTag(null, TAG_ENTRY); + serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BUNDLE); + writeBundle((Bundle) parcelable, serializer); + serializer.endTag(null, TAG_ENTRY); + } + } else { + serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING_ARRAY); + String[] values = (String[]) value; + serializer.attributeInt(null, ATTR_MULTIPLE, values.length); + for (String choice : values) { + serializer.startTag(null, TAG_VALUE); + serializer.text(choice != null ? choice : ""); + serializer.endTag(null, TAG_VALUE); + } + } + serializer.endTag(null, TAG_ENTRY); + } + } +} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java index d40000094297..d1c6bcb8b48c 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java @@ -18,6 +18,8 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.admin.ComponentNamePolicyValue; +import android.app.admin.PolicyKey; import android.content.ComponentName; import android.util.Log; @@ -32,9 +34,8 @@ final class ComponentNamePolicySerializer extends PolicySerializer<ComponentName private static final String ATTR_CLASS_NAME = ":class-name"; @Override - void saveToXml( - TypedXmlSerializer serializer, String attributeNamePrefix, @NonNull ComponentName value) - throws IOException { + void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeNamePrefix, + @NonNull ComponentName value) throws IOException { Objects.requireNonNull(value); serializer.attribute( /* namespace= */ null, @@ -46,7 +47,7 @@ final class ComponentNamePolicySerializer extends PolicySerializer<ComponentName @Nullable @Override - ComponentName readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) { + ComponentNamePolicyValue readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) { String packageName = parser.getAttributeValue( /* namespace= */ null, attributeNamePrefix + ATTR_PACKAGE_NAME); String className = parser.getAttributeValue( @@ -55,6 +56,6 @@ final class ComponentNamePolicySerializer extends PolicySerializer<ComponentName Log.e(DevicePolicyEngine.TAG, "Error parsing ComponentName policy."); return null; } - return new ComponentName(packageName, className); + return new ComponentNamePolicyValue(new ComponentName(packageName, className)); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DefaultPolicyKey.java b/services/devicepolicy/java/com/android/server/devicepolicy/DefaultPolicyKey.java deleted file mode 100644 index ab0fc992db8a..000000000000 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DefaultPolicyKey.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2022 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.android.server.devicepolicy; - -import com.android.modules.utils.TypedXmlPullParser; - -/** - * Default implementation for {@link PolicyKey} used to identify a policy that doesn't require any - * additional arguments to be represented in the policy engine's data structure. - */ -final class DefaultPolicyKey extends PolicyKey { - private static final String ATTR_GENERIC_POLICY_KEY = "generic-policy-key"; - - DefaultPolicyKey(String policyKey) { - super(policyKey); - } - - String getKey() { - return mKey; - } - - static DefaultPolicyKey readGenericPolicyKeyFromXml(TypedXmlPullParser parser) { - String genericPolicyKey = parser.getAttributeValue( - /* namespace= */ null, ATTR_GENERIC_POLICY_KEY); - return new DefaultPolicyKey(genericPolicyKey); - } - -} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index 27371c0f1a52..71764dc45462 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java @@ -17,16 +17,21 @@ package com.android.server.devicepolicy; import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_CONFLICTING_ADMIN_POLICY; +import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_CLEARED; import static android.app.admin.PolicyUpdateResult.RESULT_SUCCESS; import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_TARGET_USER_ID; import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_UPDATE_RESULT_KEY; import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT; +import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyState; +import android.app.admin.PolicyKey; import android.app.admin.PolicyUpdatesReceiver; +import android.app.admin.PolicyValue; import android.app.admin.TargetUser; import android.content.Context; import android.content.Intent; @@ -39,6 +44,7 @@ import android.os.Bundle; import android.os.Environment; import android.os.UserHandle; import android.os.UserManager; +import android.provider.DeviceConfig; import android.util.AtomicFile; import android.util.Log; import android.util.SparseArray; @@ -59,6 +65,7 @@ import java.io.InputStream; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -71,6 +78,9 @@ import java.util.Set; final class DevicePolicyEngine { static final String TAG = "DevicePolicyEngine"; + private static final String ENABLE_COEXISTENCE_FLAG = "enable_coexistence"; + private static final boolean DEFAULT_ENABLE_COEXISTENCE_FLAG = true; + private final Context mContext; private final UserManager mUserManager; @@ -105,17 +115,20 @@ final class DevicePolicyEngine { mEnforcingAdmins = new SparseArray<>(); } - // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values /** - * Set the policy for the provided {@code policyDefinition} - * (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}. + * Set the policy for the provided {@code policyDefinition} (see {@link PolicyDefinition}) and + * {@code enforcingAdmin} to the provided {@code value}. + * + * <p>If {@code skipEnforcePolicy} is true, it sets the policies in the internal data structure + * but doesn't call the enforcing logic. + * */ <V> void setLocalPolicy( @NonNull PolicyDefinition<V> policyDefinition, @NonNull EnforcingAdmin enforcingAdmin, - @NonNull V value, - int userId) { - + @NonNull PolicyValue<V> value, + int userId, + boolean skipEnforcePolicy) { Objects.requireNonNull(policyDefinition); Objects.requireNonNull(enforcingAdmin); Objects.requireNonNull(value); @@ -123,6 +136,12 @@ final class DevicePolicyEngine { synchronized (mLock) { PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId); + if (policyDefinition.isNonCoexistablePolicy()) { + setNonCoexistableLocalPolicy(policyDefinition, localPolicyState, enforcingAdmin, + value, userId, skipEnforcePolicy); + return; + } + boolean hasGlobalPolicies = hasGlobalPolicyLocked(policyDefinition); boolean policyChanged; if (hasGlobalPolicies) { @@ -135,19 +154,22 @@ final class DevicePolicyEngine { policyChanged = localPolicyState.addPolicy(enforcingAdmin, value); } - if (policyChanged) { - onLocalPolicyChanged(policyDefinition, enforcingAdmin, userId); + // No need to notify admins as no new policy is actually enforced, we're just filling in + // the data structures. + if (!skipEnforcePolicy) { + if (policyChanged) { + onLocalPolicyChanged(policyDefinition, enforcingAdmin, userId); + } + boolean policyEnforced = Objects.equals( + localPolicyState.getCurrentResolvedPolicy(), value); + sendPolicyResultToAdmin( + enforcingAdmin, + policyDefinition, + // TODO: we're always sending this for now, should properly handle errors. + policyEnforced ? RESULT_SUCCESS : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY, + userId); } - boolean policyEnforced = Objects.equals( - localPolicyState.getCurrentResolvedPolicy(), value); - sendPolicyResultToAdmin( - enforcingAdmin, - policyDefinition, - // TODO: we're always sending this for now, should properly handle errors. - policyEnforced ? RESULT_SUCCESS : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY, - userId); - updateDeviceAdminServiceOnPolicyAddLocked(enforcingAdmin); write(); @@ -156,6 +178,49 @@ final class DevicePolicyEngine { } } + /** + * Sets a non-coexistable policy, meaning it doesn't get resolved against other policies set + * by other admins, and no callbacks are sent to admins, this is just storing and + * enforcing the policy. + * + * <p>Passing a {@code null} value means the policy set by this admin should be removed. + */ + private <V> void setNonCoexistableLocalPolicy( + PolicyDefinition<V> policyDefinition, + PolicyState<V> localPolicyState, + EnforcingAdmin enforcingAdmin, + @Nullable PolicyValue<V> value, + int userId, + boolean skipEnforcePolicy) { + if (value == null) { + localPolicyState.removePolicy(enforcingAdmin); + } else { + localPolicyState.addPolicy(enforcingAdmin, value); + } + if (!skipEnforcePolicy) { + enforcePolicy(policyDefinition, value, userId); + } + if (localPolicyState.getPoliciesSetByAdmins().isEmpty()) { + removeLocalPolicyStateLocked(policyDefinition, userId); + } + updateDeviceAdminServiceOnPolicyAddLocked(enforcingAdmin); + write(); + } + + // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values + /** + * Set the policy for the provided {@code policyDefinition} + * (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}. + */ + <V> void setLocalPolicy( + @NonNull PolicyDefinition<V> policyDefinition, + @NonNull EnforcingAdmin enforcingAdmin, + @NonNull PolicyValue<V> value, + int userId) { + setLocalPolicy( + policyDefinition, enforcingAdmin, value, userId, /* skipEnforcePolicy= */ false); + } + // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values /** * Removes any previously set policy for the provided {@code policyDefinition} @@ -174,6 +239,12 @@ final class DevicePolicyEngine { } PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId); + if (policyDefinition.isNonCoexistablePolicy()) { + setNonCoexistableLocalPolicy(policyDefinition, localPolicyState, enforcingAdmin, + /* value= */ null, userId, /* skipEnforcePolicy= */ false); + return; + } + boolean policyChanged; if (hasGlobalPolicyLocked(policyDefinition)) { PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition); @@ -189,12 +260,11 @@ final class DevicePolicyEngine { } // For a removePolicy to be enforced, it means no current policy exists - boolean policyEnforced = localPolicyState.getCurrentResolvedPolicy() == null; sendPolicyResultToAdmin( enforcingAdmin, policyDefinition, // TODO: we're always sending this for now, should properly handle errors. - policyEnforced ? RESULT_SUCCESS : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY, + RESULT_POLICY_CLEARED, userId); if (localPolicyState.getPoliciesSetByAdmins().isEmpty()) { @@ -215,7 +285,7 @@ final class DevicePolicyEngine { * else remove the policy from child. */ private <V> void applyToInheritableProfiles(PolicyDefinition<V> policyDefinition, - EnforcingAdmin enforcingAdmin, V value, int userId) { + EnforcingAdmin enforcingAdmin, PolicyValue<V> value, int userId) { if (policyDefinition.isInheritable()) { Binder.withCleanCallingIdentity(() -> { List<UserInfo> userInfos = mUserManager.getProfiles(userId); @@ -280,6 +350,18 @@ final class DevicePolicyEngine { userId); } } + + /** + * Set the policy for the provided {@code policyDefinition} + * (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}. + */ + <V> void setGlobalPolicy( + @NonNull PolicyDefinition<V> policyDefinition, + @NonNull EnforcingAdmin enforcingAdmin, + @NonNull PolicyValue<V> value) { + setGlobalPolicy(policyDefinition, enforcingAdmin, value, /* skipEnforcePolicy= */ false); + } + // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values /** * Set the policy for the provided {@code policyDefinition} @@ -288,7 +370,8 @@ final class DevicePolicyEngine { <V> void setGlobalPolicy( @NonNull PolicyDefinition<V> policyDefinition, @NonNull EnforcingAdmin enforcingAdmin, - @NonNull V value) { + @NonNull PolicyValue<V> value, + boolean skipEnforcePolicy) { Objects.requireNonNull(policyDefinition); Objects.requireNonNull(enforcingAdmin); @@ -298,22 +381,27 @@ final class DevicePolicyEngine { PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition); boolean policyChanged = globalPolicyState.addPolicy(enforcingAdmin, value); - if (policyChanged) { - onGlobalPolicyChanged(policyDefinition, enforcingAdmin); - } + boolean policyAppliedOnAllUsers = applyGlobalPolicyOnUsersWithLocalPoliciesLocked( + policyDefinition, enforcingAdmin, value, skipEnforcePolicy); + + // No need to notify admins as no new policy is actually enforced, we're just filling in + // the data structures. + if (!skipEnforcePolicy) { + if (policyChanged) { + onGlobalPolicyChanged(policyDefinition, enforcingAdmin); + } - boolean policyEnforcedOnAllUsers = enforceGlobalPolicyOnUsersWithLocalPoliciesLocked( - policyDefinition, enforcingAdmin, value); - boolean policyEnforcedGlobally = Objects.equals( - globalPolicyState.getCurrentResolvedPolicy(), value); - boolean policyEnforced = policyEnforcedGlobally && policyEnforcedOnAllUsers; + boolean policyAppliedGlobally = Objects.equals( + globalPolicyState.getCurrentResolvedPolicy(), value); + boolean policyApplied = policyAppliedGlobally && policyAppliedOnAllUsers; - sendPolicyResultToAdmin( - enforcingAdmin, - policyDefinition, - // TODO: we're always sending this for now, should properly handle errors. - policyEnforced ? RESULT_SUCCESS : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY, - UserHandle.USER_ALL); + sendPolicyResultToAdmin( + enforcingAdmin, + policyDefinition, + // TODO: we're always sending this for now, should properly handle errors. + policyApplied ? RESULT_SUCCESS : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY, + UserHandle.USER_ALL); + } updateDeviceAdminServiceOnPolicyAddLocked(enforcingAdmin); @@ -341,17 +429,14 @@ final class DevicePolicyEngine { onGlobalPolicyChanged(policyDefinition, enforcingAdmin); } - boolean policyEnforcedOnAllUsers = enforceGlobalPolicyOnUsersWithLocalPoliciesLocked( - policyDefinition, enforcingAdmin, /* value= */ null); - // For a removePolicy to be enforced, it means no current policy exists - boolean policyEnforcedGlobally = policyState.getCurrentResolvedPolicy() == null; - boolean policyEnforced = policyEnforcedGlobally && policyEnforcedOnAllUsers; + applyGlobalPolicyOnUsersWithLocalPoliciesLocked( + policyDefinition, enforcingAdmin, /* value= */ null, /* enforcePolicy= */ true); sendPolicyResultToAdmin( enforcingAdmin, policyDefinition, // TODO: we're always sending this for now, should properly handle errors. - policyEnforced ? RESULT_SUCCESS : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY, + RESULT_POLICY_CLEARED, UserHandle.USER_ALL); if (policyState.getPoliciesSetByAdmins().isEmpty()) { @@ -392,15 +477,16 @@ final class DevicePolicyEngine { * * <p>Returns {@code true} if the policy is enforced successfully on all users. */ - private <V> boolean enforceGlobalPolicyOnUsersWithLocalPoliciesLocked( + private <V> boolean applyGlobalPolicyOnUsersWithLocalPoliciesLocked( @NonNull PolicyDefinition<V> policyDefinition, @NonNull EnforcingAdmin enforcingAdmin, - @Nullable V value) { + @Nullable PolicyValue<V> value, + boolean skipEnforcePolicy) { // Global only policies can't be applied locally, return early. if (policyDefinition.isGlobalOnlyPolicy()) { return true; } - boolean isAdminPolicyEnforced = true; + boolean isAdminPolicyApplied = true; for (int i = 0; i < mLocalPolicies.size(); i++) { int userId = mLocalPolicies.keyAt(i); if (!hasLocalPolicyLocked(policyDefinition, userId)) { @@ -412,9 +498,11 @@ final class DevicePolicyEngine { boolean policyChanged = localPolicyState.resolvePolicy( globalPolicyState.getPoliciesSetByAdmins()); - if (policyChanged) { + if (policyChanged && !skipEnforcePolicy) { enforcePolicy( - policyDefinition, localPolicyState.getCurrentResolvedPolicy(), userId); + policyDefinition, + localPolicyState.getCurrentResolvedPolicy(), + userId); sendPolicyChangedToAdmins( localPolicyState, enforcingAdmin, @@ -424,10 +512,10 @@ final class DevicePolicyEngine { userId); } - isAdminPolicyEnforced &= Objects.equals( + isAdminPolicyApplied &= Objects.equals( value, localPolicyState.getCurrentResolvedPolicy()); } - return isAdminPolicyEnforced; + return isAdminPolicyApplied; } /** @@ -438,14 +526,16 @@ final class DevicePolicyEngine { Objects.requireNonNull(policyDefinition); synchronized (mLock) { + PolicyValue<V> resolvedValue = null; if (hasLocalPolicyLocked(policyDefinition, userId)) { - return getLocalPolicyStateLocked( + resolvedValue = getLocalPolicyStateLocked( policyDefinition, userId).getCurrentResolvedPolicy(); } if (hasGlobalPolicyLocked(policyDefinition)) { - return getGlobalPolicyStateLocked(policyDefinition).getCurrentResolvedPolicy(); + resolvedValue = getGlobalPolicyStateLocked( + policyDefinition).getCurrentResolvedPolicy(); } - return null; + return resolvedValue == null ? null : resolvedValue.getValue(); } } @@ -466,13 +556,30 @@ final class DevicePolicyEngine { return null; } return getLocalPolicyStateLocked(policyDefinition, userId) - .getPoliciesSetByAdmins().get(enforcingAdmin); + .getPoliciesSetByAdmins().get(enforcingAdmin).getValue(); + } + } + + /** + * Retrieves the values set for the provided {@code policyDefinition} by each admin. + */ + @NonNull + <V> LinkedHashMap<EnforcingAdmin, PolicyValue<V>> getLocalPoliciesSetByAdmins( + @NonNull PolicyDefinition<V> policyDefinition, + int userId) { + Objects.requireNonNull(policyDefinition); + + synchronized (mLock) { + if (!hasLocalPolicyLocked(policyDefinition, userId)) { + return new LinkedHashMap<>(); + } + return getLocalPolicyStateLocked(policyDefinition, userId).getPoliciesSetByAdmins(); } } /** - * Returns the policies set by the given admin that share the same {@link PolicyKey#getKey()} as - * the provided {@code policyDefinition}. + * Returns the policies set by the given admin that share the same + * {@link PolicyKey#getIdentifier()} as the provided {@code policyDefinition}. * * <p>For example, getLocalPolicyKeysSetByAdmin(PERMISSION_GRANT, admin) returns all permission * grants set by the given admin. @@ -496,7 +603,7 @@ final class DevicePolicyEngine { } Set<PolicyKey> keys = new HashSet<>(); for (PolicyKey key : mLocalPolicies.get(userId).keySet()) { - if (key.hasSameKeyAs(policyDefinition.getPolicyKey()) + if (key.hasSameIdentifierAs(policyDefinition.getPolicyKey()) && mLocalPolicies.get(userId).get(key).getPoliciesSetByAdmins() .containsKey(enforcingAdmin)) { keys.add(key); @@ -591,11 +698,12 @@ final class DevicePolicyEngine { } } - private <V> void enforcePolicy( - PolicyDefinition<V> policyDefinition, @Nullable V policyValue, int userId) { + private <V> void enforcePolicy(PolicyDefinition<V> policyDefinition, + @Nullable PolicyValue<V> policyValue, int userId) { // null policyValue means remove any enforced policies, ensure callbacks handle this // properly - policyDefinition.enforcePolicy(policyValue, mContext, userId); + policyDefinition.enforcePolicy( + policyValue == null ? null : policyValue.getValue(), mContext, userId); } private <V> void sendPolicyResultToAdmin( @@ -766,7 +874,7 @@ final class DevicePolicyEngine { if (!policyState.getPolicyDefinition().isInheritable()) { return; } - for (Map.Entry<EnforcingAdmin, V> enforcingAdminEntry : + for (Map.Entry<EnforcingAdmin, PolicyValue<V>> enforcingAdminEntry : policyState.getPoliciesSetByAdmins().entrySet()) { setLocalPolicy(policyState.getPolicyDefinition(), enforcingAdminEntry.getKey(), @@ -810,6 +918,34 @@ final class DevicePolicyEngine { } /** + * Returns all current enforced policies set on the device, and the individual values set by + * each admin. Global policies are returned under {@link UserHandle#ALL}. + */ + @NonNull + DevicePolicyState getDevicePolicyState() { + Map<UserHandle, Map<PolicyKey, android.app.admin.PolicyState<?>>> policies = + new HashMap<>(); + for (int i = 0; i < mLocalPolicies.size(); i++) { + UserHandle user = UserHandle.of(mLocalPolicies.keyAt(i)); + policies.put(user, new HashMap<>()); + for (PolicyKey policyKey : mLocalPolicies.valueAt(i).keySet()) { + policies.get(user).put( + policyKey, + mLocalPolicies.valueAt(i).get(policyKey).getParcelablePolicyState()); + } + } + if (!mGlobalPolicies.isEmpty()) { + policies.put(UserHandle.ALL, new HashMap<>()); + for (PolicyKey policyKey : mGlobalPolicies.keySet()) { + policies.get(UserHandle.ALL).put( + policyKey, + mGlobalPolicies.get(policyKey).getParcelablePolicyState()); + } + } + return new DevicePolicyState(policies); + } + + /** * Reestablishes the service that handles * {@link DevicePolicyManager#ACTION_DEVICE_ADMIN_SERVICE} in the enforcing admin if the package * was updated, as a package update results in the persistent connection getting reset. @@ -835,11 +971,6 @@ final class DevicePolicyEngine { private void updateDeviceAdminServiceOnPolicyAddLocked(@NonNull EnforcingAdmin enforcingAdmin) { int userId = enforcingAdmin.getUserId(); - // A connection is established with DPCs as soon as they are provisioned, so no need to - // connect when a policy is set. - if (enforcingAdmin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) { - return; - } if (mEnforcingAdmins.contains(userId) && mEnforcingAdmins.get(userId).contains(enforcingAdmin)) { return; @@ -850,6 +981,11 @@ final class DevicePolicyEngine { } mEnforcingAdmins.get(enforcingAdmin.getUserId()).add(enforcingAdmin); + // A connection is established with DPCs as soon as they are provisioned, so no need to + // connect when a policy is set. + if (enforcingAdmin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) { + return; + } mDeviceAdminServiceController.startServiceForAdmin( enforcingAdmin.getPackageName(), userId, @@ -862,18 +998,10 @@ final class DevicePolicyEngine { */ private void updateDeviceAdminServiceOnPolicyRemoveLocked( @NonNull EnforcingAdmin enforcingAdmin) { - // TODO(b/263364434): centralise handling in one place. - // DPCs rely on a constant connection being established as soon as they are provisioned, - // so we shouldn't disconnect it even if they no longer have policies set. - if (enforcingAdmin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) { - return; - } if (doesAdminHavePolicies(enforcingAdmin)) { return; } - int userId = enforcingAdmin.getUserId(); - if (mEnforcingAdmins.contains(userId)) { mEnforcingAdmins.get(userId).remove(enforcingAdmin); if (mEnforcingAdmins.get(userId).isEmpty()) { @@ -881,6 +1009,12 @@ final class DevicePolicyEngine { } } + // TODO(b/263364434): centralise handling in one place. + // DPCs rely on a constant connection being established as soon as they are provisioned, + // so we shouldn't disconnect it even if they no longer have policies set. + if (enforcingAdmin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) { + return; + } mDeviceAdminServiceController.stopServiceForAdmin( enforcingAdmin.getPackageName(), userId, @@ -927,21 +1061,70 @@ final class DevicePolicyEngine { } } + /** + * Clear all policies set in the policy engine. + * + * <p>Note that this doesn't clear any enforcements, it only clears the data structures. + */ + void clearAllPolicies() { + synchronized (mLock) { + clear(); + write(); + } + } private void clear() { synchronized (mLock) { mGlobalPolicies.clear(); mLocalPolicies.clear(); + mEnforcingAdmins.clear(); + } + } + + // TODO: we need to listen for user removal and package removal and update out internal policy + // map and enforcing admins for this is be accurate. + boolean hasActivePolicies() { + return mEnforcingAdmins.size() > 0; + } + + /** + * Returns {@code true} if the coexistence flag is enabled or: + * <ul> + * <li>If the provided package is an admin with existing policies + * <li>A new admin and no other admin have policies set + * <li>More than one admin have policies set + */ + boolean canAdminAddPolicies(String packageName, int userId) { + if (isCoexistenceFlagEnabled()) { + return true; + } + + if (mEnforcingAdmins.contains(userId) + && mEnforcingAdmins.get(userId).stream().anyMatch(admin -> + admin.getPackageName().equals(packageName))) { + return true; } + + int numOfEnforcingAdmins = 0; + for (int i = 0; i < mEnforcingAdmins.size(); i++) { + numOfEnforcingAdmins += mEnforcingAdmins.get(i).size(); + } + return numOfEnforcingAdmins == 0 || numOfEnforcingAdmins > 1; + } + + private boolean isCoexistenceFlagEnabled() { + return DeviceConfig.getBoolean( + NAMESPACE_DEVICE_POLICY_MANAGER, + ENABLE_COEXISTENCE_FLAG, + DEFAULT_ENABLE_COEXISTENCE_FLAG); } private class DevicePoliciesReaderWriter { - private static final String DEVICE_POLICIES_XML = "device_policies.xml"; + private static final String DEVICE_POLICIES_XML = "device_policy_state.xml"; private static final String TAG_LOCAL_POLICY_ENTRY = "local-policy-entry"; private static final String TAG_GLOBAL_POLICY_ENTRY = "global-policy-entry"; private static final String TAG_ADMINS_POLICY_ENTRY = "admins-policy-entry"; private static final String TAG_ENFORCING_ADMINS_ENTRY = "enforcing-admins-entry"; private static final String ATTR_USER_ID = "user-id"; - private static final String ATTR_POLICY_ID = "policy-id"; private final File mFile; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index fbde1ba1bbb1..90b7c122b858 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -21,6 +21,7 @@ import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY; import static android.Manifest.permission.QUERY_ADMIN_POLICY; import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY; import static android.Manifest.permission.SET_TIME; @@ -41,6 +42,7 @@ import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEV import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER; import static android.app.admin.DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED; +import static android.app.admin.DevicePolicyManager.AUTO_TIMEZONE_POLICY; import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS; import static android.app.admin.DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL; import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL; @@ -205,6 +207,9 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.StatusBarManager; +import android.app.admin.BooleanPolicyValue; +import android.app.admin.BundlePolicyValue; +import android.app.admin.ComponentNamePolicyValue; import android.app.admin.DeviceAdminInfo; import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyCache; @@ -219,11 +224,15 @@ import android.app.admin.DevicePolicyManager.PersonalAppsSuspensionReason; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DevicePolicyManagerLiteInternal; import android.app.admin.DevicePolicySafetyChecker; +import android.app.admin.DevicePolicyState; import android.app.admin.DevicePolicyStringResource; import android.app.admin.DeviceStateCache; import android.app.admin.FactoryResetProtectionPolicy; import android.app.admin.FullyManagedDeviceProvisioningParams; import android.app.admin.IDevicePolicyManager; +import android.app.admin.IntegerPolicyValue; +import android.app.admin.IntentFilterPolicyKey; +import android.app.admin.LockTaskPolicy; import android.app.admin.ManagedProfileProvisioningParams; import android.app.admin.ManagedSubscriptionsPolicy; import android.app.admin.NetworkEvent; @@ -232,10 +241,13 @@ import android.app.admin.ParcelableGranteeMap; import android.app.admin.ParcelableResource; import android.app.admin.PasswordMetrics; import android.app.admin.PasswordPolicy; +import android.app.admin.PolicyKey; +import android.app.admin.PolicyValue; import android.app.admin.PreferentialNetworkServiceConfig; import android.app.admin.SecurityLog; import android.app.admin.SecurityLog.SecurityEvent; import android.app.admin.StartInstallingUpdateCallback; +import android.app.admin.StringSetPolicyValue; import android.app.admin.SystemUpdateInfo; import android.app.admin.SystemUpdatePolicy; import android.app.admin.UnsafeStateException; @@ -280,6 +292,7 @@ import android.content.pm.Signature; import android.content.pm.StringParceledListSlice; import android.content.pm.UserInfo; import android.content.pm.UserPackage; +import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.content.res.Resources; import android.database.ContentObserver; import android.database.Cursor; @@ -432,6 +445,7 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -736,11 +750,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + "management app's authentication policy"; private static final String NOT_SYSTEM_CALLER_MSG = "Only the system can %s"; + // ENABLE_DEVICE_POLICY_ENGINE_FLAG must be enabled before this could be enabled. private static final String PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG = "enable_permission_based_access"; - private static final String ENABLE_COEXISTENCE_FLAG = "enable_coexistence"; private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false; - private static final boolean DEFAULT_ENABLE_COEXISTENCE_FLAG = false; + + // This must be enabled before PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG is enabled, the reason + // we're not just relying on PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG to enable the policy engine + // is that we might want to enable it before the permission changes are ready if we want to test + // it on DPCs. + // Once this is enabled, it can no longer be disabled in production + private static final String ENABLE_DEVICE_POLICY_ENGINE_FLAG = "enable_device_policy_engine"; + private static final boolean DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FLAG = false; // TODO(b/258425381) remove the flag after rollout. private static final String KEEP_PROFILES_RUNNING_FLAG = "enable_keep_profiles_running"; @@ -1300,10 +1321,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { && (owner.getPackageName().equals(packageName))) { startOwnerService(userHandle, "package-broadcast"); } - if (isCoexistenceFlagEnabled()) { + if (shouldMigrateToDevicePolicyEngine()) { + migratePoliciesToDevicePolicyEngine(); + } + if (isDevicePolicyEngineFlagEnabled()) { mDevicePolicyEngine.handlePackageChanged(packageName, userHandle); } - // Persist updates if the removed package was an admin or delegate. if (removedAdmin || removedDelegate) { saveSettingsLocked(policy.mUserId); @@ -1993,7 +2016,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener()); mDeviceManagementResourcesProvider.load(); - if (isCoexistenceFlagEnabled()) { + if (isDevicePolicyEngineFlagEnabled()) { mDevicePolicyEngine.load(); } @@ -3124,6 +3147,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (getLockObject()) { migrateToProfileOnOrganizationOwnedDeviceIfCompLocked(); applyProfileRestrictionsIfDeviceOwnerLocked(); + + // TODO: Is this the right place to trigger the migration? + if (shouldMigrateToDevicePolicyEngine()) { + migratePoliciesToDevicePolicyEngine(); + } } maybeStartSecurityLogMonitorOnActivityManagerReady(); break; @@ -3332,7 +3360,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { updateNetworkPreferenceForUser(userId, preferentialNetworkServiceConfigs); startOwnerService(userId, "start-user"); - if (isCoexistenceFlagEnabled()) { + if (isDevicePolicyEngineFlagEnabled()) { mDevicePolicyEngine.handleStartUser(userId); } } @@ -3357,7 +3385,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { void handleUnlockUser(int userId) { startOwnerService(userId, "unlock-user"); - if (isCoexistenceFlagEnabled()) { + if (isDevicePolicyEngineFlagEnabled()) { mDevicePolicyEngine.handleUnlockUser(userId); } } @@ -3369,7 +3397,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { void handleStopUser(int userId) { updateNetworkPreferenceForUser(userId, List.of(PreferentialNetworkServiceConfig.DEFAULT)); mDeviceAdminServiceController.stopServicesForUser(userId, /* actionForLog= */ "stop-user"); - if (isCoexistenceFlagEnabled()) { + if (isDevicePolicyEngineFlagEnabled()) { mDevicePolicyEngine.handleStopUser(userId); } } @@ -3477,7 +3505,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * @param refreshing true = update an active admin, no error */ @Override - public void setActiveAdmin(ComponentName adminReceiver, boolean refreshing, int userHandle) { + public void setActiveAdmin( + ComponentName adminReceiver, boolean refreshing, int userHandle) { if (!mHasFeature) { return; } @@ -3494,6 +3523,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (getLockObject()) { checkActiveAdminPrecondition(adminReceiver, info, policy); mInjector.binderWithCleanCallingIdentity(() -> { + if (!canAddActiveAdminIfPolicyEngineEnabled( + adminReceiver.getPackageName(), userHandle)) { + throw new IllegalStateException("Can't add non-coexistable admin."); + } + final ActiveAdmin existingAdmin = getActiveAdminUncheckedLocked(adminReceiver, userHandle); if (!refreshing && existingAdmin != null) { @@ -8237,14 +8271,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { caller)); } - if (isCoexistenceEnabled(caller)) { + if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) { mDevicePolicyEngine.setGlobalPolicy( PolicyDefinition.AUTO_TIMEZONE, // TODO(b/260573124): add correct enforcing admin when permission changes are // merged. EnforcingAdmin.createEnterpriseEnforcingAdmin( caller.getComponentName(), caller.getUserId()), - enabled); + new BooleanPolicyValue(enabled)); } else { mInjector.binderWithCleanCallingIdentity(() -> mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0)); @@ -10347,11 +10381,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller)); final int userHandle = caller.getUserId(); - if (isCoexistenceEnabled(caller)) { + if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) { mDevicePolicyEngine.setLocalPolicy( PolicyDefinition.PERSISTENT_PREFERRED_ACTIVITY(filter), EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userHandle), - activity, + new ComponentNamePolicyValue(activity), userHandle); } else { synchronized (getLockObject()) { @@ -10385,7 +10419,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int userHandle = caller.getUserId(); - if (isCoexistenceEnabled(caller)) { + if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) { clearPackagePersistentPreferredActivitiesFromPolicyEngine( EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userHandle), packageName, @@ -10421,13 +10455,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { admin, userId); for (PolicyKey key : keys) { - if (!(key instanceof PersistentPreferredActivityPolicyKey)) { + if (!(key instanceof IntentFilterPolicyKey)) { throw new IllegalStateException("PolicyKey for PERSISTENT_PREFERRED_ACTIVITY is not" + "of type PersistentPreferredActivityPolicyKey"); } - PersistentPreferredActivityPolicyKey parsedKey = - (PersistentPreferredActivityPolicyKey) key; - IntentFilter filter = Objects.requireNonNull(parsedKey.getFilter()); + IntentFilterPolicyKey parsedKey = + (IntentFilterPolicyKey) key; + IntentFilter filter = Objects.requireNonNull(parsedKey.getIntentFilter()); ComponentName preferredActivity = mDevicePolicyEngine.getLocalPolicySetByAdmin( PolicyDefinition.PERSISTENT_PREFERRED_ACTIVITY(filter), @@ -10485,25 +10519,79 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setApplicationRestrictions(ComponentName who, String callerPackage, - String packageName, Bundle settings) { + String packageName, Bundle restrictions) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS))); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS); + if (useDevicePolicyEngine(caller, DELEGATION_APP_RESTRICTIONS)) { + // This check is eventually made in UMS, checking here to fail early. + String validationResult = + FrameworkParsingPackageUtils.validateName(packageName, false, false); + if (validationResult != null) { + throw new IllegalArgumentException("Invalid package name: " + validationResult); + } + + EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin( + who == null + ? new ComponentName(caller.getPackageName(), "Delegate") + : who, + caller.getUserId()); + + if (restrictions == null || restrictions.isEmpty()) { + mDevicePolicyEngine.removeLocalPolicy( + PolicyDefinition.APPLICATION_RESTRICTIONS(packageName), + admin, + caller.getUserId()); + } else { + mDevicePolicyEngine.setLocalPolicy( + PolicyDefinition.APPLICATION_RESTRICTIONS(packageName), + admin, + new BundlePolicyValue(restrictions), + caller.getUserId()); + } + setBackwardsCompatibleAppRestrictions( + packageName, restrictions, caller.getUserHandle()); + } else { + mInjector.binderWithCleanCallingIdentity(() -> { + mUserManager.setApplicationRestrictions(packageName, restrictions, + caller.getUserHandle()); + }); + } + + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_APPLICATION_RESTRICTIONS) + .setAdmin(caller.getPackageName()) + .setBoolean(/* isDelegate */ who == null) + .setStrings(packageName) + .write(); + } + + /** + * Set app restrictions in user manager to keep backwards compatibility for the old + * getApplicationRestrictions API. + */ + private void setBackwardsCompatibleAppRestrictions( + String packageName, Bundle restrictions, UserHandle userHandle) { + Bundle restrictionsToApply = restrictions == null || restrictions.isEmpty() + ? getAppRestrictionsSetByAnyAdmin(packageName, userHandle) + : restrictions; mInjector.binderWithCleanCallingIdentity(() -> { - mUserManager.setApplicationRestrictions(packageName, settings, - caller.getUserHandle()); - DevicePolicyEventLogger - .createEvent(DevicePolicyEnums.SET_APPLICATION_RESTRICTIONS) - .setAdmin(caller.getPackageName()) - .setBoolean(/* isDelegate */ who == null) - .setStrings(packageName) - .write(); + mUserManager.setApplicationRestrictions(packageName, restrictionsToApply, userHandle); }); } + private Bundle getAppRestrictionsSetByAnyAdmin(String packageName, UserHandle userHandle) { + LinkedHashMap<EnforcingAdmin, PolicyValue<Bundle>> policies = + mDevicePolicyEngine.getLocalPoliciesSetByAdmins( + PolicyDefinition.APPLICATION_RESTRICTIONS(packageName), + userHandle.getIdentifier()); + return policies.isEmpty() + ? null : policies.entrySet().stream().findAny().get().getValue().getValue(); + } + @Override public void setTrustAgentConfiguration(ComponentName admin, ComponentName agent, PersistableBundle args, boolean parent) { @@ -11339,7 +11427,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int userId = user.id; - if (isCoexistenceFlagEnabled()) { + if (isDevicePolicyEngineFlagEnabled()) { mDevicePolicyEngine.handleUserCreated(user); } @@ -11712,13 +11800,28 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS))); - return mInjector.binderWithCleanCallingIdentity(() -> { - Bundle bundle = mUserManager.getApplicationRestrictions(packageName, - caller.getUserHandle()); - // if no restrictions were saved, mUserManager.getApplicationRestrictions - // returns null, but DPM method should return an empty Bundle as per JavaDoc - return bundle != null ? bundle : Bundle.EMPTY; - }); + if (useDevicePolicyEngine(caller, DELEGATION_APP_RESTRICTIONS)) { + EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin( + who != null ? who : new ComponentName(callerPackage, "Delegate"), + caller.getUserId()); + + LinkedHashMap<EnforcingAdmin, PolicyValue<Bundle>> policies = + mDevicePolicyEngine.getLocalPoliciesSetByAdmins( + PolicyDefinition.APPLICATION_RESTRICTIONS(packageName), + caller.getUserId()); + if (policies.isEmpty() || !policies.containsKey(admin)) { + return Bundle.EMPTY; + } + return policies.get(admin).getValue(); + } else { + return mInjector.binderWithCleanCallingIdentity(() -> { + Bundle bundle = mUserManager.getApplicationRestrictions(packageName, + caller.getUserHandle()); + // if no restrictions were saved, mUserManager.getApplicationRestrictions + // returns null, but DPM method should return an empty Bundle as per JavaDoc + return bundle != null ? bundle : Bundle.EMPTY; + }); + } } /** @@ -12369,7 +12472,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || isFinancedDeviceOwner(caller))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_BLOCK_UNINSTALL))); - if (isCoexistenceEnabled(caller)) { + if (useDevicePolicyEngine(caller, DELEGATION_BLOCK_UNINSTALL)) { // TODO(b/260573124): Add correct enforcing admin when permission changes are // merged, and don't forget to handle delegates! Enterprise admins assume // component name isn't null. @@ -12379,7 +12482,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mDevicePolicyEngine.setLocalPolicy( PolicyDefinition.PACKAGE_UNINSTALL_BLOCKED(packageName), admin, - uninstallBlocked, + new BooleanPolicyValue(uninstallBlocked), caller.getUserId()); } else { final int userId = caller.getUserId(); @@ -12933,7 +13036,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_PACKAGES); } - if (isCoexistenceEnabled(caller)) { + if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) { EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin( who, caller.getUserId()); if (packages.length == 0) { @@ -12950,7 +13053,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (currentPolicy == null) { policy = new LockTaskPolicy(Set.of(packages)); } else { - policy = currentPolicy.clone(); + policy = new LockTaskPolicy(currentPolicy); policy.setPackages(Set.of(packages)); } @@ -12987,7 +13090,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { enforceCanCallLockTaskLocked(caller); } - if (isCoexistenceEnabled(caller)) { + if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) { LockTaskPolicy policy = mDevicePolicyEngine.getResolvedPolicy( PolicyDefinition.LOCK_TASK, userHandle); if (policy == null) { @@ -13015,9 +13118,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } final int userId = mInjector.userHandleGetCallingUserId(); - // TODO(b/260560985): This is not the right check, as the flag could be enabled but there - // could be an admin that hasn't targeted U. - if (isCoexistenceFlagEnabled()) { + // Is it ok to just check that no active policies exist currently? + if (mDevicePolicyEngine.hasActivePolicies()) { LockTaskPolicy policy = mDevicePolicyEngine.getResolvedPolicy( PolicyDefinition.LOCK_TASK, userId); if (policy == null) { @@ -13051,7 +13153,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { enforceCanSetLockTaskFeaturesOnFinancedDevice(caller, flags); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES); } - if (isCoexistenceEnabled(caller)) { + if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) { EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userHandle); LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicySetByAdmin( PolicyDefinition.LOCK_TASK, @@ -13061,7 +13163,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { throw new IllegalArgumentException("Can't set a lock task flags without setting " + "lock task packages first."); } - LockTaskPolicy policy = currentPolicy.clone(); + LockTaskPolicy policy = new LockTaskPolicy(currentPolicy); policy.setFlags(flags); mDevicePolicyEngine.setLocalPolicy( @@ -13092,7 +13194,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { enforceCanCallLockTaskLocked(caller); } - if (isCoexistenceEnabled(caller)) { + if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) { LockTaskPolicy policy = mDevicePolicyEngine.getResolvedPolicy( PolicyDefinition.LOCK_TASK, userHandle); if (policy == null) { @@ -14237,6 +14339,37 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { APPLICATION_EXEMPTIONS_FLAG, DEFAULT_APPLICATION_EXEMPTIONS_FLAG); } + + @Override + public Map<String, Bundle> getApplicationRestrictionsPerAdmin( + String packageName, int userId) { + LinkedHashMap<EnforcingAdmin, PolicyValue<Bundle>> policies = + mDevicePolicyEngine.getLocalPoliciesSetByAdmins( + PolicyDefinition.APPLICATION_RESTRICTIONS(packageName), + userId); + Map<String, Bundle> restrictions = new HashMap<>(); + for (EnforcingAdmin admin : policies.keySet()) { + restrictions.put(admin.getPackageName(), policies.get(admin).getValue()); + } + if (!restrictions.isEmpty()) { + return restrictions; + } + + return mInjector.binderWithCleanCallingIdentity(() -> { + // Could be a device that hasn't migrated yet, so just return any restrictions saved + // in userManager. + Bundle bundle = mUserManager.getApplicationRestrictions( + packageName, UserHandle.of(userId)); + if (bundle == null || bundle.isEmpty()) { + return new HashMap<>(); + } + ActiveAdmin admin = getMostProbableDPCAdminForLocalPolicy(userId); + if (admin == null) { + return new HashMap<>(); + } + return Map.of(admin.info.getPackageName(), bundle); + }); + } } private Intent createShowAdminSupportIntent(int userId) { @@ -14764,7 +14897,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { enforcePermissionGrantStateOnFinancedDevice(packageName, permission); } } - if (isCoexistenceEnabled(caller)) { + if (useDevicePolicyEngine(caller, DELEGATION_PERMISSION_GRANT)) { + // TODO(b/266924257): decide how to handle the internal state if the package doesn't + // exist, or the permission isn't requested by the app, because we could end up with + // inconsistent state between the policy engine and package manager. Also a package + // might get removed or has it's permission updated after we've set the policy. mDevicePolicyEngine.setLocalPolicy( PolicyDefinition.PERMISSION_GRANT(packageName, permission), // TODO(b/260573124): Add correct enforcing admin when permission changes are @@ -14772,7 +14909,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // component name isn't null. EnforcingAdmin.createEnterpriseEnforcingAdmin( caller.getComponentName(), caller.getUserId()), - grantState, + new IntegerPolicyValue(grantState), caller.getUserId()); // TODO: update javadoc to reflect that callback no longer return success/failure callback.sendResult(Bundle.EMPTY); @@ -14859,41 +14996,47 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { enforcePermissionGrantStateOnFinancedDevice(packageName, permission); } return mInjector.binderWithCleanCallingIdentity(() -> { - int granted; - if (getTargetSdk(caller.getPackageName(), caller.getUserId()) - < android.os.Build.VERSION_CODES.Q) { - // The per-Q behavior was to not check the app-ops state. - granted = mIPackageManager.checkPermission(permission, packageName, - caller.getUserId()); - } else { - try { - int uid = mInjector.getPackageManager().getPackageUidAsUser(packageName, - caller.getUserId()); - if (PermissionChecker.checkPermissionForPreflight(mContext, permission, - PermissionChecker.PID_UNKNOWN, uid, packageName) - != PermissionChecker.PERMISSION_GRANTED) { - granted = PackageManager.PERMISSION_DENIED; - } else { - granted = PackageManager.PERMISSION_GRANTED; - } - } catch (NameNotFoundException e) { - // Package does not exit - return DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT; - } - } - int permFlags = mInjector.getPackageManager().getPermissionFlags( - permission, packageName, caller.getUserHandle()); - if ((permFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) - != PackageManager.FLAG_PERMISSION_POLICY_FIXED) { - // Not controlled by policy - return DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT; + return getPermissionGrantStateForUser( + packageName, permission, caller, caller.getUserId()); + }); + } + } + + private int getPermissionGrantStateForUser( + String packageName, String permission, CallerIdentity caller, int userId) + throws RemoteException { + int granted; + if (getTargetSdk(caller.getPackageName(), caller.getUserId()) + < android.os.Build.VERSION_CODES.Q) { + // The per-Q behavior was to not check the app-ops state. + granted = mIPackageManager.checkPermission(permission, packageName, userId); + } else { + try { + int uid = mInjector.getPackageManager().getPackageUidAsUser( + packageName, userId); + if (PermissionChecker.checkPermissionForPreflight(mContext, permission, + PermissionChecker.PID_UNKNOWN, uid, packageName) + != PermissionChecker.PERMISSION_GRANTED) { + granted = PackageManager.PERMISSION_DENIED; } else { - // Policy controlled so return result based on permission grant state - return granted == PackageManager.PERMISSION_GRANTED - ? DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED - : DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED; + granted = PackageManager.PERMISSION_GRANTED; } - }); + } catch (NameNotFoundException e) { + // Package does not exit + return DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT; + } + } + int permFlags = mInjector.getPackageManager().getPermissionFlags( + permission, packageName, UserHandle.of(userId)); + if ((permFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) + != PackageManager.FLAG_PERMISSION_POLICY_FIXED) { + // Not controlled by policy + return DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT; + } else { + // Policy controlled so return result based on permission grant state + return granted == PackageManager.PERMISSION_GRANTED + ? DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED + : DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED; } } @@ -15436,16 +15579,27 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public void setOrganizationName(@NonNull ComponentName who, CharSequence text) { + public void setOrganizationName(@Nullable ComponentName who, CharSequence text) { if (!mHasFeature) { return; } - Objects.requireNonNull(who, "ComponentName is null"); - final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + CallerIdentity caller = getCallerIdentity(who); + ActiveAdmin admin = null; + + if (isPermissionCheckFlagEnabled()) { + EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( + who, + MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, + caller.getUserId()); + admin = enforcingAdmin.getActiveAdmin(); + } else { + Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + } synchronized (getLockObject()) { - ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); + if (!isPermissionCheckFlagEnabled()) { + admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); + } if (!TextUtils.equals(admin.organizationName, text)) { admin.organizationName = (text == null || text.length() == 0) ? null : text.toString(); @@ -15455,20 +15609,29 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public CharSequence getOrganizationName(@NonNull ComponentName who) { + public CharSequence getOrganizationName(@Nullable ComponentName who) { if (!mHasFeature) { return null; } - Objects.requireNonNull(who, "ComponentName is null"); + CallerIdentity caller = getCallerIdentity(who); + ActiveAdmin admin = null; - final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallingUser(isManagedProfile(caller.getUserId())); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + if (isPermissionCheckFlagEnabled()) { + EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin( + who, + MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, + caller.getUserId()); + admin = enforcingAdmin.getActiveAdmin(); + } else { + Preconditions.checkCallingUser(isManagedProfile(caller.getUserId())); + Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); - synchronized (getLockObject()) { - ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); - return admin.organizationName; + synchronized (getLockObject()) { + admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); + } } + + return admin.organizationName; } /** @@ -16154,6 +16317,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // The removed admin might have disabled camera, so update user // restrictions. pushUserRestrictions(userHandle); + + // The removed admin might've been stopping the migration if it was targeting pre Android U + if (shouldMigrateToDevicePolicyEngine()) { + migratePoliciesToDevicePolicyEngine(); + } } @Override @@ -18047,7 +18215,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { checkCanExecuteOrThrowUnsafe( DevicePolicyManager.OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES); - if (isCoexistenceEnabled(caller)) { + if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) { Binder.withCleanCallingIdentity(() -> { if (packages.isEmpty()) { removeUserControlDisabledPackages(caller); @@ -18081,7 +18249,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // merged. EnforcingAdmin.createEnterpriseEnforcingAdmin( caller.getComponentName(), caller.getUserId()), - packages); + new StringSetPolicyValue(packages)); } else { mDevicePolicyEngine.setLocalPolicy( PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES, @@ -18089,7 +18257,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // merged. EnforcingAdmin.createEnterpriseEnforcingAdmin( caller.getComponentName(), caller.getUserId()), - packages, + new StringSetPolicyValue(packages), caller.getUserId()); } } @@ -18127,7 +18295,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) || isProfileOwner(caller) || isFinancedDeviceOwner(caller)); - if (isCoexistenceEnabled(caller)) { + if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) { // This retrieves the policy for the calling user only, DOs for example can't know // what's enforced globally or on another user. Set<String> packages = mDevicePolicyEngine.getResolvedPolicy( @@ -20092,22 +20260,26 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_ACROSS_USERS, MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL, SET_TIME, - SET_TIME_ZONE); + SET_TIME_ZONE, + MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY); private static final List<String> FINANCED_DEVICE_OWNER_PERMISSIONS = List.of( MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL, MANAGE_DEVICE_POLICY_ACROSS_USERS, - MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL); + MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL, + MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY); private static final List<String> PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS = List.of( MANAGE_DEVICE_POLICY_ACROSS_USERS, MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL, SET_TIME, - SET_TIME_ZONE); + SET_TIME_ZONE, + MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY); private static final List<String> PROFILE_OWNER_ON_USER_0_PERMISSIONS = List.of( SET_TIME, SET_TIME_ZONE); private static final List<String> PROFILE_OWNER_PERMISSIONS = List.of( - MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL); + MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL, + MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY); private static final HashMap<Integer, List<String>> DPC_PERMISSIONS = new HashMap<>(); { @@ -20125,9 +20297,53 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final HashMap<String, String> CROSS_USER_PERMISSIONS = new HashMap<>(); { - // Auto time is intrinsically global so there is no cross-user permission. + // Time and Timezone is intrinsically global so there is no cross-user permission. CROSS_USER_PERMISSIONS.put(SET_TIME, null); CROSS_USER_PERMISSIONS.put(SET_TIME_ZONE, null); + // Organisation identity policy will involve data of other organisations on the device and + // therefore the FULL cross-user permission is required. + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); + } + + /** + * Checks if the calling process has been granted permission to apply a device policy on a + * specific user. + * The given permission will be checked along with its associated cross-user permission if it + * exists and the target user is different to the calling user. + * Returns the {@link ActiveAdmin} of the caller. + * + * @param permission The name of the permission being checked. + * @param targetUserId The userId of the user which the caller needs permission to act on. + * @throws SecurityException if the caller has not been granted the given permission, + * the associated cross-user permission if the caller's user is different to the target user. + */ + private EnforcingAdmin enforcePermissionAndGetEnforcingAdmin(@Nullable ComponentName admin, + String permission, int targetUserId) { + enforcePermission(permission, targetUserId); + return getEnforcingAdminForCaller(admin, getCallerIdentity()); + } + + /** + * Checks whether the calling process has been granted permission to query a device policy on + * a specific user. + * The given permission will be checked along with its associated cross-user permission if it + * exists and the target user is different to the calling user. + * + * @param permission The name of the permission being checked. + * @param targetUserId The userId of the user which the caller needs permission to act on. + * @throws SecurityException if the caller has not been granted the given permission, + * the associated cross-user permission if the caller's user is different to the target user. + */ + private EnforcingAdmin enforceCanQueryAndGetEnforcingAdmin(@Nullable ComponentName admin, + String permission, int targetUserId) { + enforceCanQuery(permission, targetUserId); + return getEnforcingAdminForCaller(admin, getCallerIdentity()); + } + + private static final HashMap<String, String> POLICY_IDENTIFIER_TO_PERMISSION = new HashMap<>(); + { + POLICY_IDENTIFIER_TO_PERMISSION.put(AUTO_TIMEZONE_POLICY, SET_TIME_ZONE); } /** @@ -20139,7 +20355,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * @param permission The name of the permission being checked. * @param targetUserId The userId of the user which the caller needs permission to act on. * @throws SecurityException if the caller has not been granted the given permission, - * the associtated cross-user permission if the caller's user is different to the target user. + * the associated cross-user permission if the caller's user is different to the target user. */ private void enforcePermission(String permission, int targetUserId) throws SecurityException { @@ -20154,13 +20370,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } /** - * Return whether the calling process has been granted permission to query a device policy on + * Checks whether the calling process has been granted permission to query a device policy on * a specific user. + * The given permission will be checked along with its associated cross-user permission if it + * exists and the target user is different to the calling user. * * @param permission The name of the permission being checked. * @param targetUserId The userId of the user which the caller needs permission to act on. * @throws SecurityException if the caller has not been granted the given permission, - * the associatated cross-user permission if the caller's user is different to the target user + * the associated cross-user permission if the caller's user is different to the target user * and if the user has not been granted {@link QUERY_ADMIN_POLICY}. */ private void enforceCanQuery(String permission, int targetUserId) throws SecurityException { @@ -20225,10 +20443,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } // Check if the caller is an active admin that uses a certain policy. if (ACTIVE_ADMIN_POLICIES.containsKey(permission)) { - return getActiveAdminForCallerLocked( - null, ACTIVE_ADMIN_POLICIES.get(permission), false) != null; + try { + return getActiveAdminForCallerLocked( + null, ACTIVE_ADMIN_POLICIES.get(permission), false) != null; + } catch (SecurityException e) { + // A security exception means there is not an active admin with permission and + // therefore + return false; + } } - return false; } @@ -20258,6 +20481,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } + private EnforcingAdmin getEnforcingAdminForCaller(@Nullable ComponentName who, + CallerIdentity caller) { + int userId = caller.getUserId(); + ActiveAdmin admin = null; + synchronized (getLockObject()) { + admin = getActiveAdminUncheckedLocked(who, userId); + } + if (isDeviceOwner(caller) || isProfileOwner(caller)) { + return EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userId, admin); + } + if (getActiveAdminUncheckedLocked(who, userId) != null) { + return EnforcingAdmin.createDeviceAdminEnforcingAdmin(who, userId, admin); + } + return EnforcingAdmin.createEnforcingAdmin(caller.getPackageName(), userId); + } + private boolean isPermissionCheckFlagEnabled() { return DeviceConfig.getBoolean( NAMESPACE_DEVICE_POLICY_MANAGER, @@ -20265,20 +20504,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG); } - // TODO(b/260560985): properly gate coexistence changes - private boolean isCoexistenceEnabled(CallerIdentity caller) { - return isCoexistenceFlagEnabled() - && mInjector.isChangeEnabled( - ENABLE_COEXISTENCE_CHANGE, caller.getPackageName(), caller.getUserId()); - } - - private boolean isCoexistenceFlagEnabled() { - return DeviceConfig.getBoolean( - NAMESPACE_DEVICE_POLICY_MANAGER, - ENABLE_COEXISTENCE_FLAG, - DEFAULT_ENABLE_COEXISTENCE_FLAG); - } - private static boolean isKeepProfilesRunningFlagEnabled() { return DeviceConfig.getBoolean( NAMESPACE_DEVICE_POLICY_MANAGER, @@ -20522,4 +20747,363 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } + + @Override + public DevicePolicyState getDevicePolicyState() { + Preconditions.checkCallAuthorization( + hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + return mInjector.binderWithCleanCallingIdentity(mDevicePolicyEngine::getDevicePolicyState); + } + + @Override + public boolean triggerDevicePolicyEngineMigration(boolean forceMigration) { + Preconditions.checkCallAuthorization( + hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + return mInjector.binderWithCleanCallingIdentity(() -> { + boolean canForceMigration = forceMigration && !hasNonTestOnlyActiveAdmins(); + if (!canForceMigration && !shouldMigrateToDevicePolicyEngine()) { + return false; + } + return migratePoliciesToDevicePolicyEngine(); + }); + } + + private boolean hasNonTestOnlyActiveAdmins() { + return mInjector.binderWithCleanCallingIdentity(() -> { + for (UserInfo userInfo : mUserManager.getUsers()) { + List<ComponentName> activeAdmins = getActiveAdmins(userInfo.id); + if (activeAdmins == null) { + continue; + } + for (ComponentName admin : activeAdmins) { + if (!isAdminTestOnlyLocked(admin, userInfo.id)) { + return true; + } + } + } + return false; + }); + } + + // TODO(b/266808047): handle DeviceAdmin migration when there is no DPCs on the device + private boolean shouldMigrateToDevicePolicyEngine() { + return mInjector.binderWithCleanCallingIdentity(() -> { + if (!isDevicePolicyEngineFlagEnabled()) { + return false; + } + if (mOwners.isMigratedToPolicyEngine()) { + return false; + } + // We're only checking if existing DPCs are not targeting U, regardless of what + // DeviceAdmins are targeting, as they can access very limited APIs, and we'll ensure + // that these APIs maintain the current behaviour of strictest applies. + boolean hasDPCs = false; + for (UserInfo userInfo : mUserManager.getUsers()) { + List<ComponentName> activeAdmins = getActiveAdmins(userInfo.id); + if (activeAdmins == null) { + continue; + } + for (ComponentName admin : activeAdmins) { + if ((isProfileOwner(admin, userInfo.id) || isDeviceOwner(admin, userInfo.id))) { + if (!mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE, + admin.getPackageName(), userInfo.id)) { + return false; + } + hasDPCs = true; + } + } + } + return hasDPCs; + }); + } + + /** + * @return {@code true} if policies were migrated successfully, {@code false} otherwise. + */ + private boolean migratePoliciesToDevicePolicyEngine() { + return mInjector.binderWithCleanCallingIdentity(() -> { + try { + Slogf.i(LOG_TAG, "Started device policies migration to the device policy engine."); + migrateAutoTimezonePolicy(); + migratePermissionGrantStatePolicies(); + // TODO(b/258811766): add migration logic for all policies + + mOwners.markMigrationToPolicyEngine(); + return true; + } catch (Exception e) { + mDevicePolicyEngine.clearAllPolicies(); + Slogf.e(LOG_TAG, e, "Error occurred during device policy migration, will " + + "reattempt on the next system server restart."); + return false; + } + }); + } + + private void migrateAutoTimezonePolicy() { + Slogf.i(LOG_TAG, "Skipping Migration of AUTO_TIMEZONE policy to device policy engine," + + "as no way to identify if the value was set by the admin or the user."); + } + + private void migratePermissionGrantStatePolicies() { + Slogf.i(LOG_TAG, "Migrating PERMISSION_GRANT policy to device policy engine."); + for (UserInfo userInfo : mUserManager.getUsers()) { + ActiveAdmin admin = getMostProbableDPCAdminForLocalPolicy(userInfo.id); + if (admin == null) { + Slogf.i(LOG_TAG, "No admin found that can set permission grant state on user " + + userInfo.id); + continue; + } + for (PackageInfo packageInfo : getInstalledPackagesOnUser(userInfo.id)) { + if (packageInfo.requestedPermissions == null) { + continue; + } + for (String permission : packageInfo.requestedPermissions) { + if (!isRuntimePermission(permission)) { + continue; + } + int grantState = DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT; + try { + grantState = getPermissionGrantStateForUser( + packageInfo.packageName, permission, + new CallerIdentity( + mInjector.binderGetCallingUid(), + admin.info.getComponent().getPackageName(), + admin.info.getComponent()), + userInfo.id); + } catch (RemoteException e) { + Slogf.e(LOG_TAG, e, "Error retrieving permission grant state for %s " + + "and %s", packageInfo.packageName, permission); + } + if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) { + // Not Controlled by a policy + continue; + } + + mDevicePolicyEngine.setLocalPolicy( + PolicyDefinition.PERMISSION_GRANT(packageInfo.packageName, + permission), + EnforcingAdmin.createEnterpriseEnforcingAdmin( + admin.info.getComponent(), + admin.getUserHandle().getIdentifier()), + new IntegerPolicyValue(grantState), + userInfo.id, + /* skipEnforcePolicy= */ true); + } + } + } + } + + private List<PackageInfo> getInstalledPackagesOnUser(int userId) { + return mInjector.binderWithCleanCallingIdentity(() -> + mContext.getPackageManager().getInstalledPackagesAsUser( + PackageManager.PackageInfoFlags.of( + PackageManager.GET_PERMISSIONS), userId)); + } + + /** + * Returns the most probable admin to have set a global policy according to the following + * heuristics: + * + * <ul> + * <li>The device owner on any user</li> + * <li>The org owned profile owner on any user</li> + * <li>The profile owner on any user</li> + * </ul> + */ + @Nullable + // TODO(b/266928216): Check what the admin capabilities are when deciding which admin to return. + private ActiveAdmin getMostProbableDPCAdminForGlobalPolicy() { + synchronized (getLockObject()) { + ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); + if (deviceOwner != null) { + return deviceOwner; + } + + List<UserInfo> users = mUserManager.getUsers(); + for (UserInfo userInfo : users) { + if (isProfileOwnerOfOrganizationOwnedDevice(userInfo.id)) { + return getProfileOwnerAdminLocked(userInfo.id); + } + } + + for (UserInfo userInfo : users) { + ActiveAdmin profileOwner = getProfileOwnerLocked(userInfo.id); + if (profileOwner != null) { + return profileOwner; + } + } + return null; + } + } + + /** + * Returns the most probable admin to have set a policy on the given {@code userId} according + * to the following heuristics: + * + * <ul> + * <li>The device owner on the given userId</li> + * <li>The profile owner on the given userId</li> + * <li>The org owned profile owner of which the given userId is its parent</li> + * <li>The profile owner of which the given userId is its parent</li> + * <li>The device owner on any user</li> + * <li>The profile owner on any user</li> + * </ul> + */ + @Nullable + // TODO(b/266928216): Check what the admin capabilities are when deciding which admin to return. + private ActiveAdmin getMostProbableDPCAdminForLocalPolicy(int userId) { + synchronized (getLockObject()) { + ActiveAdmin localDeviceOwner = getDeviceOwnerLocked(userId); + if (localDeviceOwner != null) { + return localDeviceOwner; + } + + ActiveAdmin localProfileOwner = getProfileOwnerLocked(userId); + if (localProfileOwner != null) { + return localProfileOwner; + } + + int[] profileIds = mUserManager.getProfileIds(userId, /* enabledOnly= */ false); + for (int id : profileIds) { + if (id == userId) { + continue; + } + if (isProfileOwnerOfOrganizationOwnedDevice(id)) { + return getProfileOwnerAdminLocked(id); + } + } + + for (int id : profileIds) { + if (id == userId) { + continue; + } + if (isManagedProfile(id)) { + return getProfileOwnerAdminLocked(id); + } + } + + ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); + if (deviceOwner != null) { + return deviceOwner; + } + + for (UserInfo userInfo : mUserManager.getUsers()) { + ActiveAdmin profileOwner = getProfileOwnerLocked(userInfo.id); + if (profileOwner != null) { + return profileOwner; + } + } + return null; + } + } + + // We need to add a mapping of policyId to permission in POLICY_IDENTIFIER_TO_PERMISSION + // for each migrated permission. + private List<ActiveAdmin> getNonDPCActiveAdminsForPolicyLocked(String policyIdentifier) { + String permission = POLICY_IDENTIFIER_TO_PERMISSION.get(policyIdentifier); + if (permission == null) { + Slogf.e(LOG_TAG, "Can't find a permission for %s in POLICY_IDENTIFIER_TO_PERMISSION", + policyIdentifier); + return new ArrayList<>(); + } + if (!ACTIVE_ADMIN_POLICIES.containsKey(permission)) { + return new ArrayList<>(); + } + + List<ActiveAdmin> admins = new ArrayList<>(); + for (UserInfo userInfo : mUserManager.getUsers()) { + List<ComponentName> activeAdmins = getActiveAdmins(userInfo.id); + for (ComponentName admin : activeAdmins) { + if (isDeviceOwner(admin, userInfo.id) || isProfileOwner(admin, userInfo.id)) { + continue; + } + DevicePolicyData policy = getUserDataUnchecked(userInfo.id); + if (isActiveAdminWithPolicyForUserLocked( + policy.mAdminMap.get(admin), ACTIVE_ADMIN_POLICIES.get(permission), + userInfo.id)) { + admins.add(policy.mAdminMap.get(admin)); + } + } + } + return admins; + } + + // TODO: This can actually accept an EnforcingAdmin that gets created in the permission check + // method. + private boolean useDevicePolicyEngine(CallerIdentity caller, @Nullable String delegateScope) { + if (!isCallerActiveAdminOrDelegate(caller, delegateScope)) { + if (!isDevicePolicyEngineFlagEnabled()) { + throw new IllegalStateException("Non DPC caller can't set device policies."); + } + if (hasDPCsNotSupportingCoexistence()) { + throw new IllegalStateException("Non DPC caller can't set device policies with " + + "existing legacy admins on the device."); + } + return true; + } else { + return isDevicePolicyEngineFlagEnabled() && !hasDPCsNotSupportingCoexistence(); + } + } + + private boolean isDevicePolicyEngineFlagEnabled() { + return DeviceConfig.getBoolean( + NAMESPACE_DEVICE_POLICY_MANAGER, + ENABLE_DEVICE_POLICY_ENGINE_FLAG, + DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FLAG); + } + + private boolean hasDPCsNotSupportingCoexistence() { + return mInjector.binderWithCleanCallingIdentity(() -> { + for (UserInfo userInfo : mUserManager.getUsers()) { + List<ComponentName> activeAdmins = getActiveAdmins(userInfo.id); + if (activeAdmins == null) { + continue; + } + for (ComponentName admin : activeAdmins) { + if ((isProfileOwner(admin, userInfo.id) || isDeviceOwner(admin, userInfo.id)) + && !mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE, + admin.getPackageName(), userInfo.id)) { + return true; + } + } + } + return false; + }); + } + + // TODO: this can actually accept an EnforcingAdmin that gets created in the permission + // check method. + private boolean isCallerActiveAdminOrDelegate( + CallerIdentity caller, @Nullable String delegateScope) { + return mInjector.binderWithCleanCallingIdentity(() -> { + List<ComponentName> activeAdmins = getActiveAdmins(caller.getUserId()); + if (activeAdmins != null) { + for (ComponentName admin : activeAdmins) { + if (admin.getPackageName().equals(caller.getPackageName())) { + return true; + } + } + } + return delegateScope != null && isCallerDelegate(caller, delegateScope); + }); + } + + // TODO(b/266808047): This will return false for DeviceAdmins not targetting U, which is + // inconsistent with the migration logic that allows migration with old DeviceAdmins. + private boolean canAddActiveAdminIfPolicyEngineEnabled(String packageName, int userId) { + if (!isDevicePolicyEngineFlagEnabled()) { + return true; + } + if (hasDPCsNotSupportingCoexistence()) { + return true; + } + if (mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE, packageName, userId)) { + // This will always return true unless we turn off coexistence, in which case it will + // return true if no current admins exist, or more than one admin exist + return mDevicePolicyEngine.canAdminAddPolicies(packageName, userId); + } + // Is it ok to just check that no active policies exist currently, or should we return false + // if the policy engine was ever used? + return !mDevicePolicyEngine.hasActivePolicies(); + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java index da895f46f1aa..10e972d3698a 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java @@ -18,7 +18,13 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.admin.Authority; +import android.app.admin.UnknownAuthority; +import android.app.admin.DeviceAdminAuthority; +import android.app.admin.DpcAuthority; +import android.app.admin.RoleAuthority; import android.content.ComponentName; +import android.os.UserHandle; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -63,6 +69,7 @@ final class EnforcingAdmin { private Set<String> mAuthorities; private final int mUserId; private final boolean mIsRoleAuthority; + private final ActiveAdmin mActiveAdmin; static EnforcingAdmin createEnforcingAdmin(@NonNull String packageName, int userId) { Objects.requireNonNull(packageName); @@ -73,14 +80,31 @@ final class EnforcingAdmin { @NonNull ComponentName componentName, int userId) { Objects.requireNonNull(componentName); return new EnforcingAdmin( - componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY), userId); + componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY), userId, + /* activeAdmin=*/ null); + } + + static EnforcingAdmin createEnterpriseEnforcingAdmin( + @NonNull ComponentName componentName, int userId, ActiveAdmin activeAdmin) { + Objects.requireNonNull(componentName); + return new EnforcingAdmin( + componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY), userId, + activeAdmin); } static EnforcingAdmin createDeviceAdminEnforcingAdmin(ComponentName componentName, int userId) { Objects.requireNonNull(componentName); return new EnforcingAdmin( componentName.getPackageName(), componentName, Set.of(DEVICE_ADMIN_AUTHORITY), - userId); + userId, /* activeAdmin=*/ null); + } + + static EnforcingAdmin createDeviceAdminEnforcingAdmin(ComponentName componentName, int userId, + ActiveAdmin activeAdmin) { + Objects.requireNonNull(componentName); + return new EnforcingAdmin( + componentName.getPackageName(), componentName, Set.of(DEVICE_ADMIN_AUTHORITY), + userId, activeAdmin); } static String getRoleAuthorityOf(String roleName) { @@ -88,7 +112,8 @@ final class EnforcingAdmin { } private EnforcingAdmin( - String packageName, ComponentName componentName, Set<String> authorities, int userId) { + String packageName, ComponentName componentName, Set<String> authorities, int userId, + ActiveAdmin activeAdmin) { Objects.requireNonNull(packageName); Objects.requireNonNull(componentName); Objects.requireNonNull(authorities); @@ -99,6 +124,7 @@ final class EnforcingAdmin { mComponentName = componentName; mAuthorities = new HashSet<>(authorities); mUserId = userId; + mActiveAdmin = activeAdmin; } private EnforcingAdmin(String packageName, int userId) { @@ -111,6 +137,7 @@ final class EnforcingAdmin { mComponentName = null; // authorities will be loaded when needed mAuthorities = null; + mActiveAdmin = null; } private static Set<String> getRoleAuthoritiesOrDefault(String packageName, int userId) { @@ -156,6 +183,34 @@ final class EnforcingAdmin { return mUserId; } + @Nullable + public ActiveAdmin getActiveAdmin() { + return mActiveAdmin; + } + + @NonNull + android.app.admin.EnforcingAdmin getParcelableAdmin() { + Authority authority; + if (mIsRoleAuthority) { + Set<String> roles = getRoles(mPackageName, mUserId); + if (roles.isEmpty()) { + authority = UnknownAuthority.UNKNOWN_AUTHORITY; + } else { + authority = new RoleAuthority(roles); + } + } else if (mAuthorities.contains(DPC_AUTHORITY)) { + authority = DpcAuthority.DPC_AUTHORITY; + } else if (mAuthorities.contains(DEVICE_ADMIN_AUTHORITY)) { + authority = DeviceAdminAuthority.DEVICE_ADMIN_AUTHORITY; + } else { + authority = UnknownAuthority.UNKNOWN_AUTHORITY; + } + return new android.app.admin.EnforcingAdmin( + mPackageName, + authority, + UserHandle.of(mUserId)); + } + /** * For two EnforcingAdmins to be equal they must: * @@ -224,7 +279,7 @@ final class EnforcingAdmin { String className = parser.getAttributeValue(/* namespace= */ null, ATTR_CLASS_NAME); ComponentName componentName = new ComponentName(packageName, className); Set<String> authorities = Set.of(authoritiesStr.split(ATTR_AUTHORITIES_SEPARATOR)); - return new EnforcingAdmin(packageName, componentName, authorities, userId); + return new EnforcingAdmin(packageName, componentName, authorities, userId, null); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerUnion.java b/services/devicepolicy/java/com/android/server/devicepolicy/FlagUnion.java index a051a2bebae6..6c4be2164edf 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerUnion.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/FlagUnion.java @@ -17,24 +17,32 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; +import android.app.admin.IntegerPolicyValue; +import android.app.admin.PolicyValue; import java.util.LinkedHashMap; import java.util.Objects; -final class IntegerUnion extends ResolutionMechanism<Integer> { +final class FlagUnion extends ResolutionMechanism<Integer> { @Override - Integer resolve(@NonNull LinkedHashMap<EnforcingAdmin, Integer> adminPolicies) { + IntegerPolicyValue resolve( + @NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<Integer>> adminPolicies) { Objects.requireNonNull(adminPolicies); if (adminPolicies.isEmpty()) { return null; } Integer unionOfPolicies = 0; - for (Integer policy : adminPolicies.values()) { - unionOfPolicies |= policy; + for (PolicyValue<Integer> policy : adminPolicies.values()) { + unionOfPolicies |= policy.getValue(); } - return unionOfPolicies; + return new IntegerPolicyValue(unionOfPolicies); + } + + @Override + android.app.admin.FlagUnion getParcelableResolutionMechanism() { + return android.app.admin.FlagUnion.FLAG_UNION; } @Override diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java index d5949dda8b30..bff6d3288dca 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java @@ -18,6 +18,8 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.admin.IntegerPolicyValue; +import android.app.admin.PolicyKey; import android.util.Log; import com.android.modules.utils.TypedXmlPullParser; @@ -31,17 +33,18 @@ import java.util.Objects; final class IntegerPolicySerializer extends PolicySerializer<Integer> { @Override - void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull Integer value) - throws IOException { + void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeName, + @NonNull Integer value) throws IOException { Objects.requireNonNull(value); serializer.attributeInt(/* namespace= */ null, attributeName, value); } @Nullable @Override - Integer readFromXml(TypedXmlPullParser parser, String attributeName) { + IntegerPolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) { try { - return parser.getAttributeInt(/* namespace= */ null, attributeName); + return new IntegerPolicyValue( + parser.getAttributeInt(/* namespace= */ null, attributeName)); } catch (XmlPullParserException e) { Log.e(DevicePolicyEngine.TAG, "Error parsing Integer policy value", e); return null; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java deleted file mode 100644 index d3e8de488e0b..000000000000 --- a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2022 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.android.server.devicepolicy; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.admin.DevicePolicyManager; -import android.util.Log; - -import com.android.modules.utils.TypedXmlPullParser; -import com.android.modules.utils.TypedXmlSerializer; - -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -final class LockTaskPolicy { - static final int DEFAULT_LOCK_TASK_FLAG = DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS; - private Set<String> mPackages = new HashSet<>(); - private int mFlags = DEFAULT_LOCK_TASK_FLAG; - - @NonNull - Set<String> getPackages() { - return mPackages; - } - - int getFlags() { - return mFlags; - } - - LockTaskPolicy(Set<String> packages) { - Objects.requireNonNull(packages); - mPackages.addAll(packages); - } - - private LockTaskPolicy(Set<String> packages, int flags) { - Objects.requireNonNull(packages); - mPackages = new HashSet<>(packages); - mFlags = flags; - } - - void setPackages(@NonNull Set<String> packages) { - Objects.requireNonNull(packages); - mPackages = new HashSet<>(packages); - } - - void setFlags(int flags) { - mFlags = flags; - } - - @Override - public LockTaskPolicy clone() { - LockTaskPolicy policy = new LockTaskPolicy(mPackages); - policy.setFlags(mFlags); - return policy; - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - LockTaskPolicy other = (LockTaskPolicy) o; - return Objects.equals(mPackages, other.mPackages) - && mFlags == other.mFlags; - } - - @Override - public int hashCode() { - return Objects.hash(mPackages, mFlags); - } - - @Override - public String toString() { - return "mPackages= " + String.join(", ", mPackages) + "; mFlags= " + mFlags; - } - - static final class LockTaskPolicySerializer extends PolicySerializer<LockTaskPolicy> { - - private static final String ATTR_PACKAGES = ":packages"; - private static final String ATTR_PACKAGES_SEPARATOR = ";"; - private static final String ATTR_FLAGS = ":flags"; - - @Override - void saveToXml(TypedXmlSerializer serializer, String attributeNamePrefix, - @NonNull LockTaskPolicy value) throws IOException { - Objects.requireNonNull(value); - if (value.mPackages == null || value.mPackages.isEmpty()) { - throw new IllegalArgumentException("Error saving LockTaskPolicy to file, lock task " - + "packages must be present"); - } - serializer.attribute( - /* namespace= */ null, - attributeNamePrefix + ATTR_PACKAGES, - String.join(ATTR_PACKAGES_SEPARATOR, value.mPackages)); - serializer.attributeInt( - /* namespace= */ null, - attributeNamePrefix + ATTR_FLAGS, - value.mFlags); - } - - @Override - LockTaskPolicy readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) { - String packagesStr = parser.getAttributeValue( - /* namespace= */ null, - attributeNamePrefix + ATTR_PACKAGES); - if (packagesStr == null) { - Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value."); - return null; - } - Set<String> packages = Set.of(packagesStr.split(ATTR_PACKAGES_SEPARATOR)); - try { - int flags = parser.getAttributeInt( - /* namespace= */ null, - attributeNamePrefix + ATTR_FLAGS); - return new LockTaskPolicy(packages, flags); - } catch (XmlPullParserException e) { - Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value", e); - return null; - } - } - } -} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java new file mode 100644 index 000000000000..3265b61a3543 --- /dev/null +++ b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 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.android.server.devicepolicy; + +import android.annotation.NonNull; +import android.app.admin.LockTaskPolicy; +import android.app.admin.PolicyKey; +import android.util.Log; + +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.Objects; +import java.util.Set; + +final class LockTaskPolicySerializer extends PolicySerializer<LockTaskPolicy> { + + private static final String ATTR_PACKAGES = ":packages"; + private static final String ATTR_PACKAGES_SEPARATOR = ";"; + private static final String ATTR_FLAGS = ":flags"; + + @Override + void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeNamePrefix, + @NonNull LockTaskPolicy value) throws IOException { + Objects.requireNonNull(value); + if (value.getPackages() == null || value.getPackages().isEmpty()) { + throw new IllegalArgumentException("Error saving LockTaskPolicy to file, lock task " + + "packages must be present"); + } + serializer.attribute( + /* namespace= */ null, + attributeNamePrefix + ATTR_PACKAGES, + String.join(ATTR_PACKAGES_SEPARATOR, value.getPackages())); + serializer.attributeInt( + /* namespace= */ null, + attributeNamePrefix + ATTR_FLAGS, + value.getFlags()); + } + + @Override + LockTaskPolicy readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) { + String packagesStr = parser.getAttributeValue( + /* namespace= */ null, + attributeNamePrefix + ATTR_PACKAGES); + if (packagesStr == null) { + Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value."); + return null; + } + Set<String> packages = Set.of(packagesStr.split(ATTR_PACKAGES_SEPARATOR)); + try { + int flags = parser.getAttributeInt( + /* namespace= */ null, + attributeNamePrefix + ATTR_FLAGS); + return new LockTaskPolicy(packages, flags); + } catch (XmlPullParserException e) { + Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value", e); + return null; + } + } +} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SetPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/MostRecent.java index 736627b610b0..423a497252b7 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/SetPolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/MostRecent.java @@ -16,28 +16,31 @@ package com.android.server.devicepolicy; + import android.annotation.NonNull; -import android.annotation.Nullable; +import android.app.admin.PolicyValue; -import com.android.modules.utils.TypedXmlPullParser; -import com.android.modules.utils.TypedXmlSerializer; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; -import java.io.IOException; -import java.util.Objects; -import java.util.Set; +final class MostRecent<V> extends ResolutionMechanism<V> { -// TODO(scottjonathan): Replace with actual implementation -final class SetPolicySerializer<V> extends PolicySerializer<Set<V>> { + @Override + PolicyValue<V> resolve(@NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<V>> adminPolicies) { + List<Map.Entry<EnforcingAdmin, PolicyValue<V>>> policiesList = new ArrayList<>( + adminPolicies.entrySet()); + return policiesList.isEmpty() ? null : policiesList.get(policiesList.size() - 1).getValue(); + } @Override - void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull Set<V> value) - throws IOException { - Objects.requireNonNull(value); + android.app.admin.MostRecent<V> getParcelableResolutionMechanism() { + return new android.app.admin.MostRecent<V>(); } - @Nullable @Override - Set<V> readFromXml(TypedXmlPullParser parser, String attributeName) { - return null; + public String toString() { + return "MostRecent {}"; } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/MostRestrictive.java b/services/devicepolicy/java/com/android/server/devicepolicy/MostRestrictive.java index edb3d2ef4856..7e8eaa76431b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/MostRestrictive.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/MostRestrictive.java @@ -17,6 +17,7 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; +import android.app.admin.PolicyValue; import java.util.LinkedHashMap; import java.util.List; @@ -24,28 +25,34 @@ import java.util.Map; final class MostRestrictive<V> extends ResolutionMechanism<V> { - private List<V> mMostToLeastRestrictive; + private List<PolicyValue<V>> mMostToLeastRestrictive; - MostRestrictive(@NonNull List<V> mostToLeastRestrictive) { + MostRestrictive(@NonNull List<PolicyValue<V>> mostToLeastRestrictive) { mMostToLeastRestrictive = mostToLeastRestrictive; } @Override - V resolve(@NonNull LinkedHashMap<EnforcingAdmin, V> adminPolicies) { + PolicyValue<V> resolve(@NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<V>> adminPolicies) { if (adminPolicies.isEmpty()) { return null; } - for (V value : mMostToLeastRestrictive) { + for (PolicyValue<V> value : mMostToLeastRestrictive) { if (adminPolicies.containsValue(value)) { return value; } } // Return first set policy if none can be found in known values - Map.Entry<EnforcingAdmin, V> policy = adminPolicies.entrySet().stream().findFirst().get(); + Map.Entry<EnforcingAdmin, PolicyValue<V>> policy = + adminPolicies.entrySet().stream().findFirst().get(); return policy.getValue(); } @Override + android.app.admin.MostRestrictive<V> getParcelableResolutionMechanism() { + return new android.app.admin.MostRestrictive<V>(mMostToLeastRestrictive); + } + + @Override public String toString() { return "MostRestrictive { mMostToLeastRestrictive= " + mMostToLeastRestrictive + " }"; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index 581a19913530..3ca158dc9c96 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -579,6 +579,19 @@ class Owners { } } + void markMigrationToPolicyEngine() { + synchronized (mData) { + mData.mMigratedToPolicyEngine = true; + mData.writeDeviceOwner(); + } + } + + boolean isMigratedToPolicyEngine() { + synchronized (mData) { + return mData.mMigratedToPolicyEngine; + } + } + @GuardedBy("mData") void pushToAppOpsLocked() { if (!mSystemReady) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java index 3040af23155e..63b250d4acfc 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java @@ -66,6 +66,7 @@ class OwnersData { private static final String TAG_DEVICE_OWNER_TYPE = "device-owner-type"; private static final String TAG_DEVICE_OWNER_PROTECTED_PACKAGES = "device-owner-protected-packages"; + private static final String TAG_POLICY_ENGINE_MIGRATION = "policy-engine-migration"; private static final String ATTR_NAME = "name"; private static final String ATTR_PACKAGE = "package"; @@ -84,6 +85,8 @@ class OwnersData { "isPoOrganizationOwnedDevice"; private static final String ATTR_DEVICE_OWNER_TYPE_VALUE = "value"; + private static final String ATTR_MIGRATED_TO_POLICY_ENGINE = "migratedToPolicyEngine"; + // Internal state for the device owner package. OwnerInfo mDeviceOwner; int mDeviceOwnerUserId = UserHandle.USER_NULL; @@ -109,6 +112,8 @@ class OwnersData { SystemUpdateInfo mSystemUpdateInfo; private final PolicyPathProvider mPathProvider; + boolean mMigratedToPolicyEngine = false; + OwnersData(PolicyPathProvider pathProvider) { mPathProvider = pathProvider; } @@ -389,6 +394,11 @@ class OwnersData { } out.endTag(null, TAG_FREEZE_PERIOD_RECORD); } + + out.startTag(null, TAG_POLICY_ENGINE_MIGRATION); + out.attributeBoolean(null, ATTR_MIGRATED_TO_POLICY_ENGINE, mMigratedToPolicyEngine); + out.endTag(null, TAG_POLICY_ENGINE_MIGRATION); + } @Override @@ -444,6 +454,9 @@ class OwnersData { } mDeviceOwnerProtectedPackages.put(packageName, protectedPackages); break; + case TAG_POLICY_ENGINE_MIGRATION: + mMigratedToPolicyEngine = parser.getAttributeBoolean( + null, ATTR_MIGRATED_TO_POLICY_ENGINE, false); default: Slog.e(TAG, "Unexpected tag: " + tag); return false; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PermissionGrantStatePolicyKey.java b/services/devicepolicy/java/com/android/server/devicepolicy/PermissionGrantStatePolicyKey.java deleted file mode 100644 index b7d805e5be33..000000000000 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PermissionGrantStatePolicyKey.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2022 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.android.server.devicepolicy; - -import static android.app.admin.PolicyUpdatesReceiver.EXTRA_PACKAGE_NAME; -import static android.app.admin.PolicyUpdatesReceiver.EXTRA_PERMISSION_NAME; -import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_BUNDLE_KEY; - -import android.annotation.Nullable; -import android.os.Bundle; - -import com.android.modules.utils.TypedXmlPullParser; -import com.android.modules.utils.TypedXmlSerializer; - -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.util.Objects; - -/** - * Class used to identify a PermissionGrantState policy in the policy engine's data structure. - */ -final class PermissionGrantStatePolicyKey extends PolicyKey { - private static final String ATTR_POLICY_KEY = "policy-key"; - private static final String ATTR_PACKAGE_NAME = "package-name"; - private static final String ATTR_PERMISSION_NAME = "permission-name"; - - private final String mPackageName; - private final String mPermissionName; - - PermissionGrantStatePolicyKey(String key, String packageName, String permissionName) { - super(key); - mPackageName = Objects.requireNonNull((packageName)); - mPermissionName = Objects.requireNonNull((permissionName)); - } - - PermissionGrantStatePolicyKey(String key) { - super(key); - mPackageName = null; - mPermissionName = null; - } - - @Nullable - String getPackageName() { - return mPackageName; - } - - @Nullable - String getPermissionName() { - return mPermissionName; - } - - @Override - void saveToXml(TypedXmlSerializer serializer) throws IOException { - serializer.attribute(/* namespace= */ null, ATTR_POLICY_KEY, mKey); - serializer.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, mPackageName); - serializer.attribute(/* namespace= */ null, ATTR_PERMISSION_NAME, mPermissionName); - } - - @Override - PermissionGrantStatePolicyKey readFromXml(TypedXmlPullParser parser) - throws XmlPullParserException, IOException { - String policyKey = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_KEY); - String packageName = parser.getAttributeValue(/* namespace= */ null, ATTR_PACKAGE_NAME); - String permissionName = parser.getAttributeValue( - /* namespace= */ null, ATTR_PERMISSION_NAME); - return new PermissionGrantStatePolicyKey(policyKey, packageName, permissionName); - } - - @Override - void writeToBundle(Bundle bundle) { - super.writeToBundle(bundle); - Bundle extraPolicyParams = new Bundle(); - extraPolicyParams.putString(EXTRA_PACKAGE_NAME, mPackageName); - extraPolicyParams.putString(EXTRA_PERMISSION_NAME, mPermissionName); - bundle.putBundle(EXTRA_POLICY_BUNDLE_KEY, extraPolicyParams); - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PermissionGrantStatePolicyKey other = (PermissionGrantStatePolicyKey) o; - return Objects.equals(mKey, other.mKey) - && Objects.equals(mPackageName, other.mPackageName) - && Objects.equals(mPermissionName, other.mPermissionName); - } - - @Override - public int hashCode() { - return Objects.hash(mKey, mPackageName, mPermissionName); - } - - @Override - public String toString() { - return "mPolicyKey= " + mKey + "; mPackageName= " + mPackageName + "; mPermissionName= " - + mPermissionName; - } -} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersistentPreferredActivityPolicyKey.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersistentPreferredActivityPolicyKey.java deleted file mode 100644 index f8c075950fbb..000000000000 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PersistentPreferredActivityPolicyKey.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2022 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.android.server.devicepolicy; - -import static android.app.admin.PolicyUpdatesReceiver.EXTRA_INTENT_FILTER; -import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_BUNDLE_KEY; - -import android.annotation.Nullable; -import android.content.IntentFilter; -import android.os.Bundle; - -import com.android.modules.utils.TypedXmlPullParser; -import com.android.modules.utils.TypedXmlSerializer; -import com.android.server.IntentResolver; - -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.util.Objects; - -/** - * Class used to identify a PersistentPreferredActivity policy in the policy engine's data - * structure. - */ -final class PersistentPreferredActivityPolicyKey extends PolicyKey { - private static final String ATTR_POLICY_KEY = "policy-key"; - private IntentFilter mFilter; - - PersistentPreferredActivityPolicyKey(String policyKey, IntentFilter filter) { - super(policyKey); - mFilter = Objects.requireNonNull((filter)); - } - - PersistentPreferredActivityPolicyKey(String policyKey) { - super(policyKey); - mFilter = null; - } - - @Nullable - IntentFilter getFilter() { - return mFilter; - } - - @Override - void saveToXml(TypedXmlSerializer serializer) throws IOException { - serializer.attribute(/* namespace= */ null, ATTR_POLICY_KEY, mKey); - mFilter.writeToXml(serializer); - } - - @Override - PersistentPreferredActivityPolicyKey readFromXml(TypedXmlPullParser parser) - throws XmlPullParserException, IOException { - String policyKey = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_KEY); - IntentFilter filter = new IntentFilter(); - filter.readFromXml(parser); - return new PersistentPreferredActivityPolicyKey(policyKey, filter); - } - - @Override - void writeToBundle(Bundle bundle) { - super.writeToBundle(bundle); - Bundle extraPolicyParams = new Bundle(); - extraPolicyParams.putParcelable(EXTRA_INTENT_FILTER, mFilter); - bundle.putBundle(EXTRA_POLICY_BUNDLE_KEY, extraPolicyParams); - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PersistentPreferredActivityPolicyKey other = (PersistentPreferredActivityPolicyKey) o; - return Objects.equals(mKey, other.mKey) - && IntentResolver.filterEquals(mFilter, other.mFilter); - } - - @Override - public int hashCode() { - return Objects.hash(mKey, mFilter); - } - - @Override - public String toString() { - return "mKey= " + mKey + "; mFilter= " + mFilter; - } -} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index a7c4d5a6fb4e..ab6f7327b0c2 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -18,10 +18,20 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.admin.BooleanPolicyValue; import android.app.admin.DevicePolicyManager; +import android.app.admin.IntegerPolicyValue; +import android.app.admin.IntentFilterPolicyKey; +import android.app.admin.LockTaskPolicy; +import android.app.admin.NoArgsPolicyKey; +import android.app.admin.PackagePermissionPolicyKey; +import android.app.admin.PackagePolicyKey; +import android.app.admin.PolicyKey; +import android.app.admin.PolicyValue; import android.content.ComponentName; import android.content.Context; import android.content.IntentFilter; +import android.os.Bundle; import com.android.internal.util.function.QuadFunction; import com.android.modules.utils.TypedXmlPullParser; @@ -47,35 +57,47 @@ final class PolicyDefinition<V> { // Only use this flag if a policy is inheritable by child profile from parent. private static final int POLICY_FLAG_INHERITABLE = 1 << 2; + // Use this flag if admin policies should be treated independently of each other and should not + // have any resolution logic applied, this should only be used for very limited policies were + // this would make sense and the enforcing logic should handle it appropriately, e.g. + // application restrictions set by different admins for a single package should not be merged, + // but saved and queried independent of each other. + // Currently, support is added for local only policies, if you need to add a non coexistable + // global policy please add support. + private static final int POLICY_FLAG_NON_COEXISTABLE_POLICY = 1 << 3; + private static final MostRestrictive<Boolean> FALSE_MORE_RESTRICTIVE = new MostRestrictive<>( - List.of(false, true)); + List.of(new BooleanPolicyValue(false), new BooleanPolicyValue(true))); private static final MostRestrictive<Boolean> TRUE_MORE_RESTRICTIVE = new MostRestrictive<>( - List.of(true, false)); + List.of(new BooleanPolicyValue(true), new BooleanPolicyValue(false))); static PolicyDefinition<Boolean> AUTO_TIMEZONE = new PolicyDefinition<>( - new DefaultPolicyKey(DevicePolicyManager.AUTO_TIMEZONE_POLICY), - // auto timezone is enabled by default, hence disabling it is more restrictive. - FALSE_MORE_RESTRICTIVE, + new NoArgsPolicyKey(DevicePolicyManager.AUTO_TIMEZONE_POLICY), + // auto timezone is disabled by default, hence enabling it is more restrictive. + TRUE_MORE_RESTRICTIVE, POLICY_FLAG_GLOBAL_ONLY_POLICY, (Boolean value, Context context, Integer userId, PolicyKey policyKey) -> PolicyEnforcerCallbacks.setAutoTimezoneEnabled(value, context), new BooleanPolicySerializer()); // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the - // actual permission grant policy with the correct arguments (packageName and permission name) + // actual policy with the correct arguments (packageName and permission name) // when reading the policies from xml. static final PolicyDefinition<Integer> GENERIC_PERMISSION_GRANT = new PolicyDefinition<>( - new PermissionGrantStatePolicyKey( - DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY), + new PackagePermissionPolicyKey(DevicePolicyManager.PERMISSION_GRANT_POLICY), // TODO: is this really the best mechanism, what makes denied more // restrictive than // granted? new MostRestrictive<>( - List.of(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED, - DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED, - DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT)), + List.of( + new IntegerPolicyValue( + DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED), + new IntegerPolicyValue( + DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED), + new IntegerPolicyValue( + DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT))), POLICY_FLAG_LOCAL_ONLY_POLICY, PolicyEnforcerCallbacks::setPermissionGrantState, new IntegerPolicySerializer()); @@ -90,14 +112,14 @@ final class PolicyDefinition<V> { return GENERIC_PERMISSION_GRANT; } return GENERIC_PERMISSION_GRANT.createPolicyDefinition( - new PermissionGrantStatePolicyKey( - DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY, + new PackagePermissionPolicyKey( + DevicePolicyManager.PERMISSION_GRANT_POLICY, packageName, permissionName)); } static PolicyDefinition<LockTaskPolicy> LOCK_TASK = new PolicyDefinition<>( - new DefaultPolicyKey(DevicePolicyManager.LOCK_TASK_POLICY), + new NoArgsPolicyKey(DevicePolicyManager.LOCK_TASK_POLICY), new TopPriority<>(List.of( // TODO(b/258166155): add correct device lock role name EnforcingAdmin.getRoleAuthorityOf("DeviceLock"), @@ -105,21 +127,22 @@ final class PolicyDefinition<V> { POLICY_FLAG_LOCAL_ONLY_POLICY, (LockTaskPolicy value, Context context, Integer userId, PolicyKey policyKey) -> PolicyEnforcerCallbacks.setLockTask(value, context, userId), - new LockTaskPolicy.LockTaskPolicySerializer()); + new LockTaskPolicySerializer()); - static PolicyDefinition<Set<String>> USER_CONTROLLED_DISABLED_PACKAGES = new PolicyDefinition<>( - new DefaultPolicyKey(DevicePolicyManager.USER_CONTROL_DISABLED_PACKAGES_POLICY), - new SetUnion<>(), - (Set<String> value, Context context, Integer userId, PolicyKey policyKey) -> - PolicyEnforcerCallbacks.setUserControlDisabledPackages(value, userId), - new SetPolicySerializer<>()); + static PolicyDefinition<Set<String>> USER_CONTROLLED_DISABLED_PACKAGES = + new PolicyDefinition<>( + new NoArgsPolicyKey(DevicePolicyManager.USER_CONTROL_DISABLED_PACKAGES_POLICY), + new StringSetUnion(), + (Set<String> value, Context context, Integer userId, PolicyKey policyKey) -> + PolicyEnforcerCallbacks.setUserControlDisabledPackages(value, userId), + new StringSetPolicySerializer()); // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the - // actual permission grant policy with the correct arguments (packageName and permission name) - // when reading the policies from xml. + // actual policy with the correct arguments (i.e. packageName) when reading the policies from + // xml. static PolicyDefinition<ComponentName> GENERIC_PERSISTENT_PREFERRED_ACTIVITY = new PolicyDefinition<>( - new PersistentPreferredActivityPolicyKey( + new IntentFilterPolicyKey( DevicePolicyManager.PERSISTENT_PREFERRED_ACTIVITY_POLICY), new TopPriority<>(List.of( // TODO(b/258166155): add correct device lock role name @@ -139,16 +162,16 @@ final class PolicyDefinition<V> { return GENERIC_PERSISTENT_PREFERRED_ACTIVITY; } return GENERIC_PERSISTENT_PREFERRED_ACTIVITY.createPolicyDefinition( - new PersistentPreferredActivityPolicyKey( + new IntentFilterPolicyKey( DevicePolicyManager.PERSISTENT_PREFERRED_ACTIVITY_POLICY, intentFilter)); } // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the - // actual uninstall blocked policy with the correct arguments (i.e. packageName) - // when reading the policies from xml. + // actual policy with the correct arguments (i.e. packageName) when reading the policies from + // xml. static PolicyDefinition<Boolean> GENERIC_PACKAGE_UNINSTALL_BLOCKED = new PolicyDefinition<>( - new PackageSpecificPolicyKey( + new PackagePolicyKey( DevicePolicyManager.PACKAGE_UNINSTALL_BLOCKED_POLICY), TRUE_MORE_RESTRICTIVE, POLICY_FLAG_LOCAL_ONLY_POLICY, @@ -165,19 +188,49 @@ final class PolicyDefinition<V> { return GENERIC_PACKAGE_UNINSTALL_BLOCKED; } return GENERIC_PACKAGE_UNINSTALL_BLOCKED.createPolicyDefinition( - new PackageSpecificPolicyKey( + new PackagePolicyKey( DevicePolicyManager.PACKAGE_UNINSTALL_BLOCKED_POLICY, packageName)); } + // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the + // actual policy with the correct arguments (i.e. packageName) when reading the policies from + // xml. + static PolicyDefinition<Bundle> GENERIC_APPLICATION_RESTRICTIONS = + new PolicyDefinition<>( + new PackagePolicyKey( + DevicePolicyManager.APPLICATION_RESTRICTIONS_POLICY), + new MostRecent<>(), + POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_NON_COEXISTABLE_POLICY, + // Application restrictions are now stored and retrieved from DPMS, so no + // enforcing is required, however DPMS calls into UM to set restrictions for + // backwards compatibility. + (Bundle value, Context context, Integer userId, PolicyKey policyKey) -> true, + new BundlePolicySerializer()); + + /** + * Passing in {@code null} for {@code packageName} will return + * {@link #GENERIC_APPLICATION_RESTRICTIONS}. + */ + static PolicyDefinition<Bundle> APPLICATION_RESTRICTIONS( + String packageName) { + if (packageName == null) { + return GENERIC_APPLICATION_RESTRICTIONS; + } + return GENERIC_APPLICATION_RESTRICTIONS.createPolicyDefinition( + new PackagePolicyKey( + DevicePolicyManager.APPLICATION_RESTRICTIONS_POLICY, packageName)); + } + private static final Map<String, PolicyDefinition<?>> sPolicyDefinitions = Map.of( DevicePolicyManager.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE, - DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY, GENERIC_PERMISSION_GRANT, + DevicePolicyManager.PERMISSION_GRANT_POLICY, GENERIC_PERMISSION_GRANT, DevicePolicyManager.LOCK_TASK_POLICY, LOCK_TASK, DevicePolicyManager.USER_CONTROL_DISABLED_PACKAGES_POLICY, USER_CONTROLLED_DISABLED_PACKAGES, DevicePolicyManager.PERSISTENT_PREFERRED_ACTIVITY_POLICY, GENERIC_PERSISTENT_PREFERRED_ACTIVITY, - DevicePolicyManager.PACKAGE_UNINSTALL_BLOCKED_POLICY, GENERIC_PACKAGE_UNINSTALL_BLOCKED + DevicePolicyManager.PACKAGE_UNINSTALL_BLOCKED_POLICY, GENERIC_PACKAGE_UNINSTALL_BLOCKED, + DevicePolicyManager.APPLICATION_RESTRICTIONS_POLICY, GENERIC_APPLICATION_RESTRICTIONS ); @@ -199,6 +252,10 @@ final class PolicyDefinition<V> { return mPolicyKey; } + @NonNull + ResolutionMechanism<V> getResolutionMechanism() { + return mResolutionMechanism; + } /** * Returns {@code true} if the policy is a global policy by nature and can't be applied locally. */ @@ -220,8 +277,16 @@ final class PolicyDefinition<V> { return (mPolicyFlags & POLICY_FLAG_INHERITABLE) != 0; } + /** + * Returns {@code true} if the policy engine should not try to resolve policies set by different + * admins and should just store it and pass it on to the enforcing logic. + */ + boolean isNonCoexistablePolicy() { + return (mPolicyFlags & POLICY_FLAG_NON_COEXISTABLE_POLICY) != 0; + } + @Nullable - V resolvePolicy(LinkedHashMap<EnforcingAdmin, V> adminsPolicy) { + PolicyValue<V> resolvePolicy(LinkedHashMap<EnforcingAdmin, PolicyValue<V>> adminsPolicy) { return mResolutionMechanism.resolve(adminsPolicy); } @@ -257,6 +322,10 @@ final class PolicyDefinition<V> { mPolicyEnforcerCallback = policyEnforcerCallback; mPolicySerializer = policySerializer; + if (isNonCoexistablePolicy() && !isLocalOnlyPolicy()) { + throw new UnsupportedOperationException("Non-coexistable global policies not supported," + + "please add support."); + } // TODO: maybe use this instead of manually adding to the map // sPolicyDefinitions.put(policyDefinitionKey, this); } @@ -271,26 +340,27 @@ final class PolicyDefinition<V> { // TODO: can we avoid casting? PolicyKey policyKey = readPolicyKeyFromXml(parser); PolicyDefinition<V> genericPolicyDefinition = - (PolicyDefinition<V>) sPolicyDefinitions.get(policyKey.mKey); + (PolicyDefinition<V>) sPolicyDefinitions.get(policyKey.getIdentifier()); return genericPolicyDefinition.createPolicyDefinition(policyKey); } static <V> PolicyKey readPolicyKeyFromXml(TypedXmlPullParser parser) throws XmlPullParserException, IOException { // TODO: can we avoid casting? - PolicyKey policyKey = DefaultPolicyKey.readGenericPolicyKeyFromXml(parser); - PolicyDefinition<V> genericPolicyDefinition = - (PolicyDefinition<V>) sPolicyDefinitions.get(policyKey.mKey); + PolicyKey policyKey = PolicyKey.readGenericPolicyKeyFromXml(parser); + PolicyDefinition<PolicyValue<V>> genericPolicyDefinition = + (PolicyDefinition<PolicyValue<V>>) sPolicyDefinitions.get( + policyKey.getIdentifier()); return genericPolicyDefinition.mPolicyKey.readFromXml(parser); } void savePolicyValueToXml(TypedXmlSerializer serializer, String attributeName, V value) throws IOException { - mPolicySerializer.saveToXml(serializer, attributeName, value); + mPolicySerializer.saveToXml(mPolicyKey, serializer, attributeName, value); } @Nullable - V readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName) { + PolicyValue<V> readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName) { return mPolicySerializer.readFromXml(parser, attributeName); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java index e2aa23d7fc01..4ae7ca6377a4 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java @@ -20,6 +20,11 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppGlobals; import android.app.admin.DevicePolicyManager; +import android.app.admin.IntentFilterPolicyKey; +import android.app.admin.LockTaskPolicy; +import android.app.admin.PackagePermissionPolicyKey; +import android.app.admin.PackagePolicyKey; +import android.app.admin.PolicyKey; import android.content.ComponentName; import android.content.Context; import android.content.IntentFilter; @@ -64,11 +69,11 @@ final class PolicyEnforcerCallbacks { @Nullable Integer grantState, @NonNull Context context, int userId, @NonNull PolicyKey policyKey) { return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> { - if (!(policyKey instanceof PermissionGrantStatePolicyKey)) { + if (!(policyKey instanceof PackagePermissionPolicyKey)) { throw new IllegalArgumentException("policyKey is not of type " + "PermissionGrantStatePolicyKey"); } - PermissionGrantStatePolicyKey parsedKey = (PermissionGrantStatePolicyKey) policyKey; + PackagePermissionPolicyKey parsedKey = (PackagePermissionPolicyKey) policyKey; Objects.requireNonNull(parsedKey.getPermissionName()); Objects.requireNonNull(parsedKey.getPackageName()); Objects.requireNonNull(context); @@ -156,13 +161,13 @@ final class PolicyEnforcerCallbacks { @NonNull PolicyKey policyKey) { Binder.withCleanCallingIdentity(() -> { try { - if (!(policyKey instanceof PersistentPreferredActivityPolicyKey)) { + if (!(policyKey instanceof IntentFilterPolicyKey)) { throw new IllegalArgumentException("policyKey is not of type " - + "PersistentPreferredActivityPolicyKey"); + + "IntentFilterPolicyKey"); } - PersistentPreferredActivityPolicyKey parsedKey = - (PersistentPreferredActivityPolicyKey) policyKey; - IntentFilter filter = Objects.requireNonNull(parsedKey.getFilter()); + IntentFilterPolicyKey parsedKey = + (IntentFilterPolicyKey) policyKey; + IntentFilter filter = Objects.requireNonNull(parsedKey.getIntentFilter()); IPackageManager packageManager = AppGlobals.getPackageManager(); if (preferredActivity != null) { @@ -184,11 +189,11 @@ final class PolicyEnforcerCallbacks { @Nullable Boolean uninstallBlocked, @NonNull Context context, int userId, @NonNull PolicyKey policyKey) { return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> { - if (!(policyKey instanceof PackageSpecificPolicyKey)) { + if (!(policyKey instanceof PackagePolicyKey)) { throw new IllegalArgumentException("policyKey is not of type " - + "PackageSpecificPolicyKey"); + + "PackagePolicyKey"); } - PackageSpecificPolicyKey parsedKey = (PackageSpecificPolicyKey) policyKey; + PackagePolicyKey parsedKey = (PackagePolicyKey) policyKey; String packageName = Objects.requireNonNull(parsedKey.getPackageName()); DevicePolicyManagerService.setUninstallBlockedUnchecked( packageName, diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyKey.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyKey.java deleted file mode 100644 index 571f0ee8530c..000000000000 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyKey.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2022 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.android.server.devicepolicy; - -import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_KEY; - -import android.annotation.Nullable; -import android.os.Bundle; - -import com.android.modules.utils.TypedXmlPullParser; -import com.android.modules.utils.TypedXmlSerializer; - -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.util.Objects; - -/** - * Abstract class used to identify a policy in the policy engine's data structure. - */ -abstract class PolicyKey { - private static final String ATTR_GENERIC_POLICY_KEY = "generic-policy-key"; - - protected final String mKey; - - PolicyKey(String policyKey) { - mKey = Objects.requireNonNull(policyKey); - } - - String getKey() { - return mKey; - } - - boolean hasSameKeyAs(PolicyKey other) { - if (other == null) { - return false; - } - return mKey.equals(other.mKey); - } - - void saveToXml(TypedXmlSerializer serializer) throws IOException { - serializer.attribute(/* namespace= */ null, ATTR_GENERIC_POLICY_KEY, mKey); - } - - PolicyKey readFromXml(TypedXmlPullParser parser) - throws XmlPullParserException, IOException { - // No need to read anything - return this; - } - - void writeToBundle(Bundle bundle) { - bundle.putString(EXTRA_POLICY_KEY, mKey); - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PolicyKey other = (PolicyKey) o; - return Objects.equals(mKey, other.mKey); - } - - @Override - public int hashCode() { - return Objects.hash(mKey); - } -} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java index 528d3b0c8055..0ef431f6ff90 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java @@ -17,6 +17,8 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; +import android.app.admin.PolicyKey; +import android.app.admin.PolicyValue; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -24,7 +26,8 @@ import com.android.modules.utils.TypedXmlSerializer; import java.io.IOException; abstract class PolicySerializer<V> { - abstract void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull V value) + abstract void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, + String attributeName, @NonNull V value) throws IOException; - abstract V readFromXml(TypedXmlPullParser parser, String attributeName); + abstract PolicyValue<V> readFromXml(TypedXmlPullParser parser, String attributeName); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java index c293e09daae9..3a792d82d2ba 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java @@ -18,6 +18,7 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.admin.PolicyValue; import android.util.Log; import com.android.internal.util.XmlUtils; @@ -40,8 +41,9 @@ final class PolicyState<V> { private static final String ATTR_RESOLVED_POLICY = "resolved-policy"; private final PolicyDefinition<V> mPolicyDefinition; - private final LinkedHashMap<EnforcingAdmin, V> mPoliciesSetByAdmins = new LinkedHashMap<>(); - private V mCurrentResolvedPolicy; + private final LinkedHashMap<EnforcingAdmin, PolicyValue<V>> mPoliciesSetByAdmins = + new LinkedHashMap<>(); + private PolicyValue<V> mCurrentResolvedPolicy; PolicyState(@NonNull PolicyDefinition<V> policyDefinition) { mPolicyDefinition = Objects.requireNonNull(policyDefinition); @@ -49,8 +51,8 @@ final class PolicyState<V> { private PolicyState( @NonNull PolicyDefinition<V> policyDefinition, - @NonNull LinkedHashMap<EnforcingAdmin, V> policiesSetByAdmins, - V currentEnforcedPolicy) { + @NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<V>> policiesSetByAdmins, + PolicyValue<V> currentEnforcedPolicy) { Objects.requireNonNull(policyDefinition); Objects.requireNonNull(policiesSetByAdmins); @@ -62,8 +64,14 @@ final class PolicyState<V> { /** * Returns {@code true} if the resolved policy has changed, {@code false} otherwise. */ - boolean addPolicy(@NonNull EnforcingAdmin admin, @NonNull V policy) { - mPoliciesSetByAdmins.put(Objects.requireNonNull(admin), Objects.requireNonNull(policy)); + boolean addPolicy(@NonNull EnforcingAdmin admin, @NonNull PolicyValue<V> policy) { + Objects.requireNonNull(admin); + Objects.requireNonNull(policy); + + //LinkedHashMap doesn't update the insertion order of existing keys, removing the existing + // key will cause it to update. + mPoliciesSetByAdmins.remove(admin); + mPoliciesSetByAdmins.put(admin, policy); return resolvePolicy(); } @@ -78,8 +86,8 @@ final class PolicyState<V> { * Returns {@code true} if the resolved policy has changed, {@code false} otherwise. */ boolean addPolicy( - @NonNull EnforcingAdmin admin, @NonNull V policy, - LinkedHashMap<EnforcingAdmin, V> globalPoliciesSetByAdmins) { + @NonNull EnforcingAdmin admin, @NonNull PolicyValue<V> policy, + LinkedHashMap<EnforcingAdmin, PolicyValue<V>> globalPoliciesSetByAdmins) { mPoliciesSetByAdmins.put(Objects.requireNonNull(admin), Objects.requireNonNull(policy)); return resolvePolicy(globalPoliciesSetByAdmins); @@ -109,7 +117,7 @@ final class PolicyState<V> { */ boolean removePolicy( @NonNull EnforcingAdmin admin, - LinkedHashMap<EnforcingAdmin, V> globalPoliciesSetByAdmins) { + LinkedHashMap<EnforcingAdmin, PolicyValue<V>> globalPoliciesSetByAdmins) { Objects.requireNonNull(admin); if (mPoliciesSetByAdmins.remove(admin) == null) { @@ -128,13 +136,17 @@ final class PolicyState<V> { * * Returns {@code true} if the resolved policy has changed, {@code false} otherwise. */ - boolean resolvePolicy(LinkedHashMap<EnforcingAdmin, V> globalPoliciesSetByAdmins) { + boolean resolvePolicy(LinkedHashMap<EnforcingAdmin, PolicyValue<V>> globalPoliciesSetByAdmins) { + //Non coexistable policies don't need resolving + if (mPolicyDefinition.isNonCoexistablePolicy()) { + return false; + } // Add global policies first then override with local policies for the same admin. - LinkedHashMap<EnforcingAdmin, V> mergedPolicies = + LinkedHashMap<EnforcingAdmin, PolicyValue<V>> mergedPolicies = new LinkedHashMap<>(globalPoliciesSetByAdmins); mergedPolicies.putAll(mPoliciesSetByAdmins); - V resolvedPolicy = mPolicyDefinition.resolvePolicy(mergedPolicies); + PolicyValue<V> resolvedPolicy = mPolicyDefinition.resolvePolicy(mergedPolicies); boolean policyChanged = !Objects.equals(resolvedPolicy, mCurrentResolvedPolicy); mCurrentResolvedPolicy = resolvedPolicy; @@ -142,12 +154,16 @@ final class PolicyState<V> { } @NonNull - LinkedHashMap<EnforcingAdmin, V> getPoliciesSetByAdmins() { + LinkedHashMap<EnforcingAdmin, PolicyValue<V>> getPoliciesSetByAdmins() { return new LinkedHashMap<>(mPoliciesSetByAdmins); } private boolean resolvePolicy() { - V resolvedPolicy = mPolicyDefinition.resolvePolicy(mPoliciesSetByAdmins); + //Non coexistable policies don't need resolving + if (mPolicyDefinition.isNonCoexistablePolicy()) { + return false; + } + PolicyValue<V> resolvedPolicy = mPolicyDefinition.resolvePolicy(mPoliciesSetByAdmins); boolean policyChanged = !Objects.equals(resolvedPolicy, mCurrentResolvedPolicy); mCurrentResolvedPolicy = resolvedPolicy; @@ -155,10 +171,20 @@ final class PolicyState<V> { } @Nullable - V getCurrentResolvedPolicy() { + PolicyValue<V> getCurrentResolvedPolicy() { return mCurrentResolvedPolicy; } + android.app.admin.PolicyState<V> getParcelablePolicyState() { + LinkedHashMap<android.app.admin.EnforcingAdmin, PolicyValue<V>> adminPolicies = + new LinkedHashMap<>(); + for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) { + adminPolicies.put(admin.getParcelableAdmin(), mPoliciesSetByAdmins.get(admin)); + } + return new android.app.admin.PolicyState<>(adminPolicies, mCurrentResolvedPolicy, + mPolicyDefinition.getResolutionMechanism().getParcelableResolutionMechanism()); + } + @Override public String toString() { return "PolicyState { mPolicyDefinition= " + mPolicyDefinition + ", mPoliciesSetByAdmins= " @@ -171,14 +197,14 @@ final class PolicyState<V> { if (mCurrentResolvedPolicy != null) { mPolicyDefinition.savePolicyValueToXml( - serializer, ATTR_RESOLVED_POLICY, mCurrentResolvedPolicy); + serializer, ATTR_RESOLVED_POLICY, mCurrentResolvedPolicy.getValue()); } for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) { serializer.startTag(/* namespace= */ null, TAG_ADMIN_POLICY_ENTRY); mPolicyDefinition.savePolicyValueToXml( - serializer, ATTR_POLICY_VALUE, mPoliciesSetByAdmins.get(admin)); + serializer, ATTR_POLICY_VALUE, mPoliciesSetByAdmins.get(admin).getValue()); serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_ENTRY); admin.saveToXml(serializer); @@ -193,15 +219,15 @@ final class PolicyState<V> { PolicyDefinition<V> policyDefinition = PolicyDefinition.readFromXml(parser); - V currentResolvedPolicy = policyDefinition.readPolicyValueFromXml( + PolicyValue<V> currentResolvedPolicy = policyDefinition.readPolicyValueFromXml( parser, ATTR_RESOLVED_POLICY); - LinkedHashMap<EnforcingAdmin, V> policiesSetByAdmins = new LinkedHashMap<>(); + LinkedHashMap<EnforcingAdmin, PolicyValue<V>> policiesSetByAdmins = new LinkedHashMap<>(); int outerDepth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, outerDepth)) { String tag = parser.getName(); if (TAG_ADMIN_POLICY_ENTRY.equals(tag)) { - V value = policyDefinition.readPolicyValueFromXml( + PolicyValue<V> value = policyDefinition.readPolicyValueFromXml( parser, ATTR_POLICY_VALUE); EnforcingAdmin admin; int adminPolicyDepth = parser.getDepth(); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ResolutionMechanism.java b/services/devicepolicy/java/com/android/server/devicepolicy/ResolutionMechanism.java index 7b720bcd12e6..c321aa1ef89e 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/ResolutionMechanism.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/ResolutionMechanism.java @@ -17,10 +17,12 @@ package com.android.server.devicepolicy; import android.annotation.Nullable; +import android.app.admin.PolicyValue; import java.util.LinkedHashMap; abstract class ResolutionMechanism<V> { @Nullable - abstract V resolve(LinkedHashMap<EnforcingAdmin, V> adminPolicies); + abstract PolicyValue<V> resolve(LinkedHashMap<EnforcingAdmin, PolicyValue<V>> adminPolicies); + abstract android.app.admin.ResolutionMechanism<V> getParcelableResolutionMechanism(); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java new file mode 100644 index 000000000000..dc6592d73116 --- /dev/null +++ b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2022 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.android.server.devicepolicy; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.admin.PolicyKey; +import android.app.admin.PolicyValue; +import android.app.admin.StringSetPolicyValue; +import android.util.Log; + +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import java.io.IOException; +import java.util.Objects; +import java.util.Set; + +// TODO(scottjonathan): Replace with generic set implementation +final class StringSetPolicySerializer extends PolicySerializer<Set<String>> { + private static final String ATTR_VALUES = ":strings"; + private static final String ATTR_VALUES_SEPARATOR = ";"; + + @Override + void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeNamePrefix, + @NonNull Set<String> value) throws IOException { + Objects.requireNonNull(value); + serializer.attribute( + /* namespace= */ null, + attributeNamePrefix + ATTR_VALUES, + String.join(ATTR_VALUES_SEPARATOR, value)); + } + + @Nullable + @Override + PolicyValue<Set<String>> readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) { + String valuesStr = parser.getAttributeValue( + /* namespace= */ null, + attributeNamePrefix + ATTR_VALUES); + if (valuesStr == null) { + Log.e(DevicePolicyEngine.TAG, "Error parsing StringSet policy value."); + return null; + } + Set<String> values = Set.of(valuesStr.split(ATTR_VALUES_SEPARATOR)); + return new StringSetPolicyValue(values); + } +} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SetUnion.java b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetUnion.java index cf2698357d2c..5298960892a3 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/SetUnion.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetUnion.java @@ -17,28 +17,37 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; +import android.app.admin.PolicyValue; +import android.app.admin.StringSetPolicyValue; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Objects; import java.util.Set; -final class SetUnion<V> extends ResolutionMechanism<Set<V>> { +final class StringSetUnion extends ResolutionMechanism<Set<String>> { @Override - Set<V> resolve(@NonNull LinkedHashMap<EnforcingAdmin, Set<V>> adminPolicies) { + PolicyValue<Set<String>> resolve( + @NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<Set<String>>> adminPolicies) { Objects.requireNonNull(adminPolicies); if (adminPolicies.isEmpty()) { return null; } - Set<V> unionOfPolicies = new HashSet<>(); - for (Set<V> policy : adminPolicies.values()) { - unionOfPolicies.addAll(policy); + Set<String> unionOfPolicies = new HashSet<>(); + for (PolicyValue<Set<String>> policy : adminPolicies.values()) { + unionOfPolicies.addAll(policy.getValue()); } - return unionOfPolicies; + return new StringSetPolicyValue(unionOfPolicies); } @Override + android.app.admin.StringSetUnion getParcelableResolutionMechanism() { + return new android.app.admin.StringSetUnion(); + } + + + @Override public String toString() { return "SetUnion {}"; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/TopPriority.java b/services/devicepolicy/java/com/android/server/devicepolicy/TopPriority.java index 571cf64978d8..839840b33a03 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/TopPriority.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/TopPriority.java @@ -17,6 +17,7 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; +import android.app.admin.PolicyValue; import java.util.LinkedHashMap; import java.util.List; @@ -34,7 +35,7 @@ final class TopPriority<V> extends ResolutionMechanism<V> { } @Override - V resolve(@NonNull LinkedHashMap<EnforcingAdmin, V> adminPolicies) { + PolicyValue<V> resolve(@NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<V>> adminPolicies) { if (adminPolicies.isEmpty()) { return null; } @@ -46,11 +47,17 @@ final class TopPriority<V> extends ResolutionMechanism<V> { } } // Return first set policy if no known authority is found - Map.Entry<EnforcingAdmin, V> policy = adminPolicies.entrySet().stream().findFirst().get(); + Map.Entry<EnforcingAdmin, PolicyValue<V>> policy = + adminPolicies.entrySet().stream().findFirst().get(); return policy.getValue(); } @Override + android.app.admin.TopPriority<V> getParcelableResolutionMechanism() { + return new android.app.admin.TopPriority<>(mHighestToLowestPriorityAuthorities); + } + + @Override public String toString() { return "TopPriority { mHighestToLowestPriorityAuthorities= " + mHighestToLowestPriorityAuthorities + " }"; diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index 7ed95991642f..81a547290d5e 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -419,6 +419,17 @@ int64_t IncrementalService::elapsedUsSinceMonoTs(uint64_t monoTsUs) { return nowUs - monoTsUs; } +static const char* loadingStateToString(incfs::LoadingState state) { + switch (state) { + case (incfs::LoadingState::Full): + return "Full"; + case (incfs::LoadingState::MissingBlocks): + return "MissingBlocks"; + default: + return "error obtaining loading state"; + } +} + void IncrementalService::onDump(int fd) { dprintf(fd, "Incremental is %s\n", incfs::enabled() ? "ENABLED" : "DISABLED"); dprintf(fd, "IncFs features: 0x%x\n", int(mIncFs->features())); @@ -453,9 +464,13 @@ void IncrementalService::onDump(int fd) { } dprintf(fd, " storages (%d): {\n", int(mnt.storages.size())); for (auto&& [storageId, storage] : mnt.storages) { - dprintf(fd, " [%d] -> [%s] (%d %% loaded) \n", storageId, storage.name.c_str(), + auto&& ifs = getIfsLocked(storageId); + dprintf(fd, " [%d] -> [%s] (%d %% loaded)(%s) \n", storageId, + storage.name.c_str(), (int)(getLoadingProgressFromPath(mnt, storage.name.c_str()).getProgress() * - 100)); + 100), + ifs ? loadingStateToString(mIncFs->isEverythingFullyLoaded(ifs->control)) + : "error obtaining ifs"); } dprintf(fd, " }\n"); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 271a3d3779b2..b117cae5c97b 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1793,6 +1793,10 @@ public final class SystemServer implements Dumpable { } t.traceEnd(); + t.traceBegin("ArtManagerLocal"); + DexOptHelper.initializeArtManagerLocal(context, mPackageManagerService); + t.traceEnd(); + t.traceBegin("UpdatePackagesIfNeeded"); try { Watchdog.getInstance().pauseWatchingCurrentThread("dexopt"); @@ -2766,10 +2770,6 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(PermissionPolicyService.class); t.traceEnd(); - t.traceBegin("ArtManagerLocal"); - DexOptHelper.initializeArtManagerLocal(context, mPackageManagerService); - t.traceEnd(); - t.traceBegin("MakePackageManagerServiceReady"); mPackageManagerService.systemReady(); t.traceEnd(); diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java index 0e9c171b7f42..5ebc901fae66 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -55,6 +55,8 @@ public final class DisplayDeviceConfigTest { private static final int DEFAULT_REFRESH_RATE = 120; private static final int DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE = 55; private static final int DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE = 95; + private static final int DEFAULT_REFRESH_RATE_IN_HBM_HDR = 90; + private static final int DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT = 100; private static final int[] LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{10, 30}; private static final int[] LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{1, 21}; private static final int[] HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{160}; @@ -295,9 +297,11 @@ public final class DisplayDeviceConfigTest { DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE); assertEquals(mDisplayDeviceConfig.getDefaultPeakRefreshRate(), DEFAULT_PEAK_REFRESH_RATE); assertEquals(mDisplayDeviceConfig.getDefaultRefreshRate(), DEFAULT_REFRESH_RATE); - assertEquals(0, mDisplayDeviceConfig.getRefreshRangeProfiles().size()); - + assertEquals(mDisplayDeviceConfig.getDefaultRefreshRateInHbmSunlight(), + DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT); + assertEquals(mDisplayDeviceConfig.getDefaultRefreshRateInHbmHdr(), + DEFAULT_REFRESH_RATE_IN_HBM_HDR); assertArrayEquals(mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(), LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE); assertArrayEquals(mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(), @@ -694,6 +698,12 @@ public final class DisplayDeviceConfigTest { when(mResources.getIntArray( R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate)) .thenReturn(HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE); + when(mResources.getInteger( + R.integer.config_defaultRefreshRateInHbmHdr)) + .thenReturn(DEFAULT_REFRESH_RATE_IN_HBM_HDR); + when(mResources.getInteger( + R.integer.config_defaultRefreshRateInHbmSunlight)) + .thenReturn(DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT); mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true); } diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java index 0eff0da0ed3e..7f6385b2080f 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -2264,6 +2264,10 @@ public class DisplayModeDirectorTest { .thenReturn(65); when(resources.getInteger(R.integer.config_defaultRefreshRateInZone)) .thenReturn(85); + when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmHdr)) + .thenReturn(95); + when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmSunlight)) + .thenReturn(100); when(resources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate)) .thenReturn(new int[]{5}); when(resources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate)) @@ -2274,8 +2278,21 @@ public class DisplayModeDirectorTest { when( resources.getIntArray(R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate)) .thenReturn(new int[]{7000}); + when(resources.getInteger( + com.android.internal.R.integer.config_displayWhiteBalanceBrightnessFilterHorizon)) + .thenReturn(3); + ArgumentCaptor<TypedValue> valueArgumentCaptor = ArgumentCaptor.forClass(TypedValue.class); + doAnswer((Answer<Void>) invocation -> { + valueArgumentCaptor.getValue().type = 4; + valueArgumentCaptor.getValue().data = 13; + return null; + }).when(resources).getValue(eq(com.android.internal.R.dimen + .config_displayWhiteBalanceBrightnessFilterIntercept), + valueArgumentCaptor.capture(), eq(true)); DisplayModeDirector director = createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0); + SensorManager sensorManager = createMockSensorManager(createLightSensor()); + director.start(sensorManager); // We don't expect any interaction with DeviceConfig when the director is initialized // because we explicitly avoid doing this as this can lead to a latency spike in the // startup of DisplayManagerService @@ -2285,6 +2302,8 @@ public class DisplayModeDirectorTest { 0.0); assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 65); assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 85); + assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 95); + assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 100); assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(), new int[]{250}); assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(), @@ -2294,6 +2313,7 @@ public class DisplayModeDirectorTest { assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(), new int[]{10}); + // Notify that the default display is updated, such that DisplayDeviceConfig has new values DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class); when(displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate()).thenReturn(50); @@ -2304,6 +2324,8 @@ public class DisplayModeDirectorTest { when(displayDeviceConfig.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30}); when(displayDeviceConfig.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210}); when(displayDeviceConfig.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{2100}); + when(displayDeviceConfig.getDefaultRefreshRateInHbmHdr()).thenReturn(65); + when(displayDeviceConfig.getDefaultRefreshRateInHbmSunlight()).thenReturn(75); director.defaultDisplayDeviceUpdated(displayDeviceConfig); assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0); @@ -2319,6 +2341,8 @@ public class DisplayModeDirectorTest { new int[]{25}); assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(), new int[]{30}); + assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 65); + assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 75); // Notify that the default display is updated, such that DeviceConfig has new values FakeDeviceConfig config = mInjector.getDeviceConfig(); @@ -2329,7 +2353,8 @@ public class DisplayModeDirectorTest { config.setLowDisplayBrightnessThresholds(new int[]{10}); config.setHighDisplayBrightnessThresholds(new int[]{255}); config.setHighAmbientBrightnessThresholds(new int[]{8000}); - + config.setRefreshRateInHbmHdr(70); + config.setRefreshRateInHbmSunlight(80); director.defaultDisplayDeviceUpdated(displayDeviceConfig); assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0); @@ -2345,6 +2370,8 @@ public class DisplayModeDirectorTest { new int[]{10}); assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(), new int[]{20}); + assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 70); + assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 80); } @Test diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java index 92fddc76343d..d999aa315940 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java @@ -22,6 +22,7 @@ import static android.content.pm.UserInfo.FLAG_EPHEMERAL; import static android.content.pm.UserInfo.FLAG_FULL; import static android.content.pm.UserInfo.FLAG_GUEST; import static android.content.pm.UserInfo.FLAG_INITIALIZED; +import static android.content.pm.UserInfo.FLAG_MAIN; import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE; import static android.content.pm.UserInfo.FLAG_PROFILE; import static android.content.pm.UserInfo.FLAG_RESTRICTED; @@ -206,6 +207,13 @@ public class UserManagerServiceUserInfoTest { assertFalse("Switching to a profiles should be disabled", userInfo.supportsSwitchTo()); } + /** Test UserInfo.canHaveProfile for main user */ + @Test + public void testCanHaveProfile() throws Exception { + UserInfo userInfo = createUser(100, FLAG_MAIN, null); + assertTrue("Main users can have profile", userInfo.canHaveProfile()); + } + /** Tests upgradeIfNecessaryLP (but without locking) for upgrading from version 8 to 9+. */ @Test public void testUpgradeIfNecessaryLP_9() { diff --git a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java index 2ac8b3700043..6edef75b645d 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java @@ -263,7 +263,8 @@ public class InputDeviceDelegateTest { public void vibrateIfAvailable_withNoInputDevice_returnsFalse() { assertFalse(mInputDeviceDelegate.isAvailable()); assertFalse(mInputDeviceDelegate.vibrateIfAvailable( - UID, PACKAGE_NAME, SYNCED_EFFECT, REASON, VIBRATION_ATTRIBUTES)); + new Vibration.CallerInfo(VIBRATION_ATTRIBUTES, UID, -1, PACKAGE_NAME, REASON), + SYNCED_EFFECT)); } @Test @@ -277,7 +278,8 @@ public class InputDeviceDelegateTest { mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true); assertTrue(mInputDeviceDelegate.vibrateIfAvailable( - UID, PACKAGE_NAME, SYNCED_EFFECT, REASON, VIBRATION_ATTRIBUTES)); + new Vibration.CallerInfo(VIBRATION_ATTRIBUTES, UID, -1, PACKAGE_NAME, REASON), + SYNCED_EFFECT)); verify(mIInputManagerMock).vibrateCombined(eq(1), same(SYNCED_EFFECT), any()); verify(mIInputManagerMock).vibrateCombined(eq(2), same(SYNCED_EFFECT), any()); } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java index 508e7b0f5918..d50aca94e06b 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java @@ -564,17 +564,17 @@ public class VibrationSettingsTest { for (int usage : ALL_USAGES) { // Non-system vibration - assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( - UID, "some.app", usage, vibrateStartTime)); + assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(createCallerInfo( + UID, "some.app", usage), vibrateStartTime)); // Vibration with UID zero assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( - /* uid= */ 0, "", usage, vibrateStartTime)); + createCallerInfo(/* uid= */ 0, "", usage), vibrateStartTime)); // System vibration assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( - Process.SYSTEM_UID, "", usage, vibrateStartTime)); + createCallerInfo(Process.SYSTEM_UID, "", usage), vibrateStartTime)); // SysUI vibration assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( - UID, SYSUI_PACKAGE_NAME, usage, vibrateStartTime)); + createCallerInfo(UID, SYSUI_PACKAGE_NAME, usage), vibrateStartTime)); } } @@ -592,16 +592,16 @@ public class VibrationSettingsTest { for (int usage : ALL_USAGES) { // Non-system vibration assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( - UID, "some.app", usage, vibrateStartTime)); + createCallerInfo(UID, "some.app", usage), vibrateStartTime)); // Vibration with UID zero assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( - /* uid= */ 0, "", usage, vibrateStartTime)); + createCallerInfo(/* uid= */ 0, "", usage), vibrateStartTime)); // System vibration assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( - Process.SYSTEM_UID, "", usage, vibrateStartTime)); + createCallerInfo(Process.SYSTEM_UID, "", usage), vibrateStartTime)); // SysUI vibration assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( - UID, SYSUI_PACKAGE_NAME, usage, vibrateStartTime)); + createCallerInfo(UID, SYSUI_PACKAGE_NAME, usage), vibrateStartTime)); } } } @@ -613,7 +613,7 @@ public class VibrationSettingsTest { for (int usage : ALL_USAGES) { assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff( - UID, "some.app", usage, vibrateStartTime)); + createCallerInfo(UID, "some.app", usage), vibrateStartTime)); } } @@ -626,10 +626,10 @@ public class VibrationSettingsTest { if (usage == USAGE_TOUCH || usage == USAGE_HARDWARE_FEEDBACK || usage == USAGE_PHYSICAL_EMULATION) { assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( - /* uid= */ 0, "", usage, vibrateStartTime)); + createCallerInfo(/* uid= */ 0, "", usage), vibrateStartTime)); } else { assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff( - /* uid= */ 0, "", usage, vibrateStartTime)); + createCallerInfo(/* uid= */ 0, "", usage), vibrateStartTime)); } } } @@ -643,10 +643,10 @@ public class VibrationSettingsTest { if (usage == USAGE_TOUCH || usage == USAGE_HARDWARE_FEEDBACK || usage == USAGE_PHYSICAL_EMULATION) { assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( - Process.SYSTEM_UID, "", usage, vibrateStartTime)); + createCallerInfo(Process.SYSTEM_UID, "", usage), vibrateStartTime)); } else { assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff( - Process.SYSTEM_UID, "", usage, vibrateStartTime)); + createCallerInfo(Process.SYSTEM_UID, "", usage), vibrateStartTime)); } } } @@ -660,10 +660,10 @@ public class VibrationSettingsTest { if (usage == USAGE_TOUCH || usage == USAGE_HARDWARE_FEEDBACK || usage == USAGE_PHYSICAL_EMULATION) { assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff( - UID, SYSUI_PACKAGE_NAME, usage, vibrateStartTime)); + createCallerInfo(UID, SYSUI_PACKAGE_NAME, usage), vibrateStartTime)); } else { assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff( - UID, SYSUI_PACKAGE_NAME, usage, vibrateStartTime)); + createCallerInfo(UID, SYSUI_PACKAGE_NAME, usage), vibrateStartTime)); } } } @@ -755,10 +755,10 @@ public class VibrationSettingsTest { private void assertVibrationIgnoredForUsageAndDisplay(@VibrationAttributes.Usage int usage, int displayId, Vibration.Status expectedStatus) { - assertEquals(errorMessageForUsage(usage), - expectedStatus, - mVibrationSettings.shouldIgnoreVibration(UID, displayId, - VibrationAttributes.createForUsage(usage))); + Vibration.CallerInfo callerInfo = new Vibration.CallerInfo( + VibrationAttributes.createForUsage(usage), UID, displayId, null, null); + assertEquals(errorMessageForUsage(usage), expectedStatus, + mVibrationSettings.shouldIgnoreVibration(callerInfo)); } private void assertVibrationNotIgnoredForUsage(@VibrationAttributes.Usage int usage) { @@ -767,24 +767,22 @@ public class VibrationSettingsTest { private void assertVibrationNotIgnoredForUsageAndFlags(@VibrationAttributes.Usage int usage, @VibrationAttributes.Flag int flags) { - assertVibrationNotIgnoredForUsageAndFlagsAndDidsplay(usage, Display.DEFAULT_DISPLAY, flags); + assertVibrationNotIgnoredForUsageAndFlagsAndDisplay(usage, Display.DEFAULT_DISPLAY, flags); } private void assertVibrationNotIgnoredForUsageAndDisplay(@VibrationAttributes.Usage int usage, int displayId) { - assertVibrationNotIgnoredForUsageAndFlagsAndDidsplay(usage, displayId, /* flags= */ 0); + assertVibrationNotIgnoredForUsageAndFlagsAndDisplay(usage, displayId, /* flags= */ 0); } - private void assertVibrationNotIgnoredForUsageAndFlagsAndDidsplay( + private void assertVibrationNotIgnoredForUsageAndFlagsAndDisplay( @VibrationAttributes.Usage int usage, int displayId, @VibrationAttributes.Flag int flags) { + Vibration.CallerInfo callerInfo = new Vibration.CallerInfo( + new VibrationAttributes.Builder().setUsage(usage).setFlags(flags).build(), UID, + displayId, null, null); assertNull(errorMessageForUsage(usage), - mVibrationSettings.shouldIgnoreVibration(UID, - displayId, - new VibrationAttributes.Builder() - .setUsage(usage) - .setFlags(flags) - .build())); + mVibrationSettings.shouldIgnoreVibration(callerInfo)); } @@ -826,4 +824,10 @@ public class VibrationSettingsTest { when(mPowerManagerInternalMock.getLastGoToSleep()).thenReturn( new PowerManager.SleepData(sleepTime, reason)); } + + private Vibration.CallerInfo createCallerInfo(int uid, String opPkg, + @VibrationAttributes.Usage int usage) { + VibrationAttributes attrs = VibrationAttributes.createForUsage(usage); + return new Vibration.CallerInfo(attrs, uid, VIRTUAL_DISPLAY_ID, opPkg, null); + } } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java index 9facb4bc87eb..12810bbd95e2 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java @@ -154,10 +154,10 @@ public class VibrationThreadTest { @Test public void vibrate_noVibrator_ignoresVibration() { mVibratorProviders.clear(); - long vibrationId = 1; CombinedVibration effect = CombinedVibration.createParallel( VibrationEffect.get(VibrationEffect.EFFECT_CLICK)); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId)); @@ -166,12 +166,12 @@ public class VibrationThreadTest { @Test public void vibrate_missingVibrators_ignoresVibration() { - long vibrationId = 1; CombinedVibration effect = CombinedVibration.startSequential() .addNext(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) .addNext(3, VibrationEffect.get(VibrationEffect.EFFECT_TICK)) .combine(); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId)); @@ -182,9 +182,9 @@ public class VibrationThreadTest { public void vibrate_singleVibratorOneShot_runsVibrationAndSetsAmplitude() throws Exception { mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - long vibrationId = 1; VibrationEffect effect = VibrationEffect.createOneShot(10, 100); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L)); @@ -201,9 +201,9 @@ public class VibrationThreadTest { @Test public void vibrate_oneShotWithoutAmplitudeControl_runsVibrationWithDefaultAmplitude() throws Exception { - long vibrationId = 1; VibrationEffect effect = VibrationEffect.createOneShot(10, 100); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L)); @@ -222,10 +222,10 @@ public class VibrationThreadTest { throws Exception { mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - long vibrationId = 1; VibrationEffect effect = VibrationEffect.createWaveform( new long[]{5, 5, 5}, new int[]{1, 2, 3}, -1); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(15L)); @@ -246,10 +246,10 @@ public class VibrationThreadTest { FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - long vibrationId = 1; int[] amplitudes = new int[]{1, 2, 3}; VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5, 5, 5}, amplitudes, 0); - VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; assertTrue( waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2 * amplitudes.length, @@ -259,8 +259,9 @@ public class VibrationThreadTest { assertTrue(mControllers.get(VIBRATOR_ID).isVibrating()); Vibration.EndInfo cancelVibrationInfo = new Vibration.EndInfo( - Vibration.Status.CANCELLED_SUPERSEDED, /* endedByUid= */ 1, - /* endedByUsage= */ VibrationAttributes.USAGE_ALARM); + Vibration.Status.CANCELLED_SUPERSEDED, new Vibration.CallerInfo( + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ALARM), /* uid= */ + 1, /* displayId= */ -1, /* opPkg= */ null, /* reason= */ null)); conductor.notifyCancelled( cancelVibrationInfo, /* immediate= */ false); @@ -287,11 +288,11 @@ public class VibrationThreadTest { FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - long vibrationId = 1; int[] amplitudes = new int[]{1, 2, 3}; VibrationEffect effect = VibrationEffect.createWaveform( new long[]{1, 10, 100}, amplitudes, 0); - VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS)); conductor.notifyCancelled( @@ -315,7 +316,6 @@ public class VibrationThreadTest { fakeVibrator.setMaxAmplitudes(1, 1, 1); fakeVibrator.setPwleSizeMax(10); - long vibrationId = 1; VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1)) // Very long segment so thread will be cancelled after first PWLE is triggered. .addTransition(Duration.ofMillis(100), targetFrequency(100)) @@ -323,7 +323,8 @@ public class VibrationThreadTest { VibrationEffect repeatingEffect = VibrationEffect.startComposition() .repeatEffectIndefinitely(effect) .compose(); - VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, repeatingEffect); + VibrationStepConductor conductor = startThreadAndDispatcher(repeatingEffect); + long vibrationId = conductor.getVibration().id; assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(), TEST_TIMEOUT_MILLIS)); @@ -346,7 +347,6 @@ public class VibrationThreadTest { fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK); fakeVibrator.setCompositionSizeMax(10); - long vibrationId = 1; VibrationEffect effect = VibrationEffect.startComposition() // Very long delay so thread will be cancelled after first PWLE is triggered. .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100) @@ -354,7 +354,8 @@ public class VibrationThreadTest { VibrationEffect repeatingEffect = VibrationEffect.startComposition() .repeatEffectIndefinitely(effect) .compose(); - VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, repeatingEffect); + VibrationStepConductor conductor = startThreadAndDispatcher(repeatingEffect); + long vibrationId = conductor.getVibration().id; assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(), TEST_TIMEOUT_MILLIS)); @@ -375,11 +376,11 @@ public class VibrationThreadTest { FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - long vibrationId = 1; int[] amplitudes = new int[]{1, 2, 3}; VibrationEffect effect = VibrationEffect.createWaveform( new long[]{5000, 500, 50}, amplitudes, 0); - VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS)); conductor.notifyCancelled( @@ -400,11 +401,11 @@ public class VibrationThreadTest { FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - long vibrationId = 1; int[] amplitudes = new int[]{1, 2}; VibrationEffect effect = VibrationEffect.createWaveform( new long[]{4900, 50}, amplitudes, 0); - VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() > 1, 5000 + TEST_TIMEOUT_MILLIS)); @@ -433,13 +434,13 @@ public class VibrationThreadTest { mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives( VibrationEffect.Composition.PRIMITIVE_CLICK); - long vibrationId = 1; VibrationEffect effect = VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100) .compose(); - VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(), TEST_TIMEOUT_MILLIS)); @@ -466,9 +467,9 @@ public class VibrationThreadTest { throws Exception { mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - long vibrationId = 1; VibrationEffect effect = VibrationEffect.createWaveform(new long[]{100}, new int[]{100}, 0); - VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(), TEST_TIMEOUT_MILLIS)); @@ -494,9 +495,9 @@ public class VibrationThreadTest { public void vibrate_singleVibratorPrebaked_runsVibration() throws Exception { mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_THUD); - long vibrationId = 1; VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_THUD); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L)); @@ -514,12 +515,12 @@ public class VibrationThreadTest { throws Exception { mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - long vibrationId = 1; VibrationEffect fallback = VibrationEffect.createOneShot(10, 100); - HalVibration vibration = createVibration(vibrationId, CombinedVibration.createParallel( + HalVibration vibration = createVibration(CombinedVibration.createParallel( VibrationEffect.get(VibrationEffect.EFFECT_CLICK))); vibration.addFallback(VibrationEffect.EFFECT_CLICK, fallback); - startThreadAndDispatcher(vibration); + VibrationStepConductor conductor = startThreadAndDispatcher(vibration); + long vibrationId = conductor.getVibration().id; waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L)); @@ -536,9 +537,9 @@ public class VibrationThreadTest { @Test public void vibrate_singleVibratorPrebakedAndUnsupportedEffect_ignoresVibration() throws Exception { - long vibrationId = 1; VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L)); @@ -555,12 +556,12 @@ public class VibrationThreadTest { fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK, VibrationEffect.Composition.PRIMITIVE_TICK); - long vibrationId = 1; VibrationEffect effect = VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f) .compose(); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(40L)); @@ -576,11 +577,11 @@ public class VibrationThreadTest { @Test public void vibrate_singleVibratorComposedAndNoCapability_ignoresVibration() { - long vibrationId = 1; VibrationEffect effect = VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) .compose(); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L)); @@ -600,13 +601,13 @@ public class VibrationThreadTest { VibrationEffect.Composition.PRIMITIVE_SPIN); fakeVibrator.setCompositionSizeMax(2); - long vibrationId = 1; VibrationEffect effect = VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.8f) .compose(); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED); @@ -630,7 +631,6 @@ public class VibrationThreadTest { fakeVibrator.setMaxAmplitudes( 0.5f /* 100Hz*/, 1 /* 150Hz */, 0.6f /* 200Hz */); - long vibrationId = 1; VibrationEffect effect = VibrationEffect.startComposition() .addEffect(VibrationEffect.createOneShot(10, 100)) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) @@ -643,7 +643,8 @@ public class VibrationThreadTest { .build()) .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) .compose(); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); // Use first duration the vibrator is turned on since we cannot estimate the clicks. @@ -675,7 +676,6 @@ public class VibrationThreadTest { VibrationEffect.Composition.PRIMITIVE_TICK); fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - long vibrationId = 1; VibrationEffect fallback = VibrationEffect.createOneShot(10, 100); VibrationEffect effect = VibrationEffect.startComposition() .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) @@ -683,9 +683,10 @@ public class VibrationThreadTest { .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_TICK)) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f) .compose(); - HalVibration vib = createVibration(vibrationId, CombinedVibration.createParallel(effect)); + HalVibration vib = createVibration(CombinedVibration.createParallel(effect)); vib.addFallback(VibrationEffect.EFFECT_TICK, fallback); - startThreadAndDispatcher(vib); + VibrationStepConductor conductor = startThreadAndDispatcher(vib); + long vibrationId = conductor.getVibration().id; waitForCompletion(); // Use first duration the vibrator is turned on since we cannot estimate the clicks. @@ -718,7 +719,6 @@ public class VibrationThreadTest { fakeVibrator.setMaxAmplitudes( 0.5f /* 100Hz*/, 1 /* 150Hz */, 0.6f /* 200Hz */); - long vibrationId = 1; VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1)) .addSustain(Duration.ofMillis(10)) .addTransition(Duration.ofMillis(20), targetAmplitude(0)) @@ -726,7 +726,8 @@ public class VibrationThreadTest { .addSustain(Duration.ofMillis(30)) .addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200)) .build(); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L)); @@ -756,7 +757,6 @@ public class VibrationThreadTest { fakeVibrator.setMaxAmplitudes(1, 1, 1); fakeVibrator.setPwleSizeMax(3); - long vibrationId = 1; VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1)) .addSustain(Duration.ofMillis(10)) .addTransition(Duration.ofMillis(20), targetAmplitude(0)) @@ -768,7 +768,8 @@ public class VibrationThreadTest { .addTransition(Duration.ofMillis(40), targetAmplitude(0.7f), targetFrequency(200)) .addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200)) .build(); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED); @@ -784,9 +785,9 @@ public class VibrationThreadTest { FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - long vibrationId = 1; VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5}, new int[]{100}, 0); - VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; assertTrue(waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2, TEST_TIMEOUT_MILLIS)); // Vibration still running after 2 cycles. @@ -804,9 +805,9 @@ public class VibrationThreadTest { public void vibrate_singleVibrator_skipsSyncedCallbacks() { mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - long vibrationId = 1; - startThreadAndDispatcher(vibrationId, + VibrationStepConductor conductor = startThreadAndDispatcher( VibrationEffect.createOneShot(10, 100)); + long vibrationId = conductor.getVibration().id; waitForCompletion(); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED); @@ -820,12 +821,12 @@ public class VibrationThreadTest { throws Exception { mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_TICK); - long vibrationId = 1; CombinedVibration effect = CombinedVibration.startParallel() .addVibrator(VIBRATOR_ID, VibrationEffect.get(VibrationEffect.EFFECT_TICK)) .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_TICK)) .combine(); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L)); @@ -846,10 +847,10 @@ public class VibrationThreadTest { mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_CLICK); mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK); - long vibrationId = 1; CombinedVibration effect = CombinedVibration.createParallel( VibrationEffect.get(VibrationEffect.EFFECT_CLICK)); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L)); @@ -881,7 +882,6 @@ public class VibrationThreadTest { mVibratorProviders.get(4).setSupportedPrimitives( VibrationEffect.Composition.PRIMITIVE_CLICK); - long vibrationId = 1; VibrationEffect composed = VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) .compose(); @@ -892,7 +892,8 @@ public class VibrationThreadTest { new long[]{10, 10}, new int[]{1, 2}, -1)) .addVibrator(4, composed) .combine(); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L)); @@ -929,7 +930,6 @@ public class VibrationThreadTest { VibrationEffect.Composition.PRIMITIVE_CLICK); mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK); - long vibrationId = 1; VibrationEffect composed = VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) .compose(); @@ -938,7 +938,8 @@ public class VibrationThreadTest { .addNext(1, VibrationEffect.createOneShot(10, 100), /* delay= */ 50) .addNext(2, composed, /* delay= */ 50) .combine(); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); InOrder controllerVerifier = inOrder(mControllerCallbacks); @@ -972,7 +973,6 @@ public class VibrationThreadTest { @Test public void vibrate_multipleSyncedCallbackTriggered_finishSteps() throws Exception { int[] vibratorIds = new int[]{1, 2}; - long vibrationId = 1; mockVibrators(vibratorIds); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); mVibratorProviders.get(1).setSupportedPrimitives( @@ -981,13 +981,15 @@ public class VibrationThreadTest { mVibratorProviders.get(2).setSupportedPrimitives( VibrationEffect.Composition.PRIMITIVE_CLICK); when(mManagerHooks.prepareSyncedVibration(anyLong(), eq(vibratorIds))).thenReturn(true); - when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true); VibrationEffect composed = VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100) .compose(); CombinedVibration effect = CombinedVibration.createParallel(composed); - VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; + + when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true); assertTrue(waitUntil( () -> !mVibratorProviders.get(1).getEffectSegments(vibrationId).isEmpty() @@ -1021,7 +1023,6 @@ public class VibrationThreadTest { when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true); when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(true); - long vibrationId = 1; VibrationEffect composed = VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) .compose(); @@ -1031,7 +1032,8 @@ public class VibrationThreadTest { .addVibrator(3, VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1)) .addVibrator(4, composed) .combine(); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); long expectedCap = IVibratorManager.CAP_SYNC @@ -1055,12 +1057,12 @@ public class VibrationThreadTest { mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(false); - long vibrationId = 1; CombinedVibration effect = CombinedVibration.startParallel() .addVibrator(1, VibrationEffect.createOneShot(10, 100)) .addVibrator(2, VibrationEffect.createWaveform(new long[]{5}, new int[]{200}, -1)) .combine(); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); long expectedCap = IVibratorManager.CAP_SYNC | IVibratorManager.CAP_PREPARE_ON; @@ -1084,12 +1086,12 @@ public class VibrationThreadTest { when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true); when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(false); - long vibrationId = 1; CombinedVibration effect = CombinedVibration.startParallel() .addVibrator(1, VibrationEffect.createOneShot(10, 100)) .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) .combine(); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); long expectedCap = IVibratorManager.CAP_SYNC @@ -1110,7 +1112,6 @@ public class VibrationThreadTest { mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); mVibratorProviders.get(3).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - long vibrationId = 1; CombinedVibration effect = CombinedVibration.startParallel() .addVibrator(1, VibrationEffect.createWaveform( new long[]{5, 10, 10}, new int[]{1, 2, 3}, -1)) @@ -1119,7 +1120,8 @@ public class VibrationThreadTest { .addVibrator(3, VibrationEffect.createWaveform( new long[]{60}, new int[]{6}, -1)) .combine(); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; // All vibrators are turned on in parallel. assertTrue(waitUntil( @@ -1169,8 +1171,7 @@ public class VibrationThreadTest { Arrays.fill(amplitudes, VibrationEffect.DEFAULT_AMPLITUDE); VibrationEffect effect = VibrationEffect.createWaveform(timings, amplitudes, -1); - long vibrationId = 1; - startThreadAndDispatcher(vibrationId, effect); + startThreadAndDispatcher(effect); long startTime = SystemClock.elapsedRealtime(); waitForCompletion(totalDuration + TEST_TIMEOUT_MILLIS); @@ -1192,9 +1193,9 @@ public class VibrationThreadTest { long latency = 5_000; // 5s fakeVibrator.setOnLatency(latency); - long vibrationId = 1; VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); - VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(), TEST_TIMEOUT_MILLIS)); @@ -1226,7 +1227,6 @@ public class VibrationThreadTest { mVibratorProviders.get(2).setSupportedPrimitives( VibrationEffect.Composition.PRIMITIVE_CLICK); - long vibrationId = 1; CombinedVibration effect = CombinedVibration.startParallel() .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) .addVibrator(2, VibrationEffect.startComposition() @@ -1235,7 +1235,8 @@ public class VibrationThreadTest { .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100) .compose()) .combine(); - VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; assertTrue(waitUntil(() -> mControllers.get(2).isVibrating(), TEST_TIMEOUT_MILLIS)); @@ -1264,13 +1265,13 @@ public class VibrationThreadTest { mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - long vibrationId = 1; CombinedVibration effect = CombinedVibration.startParallel() .addVibrator(1, VibrationEffect.createWaveform( new long[]{100, 100}, new int[]{1, 2}, 0)) .addVibrator(2, VibrationEffect.createOneShot(100, 100)) .combine(); - VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; assertTrue(waitUntil(() -> mControllers.get(1).isVibrating() && mControllers.get(2).isVibrating(), @@ -1296,9 +1297,9 @@ public class VibrationThreadTest { @Test public void vibrate_binderDied_cancelsVibration() throws Exception { - long vibrationId = 1; VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5}, new int[]{100}, 0); - VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(), TEST_TIMEOUT_MILLIS)); @@ -1320,10 +1321,10 @@ public class VibrationThreadTest { mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings); mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - long vibrationId = 1; VibrationEffect effect = VibrationEffect.createWaveform( new long[]{5, 5, 5}, new int[]{60, 120, 240}, -1); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); @@ -1346,9 +1347,9 @@ public class VibrationThreadTest { mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings); mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - long vibrationId = 1; VibrationEffect effect = VibrationEffect.createOneShot(10, 200); - VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; // Vibration completed but vibrator not yet released. verify(mManagerHooks, timeout(TEST_TIMEOUT_MILLIS)).onVibrationCompleted(eq(vibrationId), @@ -1381,9 +1382,9 @@ public class VibrationThreadTest { mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings); mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - long vibrationId = 1; VibrationEffect effect = VibrationEffect.createOneShot(10_000, 240); - VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(), TEST_TIMEOUT_MILLIS)); conductor.notifyCancelled( @@ -1410,9 +1411,9 @@ public class VibrationThreadTest { mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(VibrationEffect.EFFECT_CLICK); - long vibrationId = 1; VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); @@ -1432,11 +1433,11 @@ public class VibrationThreadTest { mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives( VibrationEffect.Composition.PRIMITIVE_CLICK); - long vibrationId = 1; VibrationEffect effect = VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) .compose(); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); @@ -1461,11 +1462,11 @@ public class VibrationThreadTest { fakeVibrator.setMaxAmplitudes(1, 1, 1); fakeVibrator.setPwleSizeMax(2); - long vibrationId = 1; VibrationEffect effect = VibrationEffect.startWaveform() .addTransition(Duration.ofMillis(1), targetAmplitude(1)) .build(); - startThreadAndDispatcher(vibrationId, effect); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; waitForCompletion(); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); @@ -1485,12 +1486,6 @@ public class VibrationThreadTest { mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL, IVibrator.CAP_COMPOSE_EFFECTS); - long vibrationId1 = 1; - long vibrationId2 = 2; - long vibrationId3 = 3; - long vibrationId4 = 4; - long vibrationId5 = 5; - // A simple effect, followed by a repeating effect that gets cancelled, followed by another // simple effect. VibrationEffect effect1 = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); @@ -1503,12 +1498,14 @@ public class VibrationThreadTest { VibrationEffect effect4 = VibrationEffect.createOneShot(8000, 100); VibrationEffect effect5 = VibrationEffect.createOneShot(20, 222); - startThreadAndDispatcher(vibrationId1, effect1); + VibrationStepConductor conductor1 = startThreadAndDispatcher(effect1); + long vibrationId1 = conductor1.getVibration().id; waitForCompletion(); verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibrationId1); verifyCallbacksTriggered(vibrationId1, Vibration.Status.FINISHED); - VibrationStepConductor conductor2 = startThreadAndDispatcher(vibrationId2, effect2); + VibrationStepConductor conductor2 = startThreadAndDispatcher(effect2); + long vibrationId2 = conductor2.getVibration().id; // Effect2 won't complete on its own. Cancel it after a couple of repeats. Thread.sleep(150); // More than two TICKs. conductor2.notifyCancelled( @@ -1516,12 +1513,14 @@ public class VibrationThreadTest { /* immediate= */ false); waitForCompletion(); - startThreadAndDispatcher(vibrationId3, effect3); + VibrationStepConductor conductor3 = startThreadAndDispatcher(effect3); + long vibrationId3 = conductor3.getVibration().id; waitForCompletion(); // Effect4 is a long oneshot, but it gets cancelled as fast as possible. long start4 = System.currentTimeMillis(); - VibrationStepConductor conductor4 = startThreadAndDispatcher(vibrationId4, effect4); + VibrationStepConductor conductor4 = startThreadAndDispatcher(effect4); + long vibrationId4 = conductor4.getVibration().id; conductor4.notifyCancelled( new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF), /* immediate= */ true); @@ -1529,7 +1528,8 @@ public class VibrationThreadTest { long duration4 = System.currentTimeMillis() - start4; // Effect5 is to show that things keep going after the immediate cancel. - startThreadAndDispatcher(vibrationId5, effect5); + VibrationStepConductor conductor5 = startThreadAndDispatcher(effect5); + long vibrationId5 = conductor5.getVibration().id; waitForCompletion(); FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); @@ -1582,14 +1582,12 @@ public class VibrationThreadTest { } } - private VibrationStepConductor startThreadAndDispatcher( - long vibrationId, VibrationEffect effect) { - return startThreadAndDispatcher(vibrationId, CombinedVibration.createParallel(effect)); + private VibrationStepConductor startThreadAndDispatcher(VibrationEffect effect) { + return startThreadAndDispatcher(CombinedVibration.createParallel(effect)); } - private VibrationStepConductor startThreadAndDispatcher(long vibrationId, - CombinedVibration effect) { - return startThreadAndDispatcher(createVibration(vibrationId, effect)); + private VibrationStepConductor startThreadAndDispatcher(CombinedVibration effect) { + return startThreadAndDispatcher(createVibration(effect)); } private VibrationStepConductor startThreadAndDispatcher(HalVibration vib) { @@ -1624,9 +1622,9 @@ public class VibrationThreadTest { mTestLooper.dispatchAll(); // Flush callbacks } - private HalVibration createVibration(long id, CombinedVibration effect) { - return new HalVibration(mVibrationToken, (int) id, effect, ATTRS, UID, DISPLAY_ID, - PACKAGE_NAME, "reason"); + private HalVibration createVibration(CombinedVibration effect) { + return new HalVibration(mVibrationToken, effect, + new Vibration.CallerInfo(ATTRS, UID, DISPLAY_ID, PACKAGE_NAME, "reason")); } private SparseArray<VibratorController> createVibratorControllers() { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 1ce8c61fa07a..bfd99fd38a4b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -2020,13 +2020,13 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation()); // Once transition starts, rotation is applied and transition shows DC rotating. - testPlayer.start(); + testPlayer.startTransition(); assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation()); assertNotNull(testPlayer.mLastReady); + assertTrue(testPlayer.mController.isPlaying()); WindowContainerToken dcToken = dc.mRemoteToken.toWindowContainerToken(); assertNotEquals(testPlayer.mLastReady.getChange(dcToken).getEndRotation(), testPlayer.mLastReady.getChange(dcToken).getStartRotation()); - assertTrue(testPlayer.mLastTransit.applyDisplayChangeIfNeeded()); testPlayer.finish(); } |