From 3b378b1f47ccfa9857943d18718243672c58981e Mon Sep 17 00:00:00 2001 From: kholoud mohamed Date: Tue, 19 Oct 2021 17:27:41 +0100 Subject: [PATCH 1/2] Add APIs to allow updating enterprise related drawables Add APIs to update and retrieve enterprise-related system drawables, the APIs are guarded by a new permission that will be granted to the new device management role holder. Bug: 203548565 Bug: 188410712 Test: manual Change-Id: Iab29db23a87e1481ba7e2996a704dcb2702444be --- core/api/current.txt | 33 ++ core/api/system-current.txt | 14 + .../admin/DevicePolicyDrawableResource.aidl | 19 + .../admin/DevicePolicyDrawableResource.java | 203 +++++++++ .../app/admin/DevicePolicyManager.java | 229 ++++++++++ .../app/admin/DevicePolicyResources.java | 243 +++++++++++ .../app/admin/IDevicePolicyManager.aidl | 5 + .../android/app/admin/ParcelableResource.aidl | 20 + .../android/app/admin/ParcelableResource.java | 286 +++++++++++++ .../BaseIDevicePolicyManager.java | 16 + .../DeviceManagementResourcesProvider.java | 396 ++++++++++++++++++ .../DevicePolicyManagerService.java | 45 ++ 12 files changed, 1509 insertions(+) create mode 100644 core/java/android/app/admin/DevicePolicyDrawableResource.aidl create mode 100644 core/java/android/app/admin/DevicePolicyDrawableResource.java create mode 100644 core/java/android/app/admin/DevicePolicyResources.java create mode 100644 core/java/android/app/admin/ParcelableResource.aidl create mode 100644 core/java/android/app/admin/ParcelableResource.java create mode 100644 services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java diff --git a/core/api/current.txt b/core/api/current.txt index bdf497f8ca648..e44fcd120dcc0 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -7274,6 +7274,10 @@ package android.app.admin { method @Nullable public java.util.List getDelegatePackages(@NonNull android.content.ComponentName, @NonNull String); method @NonNull public java.util.List getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String); method public CharSequence getDeviceOwnerLockScreenInfo(); + method @Nullable public android.graphics.drawable.Drawable getDrawable(int, int, @NonNull java.util.concurrent.Callable); + method @Nullable public android.graphics.drawable.Drawable getDrawable(int, int, int, @NonNull java.util.concurrent.Callable); + method @Nullable public android.graphics.drawable.Drawable getDrawableForDensity(int, int, int, @NonNull java.util.concurrent.Callable); + method @Nullable public android.graphics.drawable.Drawable getDrawableForDensity(int, int, int, int, @NonNull java.util.concurrent.Callable); method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName); method @NonNull public String getEnrollmentSpecificId(); method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName); @@ -7666,6 +7670,35 @@ package android.app.admin { method public void onApplicationUserDataCleared(String, boolean); } + public final class DevicePolicyResources { + ctor public DevicePolicyResources(); + } + + public static final class DevicePolicyResources.Drawable { + field public static final int INVALID_ID = -1; // 0xffffffff + field public static final int WORK_PROFILE_ICON = 1; // 0x1 + field public static final int WORK_PROFILE_ICON_BADGE = 0; // 0x0 + field public static final int WORK_PROFILE_OFF_ICON = 2; // 0x2 + field public static final int WORK_PROFILE_USER_ICON = 3; // 0x3 + } + + public static final class DevicePolicyResources.Drawable.Source { + field public static final int HOME_WIDGET = 2; // 0x2 + field public static final int LAUNCHER_OFF_BUTTON = 3; // 0x3 + field public static final int NOTIFICATION = 0; // 0x0 + field public static final int PROFILE_SWITCH_ANIMATION = 1; // 0x1 + field public static final int QUICK_SETTINGS = 4; // 0x4 + field public static final int STATUS_BAR = 5; // 0x5 + field public static final int UNDEFINED = -1; // 0xffffffff + } + + public static final class DevicePolicyResources.Drawable.Style { + field public static final int DEFAULT = -1; // 0xffffffff + field public static final int OUTLINE = 2; // 0x2 + field public static final int SOLID_COLORED = 0; // 0x0 + field public static final int SOLID_NOT_COLORED = 1; // 0x1 + } + public final class DnsEvent extends android.app.admin.NetworkEvent implements android.os.Parcelable { method public String getHostname(); method public java.util.List getInetAddresses(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 17ea0858b7c06..2af4cb235becd 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -993,6 +993,18 @@ package android.app { package android.app.admin { + public final class DevicePolicyDrawableResource implements android.os.Parcelable { + ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, int, int, int, @DrawableRes int); + ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, int, int, @DrawableRes int); + method public int describeContents(); + method @DrawableRes public int getCallingPackageResourceId(); + method public int getDrawableId(); + method public int getDrawableSource(); + method public int getDrawableStyle(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + public class DevicePolicyKeyguardService extends android.app.Service { ctor public DevicePolicyKeyguardService(); method @Nullable public void dismiss(); @@ -1025,8 +1037,10 @@ package android.app.admin { method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean packageHasActiveAdmins(String); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException; + method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void resetDrawables(@NonNull int[]); method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException; method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied(); + method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void setDrawables(@NonNull java.util.Set); method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName); method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setUserProvisioningState(int, @NonNull android.os.UserHandle); diff --git a/core/java/android/app/admin/DevicePolicyDrawableResource.aidl b/core/java/android/app/admin/DevicePolicyDrawableResource.aidl new file mode 100644 index 0000000000000..6b73d9815fe63 --- /dev/null +++ b/core/java/android/app/admin/DevicePolicyDrawableResource.aidl @@ -0,0 +1,19 @@ +/* + * 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; + +parcelable DevicePolicyDrawableResource; diff --git a/core/java/android/app/admin/DevicePolicyDrawableResource.java b/core/java/android/app/admin/DevicePolicyDrawableResource.java new file mode 100644 index 0000000000000..d32ff841f8023 --- /dev/null +++ b/core/java/android/app/admin/DevicePolicyDrawableResource.java @@ -0,0 +1,203 @@ +/* + * 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.DrawableRes; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Used to pass in the required information for updating an enterprise drawable resource using + * {@link DevicePolicyManager#setDrawables}. + * + * @hide + */ +@SystemApi +public final class DevicePolicyDrawableResource implements Parcelable { + private final @DevicePolicyResources.UpdatableDrawableId int mDrawableId; + private final @DevicePolicyResources.UpdatableDrawableStyle int mDrawableStyle; + private final @DevicePolicyResources.UpdatableDrawableSource int mDrawableSource; + private final @DrawableRes int mCallingPackageResourceId; + @NonNull private ParcelableResource mResource; + + /** + * Creates an object containing the required information for updating an enterprise drawable + * resource using {@link DevicePolicyManager#setDrawables}. + * + *

It will be used to update the drawable defined by {@code drawableId} with style + * {@code drawableStyle} located in source {@code drawableSource} to the drawable with ID + * {@code callingPackageResourceId} in the calling package

+ * + * @param drawableId The ID of the drawable to update. + * @param drawableStyle The style of the drawable to update. + * @param drawableSource The source of the drawable to update. + * @param callingPackageResourceId The ID of the drawable resource in the calling package to + * use as an updated resource. + * + * @throws IllegalStateException if the resource with ID + * {@code callingPackageResourceId} doesn't exist in the {@code context} package. + */ + public DevicePolicyDrawableResource( + @NonNull Context context, + @DevicePolicyResources.UpdatableDrawableId int drawableId, + @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, + @DevicePolicyResources.UpdatableDrawableSource int drawableSource, + @DrawableRes int callingPackageResourceId) { + this(drawableId, drawableStyle, drawableSource, callingPackageResourceId, + new ParcelableResource(context, callingPackageResourceId, + ParcelableResource.RESOURCE_TYPE_DRAWABLE)); + } + + private DevicePolicyDrawableResource( + @DevicePolicyResources.UpdatableDrawableId int drawableId, + @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, + @DevicePolicyResources.UpdatableDrawableSource int drawableSource, + @DrawableRes int callingPackageResourceId, + @NonNull ParcelableResource resource) { + this.mDrawableId = drawableId; + this.mDrawableStyle = drawableStyle; + this.mDrawableSource = drawableSource; + this.mCallingPackageResourceId = callingPackageResourceId; + this.mResource = resource; + } + + /** + * Creates an object containing the required information for updating an enterprise drawable + * resource using {@link DevicePolicyManager#setDrawables}. + *

It will be used to update the drawable defined by {@code drawableId} with style + * {@code drawableStyle} to the drawable with ID {@code callingPackageResourceId} in the + * calling package

+ * + * @param drawableId The ID of the drawable to update. + * @param drawableStyle The style of the drawable to update. + * @param callingPackageResourceId The ID of the drawable resource in the calling package to + * use as an updated resource. + * + * @throws IllegalStateException if the resource with ID + * {@code callingPackageResourceId} doesn't exist in the calling package. + */ + public DevicePolicyDrawableResource( + @NonNull Context context, + @DevicePolicyResources.UpdatableDrawableId int drawableId, + @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, + @DrawableRes int callingPackageResourceId) { + this(context, drawableId, drawableStyle, DevicePolicyResources.Drawable.Source.UNDEFINED, + callingPackageResourceId); + } + + /** + * Returns the ID of the drawable to update. + */ + @DevicePolicyResources.UpdatableDrawableId + public int getDrawableId() { + return mDrawableId; + } + + /** + * Returns the style of the drawable to update + */ + @DevicePolicyResources.UpdatableDrawableStyle + public int getDrawableStyle() { + return mDrawableStyle; + } + + /** + * Returns the source of the drawable to update. + */ + @DevicePolicyResources.UpdatableDrawableSource + public int getDrawableSource() { + return mDrawableSource; + } + + /** + * Returns the ID of the drawable resource in the calling package to use as an updated + * resource. + */ + @DrawableRes + public int getCallingPackageResourceId() { + return mCallingPackageResourceId; + } + + /** + * Returns the {@link ParcelableResource} of the drawable. + * + * @hide + */ + @NonNull + public ParcelableResource getResource() { + return mResource; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DevicePolicyDrawableResource other = (DevicePolicyDrawableResource) o; + return mDrawableId == other.mDrawableId + && mDrawableStyle == other.mDrawableStyle + && mDrawableSource == other.mDrawableSource + && mCallingPackageResourceId == other.mCallingPackageResourceId + && mResource.equals(other.mResource); + } + + @Override + public int hashCode() { + return Objects.hash( + mDrawableId, mDrawableStyle, mDrawableSource, mCallingPackageResourceId, mResource); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mDrawableId); + dest.writeInt(mDrawableStyle); + dest.writeInt(mDrawableSource); + dest.writeInt(mCallingPackageResourceId); + dest.writeTypedObject(mResource, flags); + } + + public static final @NonNull Creator CREATOR = + new Creator() { + @Override + public DevicePolicyDrawableResource createFromParcel(Parcel in) { + int drawableId = in.readInt(); + int drawableStyle = in.readInt(); + int drawableSource = in.readInt(); + int callingPackageResourceId = in.readInt(); + ParcelableResource resource = in.readTypedObject(ParcelableResource.CREATOR); + + return new DevicePolicyDrawableResource( + drawableId, drawableStyle, drawableSource, callingPackageResourceId, + resource); + } + + @Override + public DevicePolicyDrawableResource[] newArray(int size) { + return new DevicePolicyDrawableResource[size]; + } + }; +} diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index a60de088bab29..b5e54c29d9a3c 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -16,6 +16,9 @@ package android.app.admin; +import static android.app.admin.DevicePolicyResources.Drawable.INVALID_ID; +import static android.app.admin.DevicePolicyResources.Drawable.Source.UNDEFINED; + import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.Manifest.permission; @@ -52,7 +55,9 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; +import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; import android.net.PrivateDnsConnectivityChecker; import android.net.ProxyInfo; import android.net.Uri; @@ -89,6 +94,7 @@ import android.telephony.data.ApnSetting; import android.text.TextUtils; import android.util.ArraySet; import android.util.DebugUtils; +import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; @@ -123,6 +129,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -14372,4 +14379,226 @@ public class DevicePolicyManager { public Intent createProvisioningIntentFromNfcIntent(@NonNull Intent nfcIntent) { return ProvisioningIntentHelper.createProvisioningIntentFromNfcIntent(nfcIntent); } + + /** + * For each {@link DevicePolicyDrawableResource} item in {@code drawables}, if + * {@link DevicePolicyDrawableResource#getDrawableSource()} is not set or is set to + * {@link DevicePolicyResources.Drawable.Source#UNDEFINED}, it updates the drawable resource for + * the combination of {@link DevicePolicyDrawableResource#getDrawableId()} and + * {@link DevicePolicyDrawableResource#getDrawableStyle()}, (see + * {@link DevicePolicyResources.Drawable} and {@link DevicePolicyResources.Drawable.Style}) to + * the drawable with ID {@link DevicePolicyDrawableResource#getCallingPackageResourceId()}, + * meaning any system UI surface calling {@link #getDrawable} + * with {@code drawableId} and {@code drawableStyle} will get the new resource after this API + * is called. + * + *

Otherwise, if {@link DevicePolicyDrawableResource#getDrawableSource()} is set (see + * {@link DevicePolicyResources.Drawable.Source}, it overrides any drawables that was set for + * the same {@code drawableId} and {@code drawableStyle} for the provided source. + * + *

Important notes to consider when using this API: + *

    + *
  • {@link #getDrawable} references the resource + * {@link DevicePolicyDrawableResource#getCallingPackageResourceId()} in the + * calling package each time it gets called. You have to ensure that the resource is always + * available in the calling package as long as it is used as an updated resource. + *
  • You still have to re-call {@code setDrawables} even if you only make changes to the + * content of the resource with ID + * {@link DevicePolicyDrawableResource#getCallingPackageResourceId()} as the content might be + * cached and would need updating. + *
+ * + * @param drawables The list of {@link DevicePolicyDrawableResource} to update. + * + * @throws IllegalArgumentException if {@link DevicePolicyDrawableResource#getDrawableId()}, + * {@link DevicePolicyDrawableResource#getDrawableStyle()}, or + * {@link DevicePolicyDrawableResource#getDrawableSource()} aren't defined in + * {@link DevicePolicyResources.Drawable}. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) + public void setDrawables(@NonNull Set drawables) { + if (mService != null) { + try { + mService.setDrawables(new ArrayList<>(drawables)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Removes all updated drawables for the list of {@code drawableIds} (see + * {@link DevicePolicyResources.Drawable} that was previously set by calling + * {@link #setDrawables}, meaning any subsequent calls to {@link #getDrawable} for the provided + * IDs with any {@link DevicePolicyResources.Drawable.Style} and any + * {@link DevicePolicyResources.Drawable.Source} will return the default drawable from + * {@code defaultDrawableLoader}. + * + * @param drawableIds The list of IDs to remove + * the updated resources for. + * + * @throws IllegalArgumentException if IDs are not defined in + * {@link DevicePolicyResources.Drawable} + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) + public void resetDrawables(@NonNull int[] drawableIds) { + if (mService != null) { + try { + mService.resetDrawables(drawableIds); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Returns the appropriate updated drawable for the {@code drawableId} + * (see {@link DevicePolicyResources.Drawable}), with style {@code drawableStyle} + * (see {@link DevicePolicyResources.Drawable.Style}) if one was set using + * {@code setDrawables}, otherwise returns the drawable from {@code defaultDrawableLoader}. + * + *

Also returns the drawable from {@code defaultDrawableLoader} if + * {@link DevicePolicyResources.Drawable#INVALID_ID} was passed. + * + *

This API uses the screen density returned from {@link Resources#getConfiguration()}, to + * set a different value use + * {@link #getDrawableForDensity(int, int, int, Callable)}. + * + *

Note that each call to this API loads the resource from the package that called + * {@code setDrawables} to set the updated resource. + * + * @param drawableId The drawable ID to get the updated resource for. + * @param drawableStyle The drawable style to use. + * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for + * the provided params. + */ + @Nullable + public Drawable getDrawable( + @DevicePolicyResources.UpdatableDrawableId int drawableId, + @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, + @NonNull Callable defaultDrawableLoader) { + return getDrawable(drawableId, drawableStyle, UNDEFINED, defaultDrawableLoader); + } + + /** + * Similar to {@link #getDrawable(int, int, Callable)}, but also accepts + * a {@code drawableSource} (see {@link DevicePolicyResources.Drawable.Source}) which + * could result in returning a different drawable than {@link #getDrawable(int, int, Callable)} + * if an override was set for that specific source. + * + * @param drawableId The drawable ID to get the updated resource for. + * @param drawableStyle The drawable style to use. + * @param drawableSource The source for the caller. + * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for + * the provided params. + */ + @Nullable + public Drawable getDrawable( + @DevicePolicyResources.UpdatableDrawableId int drawableId, + @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, + @DevicePolicyResources.UpdatableDrawableSource int drawableSource, + @NonNull Callable defaultDrawableLoader) { + Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null"); + if (drawableId == INVALID_ID) { + return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); + } + if (mService != null) { + try { + ParcelableResource resource = mService.getDrawable( + drawableId, drawableStyle, drawableSource); + if (resource == null) { + return ParcelableResource.loadDefaultDrawable( + defaultDrawableLoader); + } + return resource.getDrawable( + mContext, + /* density= */ 0, + defaultDrawableLoader); + + } catch (RemoteException e) { + Log.e( + TAG, + "Error getting the updated drawable from DevicePolicyManagerService.", + e); + return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); + } + } + return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); + } + + /** + * Similar to {@link #getDrawable(int, int, Callable)}, but also accepts + * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}. + * + * @param drawableId The drawable ID to get the updated resource for. + * @param drawableStyle The drawable style to use. + * @param density The desired screen density indicated by the resource as + * found in {@link DisplayMetrics}. A value of 0 means to use the + * density returned from {@link Resources#getConfiguration()}. + * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for + * the provided params. + */ + @Nullable + public Drawable getDrawableForDensity( + @DevicePolicyResources.UpdatableDrawableId int drawableId, + @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, + int density, + @NonNull Callable defaultDrawableLoader) { + return getDrawableForDensity( + drawableId, + drawableStyle, + UNDEFINED, + density, + defaultDrawableLoader); + } + + /** + * Similar to {@link #getDrawable(int, int, int, Callable)}, but also accepts + * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}. + * + * @param drawableId The drawable ID to get the updated resource for. + * @param drawableStyle The drawable style to use. + * @param drawableSource The source for the caller. + * @param density The desired screen density indicated by the resource as + * found in {@link DisplayMetrics}. A value of 0 means to use the + * density returned from {@link Resources#getConfiguration()}. + * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for + * the provided params. + */ + @Nullable + public Drawable getDrawableForDensity( + @DevicePolicyResources.UpdatableDrawableId int drawableId, + @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, + @DevicePolicyResources.UpdatableDrawableSource int drawableSource, + int density, + @NonNull Callable defaultDrawableLoader) { + Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null"); + if (drawableId == INVALID_ID) { + return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); + } + if (mService != null) { + try { + ParcelableResource resource = mService.getDrawable( + drawableId, drawableStyle, drawableSource); + if (resource == null) { + return ParcelableResource.loadDefaultDrawable( + defaultDrawableLoader); + } + return resource.getDrawable(mContext, density, defaultDrawableLoader); + } catch (RemoteException e) { + Log.e( + TAG, + "Error getting the updated drawable from DevicePolicyManagerService.", + e); + return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); + } + } + return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); + } } diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java new file mode 100644 index 0000000000000..5133f26f81114 --- /dev/null +++ b/core/java/android/app/admin/DevicePolicyResources.java @@ -0,0 +1,243 @@ +/* + * 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.IntDef; +import android.annotation.SuppressLint; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.HashSet; +import java.util.Set; + +/** + * Class containing the required identifiers to update device management resources. + * + *

See {@link DevicePolicyManager#getDrawable}. + * + */ +public final class DevicePolicyResources { + + /** + * Resource identifiers used to update device management-related system drawable resources. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + Drawable.INVALID_ID, + Drawable.WORK_PROFILE_ICON_BADGE, + Drawable.WORK_PROFILE_ICON, + Drawable.WORK_PROFILE_OFF_ICON, + Drawable.WORK_PROFILE_USER_ICON + }) + public @interface UpdatableDrawableId {} + + /** + * Identifiers to specify the desired style for the updatable device management system + * resource. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + Drawable.Style.SOLID_COLORED, + Drawable.Style.SOLID_NOT_COLORED, + Drawable.Style.OUTLINE, + }) + public @interface UpdatableDrawableStyle {} + + /** + * Identifiers to specify the location if the updatable device management system resource. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + Drawable.Source.UNDEFINED, + Drawable.Source.NOTIFICATION, + Drawable.Source.PROFILE_SWITCH_ANIMATION, + Drawable.Source.HOME_WIDGET, + Drawable.Source.LAUNCHER_OFF_BUTTON, + Drawable.Source.QUICK_SETTINGS, + Drawable.Source.STATUS_BAR + }) + public @interface UpdatableDrawableSource {} + + + /** + * Class containing the identifiers used to update device management-related system drawable. + */ + public static final class Drawable { + + private Drawable() { + } + + /** + * An ID for any drawable that can't be updated. + */ + public static final int INVALID_ID = -1; + + /** + * Specifically used to badge work profile app icons. + */ + public static final int WORK_PROFILE_ICON_BADGE = 0; + + /** + * General purpose work profile icon (i.e. generic icon badging). For badging app icons + * specifically, see {@link #WORK_PROFILE_ICON_BADGE}. + */ + public static final int WORK_PROFILE_ICON = 1; + + /** + * General purpose icon representing the work profile off state. + */ + public static final int WORK_PROFILE_OFF_ICON = 2; + + /** + * General purpose icon for the work profile user avatar. + */ + public static final int WORK_PROFILE_USER_ICON = 3; + + /** + * @hide + */ + public static final Set UPDATABLE_DRAWABLE_IDS = buildDrawablesSet(); + + private static Set buildDrawablesSet() { + Set drawables = new HashSet<>(); + drawables.add(WORK_PROFILE_ICON_BADGE); + drawables.add(WORK_PROFILE_ICON); + drawables.add(WORK_PROFILE_OFF_ICON); + drawables.add(WORK_PROFILE_USER_ICON); + return drawables; + } + + /** + * Class containing the source identifiers used to update device management-related system + * drawable. + */ + public static final class Source { + + private Source() { + } + + /** + * A source identifier indicating that the updatable resource is used in a generic + * undefined location. + */ + public static final int UNDEFINED = -1; + + /** + * A source identifier indicating that the updatable drawable is used in notifications. + */ + public static final int NOTIFICATION = 0; + + /** + * A source identifier indicating that the updatable drawable is used in a cross + * profile switching animation. + */ + public static final int PROFILE_SWITCH_ANIMATION = 1; + + /** + * A source identifier indicating that the updatable drawable is used in a work + * profile home screen widget. + */ + public static final int HOME_WIDGET = 2; + + /** + * A source identifier indicating that the updatable drawable is used in the launcher + * turn off work button. + */ + public static final int LAUNCHER_OFF_BUTTON = 3; + + /** + * A source identifier indicating that the updatable drawable is used in quick settings. + */ + public static final int QUICK_SETTINGS = 4; + + /** + * A source identifier indicating that the updatable drawable is used in the status bar. + */ + public static final int STATUS_BAR = 5; + + /** + * @hide + */ + public static final Set UPDATABLE_DRAWABLE_SOURCES = buildSourcesSet(); + + private static Set buildSourcesSet() { + Set sources = new HashSet<>(); + sources.add(UNDEFINED); + sources.add(NOTIFICATION); + sources.add(PROFILE_SWITCH_ANIMATION); + sources.add(HOME_WIDGET); + sources.add(LAUNCHER_OFF_BUTTON); + sources.add(QUICK_SETTINGS); + sources.add(STATUS_BAR); + return sources; + } + } + + /** + * Class containing the style identifiers used to update device management-related system + * drawable. + */ + @SuppressLint("StaticUtils") + public static final class Style { + + private Style() { + } + + /** + * A style identifier indicating that the updatable drawable should use the default + * style. + */ + public static final int DEFAULT = -1; + + /** + * A style identifier indicating that the updatable drawable has a solid color fill. + */ + public static final int SOLID_COLORED = 0; + + /** + * A style identifier indicating that the updatable drawable has a solid non-colored + * fill. + */ + public static final int SOLID_NOT_COLORED = 1; + + /** + * A style identifier indicating that the updatable drawable is an outline. + */ + public static final int OUTLINE = 2; + + /** + * @hide + */ + public static final Set UPDATABLE_DRAWABLE_STYLES = buildStylesSet(); + + private static Set buildStylesSet() { + Set styles = new HashSet<>(); + styles.add(DEFAULT); + styles.add(SOLID_COLORED); + styles.add(SOLID_NOT_COLORED); + styles.add(OUTLINE); + return styles; + } + } + } +} diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index b9fcdf537806b..8320087554510 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -17,6 +17,8 @@ package android.app.admin; +import android.app.admin.DevicePolicyDrawableResource; +import android.app.admin.ParcelableResource; import android.app.admin.NetworkEvent; import android.app.IApplicationThread; import android.app.IServiceConnection; @@ -531,4 +533,7 @@ interface IDevicePolicyManager { boolean canUsbDataSignalingBeDisabled(); List listForegroundAffiliatedUsers(); + void setDrawables(in List resource); + void resetDrawables(in int[] drawableIds); + ParcelableResource getDrawable(int drawableId, int drawableStyle, int drawableSource); } diff --git a/core/java/android/app/admin/ParcelableResource.aidl b/core/java/android/app/admin/ParcelableResource.aidl new file mode 100644 index 0000000000000..dd2b9751921d6 --- /dev/null +++ b/core/java/android/app/admin/ParcelableResource.aidl @@ -0,0 +1,20 @@ +/* + * 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; + +parcelable ParcelableResource; diff --git a/core/java/android/app/admin/ParcelableResource.java b/core/java/android/app/admin/ParcelableResource.java new file mode 100644 index 0000000000000..e5171622be4a6 --- /dev/null +++ b/core/java/android/app/admin/ParcelableResource.java @@ -0,0 +1,286 @@ +/* + * 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 static java.util.Objects.requireNonNull; + +import android.annotation.AnyRes; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Slog; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; +import java.util.concurrent.Callable; + +/** + * Used to store the required information to load a resource that was updated using + * {@link DevicePolicyManager#setDrawables}. + * + * @hide + */ +public final class ParcelableResource implements Parcelable { + + private static String TAG = "DevicePolicyManager"; + + private static final String ATTR_RESOURCE_ID = "resource-id"; + private static final String ATTR_PACKAGE_NAME = "package-name"; + private static final String ATTR_RESOURCE_NAME = "resource-name"; + private static final String ATTR_RESOURCE_TYPE = "resource-type"; + + public static final int RESOURCE_TYPE_DRAWABLE = 1; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "RESOURCE_TYPE_" }, value = { + RESOURCE_TYPE_DRAWABLE + }) + public @interface ResourceType {} + + private final int mResourceId; + @NonNull private final String mPackageName; + @NonNull private final String mResourceName; + private final int mResourceType; + + /** + * + * Creates a {@code ParcelableDevicePolicyResource} for the given {@code resourceId} and + * verifies that it exists in the package of the given {@code context}. + * + * @param context for the package containing the {@code resourceId} to use as the updated + * resource + * @param resourceId of the resource to use as an updated resource + * @param resourceType see {@link ResourceType} + * @throws IllegalArgumentException if the given {@code resourceId} doesn't exist in the + * {@link Context#getResources()} of the given {@code context} + */ + public ParcelableResource(@NonNull Context context, @AnyRes int resourceId, + @ResourceType int resourceType) throws IllegalArgumentException { + Objects.requireNonNull(context, "context must be provided"); + + verifyResourceExistsInCallingPackage(context, resourceId, resourceType); + + this.mResourceId = resourceId; + this.mPackageName = context.getResources().getResourcePackageName(resourceId); + this.mResourceName = context.getResources().getResourceName(resourceId); + this.mResourceType = resourceType; + } + + /** + * Creates a {@code ParcelableDevicePolicyResource} with the given params, this DOES NOT make + * any verifications on whether the given {@code resourceId} actually exists. + */ + private ParcelableResource( + @AnyRes int resourceId, @NonNull String packageName, @NonNull String resourceName, + @ResourceType int resourceType) { + this.mResourceId = resourceId; + this.mPackageName = requireNonNull(packageName); + this.mResourceName = requireNonNull(resourceName); + this.mResourceType = resourceType; + } + + private static void verifyResourceExistsInCallingPackage( + Context context, @AnyRes int resourceId, @ResourceType int resourceType) + throws IllegalArgumentException { + switch (resourceType) { + case RESOURCE_TYPE_DRAWABLE: + if (!hasDrawableInCallingPackage(context, resourceId)) { + throw new IllegalArgumentException(String.format( + "Drawable with id %d doesn't exist in the calling package %s", + resourceId, + context.getPackageName())); + } + break; + default: + throw new IllegalArgumentException( + "Unknown ParcelableDevicePolicyResourceType: " + resourceType); + } + } + + private static boolean hasDrawableInCallingPackage(Context context, @AnyRes int resourceId) { + try { + return context.getDrawable(resourceId) != null; + } catch (Resources.NotFoundException e) { + return false; + } + } + + public @AnyRes int getResourceId() { + return mResourceId; + } + + @NonNull + public String getPackageName() { + return mPackageName; + } + + @NonNull + public String getResourceName() { + return mResourceName; + } + + public int getResourceType() { + return mResourceType; + } + + /** + * Loads the drawable with id {@code mResourceId} from {@code mPackageName} using the provided + * {@code density} and {@link Resources.Theme} and {@link Resources#getConfiguration} of the + * provided {@code context}. + * + *

Returns the default drawable by calling the {@code defaultDrawableLoader} if the updated + * drawable was not found or could not be loaded.

+ */ + @Nullable + public Drawable getDrawable( + Context context, + int density, + @NonNull Callable defaultDrawableLoader) { + // TODO(b/203548565): properly handle edge case when the device manager role holder is + // unavailable because it's being updated. + try { + Resources resources = getAppResourcesWithCallersConfiguration(context); + verifyResourceName(resources); + return resources.getDrawableForDensity(mResourceId, density, context.getTheme()); + } catch (PackageManager.NameNotFoundException | RuntimeException e) { + Slog.e(TAG, "Unable to load drawable resource " + mResourceName, e); + return loadDefaultDrawable(defaultDrawableLoader); + } + } + + private Resources getAppResourcesWithCallersConfiguration(Context context) + throws PackageManager.NameNotFoundException { + PackageManager pm = context.getPackageManager(); + ApplicationInfo ai = pm.getApplicationInfo( + mPackageName, + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.GET_SHARED_LIBRARY_FILES); + return pm.getResourcesForApplication(ai, context.getResources().getConfiguration()); + } + + private void verifyResourceName(Resources resources) throws IllegalStateException { + String name = resources.getResourceName(mResourceId); + if (!mResourceName.equals(name)) { + throw new IllegalStateException(String.format("Current resource name %s for resource id" + + " %d has changed from the previously stored resource name %s.", + name, mResourceId, mResourceName)); + } + } + + /** + * returns the {@link Drawable} loaded from calling + * {@code defaultDrawableLoader}. + */ + public static Drawable loadDefaultDrawable( + @NonNull Callable defaultDrawableLoader) { + try { + return defaultDrawableLoader.call(); + } catch (Exception e) { + throw new RuntimeException("Couldn't load default drawable", e); + } + } + + /** + * Writes the content of the current {@code ParcelableDevicePolicyResource} to the xml file + * specified by {@code xmlSerializer}. + */ + public void writeToXmlFile(TypedXmlSerializer xmlSerializer) throws IOException { + xmlSerializer.attributeInt(/* namespace= */ null, ATTR_RESOURCE_ID, mResourceId); + xmlSerializer.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, mPackageName); + xmlSerializer.attribute(/* namespace= */ null, ATTR_RESOURCE_NAME, mResourceName); + xmlSerializer.attributeInt(/* namespace= */ null, ATTR_RESOURCE_TYPE, mResourceType); + } + + /** + * Creates a new {@code ParcelableDevicePolicyResource} using the content of + * {@code xmlPullParser}. + */ + public static ParcelableResource createFromXml(TypedXmlPullParser xmlPullParser) + throws XmlPullParserException, IOException { + int resourceId = xmlPullParser.getAttributeInt(/* namespace= */ null, ATTR_RESOURCE_ID); + String packageName = xmlPullParser.getAttributeValue( + /* namespace= */ null, ATTR_PACKAGE_NAME); + String resourceName = xmlPullParser.getAttributeValue( + /* namespace= */ null, ATTR_RESOURCE_NAME); + int resourceType = xmlPullParser.getAttributeInt( + /* namespace= */ null, ATTR_RESOURCE_TYPE); + + return new ParcelableResource( + resourceId, packageName, resourceName, resourceType); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ParcelableResource other = (ParcelableResource) o; + return mResourceId == other.mResourceId + && mPackageName.equals(other.mPackageName) + && mResourceName.equals(other.mResourceName) + && mResourceType == other.mResourceType; + } + + @Override + public int hashCode() { + return Objects.hash(mResourceId, mPackageName, mResourceName, mResourceType); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mResourceId); + dest.writeString(mPackageName); + dest.writeString(mResourceName); + dest.writeInt(mResourceType); + } + + public static final @NonNull Creator CREATOR = + new Creator() { + @Override + public ParcelableResource createFromParcel(Parcel in) { + int resourceId = in.readInt(); + String packageName = in.readString(); + String resourceName = in.readString(); + int resourceType = in.readInt(); + + return new ParcelableResource( + resourceId, packageName, resourceName, resourceType); + } + + @Override + public ParcelableResource[] newArray(int size) { + return new ParcelableResource[size]; + } + }; +} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index 55ab8c3b1af6d..ebe9f93270323 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -17,16 +17,20 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; import android.annotation.UserIdInt; +import android.app.admin.DevicePolicyDrawableResource; import android.app.admin.DevicePolicySafetyChecker; import android.app.admin.FullyManagedDeviceProvisioningParams; import android.app.admin.IDevicePolicyManager; import android.app.admin.ManagedProfileProvisioningParams; +import android.app.admin.ParcelableResource; import android.content.ComponentName; import android.os.UserHandle; import android.util.Slog; import com.android.server.SystemService; +import java.util.List; + /** * Defines the required interface for IDevicePolicyManager implemenation. * @@ -161,4 +165,16 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { public boolean isKeyPairGrantedToWifiAuth(String callerPackage, String alias) { return false; } + + @Override + public void setDrawables(@NonNull List drawables){} + + @Override + public void resetDrawables(@NonNull int[] drawableIds){} + + @Override + public ParcelableResource getDrawable( + int drawableId, int drawableStyle, int drawableSource) { + return null; + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java new file mode 100644 index 0000000000000..20412e16b5afe --- /dev/null +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java @@ -0,0 +1,396 @@ +/* + * 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.DevicePolicyResources.Drawable.Source.UPDATABLE_DRAWABLE_SOURCES; +import static android.app.admin.DevicePolicyResources.Drawable.Style; +import static android.app.admin.DevicePolicyResources.Drawable.Style.UPDATABLE_DRAWABLE_STYLES; +import static android.app.admin.DevicePolicyResources.Drawable.UPDATABLE_DRAWABLE_IDS; + +import static java.util.Objects.requireNonNull; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.admin.DevicePolicyDrawableResource; +import android.app.admin.DevicePolicyResources; +import android.app.admin.ParcelableResource; +import android.os.Environment; +import android.util.AtomicFile; +import android.util.Log; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; +import android.util.Xml; + +import libcore.io.IoUtils; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * A helper class for {@link DevicePolicyManagerService} to store/retrieve updated device + * management resources. + */ +class DeviceManagementResourcesProvider { + private static final String TAG = "DevicePolicyManagerService"; + + private static final String UPDATED_RESOURCES_XML = "updated_resources.xml"; + private static final String TAG_ROOT = "root"; + private static final String TAG_DRAWABLE_STYLE_ENTRY = "drawable-style-entry"; + private static final String TAG_DRAWABLE_SOURCE_ENTRY = "drawable-source-entry"; + private static final String ATTR_DRAWABLE_STYLE_SIZE = "drawable-style-size"; + private static final String ATTR_DRAWABLE_SOURCE_SIZE = "drawable-source-size"; + private static final String ATTR_DRAWABLE_STYLE = "drawable-style"; + private static final String ATTR_DRAWABLE_SOURCE = "drawable-source"; + private static final String ATTR_DRAWABLE_ID = "drawable-id"; + + + private final Map> + mUpdatedDrawablesForStyle = new HashMap<>(); + + private final Map> + mUpdatedDrawablesForSource = new HashMap<>(); + + private final Object mLock = new Object(); + private final Injector mInjector; + + DeviceManagementResourcesProvider() { + this(new Injector()); + } + + DeviceManagementResourcesProvider(Injector injector) { + mInjector = requireNonNull(injector); + } + + void updateDrawables(@NonNull List drawables) { + for (int i = 0; i < drawables.size(); i++) { + int drawableId = drawables.get(i).getDrawableId(); + int drawableStyle = drawables.get(i).getDrawableStyle(); + int drawableSource = drawables.get(i).getDrawableSource(); + ParcelableResource resource = drawables.get(i).getResource(); + + Objects.requireNonNull(resource, "ParcelableResource must be provided."); + + if (drawableSource == DevicePolicyResources.Drawable.Source.UNDEFINED) { + updateDrawable(drawableId, drawableStyle, resource); + } else { + updateDrawableForSource(drawableId, drawableSource, resource); + } + } + if (!drawables.isEmpty()) { + synchronized (mLock) { + write(); + } + } + } + + private void updateDrawable( + int drawableId, int drawableStyle, ParcelableResource updatableResource) { + if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) { + throw new IllegalArgumentException( + "Can't update drawable resource, invalid drawable " + + "id " + drawableId); + } + if (!UPDATABLE_DRAWABLE_STYLES.contains(drawableStyle)) { + throw new IllegalArgumentException( + "Can't update drawable resource, invalid style id " + + drawableStyle); + } + synchronized (mLock) { + if (!mUpdatedDrawablesForStyle.containsKey(drawableId)) { + mUpdatedDrawablesForStyle.put(drawableId, new HashMap<>()); + } + mUpdatedDrawablesForStyle.get(drawableId).put(drawableStyle, updatableResource); + } + } + + // TODO(b/214576716): change this to respect style + private void updateDrawableForSource( + int drawableId, int drawableSource, ParcelableResource updatableResource) { + if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) { + throw new IllegalArgumentException("Can't update drawable resource, invalid drawable " + + "id " + drawableId); + } + if (!UPDATABLE_DRAWABLE_SOURCES.contains(drawableSource)) { + throw new IllegalArgumentException("Can't update drawable resource, invalid source id " + + drawableSource); + } + synchronized (mLock) { + if (!mUpdatedDrawablesForSource.containsKey(drawableId)) { + mUpdatedDrawablesForSource.put(drawableId, new HashMap<>()); + } + mUpdatedDrawablesForSource.get(drawableId).put( + drawableSource, updatableResource); + } + } + + void removeDrawables(@NonNull int[] drawableIds) { + synchronized (mLock) { + for (int i = 0; i < drawableIds.length; i++) { + int drawableId = drawableIds[i]; + mUpdatedDrawablesForStyle.remove(drawableId); + mUpdatedDrawablesForSource.remove(drawableId); + } + if (drawableIds.length != 0) { + write(); + } + } + } + + @Nullable + ParcelableResource getDrawable( + int drawableId, int drawableStyle, int drawableSource) { + if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) { + Log.e(TAG, "Can't get updated drawable resource, invalid drawable id " + + drawableId); + return null; + } + if (!UPDATABLE_DRAWABLE_STYLES.contains(drawableStyle)) { + Log.e(TAG, "Can't get updated drawable resource, invalid style id " + + drawableStyle); + return null; + } + if (!UPDATABLE_DRAWABLE_SOURCES.contains(drawableSource)) { + Log.e(TAG, "Can't get updated drawable resource, invalid source id " + + drawableSource); + return null; + } + if (mUpdatedDrawablesForSource.containsKey(drawableId) + && mUpdatedDrawablesForSource.get(drawableId).containsKey(drawableSource)) { + return mUpdatedDrawablesForSource.get(drawableId).get(drawableSource); + } + if (!mUpdatedDrawablesForStyle.containsKey(drawableId)) { + Log.d(TAG, "No updated drawable found for drawable id " + drawableId); + return null; + } + if (mUpdatedDrawablesForStyle.get(drawableId).containsKey(drawableStyle)) { + return mUpdatedDrawablesForStyle.get(drawableId).get(drawableStyle); + } + + if (mUpdatedDrawablesForStyle.get(drawableId).containsKey(Style.DEFAULT)) { + return mUpdatedDrawablesForStyle.get(drawableId).get(Style.DEFAULT); + } + Log.d(TAG, "No updated drawable found for drawable id " + drawableId); + return null; + } + + private void write() { + Log.d(TAG, "Writing updated resources to file."); + new ResourcesReaderWriter().writeToFileLocked(); + } + + void load() { + synchronized (mLock) { + new ResourcesReaderWriter().readFromFileLocked(); + } + } + + private File getResourcesFile() { + return new File(mInjector.environmentGetDataSystemDirectory(), UPDATED_RESOURCES_XML); + } + + private class ResourcesReaderWriter { + private final File mFile; + private ResourcesReaderWriter() { + mFile = getResourcesFile(); + } + + void writeToFileLocked() { + Log.d(TAG, "Writing to " + mFile); + + AtomicFile f = new AtomicFile(mFile); + FileOutputStream outputStream = null; + try { + outputStream = f.startWrite(); + TypedXmlSerializer out = Xml.resolveSerializer(outputStream); + + // Root tag + out.startDocument(null, true); + out.startTag(null, TAG_ROOT); + + // Actual content + writeInner(out); + + // Close root + out.endTag(null, TAG_ROOT); + out.endDocument(); + out.flush(); + + // Commit the content. + f.finishWrite(outputStream); + outputStream = null; + + } catch (IOException e) { + Log.e(TAG, "Exception when writing", e); + if (outputStream != null) { + f.failWrite(outputStream); + } + } + } + + void readFromFileLocked() { + if (!mFile.exists()) { + Log.d(TAG, "" + mFile + " doesn't exist"); + return; + } + + Log.d(TAG, "Reading from " + mFile); + AtomicFile f = new AtomicFile(mFile); + InputStream input = null; + try { + input = f.openRead(); + TypedXmlPullParser parser = Xml.resolvePullParser(input); + + int type; + int depth = 0; + while ((type = parser.next()) != TypedXmlPullParser.END_DOCUMENT) { + switch (type) { + case TypedXmlPullParser.START_TAG: + depth++; + break; + case TypedXmlPullParser.END_TAG: + depth--; + // fallthrough + default: + continue; + } + // Check the root tag + String tag = parser.getName(); + if (depth == 1) { + if (!TAG_ROOT.equals(tag)) { + Log.e(TAG, "Invalid root tag: " + tag); + return; + } + continue; + } + // readInner() will only see START_TAG at depth >= 2. + if (!readInner(parser, depth, tag)) { + return; // Error + } + } + } catch (XmlPullParserException | IOException e) { + Log.e(TAG, "Error parsing resources file", e); + } finally { + IoUtils.closeQuietly(input); + } + } + + void writeInner(TypedXmlSerializer out) throws IOException { + if (mUpdatedDrawablesForStyle != null && !mUpdatedDrawablesForStyle.isEmpty()) { + for (Map.Entry> drawableEntry + : mUpdatedDrawablesForStyle.entrySet()) { + out.startTag(/* namespace= */ null, TAG_DRAWABLE_STYLE_ENTRY); + out.attributeInt( + /* namespace= */ null, ATTR_DRAWABLE_ID, drawableEntry.getKey()); + out.attributeInt( + /* namespace= */ null, + ATTR_DRAWABLE_STYLE_SIZE, + drawableEntry.getValue().size()); + int counter = 0; + for (Map.Entry styleEntry + : drawableEntry.getValue().entrySet()) { + out.attributeInt( + /* namespace= */ null, + ATTR_DRAWABLE_STYLE + (counter++), + styleEntry.getKey()); + styleEntry.getValue().writeToXmlFile(out); + } + out.endTag(/* namespace= */ null, TAG_DRAWABLE_STYLE_ENTRY); + } + } + if (mUpdatedDrawablesForSource != null && !mUpdatedDrawablesForSource.isEmpty()) { + for (Map.Entry> drawableEntry + : mUpdatedDrawablesForSource.entrySet()) { + out.startTag(/* namespace= */ null, TAG_DRAWABLE_SOURCE_ENTRY); + out.attributeInt( + /* namespace= */ null, ATTR_DRAWABLE_ID, drawableEntry.getKey()); + out.attributeInt( + /* namespace= */ null, + ATTR_DRAWABLE_SOURCE_SIZE, + drawableEntry.getValue().size()); + int counter = 0; + for (Map.Entry sourceEntry + : drawableEntry.getValue().entrySet()) { + out.attributeInt( + /* namespace= */ null, + ATTR_DRAWABLE_SOURCE + (counter++), + sourceEntry.getKey()); + sourceEntry.getValue().writeToXmlFile(out); + } + out.endTag(/* namespace= */ null, TAG_DRAWABLE_SOURCE_ENTRY); + } + } + } + + private boolean readInner( + TypedXmlPullParser parser, int depth, String tag) + throws XmlPullParserException, IOException { + if (depth > 2) { + return true; // Ignore + } + switch (tag) { + case TAG_DRAWABLE_STYLE_ENTRY: + int drawableId = parser.getAttributeInt( + /* namespace= */ null, ATTR_DRAWABLE_ID); + mUpdatedDrawablesForStyle.put( + drawableId, + new HashMap<>()); + int size = parser.getAttributeInt( + /* namespace= */ null, ATTR_DRAWABLE_STYLE_SIZE); + for (int i = 0; i < size; i++) { + int style = parser.getAttributeInt( + /* namespace= */ null, ATTR_DRAWABLE_STYLE + i); + mUpdatedDrawablesForStyle.get(drawableId).put( + style, + ParcelableResource.createFromXml(parser)); + } + break; + case TAG_DRAWABLE_SOURCE_ENTRY: + drawableId = parser.getAttributeInt( + /* namespace= */ null, ATTR_DRAWABLE_ID); + mUpdatedDrawablesForSource.put(drawableId, new HashMap<>()); + size = parser.getAttributeInt( + /* namespace= */ null, ATTR_DRAWABLE_SOURCE_SIZE); + for (int i = 0; i < size; i++) { + int source = parser.getAttributeInt( + /* namespace= */ null, ATTR_DRAWABLE_SOURCE + i); + mUpdatedDrawablesForSource.get(drawableId).put( + source, + ParcelableResource.createFromXml(parser)); + } + break; + default: + Log.e(TAG, "Unexpected tag: " + tag); + return false; + } + return true; + } + } + + public static class Injector { + File environmentGetDataSystemDirectory() { + return Environment.getDataSystemDirectory(); + } + } +} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 6caf7317ed0af..af69be8680469 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -161,6 +161,7 @@ import android.app.StatusBarManager; import android.app.admin.DeviceAdminInfo; import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyCache; +import android.app.admin.DevicePolicyDrawableResource; import android.app.admin.DevicePolicyEventLogger; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager.DeviceOwnerType; @@ -177,6 +178,7 @@ import android.app.admin.FullyManagedDeviceProvisioningParams; import android.app.admin.ManagedProfileProvisioningParams; import android.app.admin.NetworkEvent; import android.app.admin.ParcelableGranteeMap; +import android.app.admin.ParcelableResource; import android.app.admin.PasswordMetrics; import android.app.admin.PasswordPolicy; import android.app.admin.SecurityLog; @@ -716,6 +718,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // Guarded by mHandler private @UserIdInt int mNetworkLoggingNotificationUserId = UserHandle.USER_NULL; + private final DeviceManagementResourcesProvider mDeviceManagementResourcesProvider; + private static final boolean ENABLE_LOCK_GUARD = true; /** @@ -1740,6 +1744,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) { mSafetyChecker = safetyChecker; } + + DeviceManagementResourcesProvider getDeviceManagementResourcesProvider() { + return new DeviceManagementResourcesProvider(); + } } /** @@ -1792,6 +1800,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mTransferOwnershipMetadataManager = mInjector.newTransferOwnershipMetadataManager(); mBugreportCollectionManager = new RemoteBugreportManager(this, mInjector); + mDeviceManagementResourcesProvider = mInjector.getDeviceManagementResourcesProvider(); + // "Lite" interface is available even when the device doesn't have the feature LocalServices.addService(DevicePolicyManagerLiteInternal.class, mLocalService); if (!mHasFeature) { @@ -1838,6 +1848,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { loadOwners(); performPolicyVersionUpgrade(); + + mDeviceManagementResourcesProvider.load(); } /** @@ -17956,4 +17968,37 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { && mInjector.getUsbManager().getUsbHalVersion() >= UsbManager.USB_HAL_V1_3 ); } + + @Override + public void setDrawables(@NonNull List drawables) { + Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( + android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES)); + + Objects.requireNonNull(drawables, "drawables must be provided."); + + // TODO(b/203548565): add new broadcast to indicate a resource has changed + mInjector.binderWithCleanCallingIdentity(() -> + mDeviceManagementResourcesProvider.updateDrawables(drawables)); + } + + + @Override + public void resetDrawables(@NonNull int[] drawableIds) { + Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( + android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES)); + + Objects.requireNonNull(drawableIds, "drawableIds must be provided."); + + // TODO(b/203548565): add new broadcast to indicate a resource has changed + mInjector.binderWithCleanCallingIdentity(() -> + mDeviceManagementResourcesProvider.removeDrawables(drawableIds)); + } + + @Override + public ParcelableResource getDrawable( + int drawableId, int drawableStyle, int drawableSource) { + return mInjector.binderWithCleanCallingIdentity(() -> + mDeviceManagementResourcesProvider.getDrawable( + drawableId, drawableStyle, drawableSource)); + } } From 85316ade7dd51dcbae92293659ea2bbd2fbcf8eb Mon Sep 17 00:00:00 2001 From: kholoud mohamed Date: Sun, 28 Nov 2021 23:28:40 +0000 Subject: [PATCH 2/2] Send broadcast when a device policy resource is updated Bug: 188410712 Bug: 203548565 Test: manual Change-Id: Icad44019c3fd2691969eaaf6b518d2ddfb91d133 --- core/api/current.txt | 3 + .../app/admin/DevicePolicyManager.java | 55 +++++++++++++++-- core/res/AndroidManifest.xml | 1 + .../DeviceManagementResourcesProvider.java | 61 +++++++++++++------ .../DevicePolicyManagerService.java | 38 +++++++++--- 5 files changed, 125 insertions(+), 33 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index e44fcd120dcc0..8dcc6c8cdf9cb 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -7501,6 +7501,7 @@ package android.app.admin { field public static final String ACTION_CHECK_POLICY_COMPLIANCE = "android.app.action.CHECK_POLICY_COMPLIANCE"; field public static final String ACTION_DEVICE_ADMIN_SERVICE = "android.app.action.DEVICE_ADMIN_SERVICE"; field public static final String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED"; + field public static final String ACTION_DEVICE_POLICY_RESOURCE_UPDATED = "android.app.action.DEVICE_POLICY_RESOURCE_UPDATED"; field public static final String ACTION_GET_PROVISIONING_MODE = "android.app.action.GET_PROVISIONING_MODE"; field public static final String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED"; field public static final String ACTION_PROFILE_OWNER_CHANGED = "android.app.action.PROFILE_OWNER_CHANGED"; @@ -7576,6 +7577,8 @@ package android.app.admin { field public static final String EXTRA_PROVISIONING_WIFI_SECURITY_TYPE = "android.app.extra.PROVISIONING_WIFI_SECURITY_TYPE"; field public static final String EXTRA_PROVISIONING_WIFI_SSID = "android.app.extra.PROVISIONING_WIFI_SSID"; field public static final String EXTRA_PROVISIONING_WIFI_USER_CERTIFICATE = "android.app.extra.PROVISIONING_WIFI_USER_CERTIFICATE"; + field public static final String EXTRA_RESOURCE_ID = "android.app.extra.RESOURCE_ID"; + field public static final String EXTRA_RESOURCE_TYPE_DRAWABLE = "android.app.extra.RESOURCE_TYPE_DRAWABLE"; field public static final int FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY = 1; // 0x1 field public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 2; // 0x2 field public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 1; // 0x1 diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index b5e54c29d9a3c..2388734823bd9 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -3188,6 +3188,36 @@ public class DevicePolicyManager { */ public static final int OPERATION_SAFETY_REASON_DRIVING_DISTRACTION = 1; + /** + * Broadcast action: notify system apps (e.g. settings, SysUI, etc) that the device management + * resources with IDs {@link #EXTRA_RESOURCE_ID} has been updated using, the updated resources + * can be retrieved using {@link #getDrawable}. + * + *

This broadcast is sent to registered receivers only. + * + *

The following extras will be included to identify the type of resource being updated: + *

    + *
  • {@link #EXTRA_RESOURCE_TYPE_DRAWABLE} for drawable resources
  • + *
+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DEVICE_POLICY_RESOURCE_UPDATED = + "android.app.action.DEVICE_POLICY_RESOURCE_UPDATED"; + + /** + * A boolean extra for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to indicate that a + * resource of type {@link Drawable} is being updated. + */ + public static final String EXTRA_RESOURCE_TYPE_DRAWABLE = + "android.app.extra.RESOURCE_TYPE_DRAWABLE"; + + /** + * An integer array extra for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to indicate which + * drawable IDs (see {@link DevicePolicyResources.UpdatableDrawableId}) have been updated. + */ + public static final String EXTRA_RESOURCE_ID = + "android.app.extra.RESOURCE_ID"; + /** @hide */ @NonNull @TestApi @@ -14396,13 +14426,16 @@ public class DevicePolicyManager { * {@link DevicePolicyResources.Drawable.Source}, it overrides any drawables that was set for * the same {@code drawableId} and {@code drawableStyle} for the provided source. * + *

Sends a broadcast with action {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to + * registered receivers when a resource has been updated successfully. + * *

Important notes to consider when using this API: *

    - *
  • {@link #getDrawable} references the resource + *
  • {@link #getDrawable} references the resource * {@link DevicePolicyDrawableResource#getCallingPackageResourceId()} in the * calling package each time it gets called. You have to ensure that the resource is always * available in the calling package as long as it is used as an updated resource. - *
  • You still have to re-call {@code setDrawables} even if you only make changes to the + *
  • You still have to re-call {@code setDrawables} even if you only make changes to the * content of the resource with ID * {@link DevicePolicyDrawableResource#getCallingPackageResourceId()} as the content might be * cached and would need updating. @@ -14437,8 +14470,10 @@ public class DevicePolicyManager { * {@link DevicePolicyResources.Drawable.Source} will return the default drawable from * {@code defaultDrawableLoader}. * - * @param drawableIds The list of IDs to remove - * the updated resources for. + *

    Sends a broadcast with action {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to + * registered receivers when a resource has been reset successfully. + * + * @param drawableIds The list of IDs to remove. * * @throws IllegalArgumentException if IDs are not defined in * {@link DevicePolicyResources.Drawable} @@ -14470,6 +14505,9 @@ public class DevicePolicyManager { * set a different value use * {@link #getDrawableForDensity(int, int, int, Callable)}. * + *

    Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get + * notified when a resource has been updated. + * *

    Note that each call to this API loads the resource from the package that called * {@code setDrawables} to set the updated resource. * @@ -14492,6 +14530,9 @@ public class DevicePolicyManager { * could result in returning a different drawable than {@link #getDrawable(int, int, Callable)} * if an override was set for that specific source. * + *

    Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get + * notified when a resource has been updated. + * * @param drawableId The drawable ID to get the updated resource for. * @param drawableStyle The drawable style to use. * @param drawableSource The source for the caller. @@ -14536,6 +14577,9 @@ public class DevicePolicyManager { * Similar to {@link #getDrawable(int, int, Callable)}, but also accepts * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}. * + *

    Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get + * notified when a resource has been updated. + * * @param drawableId The drawable ID to get the updated resource for. * @param drawableStyle The drawable style to use. * @param density The desired screen density indicated by the resource as @@ -14562,6 +14606,9 @@ public class DevicePolicyManager { * Similar to {@link #getDrawable(int, int, int, Callable)}, but also accepts * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}. * + *

    Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get + * notified when a resource has been updated. + * * @param drawableId The drawable ID to get the updated resource for. * @param drawableStyle The drawable style to use. * @param drawableSource The source for the caller. diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index d6bbe71586fa0..d0b9f880591d9 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -716,6 +716,7 @@ + diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java index 20412e16b5afe..5342294028885 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java @@ -83,7 +83,11 @@ class DeviceManagementResourcesProvider { mInjector = requireNonNull(injector); } - void updateDrawables(@NonNull List drawables) { + /** + * Returns {@code false} if no resources were updated. + */ + boolean updateDrawables(@NonNull List drawables) { + boolean updated = false; for (int i = 0; i < drawables.size(); i++) { int drawableId = drawables.get(i).getDrawableId(); int drawableStyle = drawables.get(i).getDrawableStyle(); @@ -93,40 +97,46 @@ class DeviceManagementResourcesProvider { Objects.requireNonNull(resource, "ParcelableResource must be provided."); if (drawableSource == DevicePolicyResources.Drawable.Source.UNDEFINED) { - updateDrawable(drawableId, drawableStyle, resource); + updated |= updateDrawable(drawableId, drawableStyle, resource); } else { - updateDrawableForSource(drawableId, drawableSource, resource); + updated |= updateDrawableForSource(drawableId, drawableSource, resource); } } - if (!drawables.isEmpty()) { - synchronized (mLock) { - write(); - } + if (!updated) { + return false; + } + synchronized (mLock) { + write(); + return true; } } - private void updateDrawable( + private boolean updateDrawable( int drawableId, int drawableStyle, ParcelableResource updatableResource) { if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) { throw new IllegalArgumentException( - "Can't update drawable resource, invalid drawable " - + "id " + drawableId); + "Can't update drawable resource, invalid drawable " + "id " + drawableId); } if (!UPDATABLE_DRAWABLE_STYLES.contains(drawableStyle)) { throw new IllegalArgumentException( - "Can't update drawable resource, invalid style id " - + drawableStyle); + "Can't update drawable resource, invalid style id " + drawableStyle); } synchronized (mLock) { if (!mUpdatedDrawablesForStyle.containsKey(drawableId)) { mUpdatedDrawablesForStyle.put(drawableId, new HashMap<>()); } + ParcelableResource current = mUpdatedDrawablesForStyle.get(drawableId).get( + drawableStyle); + if (updatableResource.equals(current)) { + return false; + } mUpdatedDrawablesForStyle.get(drawableId).put(drawableStyle, updatableResource); + return true; } } // TODO(b/214576716): change this to respect style - private void updateDrawableForSource( + private boolean updateDrawableForSource( int drawableId, int drawableSource, ParcelableResource updatableResource) { if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) { throw new IllegalArgumentException("Can't update drawable resource, invalid drawable " @@ -140,21 +150,32 @@ class DeviceManagementResourcesProvider { if (!mUpdatedDrawablesForSource.containsKey(drawableId)) { mUpdatedDrawablesForSource.put(drawableId, new HashMap<>()); } - mUpdatedDrawablesForSource.get(drawableId).put( - drawableSource, updatableResource); + ParcelableResource current = mUpdatedDrawablesForSource.get(drawableId).get( + drawableSource); + if (updatableResource.equals(current)) { + return false; + } + mUpdatedDrawablesForSource.get(drawableId).put(drawableSource, updatableResource); + return true; } } - void removeDrawables(@NonNull int[] drawableIds) { + /** + * Returns {@code false} if no resources were removed. + */ + boolean removeDrawables(@NonNull int[] drawableIds) { synchronized (mLock) { + boolean removed = false; for (int i = 0; i < drawableIds.length; i++) { int drawableId = drawableIds[i]; - mUpdatedDrawablesForStyle.remove(drawableId); - mUpdatedDrawablesForSource.remove(drawableId); + removed |= mUpdatedDrawablesForStyle.remove(drawableId) != null + || mUpdatedDrawablesForSource.remove(drawableId) != null; } - if (drawableIds.length != 0) { - write(); + if (!removed) { + return false; } + write(); + return true; } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index af69be8680469..0595b7397a42d 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -26,6 +26,7 @@ import static android.app.AppOpsManager.MODE_DEFAULT; import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED; import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE; import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE; +import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER; @@ -56,6 +57,8 @@ import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS; import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT; import static android.app.admin.DevicePolicyManager.DELEGATION_SECURITY_LOGGING; import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER; +import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_ID; +import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_DRAWABLE; import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO; import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI; import static android.app.admin.DevicePolicyManager.ID_TYPE_INDIVIDUAL_ATTESTATION; @@ -17976,12 +17979,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(drawables, "drawables must be provided."); - // TODO(b/203548565): add new broadcast to indicate a resource has changed - mInjector.binderWithCleanCallingIdentity(() -> - mDeviceManagementResourcesProvider.updateDrawables(drawables)); + mInjector.binderWithCleanCallingIdentity(() -> { + if (mDeviceManagementResourcesProvider.updateDrawables(drawables)) { + sendDrawableUpdatedBroadcast( + drawables.stream().mapToInt(d -> d.getDrawableId()).toArray()); + } + }); } - @Override public void resetDrawables(@NonNull int[] drawableIds) { Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( @@ -17989,16 +17994,31 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(drawableIds, "drawableIds must be provided."); - // TODO(b/203548565): add new broadcast to indicate a resource has changed - mInjector.binderWithCleanCallingIdentity(() -> - mDeviceManagementResourcesProvider.removeDrawables(drawableIds)); + mInjector.binderWithCleanCallingIdentity(() -> { + if (mDeviceManagementResourcesProvider.removeDrawables(drawableIds)) { + sendDrawableUpdatedBroadcast(drawableIds); + } + }); } @Override - public ParcelableResource getDrawable( - int drawableId, int drawableStyle, int drawableSource) { + public ParcelableResource getDrawable(int drawableId, int drawableStyle, int drawableSource) { return mInjector.binderWithCleanCallingIdentity(() -> mDeviceManagementResourcesProvider.getDrawable( drawableId, drawableStyle, drawableSource)); } + + private void sendDrawableUpdatedBroadcast(int[] drawableIds) { + final Intent intent = new Intent(ACTION_DEVICE_POLICY_RESOURCE_UPDATED); + intent.putExtra(EXTRA_RESOURCE_ID, drawableIds); + intent.putExtra(EXTRA_RESOURCE_TYPE_DRAWABLE, /* value= */ true); + intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); + intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + + List users = mUserManager.getAliveUsers(); + for (int i = 0; i < users.size(); i++) { + UserHandle user = users.get(i).getUserHandle(); + mContext.sendBroadcastAsUser(intent, user); + } + } }