From 31d06ba6b38316abead0208a39ed219f018960f3 Mon Sep 17 00:00:00 2001 From: Hai Zhang Date: Thu, 6 Dec 2018 18:14:42 -0800 Subject: [PATCH] Add listeners to observe role holders changes. This change adds the ability to add listeners to observe role holder changes. This will be used by the new role management UI and other system components that used to put the default app in settings and observe settings change. Bug: 110557011 Test: manual Change-Id: I2a8eb39220081e3be801adb970b60c55ebc297c7 --- Android.bp | 1 + api/system-current.txt | 7 + .../role/IOnRoleHoldersChangedListener.aidl | 25 +++ core/java/android/app/role/IRoleManager.aidl | 6 + .../role/OnRoleHoldersChangedListener.java | 38 +++++ core/java/android/app/role/RoleManager.java | 151 +++++++++++++++--- core/res/AndroidManifest.xml | 5 + .../server/role/RoleManagerService.java | 113 ++++++++++++- .../android/server/role/RoleUserState.java | 68 ++++++-- 9 files changed, 377 insertions(+), 37 deletions(-) create mode 100644 core/java/android/app/role/IOnRoleHoldersChangedListener.aidl create mode 100644 core/java/android/app/role/OnRoleHoldersChangedListener.java diff --git a/Android.bp b/Android.bp index 2c5fc4e11e39..a4d3d33926ae 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 7d30529a9ab1..b5344b47b11f 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -134,6 +134,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"; @@ -884,12 +885,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 getRoleHolders(java.lang.String); method public java.util.List 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); 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 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> 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) @@ -370,6 +366,96 @@ public final class RoleManager { } } + /** + * Add a listener to observe role holder changes + *

+ * Note: 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 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 + *

+ * Note: 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 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}. @@ -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 1dd42b8162b3..996b662269e3 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3344,6 +3344,11 @@ + + +