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
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d8cb961..5d4570f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -224,6 +224,7 @@
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.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 @@
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 aa791aa..bf06db0 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -42,6 +42,7 @@
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.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 @@
method public void holdLock(android.os.IBinder, int);
method @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES) public void setKeepUninstalledPackages(@NonNull java.util.List<java.lang.String>);
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 089c269..81e6ae4 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -1513,7 +1513,7 @@
}
});
- registerService(Context.COMMUNAL_MANAGER_SERVICE, CommunalManager.class,
+ registerService(Context.COMMUNAL_SERVICE, CommunalManager.class,
new CachedServiceFetcher<CommunalManager>() {
@Override
public CommunalManager createService(ContextImpl ctx) {
@@ -1522,7 +1522,7 @@
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 c2d2f27..22f07693 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 @@
* @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<CommunalModeListener, ICommunalModeListener> mCommunalModeListeners;
/**
* This change id is used to annotate packages which can run in communal mode by default,
@@ -64,6 +70,7 @@
/** @hide */
public CommunalManager(ICommunalManager service) {
mService = service;
+ mCommunalModeListeners = new ArrayMap<CommunalModeListener, ICommunalModeListener>();
}
/**
@@ -73,6 +80,7 @@
*
* @hide
*/
+ @TestApi
@RequiresPermission(Manifest.permission.WRITE_COMMUNAL_STATE)
public void setCommunalViewShowing(boolean isShowing) {
try {
@@ -83,7 +91,7 @@
}
/**
- * 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 @@
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 123b23c..869891e 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 @@
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 0000000..006e782
--- /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 73740d2c..543239b 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5862,13 +5862,14 @@
/**
* 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 bea536e..1c35b47 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3981,6 +3981,7 @@
* @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 07cff73..854b6c6 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5531,13 +5531,15 @@
<!-- Allows an application to interact with the currently active
{@link com.android.server.communal.CommunalManagerService}.
- @hide -->
+ @hide
+ @TestApi -->
<permission android:name="android.permission.WRITE_COMMUNAL_STATE"
android:protectionLevel="signature" />
<!-- Allows an application to view information from the currently active
- {@link com.android.server.communal.CommunalManagerService}.
- @hide -->
+ {@link com.android.server.communal.CommunalManagerService}.
+ @hide
+ @SystemApi -->
<permission android:name="android.permission.READ_COMMUNAL_STATE"
android:protectionLevel="signature|privileged"/>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 60cb9d3..81db63e 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -519,6 +519,9 @@
<permission name="android.permission.LOCK_DEVICE" />
<!-- Permission required for CTS test - CtsSafetyCenterTestCases -->
<permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
+ <!-- Permission required for CTS test - CommunalManagerTest -->
+ <permission name="android.permission.WRITE_COMMUNAL_STATE" />
+ <permission name="android.permission.READ_COMMUNAL_STATE" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 262cf53..e5b5285 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -606,6 +606,10 @@
<!-- Permission required for CTS test - CtsSafetyCenterTestCases -->
<uses-permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
+ <!-- Permission required for CTS test - CommunalManagerTest -->
+ <uses-permission android:name="android.permission.WRITE_COMMUNAL_STATE" />
+ <uses-permission android:name="android.permission.READ_COMMUNAL_STATE" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/services/core/java/com/android/server/communal/CommunalManagerService.java b/services/core/java/com/android/server/communal/CommunalManagerService.java
index 01c0ac0..3bf6ca2 100644
--- a/services/core/java/com/android/server/communal/CommunalManagerService.java
+++ b/services/core/java/com/android/server/communal/CommunalManagerService.java
@@ -30,6 +30,7 @@
import android.app.KeyguardManager;
import android.app.PendingIntent;
import android.app.communal.ICommunalManager;
+import android.app.communal.ICommunalModeListener;
import android.app.compat.CompatChanges;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -42,6 +43,8 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.dreams.DreamManagerInternal;
@@ -77,6 +80,8 @@
private final PackageReceiver mPackageReceiver;
private final PackageManager mPackageManager;
private final DreamManagerInternal mDreamManagerInternal;
+ private final RemoteCallbackList<ICommunalModeListener> mListeners =
+ new RemoteCallbackList<>();
private final ActivityInterceptorCallback mActivityInterceptorCallback =
new ActivityInterceptorCallback() {
@@ -129,7 +134,7 @@
@Override
public void onStart() {
- publishBinderService(Context.COMMUNAL_MANAGER_SERVICE, mBinderService);
+ publishBinderService(Context.COMMUNAL_SERVICE, mBinderService);
}
@Override
@@ -242,6 +247,27 @@
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 @@
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 @@
@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);
+ }
+ }
}
/**