diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index 30be65483f5..919dc3a4bef 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -17,12 +17,16 @@ package com.android.settings.development; import android.content.Context; +import android.os.Bundle; import android.os.UserManager; import android.provider.SearchIndexableResource; import android.util.Log; +import android.widget.Switch; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.Utils; import com.android.settings.dashboard.RestrictedDashboardFragment; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; @@ -33,21 +37,67 @@ import com.android.settingslib.development.DevelopmentSettingsEnabler; import java.util.Arrays; import java.util.List; -public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFragment { +public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFragment + implements SwitchBar.OnSwitchChangeListener { private static final String TAG = "DevSettingsDashboard"; + private boolean mIsAvailable = true; private SwitchBar mSwitchBar; + private DevelopmentSwitchBarController mSwitchBarController; public DevelopmentSettingsDashboardFragment() { super(UserManager.DISALLOW_DEBUGGING_FEATURES); } + @Override + public void onActivityCreated(Bundle icicle) { + super.onActivityCreated(icicle); + // Apply page-level restrictions + setIfOnlyAvailableForAdmins(true); + if (isUiRestricted() || !Utils.isDeviceProvisioned(getActivity())) { + // Block access to developer options if the user is not the owner, if user policy + // restricts it, or if the device has not been provisioned + mIsAvailable = false; + // Show error message + if (!isUiRestrictedByOnlyAdmin()) { + getEmptyTextView().setText(R.string.development_settings_not_available); + } + getPreferenceScreen().removeAll(); + return; + } + // Set up master switch + mSwitchBar = ((SettingsActivity) getActivity()).getSwitchBar(); + mSwitchBarController = new DevelopmentSwitchBarController( + this /* DevelopmentSettings */, mSwitchBar, mIsAvailable, getLifecycle()); + mSwitchBar.show(); + } + @Override public int getMetricsCategory() { return MetricsProto.MetricsEvent.DEVELOPMENT; } + @Override + public void onSwitchChanged(Switch switchView, boolean isChecked) { + if (switchView != mSwitchBar.getSwitch()) { + return; + } + final boolean developmentEnabledState = + DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(getContext()); + if (isChecked != developmentEnabledState) { + if (isChecked) { + EnableDevelopmentSettingWarningDialog.show(this /* host */); + } else { + // TODO: Reset dangerous options (move logic from DevelopmentSettings). + // resetDangerousOptions(); + DevelopmentSettingsEnabler.setDevelopmentSettingsEnabled(getContext(), false); + // TODO: Refresh all prefs' enabled state (move logic from DevelopmentSettings). + // setPrefsEnabledState(false); + } + } + } + @Override protected String getLogTag() { return TAG; @@ -69,6 +119,16 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra return buildPreferenceControllers(context); } + void onEnableDevelopmentOptionsConfirmed() { + DevelopmentSettingsEnabler.setDevelopmentSettingsEnabled(getContext(), true); + // TODO: Refresh all prefs' enabled state (move logic from DevelopmentSettings). + } + + void onEnableDevelopmentOptionsRejected() { + // Reset the toggle + mSwitchBar.setChecked(false); + } + private static List buildPreferenceControllers(Context context) { return null; } diff --git a/src/com/android/settings/development/DevelopmentSwitchBarController.java b/src/com/android/settings/development/DevelopmentSwitchBarController.java index 168f7c062b3..ae875b39961 100644 --- a/src/com/android/settings/development/DevelopmentSwitchBarController.java +++ b/src/com/android/settings/development/DevelopmentSwitchBarController.java @@ -22,18 +22,39 @@ import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnStart; import com.android.settingslib.core.lifecycle.events.OnStop; +import com.android.settingslib.development.DevelopmentSettingsEnabler; public class DevelopmentSwitchBarController implements LifecycleObserver, OnStart, OnStop { private final SwitchBar mSwitchBar; private final boolean mIsAvailable; private final DevelopmentSettings mSettings; + private final DevelopmentSettingsDashboardFragment mNewSettings; + /** + * @deprecated in favor of the other constructor. + */ + @Deprecated public DevelopmentSwitchBarController(DevelopmentSettings settings, SwitchBar switchBar, boolean isAvailable, Lifecycle lifecycle) { mSwitchBar = switchBar; mIsAvailable = isAvailable && !Utils.isMonkeyRunning(); mSettings = settings; + mNewSettings = null; + + if (mIsAvailable) { + lifecycle.addObserver(this); + } else { + mSwitchBar.setEnabled(false); + } + } + + public DevelopmentSwitchBarController(DevelopmentSettingsDashboardFragment settings, + SwitchBar switchBar, boolean isAvailable, Lifecycle lifecycle) { + mSwitchBar = switchBar; + mIsAvailable = isAvailable && !Utils.isMonkeyRunning(); + mSettings = null; + mNewSettings = settings; if (mIsAvailable) { lifecycle.addObserver(this); @@ -44,11 +65,24 @@ public class DevelopmentSwitchBarController implements LifecycleObserver, OnStar @Override public void onStart() { - mSwitchBar.addOnSwitchChangeListener(mSettings); + if (mSettings != null) { + mSwitchBar.addOnSwitchChangeListener(mSettings); + } + if (mNewSettings != null) { + final boolean developmentEnabledState = DevelopmentSettingsEnabler + .isDevelopmentSettingsEnabled(mNewSettings.getContext()); + mSwitchBar.setChecked(developmentEnabledState); + mSwitchBar.addOnSwitchChangeListener(mNewSettings); + } } @Override public void onStop() { - mSwitchBar.removeOnSwitchChangeListener(mSettings); + if (mSettings != null) { + mSwitchBar.removeOnSwitchChangeListener(mSettings); + } + if (mNewSettings != null) { + mSwitchBar.removeOnSwitchChangeListener(mNewSettings); + } } } diff --git a/src/com/android/settings/development/EnableDevelopmentSettingWarningDialog.java b/src/com/android/settings/development/EnableDevelopmentSettingWarningDialog.java new file mode 100644 index 00000000000..3c3d645911f --- /dev/null +++ b/src/com/android/settings/development/EnableDevelopmentSettingWarningDialog.java @@ -0,0 +1,70 @@ +/* + * 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.app.AlertDialog; +import android.app.Dialog; +import android.app.FragmentManager; +import android.content.DialogInterface; +import android.os.Bundle; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.core.instrumentation.InstrumentedDialogFragment; + +public class EnableDevelopmentSettingWarningDialog extends InstrumentedDialogFragment + implements DialogInterface.OnClickListener { + + public static final String TAG = "EnableDevSettingDlg"; + + public static void show( + DevelopmentSettingsDashboardFragment host) { + final EnableDevelopmentSettingWarningDialog dialog = + new EnableDevelopmentSettingWarningDialog(); + dialog.setTargetFragment(host, 0 /* requestCode */); + final FragmentManager manager = host.getActivity().getFragmentManager(); + if (manager.findFragmentByTag(TAG) == null) { + dialog.show(manager, TAG); + } + } + + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.DIALOG_ENABLE_DEVELOPMENT_OPTIONS; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return new AlertDialog.Builder(getActivity()) + .setMessage(R.string.dev_settings_warning_message) + .setTitle(R.string.dev_settings_warning_title) + .setPositiveButton(android.R.string.yes, this) + .setNegativeButton(android.R.string.no, this) + .create(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + final DevelopmentSettingsDashboardFragment host = + (DevelopmentSettingsDashboardFragment) getTargetFragment(); + if (which == DialogInterface.BUTTON_POSITIVE) { + host.onEnableDevelopmentOptionsConfirmed(); + } else { + host.onEnableDevelopmentOptionsRejected(); + } + } +} diff --git a/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java index 04ff721167f..a001aafcdb1 100644 --- a/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java @@ -17,22 +17,32 @@ package com.android.settings.development; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import android.content.Context; import android.provider.SearchIndexableResource; +import android.provider.Settings; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; import com.android.settings.TestConfig; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.SettingsShadowResources; +import com.android.settings.widget.SwitchBar; +import com.android.settings.widget.ToggleSwitch; import com.android.settingslib.development.DevelopmentSettingsEnabler; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.util.ReflectionHelpers; import java.util.List; @@ -44,11 +54,24 @@ import java.util.List; }) public class DevelopmentSettingsDashboardFragmentTest { + private SwitchBar mSwitchBar; + private ToggleSwitch mSwitch; + private Context mContext; private DevelopmentSettingsDashboardFragment mDashboard; @Before public void setUp() { - mDashboard = new DevelopmentSettingsDashboardFragment(); + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mSwitchBar = new SwitchBar(mContext); + mSwitch = mSwitchBar.getSwitch(); + mDashboard = spy(new DevelopmentSettingsDashboardFragment()); + ReflectionHelpers.setField(mDashboard, "mSwitchBar", mSwitchBar); + } + + @After + public void tearDown() { + ShadowEnableDevelopmentSettingWarningDialog.reset(); } @Test @@ -95,4 +118,62 @@ public class DevelopmentSettingsDashboardFragmentTest { assertThat(nonIndexableKeys).doesNotContain("development_prefs_screen"); } + + @Test + @Config(shadows = { + ShadowEnableDevelopmentSettingWarningDialog.class + }) + public void onSwitchChanged_sameState_shouldDoNothing() { + when(mDashboard.getContext()).thenReturn(mContext); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0); + + mDashboard.onSwitchChanged(mSwitch, false /* isChecked */); + assertThat(ShadowEnableDevelopmentSettingWarningDialog.mShown).isFalse(); + } + + @Test + @Config(shadows = { + ShadowEnableDevelopmentSettingWarningDialog.class + }) + public void onSwitchChanged_turnOn_shouldShowWarningDialog() { + when(mDashboard.getContext()).thenReturn(mContext); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0); + + mDashboard.onSwitchChanged(mSwitch, true /* isChecked */); + assertThat(ShadowEnableDevelopmentSettingWarningDialog.mShown).isTrue(); + } + + @Test + @Config(shadows = { + ShadowEnableDevelopmentSettingWarningDialog.class + }) + public void onSwitchChanged_turnOff_shouldTurnOff() { + when(mDashboard.getContext()).thenReturn(mContext); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1); + + mDashboard.onSwitchChanged(mSwitch, false /* isChecked */); + + assertThat(ShadowEnableDevelopmentSettingWarningDialog.mShown).isFalse(); + assertThat(DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)) + .isFalse(); + } + + @Implements(EnableDevelopmentSettingWarningDialog.class) + public static class ShadowEnableDevelopmentSettingWarningDialog { + + static boolean mShown; + + public static void reset() { + mShown = false; + } + + @Implementation + public static void show( + DevelopmentSettingsDashboardFragment host) { + mShown = true; + } + } } diff --git a/tests/robotests/src/com/android/settings/development/DevelopmentSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/development/DevelopmentSwitchBarControllerTest.java index a53b83662cb..6f79fafc98f 100644 --- a/tests/robotests/src/com/android/settings/development/DevelopmentSwitchBarControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/DevelopmentSwitchBarControllerTest.java @@ -17,6 +17,7 @@ package com.android.settings.development; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; import com.android.settings.TestConfig; import com.android.settings.testutils.SettingsRobolectricTestRunner; @@ -45,6 +46,8 @@ public class DevelopmentSwitchBarControllerTest { @Mock private DevelopmentSettings mSettings; + @Mock + private DevelopmentSettingsDashboardFragment mNewSettings; private Lifecycle mLifecycle; private SwitchBar mSwitchBar; private DevelopmentSwitchBarController mController; @@ -76,6 +79,21 @@ public class DevelopmentSwitchBarControllerTest { assertThat(listeners).doesNotContain(mSettings); } + @Test + public void runThroughLifecycle_v2_isMonkeyRun_shouldNotRegisterListener() { + ShadowUtils.setIsUserAMonkey(true); + mController = new DevelopmentSwitchBarController(mNewSettings, mSwitchBar, + true /* isAvailable */, mLifecycle); + final ArrayList listeners = + ReflectionHelpers.getField(mSwitchBar, "mSwitchChangeListeners"); + + mLifecycle.onStart(); + assertThat(listeners).doesNotContain(mNewSettings); + + mLifecycle.onStop(); + assertThat(listeners).doesNotContain(mNewSettings); + } + @Test public void runThroughLifecycle_isNotMonkeyRun_shouldRegisterAndRemoveListener() { ShadowUtils.setIsUserAMonkey(false); @@ -91,6 +109,22 @@ public class DevelopmentSwitchBarControllerTest { assertThat(listeners).doesNotContain(mSettings); } + @Test + public void runThroughLifecycle_v2_isNotMonkeyRun_shouldRegisterAndRemoveListener() { + when(mNewSettings.getContext()).thenReturn(RuntimeEnvironment.application); + ShadowUtils.setIsUserAMonkey(false); + mController = new DevelopmentSwitchBarController(mNewSettings, mSwitchBar, + true /* isAvailable */, mLifecycle); + final ArrayList listeners = + ReflectionHelpers.getField(mSwitchBar, "mSwitchChangeListeners"); + + mLifecycle.onStart(); + assertThat(listeners).contains(mNewSettings); + + mLifecycle.onStop(); + assertThat(listeners).doesNotContain(mNewSettings); + } + @Test public void buildController_unavailable_shouldDisableSwitchBar() { ShadowUtils.setIsUserAMonkey(false);