From 3aa68c8b80633b9091734fd237e1921fb7efc792 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Tue, 25 Oct 2016 12:45:00 -0700 Subject: [PATCH] Refactor Build number preference control into a controller. - Use PreferenceController structure to make things more modular and testable - Add tests - Confirm password before enabling dev settings. Bug: 24107771 Test: RunSettingsRoboTests Change-Id: I791d9452fd461f570e70e7428f00a7258663de4b --- .../android/settings/DeviceInfoSettings.java | 96 ++------- .../BuildNumberPreferenceController.java | 199 ++++++++++++++++++ .../BuildNumberPreferenceControllerTest.java | 164 +++++++++++++++ 3 files changed, 383 insertions(+), 76 deletions(-) create mode 100644 src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java diff --git a/src/com/android/settings/DeviceInfoSettings.java b/src/com/android/settings/DeviceInfoSettings.java index 5808219a8a7..24091e28b16 100644 --- a/src/com/android/settings/DeviceInfoSettings.java +++ b/src/com/android/settings/DeviceInfoSettings.java @@ -32,14 +32,13 @@ import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceGroup; import android.text.TextUtils; import android.util.Log; -import android.widget.Toast; import com.android.internal.logging.MetricsProto.MetricsEvent; import com.android.settings.dashboard.SummaryLoader; import com.android.settings.deviceinfo.AdditionalSystemUpdatePreferenceController; +import com.android.settings.deviceinfo.BuildNumberPreferenceController; import com.android.settings.deviceinfo.SystemUpdatePreferenceController; import com.android.settings.search.BaseSearchIndexProvider; -import com.android.settings.search.Index; import com.android.settings.search.Indexable; import com.android.settingslib.DeviceInfoUtils; import com.android.settingslib.RestrictedLockUtils; @@ -59,7 +58,6 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In private static final String PROPERTY_URL_SAFETYLEGAL = "ro.url.safetylegal"; private static final String PROPERTY_SELINUX_STATUS = "ro.build.selinux"; private static final String KEY_KERNEL_VERSION = "kernel_version"; - private static final String KEY_BUILD_NUMBER = "build_number"; private static final String KEY_DEVICE_MODEL = "device_model"; private static final String KEY_SELINUX_STATUS = "selinux_status"; private static final String KEY_BASEBAND_VERSION = "baseband_version"; @@ -70,20 +68,16 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In private static final String KEY_DEVICE_FEEDBACK = "device_feedback"; private static final String KEY_SAFETY_LEGAL = "safetylegal"; - static final int TAPS_TO_BE_A_DEVELOPER = 7; long[] mHits = new long[3]; - int mDevHitCountdown; - Toast mDevHitToast; private SystemUpdatePreferenceController mSystemUpdatePreferenceController; private AdditionalSystemUpdatePreferenceController mAdditionalSystemUpdatePreferenceController; + private BuildNumberPreferenceController mBuildNumberPreferenceController; private UserManager mUm; private EnforcedAdmin mFunDisallowedAdmin; private boolean mFunDisallowedBySystem; - private EnforcedAdmin mDebuggingFeaturesDisallowedAdmin; - private boolean mDebuggingFeaturesDisallowedBySystem; @Override public int getMetricsCategory() { @@ -95,6 +89,14 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In return R.string.help_uri_about; } + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (mBuildNumberPreferenceController.onActivityResult(requestCode, resultCode, data)) { + return; + } + super.onActivityResult(requestCode, resultCode, data); + } + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -103,7 +105,9 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In mSystemUpdatePreferenceController = new SystemUpdatePreferenceController(activity, mUm); mAdditionalSystemUpdatePreferenceController = new AdditionalSystemUpdatePreferenceController(activity); - + mBuildNumberPreferenceController = + new BuildNumberPreferenceController(activity, activity, this /* fragment */); + getLifecycle().addObserver(mBuildNumberPreferenceController); addPreferencesFromResource(R.xml.device_info_settings); setStringSummary(KEY_FIRMWARE_VERSION, Build.VERSION.RELEASE); @@ -119,8 +123,7 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In setValueSummary(KEY_BASEBAND_VERSION, "gsm.version.baseband"); setStringSummary(KEY_DEVICE_MODEL, Build.MODEL + DeviceInfoUtils.getMsvSuffix()); setValueSummary(KEY_EQUIPMENT_ID, PROPERTY_EQUIPMENT_ID); - setStringSummary(KEY_BUILD_NUMBER, Build.DISPLAY); - findPreference(KEY_BUILD_NUMBER).setEnabled(true); + mBuildNumberPreferenceController.displayPreference(getPreferenceScreen()); findPreference(KEY_KERNEL_VERSION).setSummary(DeviceInfoUtils.getFormattedKernelVersion()); if (!SELinux.isSELinuxEnabled()) { @@ -174,26 +177,21 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In @Override public void onResume() { super.onResume(); - mDevHitCountdown = getActivity().getSharedPreferences(DevelopmentSettings.PREF_FILE, - Context.MODE_PRIVATE).getBoolean(DevelopmentSettings.PREF_SHOW, - android.os.Build.TYPE.equals("eng")) ? -1 : TAPS_TO_BE_A_DEVELOPER; - mDevHitToast = null; mFunDisallowedAdmin = RestrictedLockUtils.checkIfRestrictionEnforced( getActivity(), UserManager.DISALLOW_FUN, UserHandle.myUserId()); mFunDisallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction( getActivity(), UserManager.DISALLOW_FUN, UserHandle.myUserId()); - mDebuggingFeaturesDisallowedAdmin = RestrictedLockUtils.checkIfRestrictionEnforced( - getActivity(), UserManager.DISALLOW_DEBUGGING_FEATURES, UserHandle.myUserId()); - mDebuggingFeaturesDisallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction( - getActivity(), UserManager.DISALLOW_DEBUGGING_FEATURES, UserHandle.myUserId()); } @Override public boolean onPreferenceTreeClick(Preference preference) { + if (mBuildNumberPreferenceController.handlePreferenceTreeClick(preference)) { + return true; + } if (preference.getKey().equals(KEY_FIRMWARE_VERSION)) { - System.arraycopy(mHits, 1, mHits, 0, mHits.length-1); - mHits[mHits.length-1] = SystemClock.uptimeMillis(); - if (mHits[0] >= (SystemClock.uptimeMillis()-500)) { + System.arraycopy(mHits, 1, mHits, 0, mHits.length - 1); + mHits[mHits.length - 1] = SystemClock.uptimeMillis(); + if (mHits[0] >= (SystemClock.uptimeMillis() - 500)) { if (mUm.hasUserRestriction(UserManager.DISALLOW_FUN)) { if (mFunDisallowedAdmin != null && !mFunDisallowedBySystem) { RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(), @@ -212,59 +210,6 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In Log.e(LOG_TAG, "Unable to start activity " + intent.toString()); } } - } else if (preference.getKey().equals(KEY_BUILD_NUMBER)) { - // Don't enable developer options for secondary users. - if (!mUm.isAdminUser()) return true; - - // Don't enable developer options until device has been provisioned - if (!Utils.isDeviceProvisioned(getActivity())) { - return true; - } - - if (mUm.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES)) { - if (mDebuggingFeaturesDisallowedAdmin != null && - !mDebuggingFeaturesDisallowedBySystem) { - RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(), - mDebuggingFeaturesDisallowedAdmin); - } - return true; - } - - if (mDevHitCountdown > 0) { - mDevHitCountdown--; - if (mDevHitCountdown == 0) { - getActivity().getSharedPreferences(DevelopmentSettings.PREF_FILE, - Context.MODE_PRIVATE).edit().putBoolean( - DevelopmentSettings.PREF_SHOW, true).apply(); - if (mDevHitToast != null) { - mDevHitToast.cancel(); - } - mDevHitToast = Toast.makeText(getActivity(), R.string.show_dev_on, - Toast.LENGTH_LONG); - mDevHitToast.show(); - // This is good time to index the Developer Options - Index.getInstance( - getActivity().getApplicationContext()).updateFromClassNameResource( - DevelopmentSettings.class.getName(), true, true); - - } else if (mDevHitCountdown > 0 - && mDevHitCountdown < (TAPS_TO_BE_A_DEVELOPER-2)) { - if (mDevHitToast != null) { - mDevHitToast.cancel(); - } - mDevHitToast = Toast.makeText(getActivity(), getResources().getQuantityString( - R.plurals.show_dev_countdown, mDevHitCountdown, mDevHitCountdown), - Toast.LENGTH_SHORT); - mDevHitToast.show(); - } - } else if (mDevHitCountdown < 0) { - if (mDevHitToast != null) { - mDevHitToast.cancel(); - } - mDevHitToast = Toast.makeText(getActivity(), R.string.show_dev_already, - Toast.LENGTH_LONG); - mDevHitToast.show(); - } } else if (preference.getKey().equals(KEY_SECURITY_PATCH)) { if (getPackageManager().queryIntentActivities(preference.getIntent(), 0).isEmpty()) { // Don't send out the intent to stop crash @@ -279,7 +224,6 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In return super.onPreferenceTreeClick(preference); } - private void removePreferenceIfPropertyMissing(PreferenceGroup preferenceGroup, String preference, String property ) { if (SystemProperties.get(property).equals("")) { diff --git a/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java b/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java new file mode 100644 index 00000000000..615fa1b8fc2 --- /dev/null +++ b/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java @@ -0,0 +1,199 @@ +/* + * 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.app.Activity; +import android.app.Fragment; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.UserHandle; +import android.os.UserManager; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.text.TextUtils; +import android.widget.Toast; + +import com.android.settings.ChooseLockSettingsHelper; +import com.android.settings.DevelopmentSettings; +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.core.PreferenceController; +import com.android.settings.core.lifecycle.LifecycleObserver; +import com.android.settings.core.lifecycle.events.OnResume; +import com.android.settings.search.Index; +import com.android.settingslib.RestrictedLockUtils; + +public class BuildNumberPreferenceController extends PreferenceController + implements LifecycleObserver, OnResume { + + static final int TAPS_TO_BE_A_DEVELOPER = 7; + static final int REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF = 100; + + private static final String KEY_BUILD_NUMBER = "build_number"; + + private final Activity mActivity; + private final Fragment mFragment; + private final UserManager mUm; + + private Toast mDevHitToast; + private RestrictedLockUtils.EnforcedAdmin mDebuggingFeaturesDisallowedAdmin; + private boolean mDebuggingFeaturesDisallowedBySystem; + private int mDevHitCountdown; + private boolean mProcessingLastDevHit; + + public BuildNumberPreferenceController(Context context, Activity activity, Fragment fragment) { + super(context); + mActivity = activity; + mFragment = fragment; + mUm = UserManager.get(activity); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + final Preference preference = screen.findPreference(KEY_BUILD_NUMBER); + if (preference != null) { + try { + preference.setSummary(Build.DISPLAY); + preference.setEnabled(true); + } catch (Exception e) { + preference.setSummary(R.string.device_info_default); + } + } + } + + @Override + public String getPreferenceKey() { + return KEY_BUILD_NUMBER; + } + + @Override + protected boolean isAvailable() { + return true; + } + + @Override + public void onResume() { + mDebuggingFeaturesDisallowedAdmin = RestrictedLockUtils.checkIfRestrictionEnforced( + mContext, UserManager.DISALLOW_DEBUGGING_FEATURES, UserHandle.myUserId()); + mDebuggingFeaturesDisallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction( + mContext, UserManager.DISALLOW_DEBUGGING_FEATURES, UserHandle.myUserId()); + mDevHitCountdown = mContext.getSharedPreferences(DevelopmentSettings.PREF_FILE, + Context.MODE_PRIVATE).getBoolean(DevelopmentSettings.PREF_SHOW, + android.os.Build.TYPE.equals("eng")) ? -1 : TAPS_TO_BE_A_DEVELOPER; + mDevHitToast = null; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!TextUtils.equals(preference.getKey(), KEY_BUILD_NUMBER)) { + return false; + } + // Don't enable developer options for secondary users. + if (!mUm.isAdminUser()) { + return false; + } + + // Don't enable developer options until device has been provisioned + if (!Utils.isDeviceProvisioned(mContext)) { + return false; + } + + if (mUm.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES)) { + if (mDebuggingFeaturesDisallowedAdmin != null && + !mDebuggingFeaturesDisallowedBySystem) { + RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, + mDebuggingFeaturesDisallowedAdmin); + } + return false; + } + + if (mDevHitCountdown > 0) { + mDevHitCountdown--; + if (mDevHitCountdown == 0 && !mProcessingLastDevHit) { + // Add 1 count back, then start password confirmation flow. + mDevHitCountdown++; + final ChooseLockSettingsHelper helper = + new ChooseLockSettingsHelper(mActivity, mFragment); + mProcessingLastDevHit = helper.launchConfirmationActivity( + REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF, + mContext.getString(R.string.unlock_set_unlock_launch_picker_title)); + if (!mProcessingLastDevHit) { + enableDevelopmentSettings(); + } + } else if (mDevHitCountdown > 0 + && mDevHitCountdown < (TAPS_TO_BE_A_DEVELOPER - 2)) { + if (mDevHitToast != null) { + mDevHitToast.cancel(); + } + mDevHitToast = Toast.makeText(mContext, + mContext.getResources().getQuantityString( + R.plurals.show_dev_countdown, mDevHitCountdown, + mDevHitCountdown), + Toast.LENGTH_SHORT); + mDevHitToast.show(); + } + } else if (mDevHitCountdown < 0) { + if (mDevHitToast != null) { + mDevHitToast.cancel(); + } + mDevHitToast = Toast.makeText(mContext, R.string.show_dev_already, + Toast.LENGTH_LONG); + mDevHitToast.show(); + } + return true; + } + + /** + * Handles password confirmation result. + * + * @return if activity result is handled. + */ + public boolean onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode != REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF) { + return false; + } + if (resultCode == Activity.RESULT_OK) { + enableDevelopmentSettings(); + } + mProcessingLastDevHit = false; + return true; + } + + /** + * Enables development settings. Only call this after confirming password. + */ + private void enableDevelopmentSettings() { + mDevHitCountdown = 0; + mProcessingLastDevHit = false; + mContext.getSharedPreferences(DevelopmentSettings.PREF_FILE, + Context.MODE_PRIVATE).edit() + .putBoolean(DevelopmentSettings.PREF_SHOW, true) + .apply(); + if (mDevHitToast != null) { + mDevHitToast.cancel(); + } + mDevHitToast = Toast.makeText(mContext, R.string.show_dev_on, + Toast.LENGTH_LONG); + mDevHitToast.show(); + // This is good time to index the Developer Options + Index.getInstance( + mContext.getApplicationContext()).updateFromClassNameResource( + DevelopmentSettings.class.getName(), true, true); + } +} diff --git a/tests/robotests/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java new file mode 100644 index 00000000000..e3a4ba366f4 --- /dev/null +++ b/tests/robotests/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java @@ -0,0 +1,164 @@ +/* + * 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.app.Activity; +import android.app.Fragment; +import android.content.Context; +import android.os.Build; +import android.os.UserManager; +import android.provider.Settings; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.DevelopmentSettings; +import com.android.settings.TestConfig; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +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.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class BuildNumberPreferenceControllerTest { + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Context mContext; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Activity mActivity; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Fragment mFragment; + @Mock(answer = RETURNS_DEEP_STUBS) + private PreferenceScreen mScreen; + @Mock + private UserManager mUserManager; + + private Preference mPreference; + private BuildNumberPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + mController = new BuildNumberPreferenceController(mContext, mActivity, mFragment); + + mPreference = new Preference(ShadowApplication.getInstance().getApplicationContext()); + mPreference.setKey(mController.getPreferenceKey()); + } + + @Test + public void displayPref_shouldAlwaysDisplay() { + mController.displayPreference(mScreen); + + verify(mScreen.findPreference(mController.getPreferenceKey())).setSummary(Build.DISPLAY); + verify(mScreen, never()).removePreference(any(Preference.class)); + } + + @Test + public void handlePrefTreeClick_onlyHandleBuildNumberPref() { + assertThat(mController.handlePreferenceTreeClick(mock(Preference.class))).isFalse(); + } + + @Test + public void handlePrefTreeClick_notAdminUser_doNothing() { + when(mUserManager.isAdminUser()).thenReturn(false); + + assertThat(mController.handlePreferenceTreeClick(mPreference)).isFalse(); + } + + @Test + public void handlePrefTreeClick_deviceNotProvisioned_doNothing() { + when(mUserManager.isAdminUser()).thenReturn(true); + final Context context = ShadowApplication.getInstance().getApplicationContext(); + Settings.Global.putInt(context.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 0); + + mController = new BuildNumberPreferenceController(context, mActivity, mFragment); + + assertThat(mController.handlePreferenceTreeClick(mPreference)).isFalse(); + } + + @Test + public void handlePrefTreeClick_userHasRestrction_doNothing() { + when(mUserManager.isAdminUser()).thenReturn(true); + final Context context = ShadowApplication.getInstance().getApplicationContext(); + Settings.Global.putInt(context.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 1); + when(mUserManager.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES)) + .thenReturn(true); + + mController = new BuildNumberPreferenceController(context, mActivity, mFragment); + + assertThat(mController.handlePreferenceTreeClick(mPreference)).isFalse(); + } + + @Test + public void onActivityResult_notConfirmPasswordRequest_doNothing() { + final boolean activityResultHandled = mController.onActivityResult( + BuildNumberPreferenceController.REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF + 1, + Activity.RESULT_OK, + null); + + assertThat(activityResultHandled).isFalse(); + verify(mContext, never()) + .getSharedPreferences(DevelopmentSettings.PREF_FILE, Context.MODE_PRIVATE); + } + + @Test + public void onActivityResult_confirmPasswordRequestFailed_doNotEnableDevPref() { + final boolean activityResultHandled = mController.onActivityResult( + BuildNumberPreferenceController.REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF, + Activity.RESULT_CANCELED, + null); + + assertThat(activityResultHandled).isTrue(); + verify(mContext, never()) + .getSharedPreferences(DevelopmentSettings.PREF_FILE, Context.MODE_PRIVATE); + } + + @Test + public void onActivityResult_confirmPasswordRequestCompleted_enableDevPref() { + final Context context = ShadowApplication.getInstance().getApplicationContext(); + + mController = new BuildNumberPreferenceController(context, mActivity, mFragment); + + final boolean activityResultHandled = mController.onActivityResult( + BuildNumberPreferenceController.REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF, + Activity.RESULT_OK, + null); + + assertThat(activityResultHandled).isTrue(); + assertThat(context.getSharedPreferences(DevelopmentSettings.PREF_FILE, + Context.MODE_PRIVATE).getBoolean(DevelopmentSettings.PREF_SHOW, false)) + .isTrue(); + } + +}