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);
+            }
+        }
     }
 
     /**