DisplayCutout: Add support for multiple cutout emulation options

Instead of a single emulation option, users can select from a list
of different styles of cutouts.

Bug: 65689439
Test: atest EmulateDisplayCutoutPreferenceControllerTest
Change-Id: I75598254849c11d9973f2b9cfdbec117bc3957da
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4182fd9..4193947 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8681,7 +8681,10 @@
     <string name="notification_log_details_ranking_none">Ranking object doesn\'t contain this key.</string>
 
     <!-- [CHAR_LIMIT=NONE] Developer Settings: Title of the setting which turns on emulation of a display cutout. -->
-    <string name="display_cutout_emulation">Emulate a display with a cutout</string>
+    <string name="display_cutout_emulation">Simulate a display with a cutout</string>
+
+    <!-- [CHAR_LIMIT=NONE] Developer Settings: Label for the option that turns off display cutout emulation. -->
+    <string name="display_cutout_emulation_none">None</string>
 
     <!-- [CHAR_LIMIT=60] Label for special access screen -->
     <string name="special_access">Special app access</string>
diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml
index 9d85ec9..9377fa0 100644
--- a/res/xml/development_settings.xml
+++ b/res/xml/development_settings.xml
@@ -346,7 +346,7 @@
             android:key="density"
             android:title="@string/developer_smallest_width" />
 
-        <SwitchPreference
+        <ListPreference
             android:key="display_cutout_emulation"
             android:title="@string/display_cutout_emulation" />
 
diff --git a/src/com/android/settings/development/EmulateDisplayCutoutPreferenceController.java b/src/com/android/settings/development/EmulateDisplayCutoutPreferenceController.java
index 1035a1b..d6c74f9 100644
--- a/src/com/android/settings/development/EmulateDisplayCutoutPreferenceController.java
+++ b/src/com/android/settings/development/EmulateDisplayCutoutPreferenceController.java
@@ -16,41 +16,52 @@
 
 package com.android.settings.development;
 
+import static android.os.UserHandle.USER_SYSTEM;
+
 import android.content.Context;
 import android.content.om.IOverlayManager;
 import android.content.om.OverlayInfo;
+import android.content.pm.PackageManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.UserHandle;
 import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.ListPreference;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceScreen;
-import android.support.v7.preference.TwoStatePreference;
+import android.text.TextUtils;
 
+import com.android.internal.util.ArrayUtils;
+import com.android.settings.R;
 import com.android.settings.core.PreferenceControllerMixin;
 import com.android.settingslib.development.DeveloperOptionsPreferenceController;
 
