From e53d1caedc876351ee68dccd48c3187686b5ae6e Mon Sep 17 00:00:00 2001 From: Svet Ganov Date: Mon, 2 Oct 2017 18:58:08 -0700 Subject: [PATCH 01/10] Use new no overlay flag instead app ops Test: updated DrawOverlayDetailsTest passes bug:66936547 Change-Id: I7c17c95a2b44fd2ffd593a6eb03a16be034e9192 --- .../applications/DrawOverlayDetails.java | 32 ++++------- .../settings/core/TouchOverlayManager.java | 40 ------------- ...otificationAccessConfirmationActivity.java | 32 +++++------ .../applications/DrawOverlayDetailsTest.java | 56 +++++++++++-------- ...ceFragment.java => ShadowAppInfoBase.java} | 10 ++-- 5 files changed, 62 insertions(+), 108 deletions(-) delete mode 100644 src/com/android/settings/core/TouchOverlayManager.java rename tests/robotests/src/com/android/settings/testutils/shadow/{ShadowPreferenceFragment.java => ShadowAppInfoBase.java} (81%) diff --git a/src/com/android/settings/applications/DrawOverlayDetails.java b/src/com/android/settings/applications/DrawOverlayDetails.java index c6f3cc0c1e6..78f1c082042 100644 --- a/src/com/android/settings/applications/DrawOverlayDetails.java +++ b/src/com/android/settings/applications/DrawOverlayDetails.java @@ -29,12 +29,13 @@ import android.support.v7.preference.Preference.OnPreferenceChangeListener; import android.support.v7.preference.Preference.OnPreferenceClickListener; import android.util.Log; +import android.view.Window; +import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.applications.AppStateAppOpsBridge.PermissionState; import com.android.settings.applications.AppStateOverlayBridge.OverlayState; -import com.android.settings.core.TouchOverlayManager; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.applications.ApplicationsState.AppEntry; @@ -60,8 +61,6 @@ public class DrawOverlayDetails extends AppInfoWithHeader implements OnPreferenc private Intent mSettingsIntent; private OverlayState mOverlayState; - private TouchOverlayManager mTouchOverlayManager; - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -69,7 +68,6 @@ public class DrawOverlayDetails extends AppInfoWithHeader implements OnPreferenc Context context = getActivity(); mOverlayBridge = new AppStateOverlayBridge(context, mState, null); mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); - mTouchOverlayManager = new TouchOverlayManager(context); // find preferences addPreferencesFromResource(R.xml.app_ops_permissions_details); @@ -92,17 +90,17 @@ public class DrawOverlayDetails extends AppInfoWithHeader implements OnPreferenc } @Override - public void onStart() { - super.onStart(); - - mTouchOverlayManager.setOverlayAllowed(false); + public void onResume() { + super.onResume(); + getActivity().getWindow().addFlags( + WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); } @Override - public void onStop() { - super.onStop(); - - mTouchOverlayManager.setOverlayAllowed(true); + public void onPause() { + getActivity().getWindow().clearFlags( + WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + super.onPause(); } @Override @@ -153,16 +151,6 @@ public class DrawOverlayDetails extends AppInfoWithHeader implements OnPreferenc .getMetricsFeatureProvider().action(getContext(), logCategory, packageName); } - private boolean canDrawOverlay(String pkgName) { - int result = mAppOpsManager.noteOpNoThrow(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, - mPackageInfo.applicationInfo.uid, pkgName); - if (result == AppOpsManager.MODE_ALLOWED) { - return true; - } - - return false; - } - @Override protected boolean refreshUi() { mOverlayState = mOverlayBridge.getOverlayInfo(mPackageName, diff --git a/src/com/android/settings/core/TouchOverlayManager.java b/src/com/android/settings/core/TouchOverlayManager.java deleted file mode 100644 index f69d1bfbb6c..00000000000 --- a/src/com/android/settings/core/TouchOverlayManager.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2017 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.settings.core; - -import android.app.AppOpsManager; -import android.content.Context; -import android.os.Binder; -import android.os.IBinder; - -public class TouchOverlayManager { - - private final Context mContext; - private final IBinder mToken = new Binder(); - - public TouchOverlayManager(Context context) { - mContext = context; - } - - public void setOverlayAllowed(boolean allowed) { - final AppOpsManager aom = mContext.getSystemService(AppOpsManager.class); - if (aom != null) { - aom.setUserRestriction(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, !allowed, mToken); - aom.setUserRestriction(AppOpsManager.OP_TOAST_WINDOW, !allowed, mToken); - } - } -} diff --git a/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java b/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java index 6a13282285d..f9eabb94994 100644 --- a/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java +++ b/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java @@ -35,15 +35,13 @@ import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.os.Bundle; import android.os.UserHandle; -import android.provider.Settings; -import android.provider.SettingsStringUtil; import android.util.Slog; +import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import com.android.internal.app.AlertActivity; import com.android.internal.app.AlertController; import com.android.settings.R; -import com.android.settings.core.TouchOverlayManager; /** @hide */ public class NotificationAccessConfirmationActivity extends Activity @@ -54,14 +52,12 @@ public class NotificationAccessConfirmationActivity extends Activity private int mUserId; private ComponentName mComponentName; - private TouchOverlayManager mTouchOverlayManager; private NotificationManager mNm; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mTouchOverlayManager = new TouchOverlayManager(this); mNm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); mComponentName = getIntent().getParcelableExtra(EXTRA_COMPONENT_NAME); @@ -84,6 +80,20 @@ public class NotificationAccessConfirmationActivity extends Activity .installContent(p); } + @Override + public void onResume() { + super.onResume(); + getWindow().addFlags( + WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + } + + @Override + public void onPause() { + getWindow().clearFlags( + WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + super.onPause(); + } + private void onAllow() { String requiredPermission = Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE; try { @@ -121,16 +131,4 @@ public class NotificationAccessConfirmationActivity extends Activity finish(); } } - - @Override - protected void onResume() { - super.onResume(); - mTouchOverlayManager.setOverlayAllowed(false); - } - - @Override - protected void onPause() { - super.onPause(); - mTouchOverlayManager.setOverlayAllowed(true); - } } diff --git a/tests/robotests/src/com/android/settings/applications/DrawOverlayDetailsTest.java b/tests/robotests/src/com/android/settings/applications/DrawOverlayDetailsTest.java index 6122576bb3e..5d20a4c9cc3 100644 --- a/tests/robotests/src/com/android/settings/applications/DrawOverlayDetailsTest.java +++ b/tests/robotests/src/com/android/settings/applications/DrawOverlayDetailsTest.java @@ -19,52 +19,59 @@ package com.android.settings.applications; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.nullable; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + +import android.app.Activity; import android.content.Context; +import android.view.Window; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; -import com.android.settings.core.TouchOverlayManager; import com.android.settings.testutils.FakeFeatureFactory; -import com.android.settings.testutils.shadow.ShadowPreferenceFragment; +import com.android.settings.testutils.shadow.ShadowAppInfoBase; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; +import org.mockito.InOrder; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowApplication; -import org.robolectric.util.ReflectionHelpers; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class DrawOverlayDetailsTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private Context mContext; - - private FakeFeatureFactory mFeatureFactory; - private DrawOverlayDetails mFragment; + private Activity mActivity; @Mock - private TouchOverlayManager mTouchOverlayManager; + private Window mWindow; + + private FakeFeatureFactory mFeatureFactory; + + @Spy + private DrawOverlayDetails mFragment; @Before public void setUp() { MockitoAnnotations.initMocks(this); - FakeFeatureFactory.setupForTest(mContext); - mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext); - - mFragment = new DrawOverlayDetails(); - ReflectionHelpers.setField(mFragment, "mTouchOverlayManager", mTouchOverlayManager); + FakeFeatureFactory.setupForTest(mActivity); + mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mActivity); } @Test public void logSpecialPermissionChange() { - mFragment.onAttach(ShadowApplication.getInstance().getApplicationContext()); + when(mFragment.getContext()).thenReturn( + ShadowApplication.getInstance().getApplicationContext()); + mFragment.logSpecialPermissionChange(true, "app"); verify(mFeatureFactory.metricsFeatureProvider).action(nullable(Context.class), eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_APPDRAW_ALLOW), eq("app")); @@ -75,16 +82,17 @@ public class DrawOverlayDetailsTest { } @Test - @Config(shadows = ShadowPreferenceFragment.class) - public void onStart_disableOverlay() { - mFragment.onStart(); - verify(mTouchOverlayManager).setOverlayAllowed(false); - } + @Config(shadows = {ShadowAppInfoBase.class}) + public void hideNonSystemOverlaysWhenResumed() { + when(mFragment.getActivity()).thenReturn(mActivity); + when(mActivity.getWindow()).thenReturn(mWindow); - @Test - @Config(shadows = ShadowPreferenceFragment.class) - public void onStop_enableOverlay() { - mFragment.onStop(); - verify(mTouchOverlayManager).setOverlayAllowed(true); + mFragment.onResume(); + mFragment.onPause(); + + InOrder inOrder = Mockito.inOrder(mWindow); + inOrder.verify(mWindow).addFlags(PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + inOrder.verify(mWindow).clearFlags(PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + inOrder.verifyNoMoreInteractions(); } } diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowPreferenceFragment.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAppInfoBase.java similarity index 81% rename from tests/robotests/src/com/android/settings/testutils/shadow/ShadowPreferenceFragment.java rename to tests/robotests/src/com/android/settings/testutils/shadow/ShadowAppInfoBase.java index cfd0ce931d6..12173c8f1de 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowPreferenceFragment.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAppInfoBase.java @@ -16,20 +16,20 @@ package com.android.settings.testutils.shadow; -import android.support.v14.preference.PreferenceFragment; +import com.android.settings.applications.AppInfoBase; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; -@Implements(PreferenceFragment.class) -public class ShadowPreferenceFragment { +@Implements(AppInfoBase.class) +public class ShadowAppInfoBase { @Implementation - public void onStart() { + public void onResume() { // No-op. } @Implementation - public void onStop() { + public void onPause() { // No-op. } } From bb4ef4d1b631cfb37ae4a50777a10efe8470b497 Mon Sep 17 00:00:00 2001 From: Salvador Martinez Date: Mon, 2 Oct 2017 18:17:29 -0700 Subject: [PATCH 02/10] Update support to not need dialog fragment This CL update support to not need the dialog fragment. Test: robotests Bug: 65552746 Change-Id: Ic4a21f05f04384795f4e616c2d6d914fc7dd3510 --- src/com/android/settings/support/SupportDashboardActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/settings/support/SupportDashboardActivity.java b/src/com/android/settings/support/SupportDashboardActivity.java index 819d5f72f44..d3fcf9a8a28 100644 --- a/src/com/android/settings/support/SupportDashboardActivity.java +++ b/src/com/android/settings/support/SupportDashboardActivity.java @@ -47,8 +47,8 @@ public class SupportDashboardActivity extends Activity implements Indexable { supportFeatureProvider.startSupportV2(this); } else { startActivity(new Intent(this, LegacySupportActivity.class)); - finish(); } + finish(); } /** From 464da0b267f952aad9e6e75b79f6be8fd6344654 Mon Sep 17 00:00:00 2001 From: jeffreyhuang Date: Tue, 3 Oct 2017 11:44:21 -0700 Subject: [PATCH 03/10] Introduce FileEncryptionPreferenceController - Create new FileEncryptionPreferenceController - Create controller inside the DashboardFragment - Port logic from DevelopmentSettings into the controller Bug: 34203528 Test: make RunSettingsRoboTests -j40 Change-Id: I19f68fa719442bad03fee7ab3939510095257dee --- .../DevelopmentSettingsDashboardFragment.java | 2 +- .../FileEncryptionPreferenceController.java | 105 ++++++++++++++ ...ileEncryptionPreferenceControllerTest.java | 134 ++++++++++++++++++ 3 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 src/com/android/settings/development/FileEncryptionPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/development/FileEncryptionPreferenceControllerTest.java diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index c8ba546584d..94e75ce8a7a 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -244,7 +244,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra controllers.add(new BluetoothSnoopLogPreferenceController(context)); controllers.add(new OemUnlockPreferenceController(context, activity, fragment)); // running services - // convert to file encryption + controllers.add(new FileEncryptionPreferenceController(context)); controllers.add(new PictureColorModePreferenceController(context, lifecycle)); // webview implementation controllers.add(new CoolColorTemperaturePreferenceController(context)); diff --git a/src/com/android/settings/development/FileEncryptionPreferenceController.java b/src/com/android/settings/development/FileEncryptionPreferenceController.java new file mode 100644 index 00000000000..463bb708581 --- /dev/null +++ b/src/com/android/settings/development/FileEncryptionPreferenceController.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2017 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.settings.development; + +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemProperties; +import android.os.storage.IStorageManager; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.text.TextUtils; + +import com.android.settings.R; + +public class FileEncryptionPreferenceController extends DeveloperOptionsPreferenceController { + + private static final String KEY_CONVERT_FBE = "convert_to_file_encryption"; + private static final String KEY_STORAGE_MANAGER = "mount"; + + @VisibleForTesting + static final String FILE_ENCRYPTION_PROPERTY_KEY = "ro.crypto.type"; + + private final IStorageManager mStorageManager; + + private Preference mPreference; + + public FileEncryptionPreferenceController(Context context) { + super(context); + + mStorageManager = getStorageManager(); + } + + @Override + public boolean isAvailable() { + if (mStorageManager == null) { + return false; + } + + try { + return mStorageManager.isConvertibleToFBE(); + } catch (RemoteException e) { + return false; + } + } + + @Override + public String getPreferenceKey() { + return KEY_CONVERT_FBE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + mPreference = screen.findPreference(getPreferenceKey()); + } + + @Override + public void updateState(Preference preference) { + if (!TextUtils.equals("file", + SystemProperties.get(FILE_ENCRYPTION_PROPERTY_KEY, "none" /* default */))) { + return; + } + + mPreference.setEnabled(false); + mPreference.setSummary( + mContext.getResources().getString(R.string.convert_to_file_encryption_done)); + } + + @Override + protected void onDeveloperOptionsSwitchEnabled() { + // intentional no-op + } + + @Override + protected void onDeveloperOptionsSwitchDisabled() { + // intentional no-op + } + + private IStorageManager getStorageManager() { + try { + return IStorageManager.Stub.asInterface( + ServiceManager.getService(KEY_STORAGE_MANAGER)); + } catch (VerifyError e) { + // Used for tests since Robolectric cannot initialize this class. + return null; + } + } +} \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/development/FileEncryptionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/FileEncryptionPreferenceControllerTest.java new file mode 100644 index 00000000000..1810b11288a --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/FileEncryptionPreferenceControllerTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2017 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.settings.development; + +import static com.android.settings.development.FileEncryptionPreferenceController + .FILE_ENCRYPTION_PROPERTY_KEY; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.os.storage.IStorageManager; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.SettingsShadowSystemProperties; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, + sdk = TestConfig.SDK_VERSION, + shadows = {SettingsShadowSystemProperties.class}) +public class FileEncryptionPreferenceControllerTest { + + @Mock + private Preference mPreference; + @Mock + private PreferenceScreen mPreferenceScreen; + @Mock + private IStorageManager mStorageManager; + + private Context mContext; + private FileEncryptionPreferenceController mController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mController = new FileEncryptionPreferenceController(mContext); + when(mPreferenceScreen.findPreference(mController.getPreferenceKey())).thenReturn( + mPreference); + } + + @After + public void teardown() { + SettingsShadowSystemProperties.clear(); + } + + @Test + public void isAvailable_storageManagerNull_shouldBeFalse() { + ReflectionHelpers.setField(mController, "mStorageManager", null); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_notConvertibleToFBE_shouldBeFalse() throws RemoteException { + ReflectionHelpers.setField(mController, "mStorageManager", mStorageManager); + when(mStorageManager.isConvertibleToFBE()).thenReturn(false); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_convertibleToFBE_shouldBeTrue() throws RemoteException { + ReflectionHelpers.setField(mController, "mStorageManager", mStorageManager); + when(mStorageManager.isConvertibleToFBE()).thenReturn(true); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void updateState_settingIsNotFile_shouldDoNothing() throws RemoteException { + ReflectionHelpers.setField(mController, "mStorageManager", mStorageManager); + when(mStorageManager.isConvertibleToFBE()).thenReturn(true); + mController.displayPreference(mPreferenceScreen); + SystemProperties.set(FILE_ENCRYPTION_PROPERTY_KEY, "foobar"); + + mController.updateState(mPreference); + + verify(mPreference, never()).setEnabled(anyBoolean()); + verify(mPreference, never()).setSummary(anyString()); + } + + @Test + public void updateState_settingIsFile_shouldSetSummaryAndDisablePreference() + throws RemoteException { + ReflectionHelpers.setField(mController, "mStorageManager", mStorageManager); + when(mStorageManager.isConvertibleToFBE()).thenReturn(true); + mController.displayPreference(mPreferenceScreen); + SystemProperties.set(FILE_ENCRYPTION_PROPERTY_KEY, "file"); + + mController.updateState(mPreference); + + verify(mPreference).setEnabled(false); + verify(mPreference).setSummary( + mContext.getResources().getString(R.string.convert_to_file_encryption_done)); + } +} + + From 7f578fb1ef3e845f2c7810b2bdaf759b648ed1bf Mon Sep 17 00:00:00 2001 From: jeffreyhuang Date: Tue, 3 Oct 2017 14:53:27 -0700 Subject: [PATCH 04/10] Introduce BugReportPreferenceControllerV2 - Create new BugReportPreferenceControllerV2 - Deprecated BugReportPreferenceController - Create controller inside the DashboardFragment - Port logic from DevelopmentSettings into the controller Bug: 34203528 Test: make RunSettingsRoboTests -j40 Change-Id: Ic932bdd8c05f75af5a9390fb4372fb4bbc7d2340 --- .../BugReportPreferenceController.java | 4 ++ .../BugReportPreferenceControllerV2.java | 53 +++++++++++++++ .../DevelopmentSettingsDashboardFragment.java | 2 +- .../BugReportPreferenceControllerTest.java | 4 ++ .../BugReportPreferenceControllerV2Test.java | 68 +++++++++++++++++++ 5 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 src/com/android/settings/development/BugReportPreferenceControllerV2.java create mode 100644 tests/robotests/src/com/android/settings/development/BugReportPreferenceControllerV2Test.java diff --git a/src/com/android/settings/development/BugReportPreferenceController.java b/src/com/android/settings/development/BugReportPreferenceController.java index 015071fdb3c..c05dd260b39 100644 --- a/src/com/android/settings/development/BugReportPreferenceController.java +++ b/src/com/android/settings/development/BugReportPreferenceController.java @@ -24,6 +24,10 @@ import android.support.v7.preference.PreferenceScreen; import com.android.settings.core.PreferenceControllerMixin; import com.android.settingslib.core.AbstractPreferenceController; +/** + * deprecated in favor of {@link BugReportPreferenceControllerV2} + */ +@Deprecated public class BugReportPreferenceController extends AbstractPreferenceController implements PreferenceControllerMixin { diff --git a/src/com/android/settings/development/BugReportPreferenceControllerV2.java b/src/com/android/settings/development/BugReportPreferenceControllerV2.java new file mode 100644 index 00000000000..7df23a6752b --- /dev/null +++ b/src/com/android/settings/development/BugReportPreferenceControllerV2.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2016 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.settings.development; + +import android.content.Context; +import android.os.UserManager; + +public class BugReportPreferenceControllerV2 extends DeveloperOptionsPreferenceController { + + private static final String KEY_BUGREPORT = "bugreport"; + + private final UserManager mUserManager; + + public BugReportPreferenceControllerV2(Context context) { + super(context); + + mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + } + + @Override + public boolean isAvailable() { + return !mUserManager.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES); + } + + @Override + public String getPreferenceKey() { + return KEY_BUGREPORT; + } + + @Override + protected void onDeveloperOptionsSwitchEnabled() { + // intentional no-op + } + + @Override + protected void onDeveloperOptionsSwitchDisabled() { + // intentional no-op + } +} diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index 2d9c5f37935..e71c50030e4 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -245,7 +245,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra private static List buildPreferenceControllers(Context context, Activity activity, Lifecycle lifecycle, DevelopmentSettingsDashboardFragment fragment) { final List controllers = new ArrayList<>(); - // take bug report + controllers.add(new BugReportPreferenceControllerV2(context)); controllers.add(new LocalBackupPasswordPreferenceController(context)); controllers.add(new StayAwakePreferenceController(context, lifecycle)); // hdcp checking diff --git a/tests/robotests/src/com/android/settings/development/BugReportPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/BugReportPreferenceControllerTest.java index 3b3b482fa41..d705610bcd1 100644 --- a/tests/robotests/src/com/android/settings/development/BugReportPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/BugReportPreferenceControllerTest.java @@ -39,6 +39,10 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +/** + * deprecated in favor of {@link BugReportPreferenceControllerV2} + */ +@Deprecated @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class BugReportPreferenceControllerTest { diff --git a/tests/robotests/src/com/android/settings/development/BugReportPreferenceControllerV2Test.java b/tests/robotests/src/com/android/settings/development/BugReportPreferenceControllerV2Test.java new file mode 100644 index 00000000000..9b4cde63652 --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/BugReportPreferenceControllerV2Test.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016 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.settings.development; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.UserManager; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class BugReportPreferenceControllerV2Test { + + @Mock + private Context mContext; + @Mock + private UserManager mUserManager; + + private BugReportPreferenceControllerV2 mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + mController = new BugReportPreferenceControllerV2(mContext); + } + + @Test + public void isAvailable_hasDebugRestriction_shouldBeFalse() { + when(mUserManager.hasUserRestriction(anyString())).thenReturn(true); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_noDebugRestriction_shouldBeTrue() { + when(mUserManager.hasUserRestriction(anyString())).thenReturn(false); + + assertThat(mController.isAvailable()).isTrue(); + } +} From 2d1ab9a193f2a4b681d0ab20d0dbfbaf644df00e Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Tue, 3 Oct 2017 11:16:44 -0700 Subject: [PATCH 05/10] Display a list of feature flags Bug: 36222960 Test: robotests Change-Id: I31fbe7f4d42e72846aa4f025ebcf8ea8a1b6d2fd --- .../featureflags/FeatureFlagPreference.java | 45 +++++++++++ .../featureflags/FeatureFlagsDashboard.java | 5 +- .../FeatureFlagsPreferenceController.java | 81 +++++++++++++++++++ .../src/android/util/FeatureFlagUtils.java | 18 +++++ .../FeatureFlagPreferenceControllerTest.java | 72 +++++++++++++++++ .../FeatureFlagPreferenceTest.java | 61 ++++++++++++++ 6 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 src/com/android/settings/development/featureflags/FeatureFlagPreference.java create mode 100644 src/com/android/settings/development/featureflags/FeatureFlagsPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/development/featureflags/FeatureFlagPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/development/featureflags/FeatureFlagPreferenceTest.java diff --git a/src/com/android/settings/development/featureflags/FeatureFlagPreference.java b/src/com/android/settings/development/featureflags/FeatureFlagPreference.java new file mode 100644 index 00000000000..80851d35dc3 --- /dev/null +++ b/src/com/android/settings/development/featureflags/FeatureFlagPreference.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017 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.settings.development.featureflags; + +import android.content.Context; +import android.support.v14.preference.SwitchPreference; +import android.util.FeatureFlagUtils; + +public class FeatureFlagPreference extends SwitchPreference { + + private final String mKey; + + public FeatureFlagPreference(Context context, String key) { + super(context); + mKey = key; + setKey(key); + setTitle(key); + setCheckedInternal(FeatureFlagUtils.isEnabled(mKey)); + } + + @Override + public void setChecked(boolean isChecked) { + setCheckedInternal(isChecked); + FeatureFlagUtils.setEnabled(mKey, isChecked); + } + + private void setCheckedInternal(boolean isChecked) { + super.setChecked(isChecked); + setSummary(Boolean.toString(isChecked)); + } +} diff --git a/src/com/android/settings/development/featureflags/FeatureFlagsDashboard.java b/src/com/android/settings/development/featureflags/FeatureFlagsDashboard.java index ee2258d25fe..998e4312c85 100644 --- a/src/com/android/settings/development/featureflags/FeatureFlagsDashboard.java +++ b/src/com/android/settings/development/featureflags/FeatureFlagsDashboard.java @@ -23,6 +23,7 @@ import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.core.AbstractPreferenceController; +import java.util.ArrayList; import java.util.List; public class FeatureFlagsDashboard extends DashboardFragment { @@ -51,6 +52,8 @@ public class FeatureFlagsDashboard extends DashboardFragment { @Override protected List getPreferenceControllers(Context context) { - return null; + final List controllers = new ArrayList<>(); + controllers.add(new FeatureFlagsPreferenceController(context, getLifecycle())); + return controllers; } } diff --git a/src/com/android/settings/development/featureflags/FeatureFlagsPreferenceController.java b/src/com/android/settings/development/featureflags/FeatureFlagsPreferenceController.java new file mode 100644 index 00000000000..7c00591c02f --- /dev/null +++ b/src/com/android/settings/development/featureflags/FeatureFlagsPreferenceController.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017 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.settings.development.featureflags; + +import android.content.Context; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.util.FeatureFlagUtils; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; + +import java.util.Map; + +public class FeatureFlagsPreferenceController extends AbstractPreferenceController + implements PreferenceControllerMixin, LifecycleObserver, OnStart { + + private PreferenceScreen mScreen; + + public FeatureFlagsPreferenceController(Context context, Lifecycle lifecycle) { + super(context); + if (lifecycle != null) { + lifecycle.addObserver(this); + } + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public String getPreferenceKey() { + return null; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mScreen = screen; + } + + @Override + public void onStart() { + if (mScreen == null) { + return; + } + final Map featureMap = FeatureFlagUtils.getAllFeatureFlags(); + if (featureMap == null) { + return; + } + mScreen.removeAll(); + final Context prefContext = mScreen.getContext(); + for (String prefixedFeature : featureMap.keySet()) { + if (prefixedFeature.startsWith(FeatureFlagUtils.FFLAG_PREFIX) + && !prefixedFeature.startsWith(FeatureFlagUtils.FFLAG_OVERRIDE_PREFIX)) { + final String feature = prefixedFeature.substring( + FeatureFlagUtils.FFLAG_PREFIX.length()); + final Preference pref = new FeatureFlagPreference(prefContext, feature); + mScreen.addPreference(pref); + } + } + } +} diff --git a/tests/robotests/src/android/util/FeatureFlagUtils.java b/tests/robotests/src/android/util/FeatureFlagUtils.java index 6bc0557a2b7..500884a6c3b 100644 --- a/tests/robotests/src/android/util/FeatureFlagUtils.java +++ b/tests/robotests/src/android/util/FeatureFlagUtils.java @@ -19,6 +19,9 @@ package android.util; import android.os.SystemProperties; import android.text.TextUtils; +import java.util.HashMap; +import java.util.Map; + /** * This class is only needed to get around Robolectric issue. */ @@ -43,4 +46,19 @@ public class FeatureFlagUtils { value = SystemProperties.get(FFLAG_PREFIX + feature); return Boolean.parseBoolean(value); } + + /** + * Override feature flag to new state. + */ + public static void setEnabled(String feature, boolean enabled) { + SystemProperties.set(FFLAG_OVERRIDE_PREFIX + feature, enabled ? "true" : "false"); + } + + + public static Map getAllFeatureFlags() { + final Map features = new HashMap<>(); + features.put(FFLAG_PREFIX + "abc", "false"); + features.put(FFLAG_OVERRIDE_PREFIX + "abc", "true"); + return features; + } } diff --git a/tests/robotests/src/com/android/settings/development/featureflags/FeatureFlagPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/featureflags/FeatureFlagPreferenceControllerTest.java new file mode 100644 index 00000000000..3d95cf44ee1 --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/featureflags/FeatureFlagPreferenceControllerTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 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.settings.development.featureflags; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class FeatureFlagPreferenceControllerTest { + + @Mock + private PreferenceScreen mScreen; + private Context mContext; + private Lifecycle mLifecycle; + private FeatureFlagsPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mLifecycle = new Lifecycle(); + mController = new FeatureFlagsPreferenceController(mContext, mLifecycle); + when(mScreen.getContext()).thenReturn(mContext); + mController.displayPreference(mScreen); + } + + @Test + public void verifyConstants() { + assertThat(mController.isAvailable()).isTrue(); + assertThat(mController.getPreferenceKey()).isNull(); + } + + @Test + public void onStart_shouldRefreshFeatureFlags() { + mLifecycle.onStart(); + + verify(mScreen).removeAll(); + verify(mScreen).addPreference(any(FeatureFlagPreference.class)); + } +} diff --git a/tests/robotests/src/com/android/settings/development/featureflags/FeatureFlagPreferenceTest.java b/tests/robotests/src/com/android/settings/development/featureflags/FeatureFlagPreferenceTest.java new file mode 100644 index 00000000000..11099b16c21 --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/featureflags/FeatureFlagPreferenceTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017 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.settings.development.featureflags; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class FeatureFlagPreferenceTest { + + private static final String KEY = "feature_key"; + + private Context mContext; + private FeatureFlagPreference mPreference; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mPreference = new FeatureFlagPreference(mContext, KEY); + } + + @Test + public void constructor_shouldSetTitleAndSummary() { + assertThat(mPreference.getTitle()).isEqualTo(KEY); + assertThat(mPreference.getSummary()).isEqualTo("false"); + assertThat(mPreference.isChecked()).isFalse(); + } + + @Test + public void toggle_shouldUpdateSummary() { + mPreference.setChecked(true); + + assertThat(mPreference.getSummary()).isEqualTo("true"); + assertThat(mPreference.isChecked()).isTrue(); + } +} From 843b4d7d0d8871b905d362a733f6662def07f477 Mon Sep 17 00:00:00 2001 From: jeffreyhuang Date: Tue, 3 Oct 2017 18:02:49 -0700 Subject: [PATCH 06/10] Introduce MockLocationAppPreferenceController - Create new MockLocationAppPreferenceController - Create controller inside the DashboardFragment - Port logic from DevelopmentSettings into the controller Bug: 34203528 Test: make RunSettingsRoboTests -j40 Change-Id: I0c306aadfbe9f10b143c63429061a7c0c244361a --- ...evelopmentOptionsActivityRequestCodes.java | 2 + .../DevelopmentSettingsDashboardFragment.java | 2 +- .../MockLocationAppPreferenceController.java | 183 ++++++++++++++++++ ...ckLocationAppPreferenceControllerTest.java | 166 ++++++++++++++++ 4 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 src/com/android/settings/development/MockLocationAppPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/development/MockLocationAppPreferenceControllerTest.java diff --git a/src/com/android/settings/development/DevelopmentOptionsActivityRequestCodes.java b/src/com/android/settings/development/DevelopmentOptionsActivityRequestCodes.java index 667ae3115e2..b7b27591df9 100644 --- a/src/com/android/settings/development/DevelopmentOptionsActivityRequestCodes.java +++ b/src/com/android/settings/development/DevelopmentOptionsActivityRequestCodes.java @@ -23,4 +23,6 @@ public interface DevelopmentOptionsActivityRequestCodes { int REQUEST_CODE_ENABLE_OEM_UNLOCK = 0; int REQUEST_CODE_DEBUG_APP = 1; + + int REQUEST_MOCK_LOCATION_APP = 2; } diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index 2d9c5f37935..b07542f6bbf 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -263,7 +263,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra controllers.add(new ClearAdbKeysPreferenceController(context, fragment)); controllers.add(new LocalTerminalPreferenceController(context)); controllers.add(new BugReportInPowerPreferenceControllerV2(context)); - // select mock location app + controllers.add(new MockLocationAppPreferenceController(context, fragment)); controllers.add(new DebugViewAttributesPreferenceController(context)); controllers.add(new SelectDebugAppPreferenceController(context, fragment)); controllers.add(new WaitForDebuggerPreferenceController(context)); diff --git a/src/com/android/settings/development/MockLocationAppPreferenceController.java b/src/com/android/settings/development/MockLocationAppPreferenceController.java new file mode 100644 index 00000000000..9f6c4d3d1e8 --- /dev/null +++ b/src/com/android/settings/development/MockLocationAppPreferenceController.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2017 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.settings.development; + +import static com.android.settings.development.DevelopmentOptionsActivityRequestCodes + .REQUEST_MOCK_LOCATION_APP; + +import android.Manifest; +import android.app.Activity; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.text.TextUtils; + +import com.android.settings.R; +import com.android.settingslib.wrapper.PackageManagerWrapper; + +import java.util.List; + +public class MockLocationAppPreferenceController extends DeveloperOptionsPreferenceController { + + private static final String MOCK_LOCATION_APP_KEY = "mock_location_app"; + private static final int[] MOCK_LOCATION_APP_OPS = new int[]{AppOpsManager.OP_MOCK_LOCATION}; + + private final DevelopmentSettingsDashboardFragment mFragment; + private final AppOpsManager mAppsOpsManager; + private final PackageManagerWrapper mPackageManager; + private Preference mPreference; + + public MockLocationAppPreferenceController(Context context, + DevelopmentSettingsDashboardFragment fragment) { + super(context); + + mFragment = fragment; + mAppsOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + mPackageManager = new PackageManagerWrapper(context.getPackageManager()); + } + + @Override + public String getPreferenceKey() { + return MOCK_LOCATION_APP_KEY; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + mPreference = screen.findPreference(getPreferenceKey()); + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { + return false; + } + final Intent intent = new Intent(mContext, AppPicker.class); + intent.putExtra(AppPicker.EXTRA_REQUESTIING_PERMISSION, + Manifest.permission.ACCESS_MOCK_LOCATION); + mFragment.startActivityForResult(intent, REQUEST_MOCK_LOCATION_APP); + return true; + } + + @Override + public void updateState(Preference preference) { + updateMockLocation(); + } + + @Override + public boolean onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode != REQUEST_MOCK_LOCATION_APP || resultCode != Activity.RESULT_OK) { + return false; + } + writeMockLocation(data.getAction()); + updateMockLocation(); + return true; + } + + @Override + protected void onDeveloperOptionsSwitchEnabled() { + mPreference.setEnabled(true); + } + + @Override + protected void onDeveloperOptionsSwitchDisabled() { + mPreference.setEnabled(false); + } + + private void updateMockLocation() { + final String mockLocationApp = getCurrentMockLocationApp(); + + if (!TextUtils.isEmpty(mockLocationApp)) { + mPreference.setSummary( + mContext.getResources().getString(R.string.mock_location_app_set, + getAppLabel(mockLocationApp))); + } else { + mPreference.setSummary( + mContext.getResources().getString(R.string.mock_location_app_not_set)); + } + } + + private void writeMockLocation(String mockLocationAppName) { + removeAllMockLocations(); + // Enable the app op of the new mock location app if such. + if (!TextUtils.isEmpty(mockLocationAppName)) { + try { + final ApplicationInfo ai = mPackageManager.getApplicationInfo( + mockLocationAppName, PackageManager.MATCH_DISABLED_COMPONENTS); + mAppsOpsManager.setMode(AppOpsManager.OP_MOCK_LOCATION, ai.uid, + mockLocationAppName, AppOpsManager.MODE_ALLOWED); + } catch (PackageManager.NameNotFoundException e) { + /* ignore */ + } + } + } + + private String getAppLabel(String mockLocationApp) { + try { + final ApplicationInfo ai = mPackageManager.getApplicationInfo( + mockLocationApp, PackageManager.MATCH_DISABLED_COMPONENTS); + final CharSequence appLabel = mPackageManager.getApplicationLabel(ai); + return appLabel != null ? appLabel.toString() : mockLocationApp; + } catch (PackageManager.NameNotFoundException e) { + return mockLocationApp; + } + } + + private void removeAllMockLocations() { + // Disable the app op of the previous mock location app if such. + final List packageOps = mAppsOpsManager.getPackagesForOps( + MOCK_LOCATION_APP_OPS); + if (packageOps == null) { + return; + } + // Should be one but in case we are in a bad state due to use of command line tools. + for (AppOpsManager.PackageOps packageOp : packageOps) { + if (packageOp.getOps().get(0).getMode() != AppOpsManager.MODE_ERRORED) { + removeMockLocationForApp(packageOp.getPackageName()); + } + } + } + + private void removeMockLocationForApp(String appName) { + try { + final ApplicationInfo ai = mPackageManager.getApplicationInfo( + appName, PackageManager.MATCH_DISABLED_COMPONENTS); + mAppsOpsManager.setMode(AppOpsManager.OP_MOCK_LOCATION, ai.uid, + appName, AppOpsManager.MODE_ERRORED); + } catch (PackageManager.NameNotFoundException e) { + /* ignore */ + } + } + + private String getCurrentMockLocationApp() { + final List packageOps = mAppsOpsManager.getPackagesForOps( + MOCK_LOCATION_APP_OPS); + if (packageOps != null) { + for (AppOpsManager.PackageOps packageOp : packageOps) { + if (packageOp.getOps().get(0).getMode() == AppOpsManager.MODE_ALLOWED) { + return packageOps.get(0).getPackageName(); + } + } + } + return null; + } +} diff --git a/tests/robotests/src/com/android/settings/development/MockLocationAppPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/MockLocationAppPreferenceControllerTest.java new file mode 100644 index 00000000000..0aab0dbc823 --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/MockLocationAppPreferenceControllerTest.java @@ -0,0 +1,166 @@ +package com.android.settings.development; + +import static com.android.settings.development.DevelopmentOptionsActivityRequestCodes + .REQUEST_MOCK_LOCATION_APP; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.wrapper.PackageManagerWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +import java.util.Collections; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class MockLocationAppPreferenceControllerTest { + + @Mock + private DevelopmentSettingsDashboardFragment mFragment; + @Mock + private AppOpsManager mAppOpsManager; + @Mock + private PackageManagerWrapper mPackageManager; + @Mock + private Preference mPreference; + @Mock + private PreferenceScreen mScreen; + @Mock + private AppOpsManager.PackageOps mPackageOps; + @Mock + private AppOpsManager.OpEntry mOpEntry; + @Mock + private ApplicationInfo mApplicationInfo; + + private Context mContext; + private MockLocationAppPreferenceController mController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mController = spy(new MockLocationAppPreferenceController(mContext, mFragment)); + ReflectionHelpers.setField(mController, "mAppsOpsManager", mAppOpsManager); + ReflectionHelpers.setField(mController, "mPackageManager", mPackageManager); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + mController.displayPreference(mScreen); + } + + @Test + public void updateState_foobarAppSelected_shouldSetSummaryToFoobar() { + final String appName = "foobar"; + when(mAppOpsManager.getPackagesForOps(any())).thenReturn( + Collections.singletonList(mPackageOps)); + when(mPackageOps.getOps()).thenReturn(Collections.singletonList(mOpEntry)); + when(mOpEntry.getMode()).thenReturn(AppOpsManager.MODE_ALLOWED); + when(mPackageOps.getPackageName()).thenReturn(appName); + + mController.updateState(mPreference); + + verify(mPreference).setSummary( + mContext.getResources().getString(R.string.mock_location_app_set, appName)); + } + + @Test + public void updateState_noAppSelected_shouldSetSummaryToDefault() { + when(mAppOpsManager.getPackagesForOps(any())).thenReturn( + Collections.emptyList()); + + mController.updateState(mPreference); + + verify(mPreference).setSummary( + mContext.getResources().getString(R.string.mock_location_app_not_set)); + } + + @Test + public void onActivityResult_fooPrevAppBarNewApp_shouldRemoveFooAndSetBarAsMockLocationApp() + throws PackageManager.NameNotFoundException { + final String prevAppName = "foo"; + final String newAppName = "bar"; + final Intent intent = new Intent(); + intent.setAction(newAppName); + when(mAppOpsManager.getPackagesForOps(any())).thenReturn( + Collections.singletonList(mPackageOps)); + when(mPackageOps.getOps()).thenReturn(Collections.singletonList(mOpEntry)); + when(mOpEntry.getMode()).thenReturn(AppOpsManager.MODE_ALLOWED); + when(mPackageOps.getPackageName()).thenReturn(prevAppName); + when(mPackageManager.getApplicationInfo(anyString(), + eq(PackageManager.MATCH_DISABLED_COMPONENTS))).thenReturn(mApplicationInfo); + + final boolean handled = mController.onActivityResult(REQUEST_MOCK_LOCATION_APP, + Activity.RESULT_OK, intent); + + assertThat(handled).isTrue(); + verify(mAppOpsManager).setMode(anyInt(), anyInt(), eq(newAppName), + eq(AppOpsManager.MODE_ALLOWED)); + verify(mAppOpsManager).setMode(anyInt(), anyInt(), eq(prevAppName), + eq(AppOpsManager.MODE_ERRORED)); + } + + @Test + public void onActivityResult_incorrectCode_shouldReturnFalse() { + final boolean handled = mController.onActivityResult(30983150 /* request code */, + Activity.RESULT_OK, null /* intent */); + + assertThat(handled).isFalse(); + } + + @Test + public void handlePreferenceTreeClick_samePreferenceKey_shouldLaunchActivity() { + final String preferenceKey = mController.getPreferenceKey(); + when(mPreference.getKey()).thenReturn(preferenceKey); + + final boolean handled = mController.handlePreferenceTreeClick(mPreference); + + assertThat(handled).isTrue(); + verify(mFragment).startActivityForResult(any(), eq(REQUEST_MOCK_LOCATION_APP)); + } + + @Test + public void handlePreferenceTreeClick_incorrectPreferenceKey_shouldReturnFalse() { + when(mPreference.getKey()).thenReturn("SomeRandomKey"); + + assertThat(mController.handlePreferenceTreeClick(mPreference)).isFalse(); + } + + @Test + public void onDeveloperOptionsSwitchDisabled_shouldDisablePreference() { + mController.onDeveloperOptionsSwitchDisabled(); + + verify(mPreference).setEnabled(false); + } + + @Test + public void onDeveloperOptionsSwitchEnabled_shouldEnablePreference() { + mController.onDeveloperOptionsSwitchEnabled(); + + verify(mPreference).setEnabled(true); + } +} From 1f4ebfe41ec68f322cd01195bedca8237b94ebae Mon Sep 17 00:00:00 2001 From: jeffreyhuang Date: Tue, 3 Oct 2017 12:28:41 -0700 Subject: [PATCH 07/10] Introduce ShortcutManagerThrottlingPreferenceCtrl - Create new ShortcutManagerThrottlingPreferenceController - Create controller inside the DashboardFragment - Port logic from DevelopmentSettings into the controller Bug: 34203528 Test: make RunSettingsRoboTests -j40 Change-Id: I5409b40b4ed7d350af34498da9a3d7d79200f69b --- .../DevelopmentSettingsDashboardFragment.java | 2 +- ...ManagerThrottlingPreferenceController.java | 91 +++++++++++++++++++ ...gerThrottlingPreferenceControllerTest.java | 84 +++++++++++++++++ 3 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 src/com/android/settings/development/ShortcutManagerThrottlingPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/development/ShortcutManagerThrottlingPreferenceControllerTest.java diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index 2d9c5f37935..559c6c51f59 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -320,7 +320,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra // inactive apps controllers.add(new AllowAppsOnExternalPreferenceController(context)); controllers.add(new ResizableActivityPreferenceController(context)); - // reset shortcutmanager rate-limiting + controllers.add(new ShortcutManagerThrottlingPreferenceController(context)); return controllers; } diff --git a/src/com/android/settings/development/ShortcutManagerThrottlingPreferenceController.java b/src/com/android/settings/development/ShortcutManagerThrottlingPreferenceController.java new file mode 100644 index 00000000000..c8fdaecc4b6 --- /dev/null +++ b/src/com/android/settings/development/ShortcutManagerThrottlingPreferenceController.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017 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.settings.development; + +import android.content.Context; +import android.content.pm.IShortcutService; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.support.v7.preference.Preference; +import android.text.TextUtils; +import android.util.Log; +import android.widget.Toast; + +import com.android.settings.R; + +public class ShortcutManagerThrottlingPreferenceController extends + DeveloperOptionsPreferenceController { + + private static final String TAG = "ShortcutMgrPrefCtrl"; + + private static final String SHORTCUT_MANAGER_RESET_KEY = "reset_shortcut_manager_throttling"; + + private final IShortcutService mShortcutService; + + public ShortcutManagerThrottlingPreferenceController(Context context) { + super(context); + + mShortcutService = getShortCutService(); + } + + @Override + public String getPreferenceKey() { + return SHORTCUT_MANAGER_RESET_KEY; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!TextUtils.equals(SHORTCUT_MANAGER_RESET_KEY, preference.getKey())) { + return false; + } + resetShortcutManagerThrottling(); + return true; + } + + @Override + protected void onDeveloperOptionsSwitchEnabled() { + // intentional no-op + } + + @Override + protected void onDeveloperOptionsSwitchDisabled() { + // intentional no-op + } + + private void resetShortcutManagerThrottling() { + if (mShortcutService == null) { + return; + } + try { + mShortcutService.resetThrottling(); + Toast.makeText(mContext, R.string.reset_shortcut_manager_throttling_complete, + Toast.LENGTH_SHORT).show(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to reset rate limiting", e); + } + } + + private IShortcutService getShortCutService() { + try { + return IShortcutService.Stub.asInterface( + ServiceManager.getService(Context.SHORTCUT_SERVICE)); + } catch (VerifyError e) { + // Used for tests since Robolectric cannot initialize this class. + return null; + } + } +} diff --git a/tests/robotests/src/com/android/settings/development/ShortcutManagerThrottlingPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/ShortcutManagerThrottlingPreferenceControllerTest.java new file mode 100644 index 00000000000..0a189cb007b --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/ShortcutManagerThrottlingPreferenceControllerTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2017 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.settings.development; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.IShortcutService; +import android.os.RemoteException; +import android.support.v14.preference.SwitchPreference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class ShortcutManagerThrottlingPreferenceControllerTest { + + @Mock + private SwitchPreference mPreference; + @Mock + private PreferenceScreen mPreferenceScreen; + @Mock + private IShortcutService mShortcutService; + + private Context mContext; + private ShortcutManagerThrottlingPreferenceController mController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mController = new ShortcutManagerThrottlingPreferenceController(mContext); + ReflectionHelpers.setField(mController, "mShortcutService", mShortcutService); + when(mPreferenceScreen.findPreference(mController.getPreferenceKey())).thenReturn( + mPreference); + mController.displayPreference(mPreferenceScreen); + } + + @Test + public void handlePreferenceTreeClick_differentPreferenceKey_shouldReturnFalse() { + when(mPreference.getKey()).thenReturn("SomeRandomKey"); + + assertThat(mController.handlePreferenceTreeClick(mPreference)).isFalse(); + } + + @Test + public void handlePreferenceTreeClick_correctPreferenceKey_shouldResetThrottling() + throws RemoteException { + when(mPreference.getKey()).thenReturn(mController.getPreferenceKey()); + + final boolean handled = mController.handlePreferenceTreeClick(mPreference); + + assertThat(handled).isTrue(); + verify(mShortcutService).resetThrottling(); + } +} From 2cb1c65a5a1cc7634c54a190da098f5f484cd3de Mon Sep 17 00:00:00 2001 From: Tony Mantler Date: Tue, 3 Oct 2017 15:45:06 -0700 Subject: [PATCH 08/10] Move SerialNumberPreferenceController to SettingsLib Bug: 67410808 Test: RunSettingsRoboTests Change-Id: If7fefed1f012521293769eadf5d6485c12b98956 --- .../SerialNumberPreferenceController.java | 42 ++------ .../SerialNumberPreferenceControllerTest.java | 97 ------------------- 2 files changed, 7 insertions(+), 132 deletions(-) delete mode 100644 tests/robotests/src/com/android/settings/deviceinfo/SerialNumberPreferenceControllerTest.java diff --git a/src/com/android/settings/deviceinfo/SerialNumberPreferenceController.java b/src/com/android/settings/deviceinfo/SerialNumberPreferenceController.java index b69844d9f4b..31b905e8e86 100644 --- a/src/com/android/settings/deviceinfo/SerialNumberPreferenceController.java +++ b/src/com/android/settings/deviceinfo/SerialNumberPreferenceController.java @@ -18,47 +18,19 @@ package com.android.settings.deviceinfo; import android.content.Context; import android.os.Build; -import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceScreen; -import android.text.TextUtils; -import com.android.internal.annotations.VisibleForTesting; import com.android.settings.core.PreferenceControllerMixin; -import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.deviceinfo.AbstractSerialNumberPreferenceController; -public class SerialNumberPreferenceController extends AbstractPreferenceController implements +/** + * Preference controller for displaying device serial number. Wraps {@link Build#getSerial()}. + */ +public class SerialNumberPreferenceController extends + AbstractSerialNumberPreferenceController implements PreferenceControllerMixin { - - private static final String KEY_SERIAL_NUMBER = "serial_number"; - - private final String mSerialNumber; - public SerialNumberPreferenceController(Context context) { - this(context, Build.getSerial()); - } - - @VisibleForTesting - SerialNumberPreferenceController(Context context, String serialNumber) { super(context); - mSerialNumber = serialNumber; } - @Override - public boolean isAvailable() { - return !TextUtils.isEmpty(mSerialNumber); - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - final Preference pref = screen.findPreference(KEY_SERIAL_NUMBER); - if (pref != null) { - pref.setSummary(mSerialNumber); - } - } - - @Override - public String getPreferenceKey() { - return KEY_SERIAL_NUMBER; - } + // This space intentionally left blank } diff --git a/tests/robotests/src/com/android/settings/deviceinfo/SerialNumberPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/SerialNumberPreferenceControllerTest.java deleted file mode 100644 index 18ef0039371..00000000000 --- a/tests/robotests/src/com/android/settings/deviceinfo/SerialNumberPreferenceControllerTest.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2016 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.settings.deviceinfo; - -import android.content.Context; -import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceScreen; - -import com.android.settings.testutils.SettingsRobolectricTestRunner; -import com.android.settings.TestConfig; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.annotation.Config; - -import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Answers.RETURNS_DEEP_STUBS; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@RunWith(SettingsRobolectricTestRunner.class) -@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) -public class SerialNumberPreferenceControllerTest { - - @Mock(answer = RETURNS_DEEP_STUBS) - private Context mContext; - @Mock(answer = RETURNS_DEEP_STUBS) - private PreferenceScreen mScreen; - - private SerialNumberPreferenceController mController; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testIsAvaiable_noSerial_shouldReturnFalse() { - mController = new SerialNumberPreferenceController(mContext, null); - - assertThat(mController.isAvailable()).isFalse(); - } - - @Test - public void testIsAvaiable_hasSerial_shouldReturnTrue() { - mController = new SerialNumberPreferenceController(mContext, "123"); - - assertThat(mController.isAvailable()).isTrue(); - } - - @Test - public void testDisplay_noSerial_shouldHidePreference() { - final Preference preference = mock(Preference.class); - when(mScreen.getPreferenceCount()).thenReturn(1); - when(mScreen.getPreference(0)).thenReturn(preference); - mController = new SerialNumberPreferenceController(mContext, null); - when(preference.getKey()).thenReturn(mController.getPreferenceKey()); - - mController.displayPreference(mScreen); - - verify(mScreen).removePreference(any(Preference.class)); - } - - @Test - public void testDisplay_hasSerial_shouldSummary() { - final String serial = "123"; - final Preference preference = mock(Preference.class); - when(mScreen.findPreference(anyString())).thenReturn(preference); - - mController = new SerialNumberPreferenceController(mContext, serial); - mController.displayPreference(mScreen); - - verify(mScreen, never()).removePreference(any(Preference.class)); - verify(preference).setSummary(serial); - } -} From 4acb1f66fd626d4f12e5d94ae670544ea253335f Mon Sep 17 00:00:00 2001 From: jeffreyhuang Date: Tue, 3 Oct 2017 15:55:21 -0700 Subject: [PATCH 09/10] Introduce HdcpCheckingPreferenceController - Create new HdcpCheckingPreferenceController - Create controller inside the DashboardFragment - Port logic from DevelopmentSettings into the controller Bug: 34203528 Test: make RunSettingsRoboTests -j40 Change-Id: Id6eed501ce658b55dc4ad38f7408994f70923194 --- .../DevelopmentSettingsDashboardFragment.java | 2 +- .../HdcpCheckingPreferenceController.java | 109 ++++++++++++ .../HdcpCheckingPreferenceControllerTest.java | 164 ++++++++++++++++++ 3 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 src/com/android/settings/development/HdcpCheckingPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/development/HdcpCheckingPreferenceControllerTest.java diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index 2d9c5f37935..00a2375eded 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -248,7 +248,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra // take bug report controllers.add(new LocalBackupPasswordPreferenceController(context)); controllers.add(new StayAwakePreferenceController(context, lifecycle)); - // hdcp checking + controllers.add(new HdcpCheckingPreferenceController(context)); controllers.add(new BluetoothSnoopLogPreferenceController(context)); controllers.add(new OemUnlockPreferenceController(context, activity, fragment)); // running services diff --git a/src/com/android/settings/development/HdcpCheckingPreferenceController.java b/src/com/android/settings/development/HdcpCheckingPreferenceController.java new file mode 100644 index 00000000000..a443f877de2 --- /dev/null +++ b/src/com/android/settings/development/HdcpCheckingPreferenceController.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2017 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.settings.development; + +import android.content.Context; +import android.os.Build; +import android.os.SystemProperties; +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.text.TextUtils; + +import com.android.settings.R; +import com.android.settingslib.development.SystemPropPoker; + +public class HdcpCheckingPreferenceController extends + DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener { + + private static final String HDCP_CHECKING_KEY = "hdcp_checking"; + + @VisibleForTesting + static final String HDCP_CHECKING_PROPERTY = "persist.sys.hdcp_checking"; + @VisibleForTesting + static final String USER_BUILD_TYPE = "user"; + + private final String[] mListValues; + private final String[] mListSummaries; + private ListPreference mPreference; + + public HdcpCheckingPreferenceController(Context context) { + super(context); + + mListValues = mContext.getResources().getStringArray(R.array.hdcp_checking_values); + mListSummaries = mContext.getResources().getStringArray(R.array.hdcp_checking_summaries); + } + + @Override + public boolean isAvailable() { + return !TextUtils.equals(USER_BUILD_TYPE, getBuildType()); + } + + @Override + public String getPreferenceKey() { + return HDCP_CHECKING_KEY; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + mPreference = (ListPreference) screen.findPreference(getPreferenceKey()); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + SystemProperties.set(HDCP_CHECKING_PROPERTY, newValue.toString()); + updateHdcpValues(); + SystemPropPoker.getInstance().poke(); + return true; + } + + @Override + public void updateState(Preference preference) { + updateHdcpValues(); + } + + @Override + protected void onDeveloperOptionsSwitchEnabled() { + mPreference.setEnabled(true); + } + + @Override + protected void onDeveloperOptionsSwitchDisabled() { + mPreference.setEnabled(false); + } + + private void updateHdcpValues() { + final String currentValue = SystemProperties.get(HDCP_CHECKING_PROPERTY); + int index = 1; // Defaults to drm-only. Needs to match with R.array.hdcp_checking_values + for (int i = 0; i < mListValues.length; i++) { + if (TextUtils.equals(currentValue, mListValues[i])) { + index = i; + break; + } + } + mPreference.setValue(mListValues[index]); + mPreference.setSummary(mListSummaries[index]); + } + + @VisibleForTesting + public String getBuildType() { + return Build.TYPE; + } +} diff --git a/tests/robotests/src/com/android/settings/development/HdcpCheckingPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/HdcpCheckingPreferenceControllerTest.java new file mode 100644 index 00000000000..434941c6186 --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/HdcpCheckingPreferenceControllerTest.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2017 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.settings.development; + +import static com.android.settings.development.HdcpCheckingPreferenceController + .HDCP_CHECKING_PROPERTY; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.SystemProperties; +import android.support.v7.preference.ListPreference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.SettingsShadowSystemProperties; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, + sdk = TestConfig.SDK_VERSION, + shadows = {SettingsShadowSystemProperties.class}) +public class HdcpCheckingPreferenceControllerTest { + + private static final String USER_DEBUG = "userdebug"; + + @Mock + private ListPreference mPreference; + @Mock + private PreferenceScreen mScreen; + + private Context mContext; + private HdcpCheckingPreferenceController mController; + + /** + * Array Values Key + * + * 0: Never Check + * 1: Check for DRM content only + * 2: Always Check + */ + private String[] mValues; + private String[] mSummaries; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mValues = mContext.getResources().getStringArray(R.array.hdcp_checking_values); + mSummaries = mContext.getResources().getStringArray(R.array.hdcp_checking_summaries); + mController = new HdcpCheckingPreferenceController(mContext); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + mController.displayPreference(mScreen); + + } + + @After + public void teardown() { + SettingsShadowSystemProperties.clear(); + } + + @Test + public void isAvailable_isUserBuildType_shouldReturnFalse() { + mController = spy(mController); + doReturn(HdcpCheckingPreferenceController.USER_BUILD_TYPE).when(mController).getBuildType(); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_isUserDebugBuildType_shouldReturnTrue() { + mController = spy(mController); + doReturn(USER_DEBUG).when(mController).getBuildType(); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void onPreferenceChange_setNeverCheckHdcp_shouldEnableNeverCheckHdcp() { + mController.onPreferenceChange(mPreference, mValues[0]); + + assertThat(SystemProperties.get(HDCP_CHECKING_PROPERTY)).isEqualTo(mValues[0]); + } + + @Test + public void onPreferenceChange_setCheckDrm_shouldEnableCheckDrm() { + mController.onPreferenceChange(mPreference, mValues[1]); + + assertThat(SystemProperties.get(HDCP_CHECKING_PROPERTY)).isEqualTo(mValues[1]); + } + + @Test + public void updateState_neverCheckHdcp_shouldEnableNeverCheckHdcp() { + SystemProperties.set(HDCP_CHECKING_PROPERTY, mValues[0]); + + mController.updateState(mPreference); + + verify(mPreference).setValue(mValues[0]); + verify(mPreference).setSummary(mSummaries[0]); + } + + @Test + public void updateState_checkDrm_shouldEnableCheckDrm() { + SystemProperties.set(HDCP_CHECKING_PROPERTY, mValues[1]); + + mController.updateState(mPreference); + + verify(mPreference).setValue(mValues[1]); + verify(mPreference).setSummary(mSummaries[1]); + } + + @Test + public void updateState_noValueSet_shouldEnableCheckDrmAsDefault() { + SystemProperties.set(HDCP_CHECKING_PROPERTY, null); + + mController.updateState(mPreference); + + verify(mPreference).setValue(mValues[1]); + verify(mPreference).setSummary(mSummaries[1]); + } + + @Test + public void onDeveloperOptionsSwitchDisabled_shouldDisablePreference() { + mController.onDeveloperOptionsSwitchDisabled(); + + verify(mPreference).setEnabled(false); + } + + @Test + public void onDeveloperOptionsSwitchEnabled_shouldEnablePreference() { + mController.onDeveloperOptionsSwitchEnabled(); + + verify(mPreference).setEnabled(true); + } +} From a0785269201282d515c9102f7cdf9ec704b100a9 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Thu, 5 Oct 2017 14:04:50 -0700 Subject: [PATCH 10/10] Support suggestion UI card with a button. Bug: 65065268 Test: robotests Change-Id: I24c8947e9b23a80de38b8cbd57404736a5d1660a --- res/layout/suggestion_tile.xml | 15 ++-- res/layout/suggestion_tile_with_button.xml | 72 +++++++++++++++++++ res/values/strings.xml | 2 + res/values/styles.xml | 5 +- .../suggestions/SuggestionAdapter.java | 43 +++++------ .../SuggestionDismissController.java | 2 +- .../settings/suggestions/Suggestion.java | 18 ++++- .../suggestions/SuggestionAdapterTest.java | 15 ++++ .../SuggestionDismissControllerTest.java | 2 +- 9 files changed, 135 insertions(+), 39 deletions(-) create mode 100644 res/layout/suggestion_tile_with_button.xml diff --git a/res/layout/suggestion_tile.xml b/res/layout/suggestion_tile.xml index e2dd13af18d..2adfab36893 100644 --- a/res/layout/suggestion_tile.xml +++ b/res/layout/suggestion_tile.xml @@ -33,26 +33,27 @@ android:layout_width="@dimen/dashboard_tile_image_size" android:layout_height="@dimen/dashboard_tile_image_size" android:layout_marginStart="14dp" - android:layout_marginEnd="24dp"/> + android:layout_marginEnd="24dp" /> - + android:fadingEdge="horizontal" /> - + android:layout_height="wrap_content" /> diff --git a/res/layout/suggestion_tile_with_button.xml b/res/layout/suggestion_tile_with_button.xml new file mode 100644 index 00000000000..0439770373a --- /dev/null +++ b/res/layout/suggestion_tile_with_button.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + +