Merge changes from topic "concurrent-keyguard" into udc-dev am: cc5495e933 am: 6b51ff203a

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23121840

Change-Id: I5f32a1cb3ffc30a23cc7fac7a96e5b10769cedfa
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 658d92c..ff423c2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -342,11 +342,22 @@
                             mRearDisplayPresentationController);
             DeviceStateRequest concurrentDisplayStateRequest = DeviceStateRequest.newBuilder(
                     mConcurrentDisplayState).build();
-            mDeviceStateManager.requestState(
-                    concurrentDisplayStateRequest,
-                    mExecutor,
-                    deviceStateCallback
-            );
+
+            try {
+                mDeviceStateManager.requestState(
+                        concurrentDisplayStateRequest,
+                        mExecutor,
+                        deviceStateCallback
+                );
+            } catch (SecurityException e) {
+                // If a SecurityException occurs when invoking DeviceStateManager#requestState
+                // (e.g. if the caller is not in the foreground, or if it does not have the required
+                // permissions), we should first clean up our local state before re-throwing the
+                // SecurityException to the caller. Otherwise, subsequent attempts to
+                // startRearDisplayPresentationSession will always fail.
+                mRearDisplayPresentationController = null;
+                throw e;
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index ec8fa92..9f21a31 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -19,27 +19,34 @@
 import android.content.Context;
 import android.graphics.Color;
 import android.graphics.Rect;
+import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.display.DisplayManager;
 import android.media.MediaRouter;
 import android.media.MediaRouter.RouteInfo;
 import android.os.Bundle;
 import android.os.Trace;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Display;
+import android.view.DisplayAddress;
 import android.view.DisplayInfo;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.util.concurrent.Executor;
 
@@ -47,6 +54,7 @@
 
 import dagger.Lazy;
 
+@SysUISingleton
 public class KeyguardDisplayManager {
     protected static final String TAG = "KeyguardDisplayManager";
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
@@ -61,6 +69,9 @@
     private boolean mShowing;
     private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
 
+    private final DeviceStateHelper mDeviceStateHelper;
+    private final KeyguardStateController mKeyguardStateController;
+
     private final SparseArray<Presentation> mPresentations = new SparseArray<>();
 
     private final DisplayTracker.Callback mDisplayCallback =
@@ -92,7 +103,9 @@
             KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
             DisplayTracker displayTracker,
             @Main Executor mainExecutor,
-            @UiBackground Executor uiBgExecutor) {
+            @UiBackground Executor uiBgExecutor,
+            DeviceStateHelper deviceStateHelper,
+            KeyguardStateController keyguardStateController) {
         mContext = context;
         mNavigationBarControllerLazy = navigationBarControllerLazy;
         mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
@@ -100,6 +113,8 @@
         mDisplayService = mContext.getSystemService(DisplayManager.class);
         mDisplayTracker = displayTracker;
         mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor);
+        mDeviceStateHelper = deviceStateHelper;
+        mKeyguardStateController = keyguardStateController;
     }
 
     private boolean isKeyguardShowable(Display display) {
@@ -122,6 +137,18 @@
             }
             return false;
         }
+        if (mKeyguardStateController.isOccluded()
+                && mDeviceStateHelper.isConcurrentDisplayActive(display)) {
+            if (DEBUG) {
+                // When activities with FLAG_SHOW_WHEN_LOCKED are shown on top of Keyguard, the
+                // Keyguard state becomes "occluded". In this case, we should not show the
+                // KeyguardPresentation, since the activity is presenting content onto the
+                // non-default display.
+                Log.i(TAG, "Do not show KeyguardPresentation when occluded and concurrent"
+                        + " display is active");
+            }
+            return false;
+        }
 
         return true;
     }
@@ -260,6 +287,53 @@
 
     }
 
