Allow per display level private flag control for physical secondary displays

- Deperecate config_localDisplaysPrivate which enables private flag for all
  secondary displays.
- Private display should be listed in config_localPrivateDisplayPorts array
  with port address to be marked as private display. Secondary displays not
  listed will remain as public displays.
- Use case: In env like car, display like instrument cluster should be only
  allowed to trusted system apps (=private display) while other non-main
  display like non-passenger side display can be accessed by 3rd party apps
  (=public display). This CL allows selectively enabling private display flag.

Bug: 131113705
Test: Run added tests
Change-Id: I60c4b6e3b2c9c841da7986c084593c530eb817bd
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 21a8f4c..04ccb74 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2542,9 +2542,10 @@
          mirror the content of the default display. -->
     <bool name="config_localDisplaysMirrorContent">true</bool>
 
-    <!-- Indicates whether local non-default displays are private.
-         {@see android.view.Display#FLAG_PRIVATE} -->
-    <bool name="config_localDisplaysPrivate">false</bool>
+    <!-- Controls if local secondary displays should be private or not. Value specified in the array
+         represents physical port address of each display and display in this list will be marked
+         as private. {@see android.view.Display#FLAG_PRIVATE} -->
+    <integer-array translatable="false" name="config_localPrivateDisplayPorts"></integer-array>
 
     <!-- The default mode for the default display. One of the following values (See Display.java):
              0 - COLOR_MODE_DEFAULT
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7cf03fea..bb560d3 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -394,7 +394,7 @@
   <java-symbol type="bool" name="config_supportsInsecureLockScreen" />
   <java-symbol type="bool" name="config_guestUserEphemeral" />
   <java-symbol type="bool" name="config_localDisplaysMirrorContent" />
-  <java-symbol type="bool" name="config_localDisplaysPrivate" />
+  <java-symbol type="array" name="config_localPrivateDisplayPorts" />
   <java-symbol type="integer" name="config_defaultDisplayDefaultColorMode" />
   <java-symbol type="bool" name="config_enableAppWidgetService" />
   <java-symbol type="string" name="config_defaultPictureInPictureScreenEdgeInsets" />
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 5e5ef26..85fbdf6 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -405,7 +405,9 @@
                 mInfo.presentationDeadlineNanos = phys.presentationDeadlineNanos;
                 mInfo.state = mState;
                 mInfo.uniqueId = getUniqueId();
-                mInfo.address = DisplayAddress.fromPhysicalDisplayId(mPhysicalDisplayId);
+                final DisplayAddress.Physical physicalAddress =
+                        DisplayAddress.fromPhysicalDisplayId(mPhysicalDisplayId);
+                mInfo.address = physicalAddress;
 
                 // Assume that all built-in displays that have secure output (eg. HDCP) also
                 // support compositing from gralloc protected buffers.
@@ -462,7 +464,7 @@
                         mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
                     }
 
-                    if (res.getBoolean(com.android.internal.R.bool.config_localDisplaysPrivate)) {
+                    if (isDisplayPrivate(physicalAddress)) {
                         mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE;
                     }
                 }
@@ -797,6 +799,24 @@
             }
             return modes;
         }
