diff --git a/core/api/system-current.txt b/core/api/system-current.txt index dd951b4e71d36..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"; @@ -1309,6 +1310,20 @@ package android.app.backup { } +package android.app.communal { + + public final class CommunalManager { + 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); + } + +} + package android.app.compat { public final class CompatChanges { @@ -2491,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 60730adb43075..22f076930c3ca 100644 --- a/core/java/android/app/communal/CommunalManager.java +++ b/core/java/android/app/communal/CommunalManager.java @@ -17,15 +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 @@ -33,10 +39,12 @@ import android.os.RemoteException; * * @hide */ -@SystemService(Context.COMMUNAL_MANAGER_SERVICE) +@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) +@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, @@ -59,15 +67,20 @@ public final class CommunalManager { @Disabled public static final long ALLOW_COMMUNAL_MODE_WITH_USER_CONSENT = 200324021L; + /** @hide */ public CommunalManager(ICommunalManager service) { mService = service; + mCommunalModeListeners = new ArrayMap(); } /** * Updates whether or not the communal view is currently showing over the lockscreen. * * @param isShowing Whether communal view is showing. + * + * @hide */ + @TestApi @RequiresPermission(Manifest.permission.WRITE_COMMUNAL_STATE) public void setCommunalViewShowing(boolean isShowing) { try { @@ -76,4 +89,72 @@ public final class CommunalManager { throw e.rethrowFromSystemServer(); } } + + /** + * Checks whether or not the communal view is currently showing over the lockscreen. + */ + @RequiresPermission(Manifest.permission.READ_COMMUNAL_STATE) + public boolean isCommunalMode() { + try { + return mService.isCommunalMode(); + } catch (RemoteException e) { + 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 02e8a65707102..869891e079e05 100644 --- a/core/java/android/app/communal/ICommunalManager.aidl +++ b/core/java/android/app/communal/ICommunalManager.aidl @@ -16,12 +16,17 @@ package android.app.communal; +import android.app.communal.ICommunalModeListener; + /** * System private API for talking with the communal manager service that handles communal mode * state. * * @hide */ -oneway interface ICommunalManager { - void setCommunalViewShowing(boolean isShowing); +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 1fed039942776..7018df21cb206 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5531,10 +5531,18 @@ + @hide + @TestApi --> + + + + + + 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,43 @@ 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); + } + + /** + * Checks whether or not we are in communal mode. + */ + @RequiresPermission(Manifest.permission.READ_COMMUNAL_STATE) + @Override + public boolean isCommunalMode() { + 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); + } } } diff --git a/services/tests/mockingservicestests/src/com/android/server/communal/CommunalManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/communal/CommunalManagerServiceTest.java index 17d7c51decc05..d6db1b2f2766f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/communal/CommunalManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/communal/CommunalManagerServiceTest.java @@ -123,6 +123,8 @@ public class CommunalManagerServiceTest { doNothing().when(mContextSpy).enforceCallingPermission( eq(Manifest.permission.WRITE_COMMUNAL_STATE), anyString()); + doNothing().when(mContextSpy).enforceCallingPermission( + eq(Manifest.permission.READ_COMMUNAL_STATE), anyString()); mService = new CommunalManagerService(mContextSpy); mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START); @@ -202,6 +204,18 @@ public class CommunalManagerServiceTest { return new Intent(action, Uri.parse("package:" + packageName)); } + @Test + public void testIsCommunalMode_isTrue() throws RemoteException { + mBinder.setCommunalViewShowing(true); + assertThat(mBinder.isCommunalMode()).isTrue(); + } + + @Test + public void testIsCommunalMode_isFalse() throws RemoteException { + mBinder.setCommunalViewShowing(false); + assertThat(mBinder.isCommunalMode()).isFalse(); + } + @Test public void testIntercept_unlocked_communalOff_appNotEnabled_showWhenLockedOff() { when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);