+    /**
+     * Helper used to receive device state info from {@link DeviceStateManager}.
+     */
+    static class DeviceStateHelper implements DeviceStateManager.DeviceStateCallback {
+
+        @Nullable
+        private final DisplayAddress.Physical mRearDisplayPhysicalAddress;
+
+        // TODO(b/271317597): These device states should be defined in DeviceStateManager
+        private final int mConcurrentState;
+        private boolean mIsInConcurrentDisplayState;
+
+        @Inject
+        DeviceStateHelper(Context context,
+                DeviceStateManager deviceStateManager,
+                @Main Executor mainExecutor) {
+
+            final String rearDisplayPhysicalAddress = context.getResources().getString(
+                    com.android.internal.R.string.config_rearDisplayPhysicalAddress);
+            if (TextUtils.isEmpty(rearDisplayPhysicalAddress)) {
+                mRearDisplayPhysicalAddress = null;
+            } else {
+                mRearDisplayPhysicalAddress = DisplayAddress
+                        .fromPhysicalDisplayId(Long.parseLong(rearDisplayPhysicalAddress));
+            }
+
+            mConcurrentState = context.getResources().getInteger(
+                    com.android.internal.R.integer.config_deviceStateConcurrentRearDisplay);
+            deviceStateManager.registerCallback(mainExecutor, this);
+        }
+
+        @Override
+        public void onStateChanged(int state) {
+            // When concurrent state ends, the display also turns off. This is enforced in various
+            // ExtensionRearDisplayPresentationTest CTS tests. So, we don't need to invoke
+            // hide() since that will happen through the onDisplayRemoved callback.
+            mIsInConcurrentDisplayState = state == mConcurrentState;
+        }
+
+        boolean isConcurrentDisplayActive(Display display) {
+            return mIsInConcurrentDisplayState
+                    && mRearDisplayPhysicalAddress != null
+                    && mRearDisplayPhysicalAddress.equals(display.getAddress());
+        }
+    }
+
+
     @VisibleForTesting
     static final class KeyguardPresentation extends Presentation {
         private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
index 30e3d09..b349696 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.hardware.display.DisplayManagerGlobal;
 import android.testing.AndroidTestingRunner;
@@ -38,6 +39,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.settings.FakeDisplayTracker;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -58,6 +60,10 @@
     private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
     @Mock
     private KeyguardDisplayManager.KeyguardPresentation mKeyguardPresentation;
+    @Mock
+    private KeyguardDisplayManager.DeviceStateHelper mDeviceStateHelper;
+    @Mock
+    private KeyguardStateController mKeyguardStateController;
 
     private Executor mMainExecutor = Runnable::run;
     private Executor mBackgroundExecutor = Runnable::run;
@@ -76,7 +82,7 @@
         MockitoAnnotations.initMocks(this);
         mManager = spy(new KeyguardDisplayManager(mContext, () -> mNavigationBarController,
                 mKeyguardStatusViewComponentFactory, mDisplayTracker, mMainExecutor,
-                mBackgroundExecutor));
+                mBackgroundExecutor, mDeviceStateHelper, mKeyguardStateController));
         doReturn(mKeyguardPresentation).when(mManager).createPresentation(any());
 
         mDefaultDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
@@ -123,4 +129,13 @@
         mManager.show();
         verify(mManager, times(1)).createPresentation(eq(mSecondaryDisplay));
     }
+
+    @Test
+    public void testShow_concurrentDisplayActive_occluded() {
+        mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay});
+
+        when(mDeviceStateHelper.isConcurrentDisplayActive(mSecondaryDisplay)).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
+        verify(mManager, never()).createPresentation(eq(mSecondaryDisplay));
+    }
 }
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 9645690..5d0177b 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.devicestate;
 
 import static android.Manifest.permission.CONTROL_DEVICE_STATE;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
@@ -73,6 +74,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 import java.util.WeakHashMap;
@@ -162,7 +164,7 @@
     @GuardedBy("mLock")
     private final SparseArray<ProcessRecord> mProcessRecords = new SparseArray<>();
 
-    private Set<Integer> mDeviceStatesAvailableForAppRequests;
+    private Set<Integer> mDeviceStatesAvailableForAppRequests = new HashSet<>();
 
     private Set<Integer> mFoldedDeviceStates;
 