+
+        private boolean isDisplayPrivate(DisplayAddress.Physical physicalAddress) {
+            if (physicalAddress == null) {
+                return false;
+            }
+            final Resources res = getOverlayContext().getResources();
+            int[] ports = res.getIntArray(
+                    com.android.internal.R.array.config_localPrivateDisplayPorts);
+            if (ports != null) {
+                int port = physicalAddress.getPort();
+                for (int p : ports) {
+                    if (p == port) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
     }
 
     /** Supplies a context whose Resources apply runtime-overlays */
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
new file mode 100644
index 0000000..9e255fe
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2019 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 com.android.server.display;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.view.DisplayAddress;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.server.LocalServices;
+import com.android.server.lights.LightsManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LocalDisplayAdapterTest {
+    private static final long HANDLER_WAIT_MS = 100;
+
+    private static final int PHYSICAL_DISPLAY_ID_MODEL_SHIFT = 8;
+
+    private StaticMockitoSession mMockitoSession;
+
+    private LocalDisplayAdapter mAdapter;
+
+    @Mock
+    private DisplayManagerService.SyncRoot mMockedSyncRoot;
+    @Mock
+    private Context mMockedContext;
+    @Mock
+    private Resources mMockedResources;
+    @Mock
+    private LightsManager mMockedLightsManager;
+
+    private Handler mHandler;
+
+    private TestListener mListener = new TestListener();
+
+    private LinkedList<Long> mDisplayIds = new LinkedList<>();
+
+    @Before
+    public void setUp() throws Exception {
+        mMockitoSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(SurfaceControl.class)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+        mHandler = new Handler(Looper.getMainLooper());
+        doReturn(mMockedResources).when(mMockedContext).getResources();
+        LocalServices.removeServiceForTest(LightsManager.class);
+        LocalServices.addService(LightsManager.class, mMockedLightsManager);
+        mAdapter = new LocalDisplayAdapter(mMockedSyncRoot, mMockedContext, mHandler,
+                mListener);
+        spyOn(mAdapter);
+        doReturn(mMockedContext).when(mAdapter).getOverlayContext();
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockitoSession != null) {
+            mMockitoSession.finishMocking();
+        }
+    }
+
+    /**
+     * Confirm that display is marked as private when it is listed in
+     * com.android.internal.R.array.config_localPrivateDisplayPorts.
+     */
+    @Test
+    public void testPrivateDisplay() throws Exception {
+        // needs default one always
+        final long displayId0 = 0;
+        setUpDisplay(new DisplayConfig(displayId0, createDummyDisplayInfo()));
+        final long displayId1 = 1;
+        setUpDisplay(new DisplayConfig(displayId1, createDummyDisplayInfo()));
+        final long displayId2 = 2;
+        setUpDisplay(new DisplayConfig(displayId2, createDummyDisplayInfo()));
+        updateAvailableDisplays();
+        // display 1 should be marked as private while display 2 is not.
+        doReturn(new int[]{(int) displayId1}).when(mMockedResources)
+                .getIntArray(com.android.internal.R.array.config_localPrivateDisplayPorts);
+        mAdapter.registerLocked();
+
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        // This should be public
+        assertDisplay(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(), displayId0,
+                false);
+        // This should be private
+        assertDisplay(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(), displayId1,
+                true);
+        // This should be public
+        assertDisplay(mListener.addedDisplays.get(2).getDisplayDeviceInfoLocked(), displayId2,
+                false);
+    }
+
+    /**
+     * Confirm that all local displays are public when config_localPrivateDisplayPorts is empty.
+     */
+    @Test
+    public void testPublicDisplaysForNoConfigLocalPrivateDisplayPorts() throws Exception {
+        // needs default one always
+        final long displayId0 = 0;
+        setUpDisplay(new DisplayConfig(displayId0, createDummyDisplayInfo()));
+        final long displayId1 = 1;
+        setUpDisplay(new DisplayConfig(displayId1, createDummyDisplayInfo()));
+        updateAvailableDisplays();
+        // config_localPrivateDisplayPorts is null
+        mAdapter.registerLocked();
+
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        // This should be public
+        assertDisplay(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(), displayId0,
+                false);
+        // This should be public
+        assertDisplay(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(), displayId1,
+                false);
+    }
+
+    private void assertDisplay(DisplayDeviceInfo info, long expectedPort, boolean shouldBePrivate) {
+        DisplayAddress.Physical physical = (DisplayAddress.Physical) info.address;
+        assertNotNull(physical);
+        assertEquals(expectedPort, physical.getPort());
+        assertEquals(shouldBePrivate, (info.flags & DisplayDeviceInfo.FLAG_PRIVATE) != 0);
+    }
+
+    private class DisplayConfig {
+        public final long displayId;
+        public final IBinder displayToken = new Binder();
+        public final SurfaceControl.PhysicalDisplayInfo displayInfo;
+
+        private DisplayConfig(long displayId, SurfaceControl.PhysicalDisplayInfo displayInfo) {
+            this.displayId = displayId | (0x1 << PHYSICAL_DISPLAY_ID_MODEL_SHIFT);
+            this.displayInfo = displayInfo;
+        }
+    }
+
+    private void setUpDisplay(DisplayConfig config) {
+        mDisplayIds.add(config.displayId);
+        doReturn(config.displayToken).when(
+                () -> SurfaceControl.getPhysicalDisplayToken(config.displayId));
+        doReturn(new SurfaceControl.PhysicalDisplayInfo[]{
+                config.displayInfo
+        }).when(() -> SurfaceControl.getDisplayConfigs(config.displayToken));
+        doReturn(0).when(() -> SurfaceControl.getActiveConfig(config.displayToken));
+        doReturn(0).when(() -> SurfaceControl.getActiveColorMode(config.displayToken));
+        doReturn(new int[]{
+                0
+        }).when(() -> SurfaceControl.getDisplayColorModes(config.displayToken));
+        doReturn(new int[]{
+                0
+        }).when(() -> SurfaceControl.getAllowedDisplayConfigs(config.displayToken));
+    }
+
+    private void updateAvailableDisplays() {
+        long[] ids = new long[mDisplayIds.size()];
+        int i = 0;
+        for (long id : mDisplayIds) {
+            ids[i] = id;
+            i++;
+        }
+        doReturn(ids).when(() -> SurfaceControl.getPhysicalDisplayIds());
+    }
+
+    private SurfaceControl.PhysicalDisplayInfo createDummyDisplayInfo() {
+        SurfaceControl.PhysicalDisplayInfo info = new SurfaceControl.PhysicalDisplayInfo();
+        info.density = 100;
+        info.xDpi = 100;
+        info.yDpi = 100;
+        info.secure = false;
+        info.width = 800;
+        info.height = 600;
+
+        return info;
+    }
+
+    private void waitForHandlerToComplete(Handler handler, long waitTimeMs)
+            throws InterruptedException {
+        final Object lock = new Object();
+        synchronized (lock) {
+            handler.post(() -> {
+                synchronized (lock) {
+                    lock.notify();
+                }
+            });
+            lock.wait(waitTimeMs);
+        }
+    }
+
+    private class TestListener implements DisplayAdapter.Listener {
+        public ArrayList<DisplayDevice> addedDisplays = new ArrayList<>();
+
+        @Override
+        public void onDisplayDeviceEvent(DisplayDevice device, int event) {
+            if (event == DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED) {
+                addedDisplays.add(device);
+            }
+        }
+
+        @Override
+        public void onTraversalRequested() {
+
+        }
+    }
+}