+import java.util.List;
+
 public class EmulateDisplayCutoutPreferenceController extends
         DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener,
         PreferenceControllerMixin {
 
-    private static final String EMULATION_OVERLAY = "com.android.internal.display.cutout.emulation";
+    public static final String EMULATION_OVERLAY_PREFIX =
+            "com.android.internal.display.cutout.emulation.";
     private static final String KEY = "display_cutout_emulation";
 
     private final IOverlayManager mOverlayManager;
     private final boolean mAvailable;
 
-    private TwoStatePreference mPreference;
+    private ListPreference mPreference;
+    private PackageManager mPackageManager;
 
     @VisibleForTesting
-    EmulateDisplayCutoutPreferenceController(Context context, IOverlayManager overlayManager) {
+    EmulateDisplayCutoutPreferenceController(Context context, PackageManager packageManager,
+            IOverlayManager overlayManager) {
         super(context);
         mOverlayManager = overlayManager;
-        mAvailable = overlayManager != null && getEmulationOverlayInfo() != null;
+        mPackageManager = packageManager;
+        mAvailable = overlayManager != null && getOverlayInfos().length > 0;
     }
 
     public EmulateDisplayCutoutPreferenceController(Context context) {
-        this(context, IOverlayManager.Stub.asInterface(
+        this(context, context.getPackageManager(), IOverlayManager.Stub.asInterface(
                 ServiceManager.getService(Context.OVERLAY_SERVICE)));
     }
 
@@ -67,45 +78,95 @@
     @Override
     public void displayPreference(PreferenceScreen screen) {
         super.displayPreference(screen);
-        setPreference((TwoStatePreference) screen.findPreference(getPreferenceKey()));
+        setPreference((ListPreference) screen.findPreference(getPreferenceKey()));
     }
 
     @VisibleForTesting
-    void setPreference(TwoStatePreference preference) {
+    void setPreference(ListPreference preference) {
         mPreference = preference;
     }
 
     @Override
     public boolean onPreferenceChange(Preference preference, Object newValue) {
-        return writeEnabled((boolean) newValue);
+        return setEmulationOverlay((String) newValue);
     }
 
-    private boolean writeEnabled(boolean newValue) {
-        OverlayInfo current = getEmulationOverlayInfo();
-        if (current == null || current.isEnabled() == newValue) {
-            return false;
+    private boolean setEmulationOverlay(String packageName) {
+        OverlayInfo[] overlays = getOverlayInfos();
+        CharSequence currentPackageName = null;
+        for (OverlayInfo o : overlays) {
+            if (o.isEnabled()) {
+                currentPackageName = o.packageName;
+            }
         }
-        try {
-            return mOverlayManager.setEnabled(EMULATION_OVERLAY, newValue, UserHandle.USER_SYSTEM);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+
+        if (TextUtils.isEmpty(packageName) && TextUtils.isEmpty(currentPackageName)
+                || TextUtils.equals(packageName, currentPackageName)) {
+            // Already set.
+            return true;
         }
+
+        for (OverlayInfo o : overlays) {
+            boolean isEnabled = o.isEnabled();
+            boolean shouldBeEnabled = TextUtils.equals(o.packageName, packageName);
+            if (isEnabled != shouldBeEnabled) {
+                try {
+                    mOverlayManager.setEnabled(o.packageName, shouldBeEnabled, USER_SYSTEM);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+        updateState(mPreference);
+        return true;
     }
 
     @Override
     public void updateState(Preference preference) {
-        OverlayInfo overlayInfo = getEmulationOverlayInfo();
-        mPreference.setChecked(overlayInfo != null && overlayInfo.isEnabled());
+        OverlayInfo[] overlays = getOverlayInfos();
+
+        CharSequence[] pkgs = new CharSequence[overlays.length + 1];
+        CharSequence[] labels = new CharSequence[pkgs.length];
+
+        int current = 0;
+        pkgs[0] = "";
+        labels[0] = mContext.getString(R.string.display_cutout_emulation_none);
+
+        for (int i = 0; i < overlays.length; i++) {
+            OverlayInfo o = overlays[i];
+            pkgs[i+1] = o.packageName;
+            if (o.isEnabled()) {
+                current = i+1;
+            }
+        }
+        for (int i = 1; i < pkgs.length; i++) {
+            try {
+                labels[i] = mPackageManager.getApplicationInfo(pkgs[i].toString(), 0)
+                        .loadLabel(mPackageManager);
+            } catch (PackageManager.NameNotFoundException e) {
+                labels[i] = pkgs[i];
+            }
+        }
+
+        mPreference.setEntries(labels);
+        mPreference.setEntryValues(pkgs);
+        mPreference.setValueIndex(current);
+        mPreference.setSummary(labels[current]);
     }
 
-    private OverlayInfo getEmulationOverlayInfo() {
-        OverlayInfo overlayInfo = null;
+    private OverlayInfo[] getOverlayInfos() {
         try {
-            overlayInfo = mOverlayManager.getOverlayInfo(EMULATION_OVERLAY, UserHandle.USER_SYSTEM);
+            @SuppressWarnings("unchecked") List<OverlayInfo> overlayInfos =
+                    mOverlayManager.getOverlayInfosForTarget("android", USER_SYSTEM);
+            for (int i = overlayInfos.size() - 1; i >= 0; i--) {
+                if (!overlayInfos.get(i).packageName.startsWith(EMULATION_OVERLAY_PREFIX)) {
+                    overlayInfos.remove(i);
+                }
+            }
+            return overlayInfos.toArray(new OverlayInfo[overlayInfos.size()]);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        return overlayInfo;
     }
 
     @Override
@@ -115,8 +176,8 @@
 
     @Override
     protected void onDeveloperOptionsSwitchDisabled() {
-        writeEnabled(false);
-        mPreference.setChecked(false);
+        setEmulationOverlay("");
+        updateState(mPreference);
         mPreference.setEnabled(false);
     }
 }
diff --git a/tests/robotests/src/android/content/om/IOverlayManager.java b/tests/robotests/src/android/content/om/IOverlayManager.java
index cc1d0ef..8a895e7 100644
--- a/tests/robotests/src/android/content/om/IOverlayManager.java
+++ b/tests/robotests/src/android/content/om/IOverlayManager.java
@@ -16,10 +16,15 @@
 
 import android.os.IBinder;
 
+import java.util.ArrayList;
+import java.util.LinkedList;
+
 public interface IOverlayManager {
 
     public OverlayInfo getOverlayInfo(String packageName, int userId);
 
+    public java.util.List getOverlayInfosForTarget(java.lang.String targetPackageName, int userId);
+
     public boolean setEnabled(java.lang.String packageName, boolean enable, int userId);
 
     public static class Stub {
diff --git a/tests/robotests/src/android/content/om/OverlayInfo.java b/tests/robotests/src/android/content/om/OverlayInfo.java
index 98ce091..fb7fef1 100644
--- a/tests/robotests/src/android/content/om/OverlayInfo.java
+++ b/tests/robotests/src/android/content/om/OverlayInfo.java
@@ -14,8 +14,17 @@
 
 package android.content.om;
 
+import android.annotation.NonNull;
+
 public class OverlayInfo {
 
+    public final String packageName;
+
+    public OverlayInfo(@NonNull String packageName, @NonNull String targetPackageName,
+            @NonNull String baseCodePath, int state, int userId) {
+        this.packageName = packageName;
+    }
+
     public boolean isEnabled() {
         return false;
     }
diff --git a/tests/robotests/src/com/android/settings/development/EmulateDisplayCutoutPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/EmulateDisplayCutoutPreferenceControllerTest.java
index c9841f6..a6af6d6 100644
--- a/tests/robotests/src/com/android/settings/development/EmulateDisplayCutoutPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/development/EmulateDisplayCutoutPreferenceControllerTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settings.development;
 
+import static com.android.settings.development.EmulateDisplayCutoutPreferenceController
+        .EMULATION_OVERLAY_PREFIX;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -29,7 +32,8 @@
 import android.content.Context;
 import android.content.om.IOverlayManager;
 import android.content.om.OverlayInfo;
-import android.support.v7.preference.TwoStatePreference;
+import android.content.pm.PackageManager;
+import android.support.v7.preference.ListPreference;
 
 import com.android.settings.TestConfig;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
@@ -41,78 +45,95 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.annotation.Config;
 
+import java.util.Arrays;
+
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public class EmulateDisplayCutoutPreferenceControllerTest {
 
+    static final OverlayInfo ONE_DISABLED =
+            new FakeOverlay(EMULATION_OVERLAY_PREFIX + ".one", false);
+    static final OverlayInfo ONE_ENABLED =
+            new FakeOverlay(EMULATION_OVERLAY_PREFIX + ".one", true);
+    static final OverlayInfo TWO_DISABLED =
+            new FakeOverlay(EMULATION_OVERLAY_PREFIX + ".two", false);
+    static final OverlayInfo TWO_ENABLED =
+            new FakeOverlay(EMULATION_OVERLAY_PREFIX + ".two", true);
+
     @Mock Context mContext;
     @Mock IOverlayManager mOverlayManager;
-    @Mock TwoStatePreference mPreference;
+    @Mock PackageManager mPackageManager;
+    @Mock ListPreference mPreference;
     EmulateDisplayCutoutPreferenceController mController;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        when(mOverlayManager.getOverlayInfo(any(), anyInt())).thenReturn(DISABLED);
-        mController = new EmulateDisplayCutoutPreferenceController(mContext, mOverlayManager);
+        mockCurrentOverlays();
+        when(mPackageManager.getApplicationInfo(any(), anyInt())).thenThrow(
+                PackageManager.NameNotFoundException.class);
+        mController = createController();
         mController.setPreference(mPreference);
     }
 
+    Object mockCurrentOverlays(OverlayInfo... overlays) {
+        return when(mOverlayManager.getOverlayInfosForTarget(eq("android"), anyInt()))
+                .thenReturn(Arrays.<OverlayInfo>asList(overlays));
+    }
+
     @Test
     public void isAvailable_true() throws Exception {
-        when(mOverlayManager.getOverlayInfo(any(), anyInt())).thenReturn(DISABLED);
+        mockCurrentOverlays(ONE_DISABLED, TWO_DISABLED);
 
-        assertThat(new EmulateDisplayCutoutPreferenceController(mContext, mOverlayManager)
-                .isAvailable()).isTrue();
+        assertThat(createController().isAvailable()).isTrue();
     }
 
     @Test
     public void isAvailable_false() throws Exception {
-        when(mOverlayManager.getOverlayInfo(any(), anyInt())).thenReturn(null);
+        mockCurrentOverlays();
 
-        assertThat(new EmulateDisplayCutoutPreferenceController(mContext, mOverlayManager)
-                .isAvailable()).isFalse();
+        assertThat(createController().isAvailable()).isFalse();
     }
 
     @Test
     public void onPreferenceChange_enable() throws Exception {
-        when(mOverlayManager.getOverlayInfo(any(), anyInt())).thenReturn(DISABLED);
+        mockCurrentOverlays(ONE_DISABLED, TWO_DISABLED);
 
-        mController.onPreferenceChange(null, true);
+        mController.onPreferenceChange(null, TWO_DISABLED.packageName);
 
-        verify(mOverlayManager).setEnabled(any(), eq(true), anyInt());
+        verify(mOverlayManager).setEnabled(eq(TWO_DISABLED.packageName), eq(true), anyInt());
     }
 
     @Test
     public void onPreferenceChange_disable() throws Exception {
-        when(mOverlayManager.getOverlayInfo(any(), anyInt())).thenReturn(ENABLED);
+        mockCurrentOverlays(ONE_DISABLED, TWO_ENABLED);
 
-        mController.onPreferenceChange(null, false);
+        mController.onPreferenceChange(null, "");
 
-        verify(mOverlayManager).setEnabled(any(), eq(false), anyInt());
+        verify(mOverlayManager).setEnabled(eq(TWO_ENABLED.packageName), eq(false), anyInt());
     }
 
     @Test
     public void updateState_enabled() throws Exception {
-        when(mOverlayManager.getOverlayInfo(any(), anyInt())).thenReturn(ENABLED);
+        mockCurrentOverlays(ONE_DISABLED, TWO_ENABLED);
 
         mController.updateState(null);
 
-        verify(mPreference).setChecked(true);
+        verify(mPreference).setValueIndex(2);
     }
 
     @Test
     public void updateState_disabled() throws Exception {
-        when(mOverlayManager.getOverlayInfo(any(), anyInt())).thenReturn(DISABLED);
+        mockCurrentOverlays(ONE_DISABLED, TWO_DISABLED);
 
         mController.updateState(null);
 
-        verify(mPreference).setChecked(false);
+        verify(mPreference).setValueIndex(0);
     }
 
     @Test
     public void onDeveloperOptionsSwitchEnabled() throws Exception {
-        when(mOverlayManager.getOverlayInfo(any(), anyInt())).thenReturn(DISABLED);
+        mockCurrentOverlays();
 
         mController.onDeveloperOptionsSwitchEnabled();
 
@@ -122,27 +143,30 @@
 
     @Test
     public void onDeveloperOptionsSwitchDisabled() throws Exception {
-        when(mOverlayManager.getOverlayInfo(any(), anyInt())).thenReturn(ENABLED);
+        mockCurrentOverlays(ONE_ENABLED, TWO_DISABLED);
 
         mController.onDeveloperOptionsSwitchDisabled();
 
         verify(mPreference).setEnabled(false);
-        verify(mPreference).setChecked(false);
-        verify(mOverlayManager).setEnabled(any(), eq(false), anyInt());
+        verify(mOverlayManager).setEnabled(eq(ONE_ENABLED.packageName), eq(false), anyInt());
     }
 
-    static final OverlayInfo ENABLED = new OverlayInfo() {
+    private EmulateDisplayCutoutPreferenceController createController() {
+        return new EmulateDisplayCutoutPreferenceController(mContext, mPackageManager,
+                mOverlayManager);
+    }
+
+    private static class FakeOverlay extends OverlayInfo {
+        private final boolean mEnabled;
+
+        public FakeOverlay(String pkg, boolean enabled) {
+            super(pkg, "android", "/", 0, 0);
+            mEnabled = enabled;
+        }
+
         @Override
         public boolean isEnabled() {
-            return true;
+            return mEnabled;
         }
-    };
-
-    static final OverlayInfo DISABLED = new OverlayInfo() {
-        @Override
-        public boolean isEnabled() {
-            return false;
-        }
-    };
-
+    }
 }
\ No newline at end of file