@@ -879,8 +881,16 @@
      * @param callingPid Process ID that is requesting this state change
      * @param state state that is being requested.
      */
-    private void assertCanRequestDeviceState(int callingPid, int state) {
-        if (!isTopApp(callingPid) || !isStateAvailableForAppRequests(state)) {
+    private void assertCanRequestDeviceState(int callingPid, int callingUid, int state) {
+        final boolean isTopApp = isTopApp(callingPid);
+        final boolean isForegroundApp = isForegroundApp(callingPid, callingUid);
+        final boolean isStateAvailableForAppRequests = isStateAvailableForAppRequests(state);
+
+        final boolean canRequestState = isTopApp
+                && isForegroundApp
+                && isStateAvailableForAppRequests;
+
+        if (!canRequestState) {
             getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
                     "Permission required to request device state, "
                             + "or the call must come from the top app "
@@ -893,15 +903,43 @@
      * not the top app, then check if this process holds the CONTROL_DEVICE_STATE permission.
      *
      * @param callingPid Process ID that is requesting this state change
+     * @param callingUid UID that is requesting this state change
      */
-    private void assertCanControlDeviceState(int callingPid) {
-        if (!isTopApp(callingPid)) {
+    private void assertCanControlDeviceState(int callingPid, int callingUid) {
+        final boolean isTopApp = isTopApp(callingPid);
+        final boolean isForegroundApp = isForegroundApp(callingPid, callingUid);
+
+        final boolean canControlState = isTopApp && isForegroundApp;
+
+        if (!canControlState) {
             getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
                     "Permission required to request device state, "
                             + "or the call must come from the top app.");
         }
     }
 
+    /**
+     * Checks if the caller is in the foreground. Note that callers may be the top app as returned
+     * from {@link #isTopApp(int)}, but not be in the foreground. For example, keyguard may be on
+     * top of the top app.
+     */
+    private boolean isForegroundApp(int callingPid, int callingUid) {
+        try {
+            final List<ActivityManager.RunningAppProcessInfo> procs =
+                    ActivityManager.getService().getRunningAppProcesses();
+            for (int i = 0; i < procs.size(); i++) {
+                ActivityManager.RunningAppProcessInfo proc = procs.get(i);
+                if (proc.pid == callingPid && proc.uid == callingUid
+                        && proc.importance <= IMPORTANCE_FOREGROUND) {
+                    return true;
+                }
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "am.getRunningAppProcesses() failed", e);
+        }
+        return false;
+    }
+
     private boolean isTopApp(int callingPid) {
         final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
         return topApp != null && topApp.getPid() == callingPid;
@@ -918,7 +956,6 @@
      */
     @GuardedBy("mLock")
     private void readStatesAvailableForRequestFromApps() {
-        mDeviceStatesAvailableForAppRequests = new HashSet<>();
         String[] availableAppStatesConfigIdentifiers = getContext().getResources()
                 .getStringArray(R.array.config_deviceStatesAvailableForAppRequests);
         for (int i = 0; i < availableAppStatesConfigIdentifiers.length; i++) {
@@ -1118,7 +1155,7 @@
             // Allow top processes to request a device state change
             // If the calling process ID is not the top app, then we check if this process
             // holds a permission to CONTROL_DEVICE_STATE
-            assertCanRequestDeviceState(callingPid, state);
+            assertCanRequestDeviceState(callingPid, callingUid, state);
 
             if (token == null) {
                 throw new IllegalArgumentException("Request token must not be null.");
@@ -1139,10 +1176,11 @@
         @Override // Binder call
         public void cancelStateRequest() {
             final int callingPid = Binder.getCallingPid();
+            final int callingUid = Binder.getCallingUid();
             // Allow top processes to cancel a device state change
             // If the calling process ID is not the top app, then we check if this process
             // holds a permission to CONTROL_DEVICE_STATE
-            assertCanControlDeviceState(callingPid);
+            assertCanControlDeviceState(callingPid, callingUid);
 
             final long callingIdentity = Binder.clearCallingIdentity();
             try {