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 @@ + + +