From a676d9b3df0f496c12ceef257f4a1bc49c230b56 Mon Sep 17 00:00:00 2001 From: Xiaowen Lei Date: Tue, 16 Nov 2021 01:19:29 +0000 Subject: [PATCH] Add callback registration mechanism for listening to communal state changes. Design: go/communal-manager-api. Note: this CL also removed "_MANAGER" from the communal service constant, and "_manager" from the communal service name. When these became unhidden, ApiLint.kt suggested the changes. - http://cs/android/tools/metalava/src/main/java/com/android/tools/metalava/ApiLint.kt;l=2369-2386;rcl=0ef26a465a89c45fcfd24d648883208cd0161fd2 Ignore-AOSP-First: tied to launch of new upcoming hardware. Test: atest CtsAppTestCases:CommunalManagerTest Test: atest FrameworksMockingServicesTests:CommunalManagerServiceTest Bug: 206054365 Change-Id: I6d225d6ddb482d9cffb16d1f9a25a98abd0cc382 --- core/api/system-current.txt | 10 ++- core/api/test-current.txt | 10 +++ .../android/app/SystemServiceRegistry.java | 4 +- .../android/app/communal/CommunalManager.java | 68 ++++++++++++++++++- .../app/communal/ICommunalManager.aidl | 4 ++ .../app/communal/ICommunalModeListener.aidl | 26 +++++++ core/java/android/content/Context.java | 7 +- .../android/content/pm/PackageManager.java | 1 + core/res/AndroidManifest.xml | 8 ++- data/etc/privapp-permissions-platform.xml | 3 + packages/Shell/AndroidManifest.xml | 4 ++ .../communal/CommunalManagerService.java | 58 ++++++++++++++-- 12 files changed, 188 insertions(+), 15 deletions(-) create mode 100644 core/java/android/app/communal/ICommunalModeListener.aidl diff --git a/core/api/system-current.txt b/core/api/system-current.txt index d8cb961c1b378..5d4570f8238f5 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -224,6 +224,7 @@ package android { field public static final String READ_APP_SPECIFIC_LOCALES = "android.permission.READ_APP_SPECIFIC_LOCALES"; field public static final String READ_CARRIER_APP_INFO = "android.permission.READ_CARRIER_APP_INFO"; field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS"; + field public static final String READ_COMMUNAL_STATE = "android.permission.READ_COMMUNAL_STATE"; field public static final String READ_CONTENT_RATING_SYSTEMS = "android.permission.READ_CONTENT_RATING_SYSTEMS"; field public static final String READ_DEVICE_CONFIG = "android.permission.READ_DEVICE_CONFIG"; field public static final String READ_DREAM_STATE = "android.permission.READ_DREAM_STATE"; @@ -1312,7 +1313,13 @@ package android.app.backup { package android.app.communal { public final class CommunalManager { - method @RequiresPermission("android.permission.READ_COMMUNAL_STATE") public boolean isCommunalMode(); + method @RequiresPermission(android.Manifest.permission.READ_COMMUNAL_STATE) public void addCommunalModeListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.communal.CommunalManager.CommunalModeListener); + method @RequiresPermission(android.Manifest.permission.READ_COMMUNAL_STATE) public boolean isCommunalMode(); + method @RequiresPermission(android.Manifest.permission.READ_COMMUNAL_STATE) public void removeCommunalModeListener(@NonNull android.app.communal.CommunalManager.CommunalModeListener); + } + + @java.lang.FunctionalInterface public static interface CommunalManager.CommunalModeListener { + method public void onCommunalModeChanged(boolean); } } @@ -2499,6 +2506,7 @@ package android.content { field public static final String BATTERY_STATS_SERVICE = "batterystats"; field @Deprecated public static final int BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS = 1048576; // 0x100000 field public static final int BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND = 262144; // 0x40000 + field public static final String COMMUNAL_SERVICE = "communal"; field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions"; field public static final String CONTEXTHUB_SERVICE = "contexthub"; field public static final String ETHERNET_SERVICE = "ethernet"; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index aa791aadfdfef..bf06db05c4697 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -42,6 +42,7 @@ package android { field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC"; field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS"; field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS"; + field public static final String WRITE_COMMUNAL_STATE = "android.permission.WRITE_COMMUNAL_STATE"; field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG"; field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE"; field public static final String WRITE_OBB = "android.permission.WRITE_OBB"; @@ -608,6 +609,14 @@ package android.app.blob { } +package android.app.communal { + + public final class CommunalManager { + method @RequiresPermission(android.Manifest.permission.WRITE_COMMUNAL_STATE) public void setCommunalViewShowing(boolean); + } + +} + package android.app.contentsuggestions { public final class ContentSuggestionsManager { @@ -819,6 +828,7 @@ package android.content.pm { method public void holdLock(android.os.IBinder, int); method @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES) public void setKeepUninstalledPackages(@NonNull java.util.List); field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage"; + field public static final String FEATURE_COMMUNAL_MODE = "android.software.communal_mode"; field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption"; field public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec"; field public static final int FLAG_PERMISSION_REVOKE_WHEN_REQUESTED = 128; // 0x80 diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 089c2691a277c..81e6ae4532772 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -1513,7 +1513,7 @@ public final class SystemServiceRegistry { } }); - registerService(Context.COMMUNAL_MANAGER_SERVICE, CommunalManager.class, + registerService(Context.COMMUNAL_SERVICE, CommunalManager.class, new CachedServiceFetcher() { @Override public CommunalManager createService(ContextImpl ctx) { @@ -1522,7 +1522,7 @@ public final class SystemServiceRegistry { return null; } IBinder iBinder = - ServiceManager.getService(Context.COMMUNAL_MANAGER_SERVICE); + ServiceManager.getService(Context.COMMUNAL_SERVICE); return iBinder != null ? new CommunalManager( ICommunalManager.Stub.asInterface(iBinder)) : null; } diff --git a/core/java/android/app/communal/CommunalManager.java b/core/java/android/app/communal/CommunalManager.java index c2d2f271a8fbe..22f076930c3ca 100644 --- a/core/java/android/app/communal/CommunalManager.java +++ b/core/java/android/app/communal/CommunalManager.java @@ -17,16 +17,21 @@ package android.app.communal; import android.Manifest; +import android.annotation.NonNull; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.compat.annotation.ChangeId; import android.compat.annotation.Disabled; import android.compat.annotation.Overridable; import android.content.Context; import android.content.pm.PackageManager; import android.os.RemoteException; +import android.util.ArrayMap; + +import java.util.concurrent.Executor; /** * System private class for talking with the @@ -35,10 +40,11 @@ import android.os.RemoteException; * @hide */ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) -@SystemService(Context.COMMUNAL_MANAGER_SERVICE) +@SystemService(Context.COMMUNAL_SERVICE) @RequiresFeature(PackageManager.FEATURE_COMMUNAL_MODE) public final class CommunalManager { private final ICommunalManager mService; + private final ArrayMap mCommunalModeListeners; /** * This change id is used to annotate packages which can run in communal mode by default, @@ -64,6 +70,7 @@ public final class CommunalManager { /** @hide */ public CommunalManager(ICommunalManager service) { mService = service; + mCommunalModeListeners = new ArrayMap(); } /** @@ -73,6 +80,7 @@ public final class CommunalManager { * * @hide */ + @TestApi @RequiresPermission(Manifest.permission.WRITE_COMMUNAL_STATE) public void setCommunalViewShowing(boolean isShowing) { try { @@ -83,7 +91,7 @@ public final class CommunalManager { } /** - * Check whether or not the communal view is currently showing over the lockscreen. + * Checks whether or not the communal view is currently showing over the lockscreen. */ @RequiresPermission(Manifest.permission.READ_COMMUNAL_STATE) public boolean isCommunalMode() { @@ -93,4 +101,60 @@ public final class CommunalManager { throw e.rethrowFromSystemServer(); } } + + /** + * Listener for communal state changes. + */ + @FunctionalInterface + public interface CommunalModeListener { + /** + * Callback function that executes when the communal state changes. + */ + void onCommunalModeChanged(boolean isCommunalMode); + } + + /** + * Registers a callback to execute when the communal state changes. + * + * @param listener The listener to add to receive communal state changes. + * @param executor {@link Executor} to dispatch to. To dispatch the callback to the main + * thread of your application, use + * {@link android.content.Context#getMainExecutor()}. + */ + @RequiresPermission(Manifest.permission.READ_COMMUNAL_STATE) + public void addCommunalModeListener(@NonNull Executor executor, + @NonNull CommunalModeListener listener) { + synchronized (mCommunalModeListeners) { + try { + ICommunalModeListener iListener = new ICommunalModeListener.Stub() { + @Override + public void onCommunalModeChanged(boolean isCommunalMode) { + executor.execute(() -> listener.onCommunalModeChanged(isCommunalMode)); + } + }; + mService.addCommunalModeListener(iListener); + mCommunalModeListeners.put(listener, iListener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Unregisters a callback that executes when communal state changes. + */ + @RequiresPermission(Manifest.permission.READ_COMMUNAL_STATE) + public void removeCommunalModeListener(@NonNull CommunalModeListener listener) { + synchronized (mCommunalModeListeners) { + ICommunalModeListener iListener = mCommunalModeListeners.get(listener); + if (iListener != null) { + try { + mService.removeCommunalModeListener(iListener); + mCommunalModeListeners.remove(listener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } } diff --git a/core/java/android/app/communal/ICommunalManager.aidl b/core/java/android/app/communal/ICommunalManager.aidl index 123b23c4b5536..869891e079e05 100644 --- a/core/java/android/app/communal/ICommunalManager.aidl +++ b/core/java/android/app/communal/ICommunalManager.aidl @@ -16,6 +16,8 @@ package android.app.communal; +import android.app.communal.ICommunalModeListener; + /** * System private API for talking with the communal manager service that handles communal mode * state. @@ -25,4 +27,6 @@ package android.app.communal; interface ICommunalManager { oneway void setCommunalViewShowing(boolean isShowing); boolean isCommunalMode(); + void addCommunalModeListener(in ICommunalModeListener listener); + void removeCommunalModeListener(in ICommunalModeListener listener); } \ No newline at end of file diff --git a/core/java/android/app/communal/ICommunalModeListener.aidl b/core/java/android/app/communal/ICommunalModeListener.aidl new file mode 100644 index 0000000000000..006e782c52eeb --- /dev/null +++ b/core/java/android/app/communal/ICommunalModeListener.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2021 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.communal; + +/** + * System private API to be notified about communal mode changes. + * + * @hide + */ +oneway interface ICommunalModeListener { + void onCommunalModeChanged(boolean isCommunalMode); +} \ No newline at end of file diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 73740d2ca3849..543239bef1010 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -5862,13 +5862,14 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve a - * {@link android.app.CommunalManager} for interacting with the global system state. + * {@link android.app.communal.CommunalManager} for interacting with the global system state. * * @see #getSystemService(String) - * @see android.app.CommunalManager + * @see android.app.communal.CommunalManager * @hide */ - public static final String COMMUNAL_MANAGER_SERVICE = "communal_manager"; + @SystemApi + public static final String COMMUNAL_SERVICE = "communal"; /** * Use with {@link #getSystemService(String)} to retrieve a diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index bea536e7db487..1c35b476473cb 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3981,6 +3981,7 @@ public abstract class PackageManager { * @hide */ @SdkConstant(SdkConstantType.FEATURE) + @TestApi public static final String FEATURE_COMMUNAL_MODE = "android.software.communal_mode"; /** @hide */ diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 07cff73a841a1..854b6c6ace77e 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5531,13 +5531,15 @@ + @hide + @TestApi --> + {@link com.android.server.communal.CommunalManagerService}. + @hide + @SystemApi --> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 60cb9d321f023..81db63eaa039d 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -519,6 +519,9 @@ applications that come with the platform + + + diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 262cf536eff55..e5b5285ceacaf 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -606,6 +606,10 @@ + + + + mListeners = + new RemoteCallbackList<>(); private final ActivityInterceptorCallback mActivityInterceptorCallback = new ActivityInterceptorCallback() { @@ -129,7 +134,7 @@ public final class CommunalManagerService extends SystemService { @Override public void onStart() { - publishBinderService(Context.COMMUNAL_MANAGER_SERVICE, mBinderService); + publishBinderService(Context.COMMUNAL_SERVICE, mBinderService); } @Override @@ -242,6 +247,27 @@ public final class CommunalManagerService extends SystemService { return !isAppAllowed(appInfo); } + private void dispatchCommunalMode(boolean isShowing) { + synchronized (mListeners) { + int i = mListeners.beginBroadcast(); + while (i > 0) { + i--; + try { + mListeners.getBroadcastItem(i).onCommunalModeChanged(isShowing); + } catch (RemoteException e) { + // Handled by the RemoteCallbackList. + } + } + mListeners.finishBroadcast(); + } + } + + private void enforceReadPermission() { + mContext.enforceCallingPermission(Manifest.permission.READ_COMMUNAL_STATE, + Manifest.permission.READ_COMMUNAL_STATE + + "permission required to read communal state."); + } + private final class BinderService extends ICommunalManager.Stub { /** * Sets whether or not we are in communal mode. @@ -252,7 +278,11 @@ public final class CommunalManagerService extends SystemService { mContext.enforceCallingPermission(Manifest.permission.WRITE_COMMUNAL_STATE, Manifest.permission.WRITE_COMMUNAL_STATE + "permission required to modify communal state."); + if (mCommunalViewIsShowing.get() == isShowing) { + return; + } mCommunalViewIsShowing.set(isShowing); + dispatchCommunalMode(isShowing); } /** @@ -261,11 +291,31 @@ public final class CommunalManagerService extends SystemService { @RequiresPermission(Manifest.permission.READ_COMMUNAL_STATE) @Override public boolean isCommunalMode() { - mContext.enforceCallingPermission(Manifest.permission.READ_COMMUNAL_STATE, - Manifest.permission.READ_COMMUNAL_STATE - + "permission required to read communal state."); + enforceReadPermission(); return mCommunalViewIsShowing.get(); } + + /** + * Adds a callback to execute when communal state changes. + */ + @RequiresPermission(Manifest.permission.READ_COMMUNAL_STATE) + public void addCommunalModeListener(ICommunalModeListener listener) { + enforceReadPermission(); + synchronized (mListeners) { + mListeners.register(listener); + } + } + + /** + * Removes an added callback that execute when communal state changes. + */ + @RequiresPermission(Manifest.permission.READ_COMMUNAL_STATE) + public void removeCommunalModeListener(ICommunalModeListener listener) { + enforceReadPermission(); + synchronized (mListeners) { + mListeners.unregister(listener); + } + } } /**