diff options
| -rw-r--r-- | Android.bp | 1 | ||||
| -rw-r--r-- | api/system-current.txt | 7 | ||||
| -rw-r--r-- | core/java/android/app/role/IOnRoleHoldersChangedListener.aidl | 25 | ||||
| -rw-r--r-- | core/java/android/app/role/IRoleManager.aidl | 6 | ||||
| -rw-r--r-- | core/java/android/app/role/OnRoleHoldersChangedListener.java | 38 | ||||
| -rw-r--r-- | core/java/android/app/role/RoleManager.java | 151 | ||||
| -rw-r--r-- | core/res/AndroidManifest.xml | 5 | ||||
| -rw-r--r-- | services/core/java/com/android/server/role/RoleManagerService.java | 113 | ||||
| -rw-r--r-- | services/core/java/com/android/server/role/RoleUserState.java | 68 |
9 files changed, 377 insertions, 37 deletions
diff --git a/Android.bp b/Android.bp index 4762cba7ecf9..fb018a5645c2 100644 --- a/Android.bp +++ b/Android.bp @@ -104,6 +104,7 @@ java_defaults { "core/java/android/app/backup/IRestoreObserver.aidl", "core/java/android/app/backup/IRestoreSession.aidl", "core/java/android/app/backup/ISelectBackupTransportCallback.aidl", + "core/java/android/app/role/IOnRoleHoldersChangedListener.aidl", "core/java/android/app/role/IRoleManager.aidl", "core/java/android/app/role/IRoleManagerCallback.aidl", "core/java/android/app/slice/ISliceManager.aidl", diff --git a/api/system-current.txt b/api/system-current.txt index d6fbacba1ab6..cd01cd5de370 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -136,6 +136,7 @@ package android { field public static final java.lang.String NOTIFICATION_DURING_SETUP = "android.permission.NOTIFICATION_DURING_SETUP"; field public static final java.lang.String NOTIFY_TV_INPUTS = "android.permission.NOTIFY_TV_INPUTS"; field public static final java.lang.String OBSERVE_APP_USAGE = "android.permission.OBSERVE_APP_USAGE"; + field public static final java.lang.String OBSERVE_ROLE_HOLDERS = "android.permission.OBSERVE_ROLE_HOLDERS"; field public static final java.lang.String OVERRIDE_WIFI_CONFIG = "android.permission.OVERRIDE_WIFI_CONFIG"; field public static final java.lang.String PACKAGE_USAGE_STATS = "android.permission.PACKAGE_USAGE_STATS"; field public static final java.lang.String PACKAGE_VERIFICATION_AGENT = "android.permission.PACKAGE_VERIFICATION_AGENT"; @@ -888,12 +889,18 @@ package android.app.job { package android.app.role { + public abstract interface OnRoleHoldersChangedListener { + method public abstract void onRoleHoldersChanged(java.lang.String, android.os.UserHandle); + } + public final class RoleManager { + method public void addOnRoleHoldersChangedListenerAsUser(java.util.concurrent.Executor, android.app.role.OnRoleHoldersChangedListener, android.os.UserHandle); method public void addRoleHolderAsUser(java.lang.String, java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback); method public boolean addRoleHolderFromController(java.lang.String, java.lang.String); method public void clearRoleHoldersAsUser(java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback); method public java.util.List<java.lang.String> getRoleHolders(java.lang.String); method public java.util.List<java.lang.String> getRoleHoldersAsUser(java.lang.String, android.os.UserHandle); + method public void removeOnRoleHoldersChangedListenerAsUser(android.app.role.OnRoleHoldersChangedListener, android.os.UserHandle); method public void removeRoleHolderAsUser(java.lang.String, java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback); method public boolean removeRoleHolderFromController(java.lang.String, java.lang.String); method public void setRoleNamesFromController(java.util.List<java.lang.String>); diff --git a/core/java/android/app/role/IOnRoleHoldersChangedListener.aidl b/core/java/android/app/role/IOnRoleHoldersChangedListener.aidl new file mode 100644 index 000000000000..6cf961fad2c4 --- /dev/null +++ b/core/java/android/app/role/IOnRoleHoldersChangedListener.aidl @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018 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.role; + +/** + * @hide + */ +oneway interface IOnRoleHoldersChangedListener { + + void onRoleHoldersChanged(String roleName, int userId); +} diff --git a/core/java/android/app/role/IRoleManager.aidl b/core/java/android/app/role/IRoleManager.aidl index 3ca8ec04f54e..4ce0f318bad5 100644 --- a/core/java/android/app/role/IRoleManager.aidl +++ b/core/java/android/app/role/IRoleManager.aidl @@ -16,6 +16,7 @@ package android.app.role; +import android.app.role.IOnRoleHoldersChangedListener; import android.app.role.IRoleManagerCallback; /** @@ -37,6 +38,11 @@ interface IRoleManager { void clearRoleHoldersAsUser(in String roleName, int userId, in IRoleManagerCallback callback); + void addOnRoleHoldersChangedListenerAsUser(IOnRoleHoldersChangedListener listener, int userId); + + void removeOnRoleHoldersChangedListenerAsUser(IOnRoleHoldersChangedListener listener, + int userId); + void setRoleNamesFromController(in List<String> roleNames); boolean addRoleHolderFromController(in String roleName, in String packageName); diff --git a/core/java/android/app/role/OnRoleHoldersChangedListener.java b/core/java/android/app/role/OnRoleHoldersChangedListener.java new file mode 100644 index 000000000000..5958debc86dd --- /dev/null +++ b/core/java/android/app/role/OnRoleHoldersChangedListener.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018 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.role; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.UserHandle; + +/** + * Listener for role holder changes. + * + * @hide + */ +@SystemApi +public interface OnRoleHoldersChangedListener { + + /** + * Called when the holders of roles are changed. + * + * @param roleName the name of the role whose holders are changed + * @param user the user for this role holder change + */ + void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user); +} diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java index f3b2153faabb..5d101ab479ac 100644 --- a/core/java/android/app/role/RoleManager.java +++ b/core/java/android/app/role/RoleManager.java @@ -23,6 +23,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.annotation.UserIdInt; import android.content.Context; import android.content.Intent; import android.os.Binder; @@ -30,8 +31,12 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; +import com.android.internal.util.function.pooled.PooledLambda; import java.util.List; import java.util.concurrent.Executor; @@ -125,6 +130,13 @@ public final class RoleManager { @NonNull private final IRoleManager mService; + @GuardedBy("mListenersLock") + @NonNull + private final SparseArray<ArrayMap<OnRoleHoldersChangedListener, + OnRoleHoldersChangedListenerDelegate>> mListeners = new SparseArray<>(); + @NonNull + private final Object mListenersLock = new Object(); + /** * @hide */ @@ -146,8 +158,6 @@ public final class RoleManager { * @param roleName the name of requested role * * @return the {@code Intent} to prompt user to grant the role - * - * @throws IllegalArgumentException if {@code role} is {@code null} or empty */ @NonNull public Intent createRequestRoleIntent(@NonNull String roleName) { @@ -164,8 +174,6 @@ public final class RoleManager { * @param roleName the name of role to checking for * * @return whether the role is available in the system - * - * @throws IllegalArgumentException if the role name is {@code null} or empty */ public boolean isRoleAvailable(@NonNull String roleName) { Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); @@ -182,8 +190,6 @@ public final class RoleManager { * @param roleName the name of the role to check for * * @return whether the calling application is holding the role - * - * @throws IllegalArgumentException if the role name is {@code null} or empty. */ public boolean isRoleHeld(@NonNull String roleName) { Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); @@ -204,8 +210,6 @@ public final class RoleManager { * * @return a list of package names of the role holders, or an empty list if none. * - * @throws IllegalArgumentException if the role name is {@code null} or empty. - * * @see #getRoleHoldersAsUser(String, UserHandle) * * @hide @@ -230,8 +234,6 @@ public final class RoleManager { * * @return a list of package names of the role holders, or an empty list if none. * - * @throws IllegalArgumentException if the role name is {@code null} or empty. - * * @see #addRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback) * @see #removeRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback) * @see #clearRoleHoldersAsUser(String, UserHandle, Executor, RoleManagerCallback) @@ -266,8 +268,6 @@ public final class RoleManager { * @param executor the {@code Executor} to run the callback on. * @param callback the callback for whether this call is successful * - * @throws IllegalArgumentException if the role name or package name is {@code null} or empty. - * * @see #getRoleHoldersAsUser(String, UserHandle) * @see #removeRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback) * @see #clearRoleHoldersAsUser(String, UserHandle, Executor, RoleManagerCallback) @@ -306,8 +306,6 @@ public final class RoleManager { * @param executor the {@code Executor} to run the callback on. * @param callback the callback for whether this call is successful * - * @throws IllegalArgumentException if the role name or package name is {@code null} or empty. - * * @see #getRoleHoldersAsUser(String, UserHandle) * @see #addRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback) * @see #clearRoleHoldersAsUser(String, UserHandle, Executor, RoleManagerCallback) @@ -345,8 +343,6 @@ public final class RoleManager { * @param executor the {@code Executor} to run the callback on. * @param callback the callback for whether this call is successful * - * @throws IllegalArgumentException if the role name is {@code null} or empty. - * * @see #getRoleHoldersAsUser(String, UserHandle) * @see #addRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback) * @see #removeRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback) @@ -371,6 +367,96 @@ public final class RoleManager { } /** + * Add a listener to observe role holder changes + * <p> + * <strong>Note:</strong> Using this API requires holding + * {@code android.permission.OBSERVE_ROLE_HOLDERS} and if the user id is not the current user + * {@code android.permission.INTERACT_ACROSS_USERS_FULL}. + * + * @param executor the {@code Executor} to call the listener on. + * @param listener the listener to be added + * @param user the user to add the listener for + * + * @see #removeOnRoleHoldersChangedListenerAsUser(OnRoleHoldersChangedListener, UserHandle) + * + * @hide + */ + @RequiresPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS) + @SystemApi + public void addOnRoleHoldersChangedListenerAsUser(@CallbackExecutor @NonNull Executor executor, + @NonNull OnRoleHoldersChangedListener listener, @NonNull UserHandle user) { + Preconditions.checkNotNull(executor, "executor cannot be null"); + Preconditions.checkNotNull(listener, "listener cannot be null"); + Preconditions.checkNotNull(user, "user cannot be null"); + int userId = user.getIdentifier(); + synchronized (mListenersLock) { + ArrayMap<OnRoleHoldersChangedListener, OnRoleHoldersChangedListenerDelegate> listeners = + mListeners.get(userId); + if (listeners == null) { + listeners = new ArrayMap<>(); + mListeners.put(userId, listeners); + } else { + if (listeners.containsKey(listener)) { + return; + } + } + OnRoleHoldersChangedListenerDelegate listenerDelegate = + new OnRoleHoldersChangedListenerDelegate(executor, listener); + try { + mService.addOnRoleHoldersChangedListenerAsUser(listenerDelegate, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + listeners.put(listener, listenerDelegate); + } + } + + /** + * Remove a listener observing role holder changes + * <p> + * <strong>Note:</strong> Using this API requires holding + * {@code android.permission.OBSERVE_ROLE_HOLDERS} and if the user id is not the current user + * {@code android.permission.INTERACT_ACROSS_USERS_FULL}. + * + * @param listener the listener to be removed + * @param user the user to remove the listener for + * + * @see #addOnRoleHoldersChangedListenerAsUser(Executor, OnRoleHoldersChangedListener, + * UserHandle) + * + * @hide + */ + @RequiresPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS) + @SystemApi + public void removeOnRoleHoldersChangedListenerAsUser( + @NonNull OnRoleHoldersChangedListener listener, @NonNull UserHandle user) { + Preconditions.checkNotNull(listener, "listener cannot be null"); + Preconditions.checkNotNull(user, "user cannot be null"); + int userId = user.getIdentifier(); + synchronized (mListenersLock) { + ArrayMap<OnRoleHoldersChangedListener, OnRoleHoldersChangedListenerDelegate> listeners = + mListeners.get(userId); + if (listeners == null) { + return; + } + OnRoleHoldersChangedListenerDelegate listenerDelegate = listeners.get(listener); + if (listenerDelegate == null) { + return; + } + try { + mService.removeOnRoleHoldersChangedListenerAsUser(listenerDelegate, + user.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + listeners.remove(listener); + if (listeners.isEmpty()) { + mListeners.remove(userId); + } + } + } + + /** * Set the names of all the available roles. Should only be called from * {@link android.rolecontrollerservice.RoleControllerService}. * <p> @@ -379,8 +465,6 @@ public final class RoleManager { * * @param roleNames the names of all the available roles * - * @throws IllegalArgumentException if the list of role names is {@code null}. - * * @hide */ @RequiresPermission(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER) @@ -408,8 +492,6 @@ public final class RoleManager { * @return whether the operation was successful, and will also be {@code true} if a matching * role holder is already found. * - * @throws IllegalArgumentException if the role name or package name is {@code null} or empty. - * * @see #getRoleHolders(String) * @see #removeRoleHolderFromController(String, String) * @@ -442,8 +524,6 @@ public final class RoleManager { * @return whether the operation was successful, and will also be {@code true} if no matching * role holder was found to remove. * - * @throws IllegalArgumentException if the role name or package name is {@code null} or empty. - * * @see #getRoleHolders(String) * @see #addRoleHolderFromController(String, String) * @@ -495,4 +575,31 @@ public final class RoleManager { } } } + + private static class OnRoleHoldersChangedListenerDelegate + extends IOnRoleHoldersChangedListener.Stub { + + @NonNull + private final Executor mExecutor; + @NonNull + private final OnRoleHoldersChangedListener mListener; + + OnRoleHoldersChangedListenerDelegate(@NonNull Executor executor, + @NonNull OnRoleHoldersChangedListener listener) { + mExecutor = executor; + mListener = listener; + } + + @Override + public void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) { + long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(PooledLambda.obtainRunnable( + OnRoleHoldersChangedListener::onRoleHoldersChanged, mListener, roleName, + UserHandle.of(userId))); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 597697be8854..9c55c752e552 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3350,6 +3350,11 @@ <permission android:name="android.permission.MANAGE_ROLE_HOLDERS" android:protectionLevel="signature|installer" /> + <!-- @SystemApi Allows an application to observe role holder changes. + @hide --> + <permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" + android:protectionLevel="signature|installer" /> + <!-- @SystemApi Allows an application to use SurfaceFlinger's low level features. <p>Not for use by third-party applications. @hide diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java index 8711ddf58f25..35013de6a4eb 100644 --- a/services/core/java/com/android/server/role/RoleManagerService.java +++ b/services/core/java/com/android/server/role/RoleManagerService.java @@ -22,8 +22,10 @@ import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.annotation.WorkerThread; import android.app.ActivityManager; import android.app.AppOpsManager; +import android.app.role.IOnRoleHoldersChangedListener; import android.app.role.IRoleManager; import android.app.role.IRoleManagerCallback; import android.app.role.RoleManager; @@ -33,6 +35,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManagerInternal; import android.content.pm.Signature; +import android.os.Handler; +import android.os.RemoteCallbackList; +import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.UserHandle; @@ -53,6 +58,8 @@ import com.android.internal.util.FunctionalUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.dump.DualDumpOutputStream; +import com.android.internal.util.function.pooled.PooledLambda; +import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -73,7 +80,7 @@ import java.util.concurrent.TimeoutException; * * @see RoleManager */ -public class RoleManagerService extends SystemService { +public class RoleManagerService extends SystemService implements RoleUserState.Callback { private static final String LOG_TAG = RoleManagerService.class.getSimpleName(); @@ -100,6 +107,17 @@ public class RoleManagerService extends SystemService { private final SparseArray<RemoteRoleControllerService> mControllerServices = new SparseArray<>(); + /** + * Maps user id to its list of listeners. + */ + @GuardedBy("mLock") + @NonNull + private final SparseArray<RemoteCallbackList<IOnRoleHoldersChangedListener>> mListeners = + new SparseArray<>(); + + @NonNull + private final Handler mListenerHandler = FgThread.getHandler(); + public RoleManagerService(@NonNull Context context) { super(context); @@ -188,7 +206,7 @@ public class RoleManagerService extends SystemService { } @Nullable - private String computeComponentStateHash(@UserIdInt int userId) { + private static String computeComponentStateHash(@UserIdInt int userId) { PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -223,7 +241,7 @@ public class RoleManagerService extends SystemService { synchronized (mLock) { RoleUserState userState = mUserStates.get(userId); if (userState == null) { - userState = new RoleUserState(userId); + userState = new RoleUserState(userId, this); mUserStates.put(userId, userState); } return userState; @@ -242,17 +260,70 @@ public class RoleManagerService extends SystemService { } } + @Nullable + private RemoteCallbackList<IOnRoleHoldersChangedListener> getListeners(@UserIdInt int userId) { + synchronized (mLock) { + return mListeners.get(userId); + } + } + + @NonNull + private RemoteCallbackList<IOnRoleHoldersChangedListener> getOrCreateListeners( + @UserIdInt int userId) { + synchronized (mLock) { + RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = mListeners.get(userId); + if (listeners == null) { + listeners = new RemoteCallbackList<>(); + mListeners.put(userId, listeners); + } + return listeners; + } + } + private void onRemoveUser(@UserIdInt int userId) { + RemoteCallbackList<IOnRoleHoldersChangedListener> listeners; RoleUserState userState; synchronized (mLock) { + listeners = mListeners.removeReturnOld(userId); mControllerServices.remove(userId); userState = mUserStates.removeReturnOld(userId); } + if (listeners != null) { + listeners.kill(); + } if (userState != null) { userState.destroy(); } } + @Override + public void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) { + mListenerHandler.sendMessage(PooledLambda.obtainMessage( + RoleManagerService::notifyRoleHoldersChanged, this, roleName, userId)); + } + + @WorkerThread + private void notifyRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) { + RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = getListeners(userId); + if (listeners == null) { + return; + } + + int broadcastCount = listeners.beginBroadcast(); + try { + for (int i = 0; i < broadcastCount; i++) { + IOnRoleHoldersChangedListener listener = listeners.getBroadcastItem(i); + try { + listener.onRoleHoldersChanged(roleName, userId); + } catch (RemoteException e) { + Slog.e(LOG_TAG, "Error calling OnRoleHoldersChangedListener", e); + } + } + } finally { + listeners.finishBroadcast(); + } + } + private class Stub extends IRoleManager.Stub { @Override @@ -357,6 +428,42 @@ public class RoleManagerService extends SystemService { } @Override + public void addOnRoleHoldersChangedListenerAsUser( + @NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) { + Preconditions.checkNotNull(listener, "listener cannot be null"); + if (!mUserManagerInternal.exists(userId)) { + Slog.e(LOG_TAG, "user " + userId + " does not exist"); + return; + } + userId = handleIncomingUser(userId, "addOnRoleHoldersChangedListenerAsUser"); + getContext().enforceCallingOrSelfPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS, + "addOnRoleHoldersChangedListenerAsUser"); + + RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = getOrCreateListeners( + userId); + listeners.register(listener); + } + + @Override + public void removeOnRoleHoldersChangedListenerAsUser( + @NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) { + Preconditions.checkNotNull(listener, "listener cannot be null"); + if (!mUserManagerInternal.exists(userId)) { + Slog.e(LOG_TAG, "user " + userId + " does not exist"); + return; + } + userId = handleIncomingUser(userId, "removeOnRoleHoldersChangedListenerAsUser"); + getContext().enforceCallingOrSelfPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS, + "removeOnRoleHoldersChangedListenerAsUser"); + + RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = getListeners(userId); + if (listener == null) { + return; + } + listeners.unregister(listener); + } + + @Override public void setRoleNamesFromController(@NonNull List<String> roleNames) { Preconditions.checkNotNull(roleNames, "roleNames cannot be null"); getContext().enforceCallingOrSelfPermission( diff --git a/services/core/java/com/android/server/role/RoleUserState.java b/services/core/java/com/android/server/role/RoleUserState.java index ec614a451f54..d55e261986d0 100644 --- a/services/core/java/com/android/server/role/RoleUserState.java +++ b/services/core/java/com/android/server/role/RoleUserState.java @@ -74,6 +74,9 @@ public class RoleUserState { private final int mUserId; @NonNull + private final Callback mCallback; + + @NonNull private final Object mLock = new Object(); @GuardedBy("mLock") @@ -100,12 +103,14 @@ public class RoleUserState { private final Handler mWriteHandler = new Handler(BackgroundThread.getHandler().getLooper()); /** - * Create a new instance of user state, and read its state from disk if previously persisted. + * Create a new user state, and read its state from disk if previously persisted. * - * @param userId the user id for the new user state + * @param userId the user id for this user state + * @param callback the callback for this user state */ - public RoleUserState(@UserIdInt int userId) { + public RoleUserState(@UserIdInt int userId, @NonNull Callback callback) { mUserId = userId; + mCallback = callback; readFile(); } @@ -116,6 +121,7 @@ public class RoleUserState { public int getVersion() { synchronized (mLock) { throwIfDestroyedLocked(); + return mVersion; } } @@ -128,6 +134,7 @@ public class RoleUserState { public void setVersion(int version) { synchronized (mLock) { throwIfDestroyedLocked(); + if (mVersion == version) { return; } @@ -156,6 +163,7 @@ public class RoleUserState { public void setPackagesHash(@Nullable String packagesHash) { synchronized (mLock) { throwIfDestroyedLocked(); + if (Objects.equals(mPackagesHash, packagesHash)) { return; } @@ -174,6 +182,7 @@ public class RoleUserState { public boolean isRoleAvailable(@NonNull String roleName) { synchronized (mLock) { throwIfDestroyedLocked(); + return mRoles.containsKey(roleName); } } @@ -189,6 +198,7 @@ public class RoleUserState { public ArraySet<String> getRoleHolders(@NonNull String roleName) { synchronized (mLock) { throwIfDestroyedLocked(); + return new ArraySet<>(mRoles.get(roleName)); } } @@ -201,29 +211,34 @@ public class RoleUserState { public void setRoleNames(@NonNull List<String> roleNames) { synchronized (mLock) { throwIfDestroyedLocked(); + boolean changed = false; + for (int i = mRoles.size() - 1; i >= 0; i--) { String roleName = mRoles.keyAt(i); + if (!roleNames.contains(roleName)) { ArraySet<String> packageNames = mRoles.valueAt(i); if (!packageNames.isEmpty()) { - Slog.e(LOG_TAG, - "Holders of a removed role should have been cleaned up, role: " - + roleName + ", holders: " + packageNames); + Slog.e(LOG_TAG, "Holders of a removed role should have been cleaned up," + + " role: " + roleName + ", holders: " + packageNames); } mRoles.removeAt(i); changed = true; } } + int roleNamesSize = roleNames.size(); for (int i = 0; i < roleNamesSize; i++) { String roleName = roleNames.get(i); + if (!mRoles.containsKey(roleName)) { mRoles.put(roleName, new ArraySet<>()); Slog.i(LOG_TAG, "Added new role: " + roleName); changed = true; } } + if (changed) { scheduleWriteFileLocked(); } @@ -241,20 +256,27 @@ public class RoleUserState { */ @CheckResult public boolean addRoleHolder(@NonNull String roleName, @NonNull String packageName) { + boolean changed; + synchronized (mLock) { throwIfDestroyedLocked(); + ArraySet<String> roleHolders = mRoles.get(roleName); if (roleHolders == null) { Slog.e(LOG_TAG, "Cannot add role holder for unknown role, role: " + roleName + ", package: " + packageName); return false; } - boolean changed = roleHolders.add(packageName); + changed = roleHolders.add(packageName); if (changed) { scheduleWriteFileLocked(); } - return true; } + + if (changed) { + mCallback.onRoleHoldersChanged(roleName, mUserId); + } + return true; } /** @@ -268,20 +290,28 @@ public class RoleUserState { */ @CheckResult public boolean removeRoleHolder(@NonNull String roleName, @NonNull String packageName) { + boolean changed; + synchronized (mLock) { throwIfDestroyedLocked(); + ArraySet<String> roleHolders = mRoles.get(roleName); if (roleHolders == null) { Slog.e(LOG_TAG, "Cannot remove role holder for unknown role, role: " + roleName + ", package: " + packageName); return false; } - boolean changed = roleHolders.remove(packageName); + + changed = roleHolders.remove(packageName); if (changed) { scheduleWriteFileLocked(); } - return true; } + + if (changed) { + mCallback.onRoleHoldersChanged(roleName, mUserId); + } + return true; } /** @@ -520,8 +550,8 @@ public class RoleUserState { } /** - * Destroy this state and delete the corresponding file. Any pending writes to the file will be - * cancelled and any future interaction with this state will throw an exception. + * Destroy this user state and delete the corresponding file. Any pending writes to the file + * will be cancelled, and any future interaction with this state will throw an exception. */ public void destroy() { synchronized (mLock) { @@ -542,4 +572,18 @@ public class RoleUserState { private static @NonNull File getFile(@UserIdInt int userId) { return new File(Environment.getUserSystemDirectory(userId), ROLES_FILE_NAME); } + + /** + * Callback for a user state. + */ + public interface Callback { + + /** + * Called when the holders of roles are changed. + * + * @param roleName the name of the role whose holders are changed + * @param userId the user id for this role holder change + */ + void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId); + } } |