From ca2b1f856417700daf9c10ff1267cb056429d951 Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Fri, 19 Jan 2018 20:54:20 +0100 Subject: [PATCH] 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 --- res/values/strings.xml | 5 +- res/xml/development_settings.xml | 2 +- ...lateDisplayCutoutPreferenceController.java | 113 ++++++++++++++---- .../android/content/om/IOverlayManager.java | 5 + .../src/android/content/om/OverlayInfo.java | 9 ++ ...DisplayCutoutPreferenceControllerTest.java | 94 +++++++++------ 6 files changed, 165 insertions(+), 63 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 4182fd95f3b..419394753fc 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -8681,7 +8681,10 @@ Ranking object doesn\'t contain this key. - Emulate a display with a cutout + Simulate a display with a cutout + + + None Special app access diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index 9d85ec9c9a1..9377fa0c5e8 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" /> - diff --git a/src/com/android/settings/development/EmulateDisplayCutoutPreferenceController.java b/src/com/android/settings/development/EmulateDisplayCutoutPreferenceController.java index 1035a1b9847..d6c74f911eb 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 @@ public class EmulateDisplayCutoutPreferenceController extends @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 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 @@ public class EmulateDisplayCutoutPreferenceController extends @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 cc1d0efa791..8a895e7afc4 100644 --- a/tests/robotests/src/android/content/om/IOverlayManager.java +++ b/tests/robotests/src/android/content/om/IOverlayManager.java @@ -16,10 +16,15 @@ package android.content.om; 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 98ce0910f09..fb7fef1d141 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 c9841f698ec..a6af6d6cb39 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 static org.mockito.Mockito.when; 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.Mock; 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.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 @@ public class EmulateDisplayCutoutPreferenceControllerTest { @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