Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit abf38e32 authored by Keun young Park's avatar Keun young Park
Browse files

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
parent 2f956b53
Loading
Loading
Loading
Loading
+4 −3
Original line number Diff line number Diff line
@@ -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
+1 −1
Original line number Diff line number Diff line
@@ -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" />
+22 −2
Original line number Diff line number Diff line
@@ -405,7 +405,9 @@ final class LocalDisplayAdapter extends DisplayAdapter {
                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 @@ final class LocalDisplayAdapter extends DisplayAdapter {
                        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 @@ final class LocalDisplayAdapter extends DisplayAdapter {
            }
            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 */
+244 −0
Original line number Diff line number Diff line
/*
 * 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() {

        }
    }
}