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;
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;
}
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<AbstractPreferenceController> buildPreferenceControllers(Context context) {
return null;
}
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);
@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);
+ }
}
}
--- /dev/null
+/*
+ * 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();
+ }
+ }
+}
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;
})
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
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;
+ }
+ }
}
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;
@Mock
private DevelopmentSettings mSettings;
+ @Mock
+ private DevelopmentSettingsDashboardFragment mNewSettings;
private Lifecycle mLifecycle;
private SwitchBar mSwitchBar;
private DevelopmentSwitchBarController mController;
}
@Test
+ public void runThroughLifecycle_v2_isMonkeyRun_shouldNotRegisterListener() {
+ ShadowUtils.setIsUserAMonkey(true);
+ mController = new DevelopmentSwitchBarController(mNewSettings, mSwitchBar,
+ true /* isAvailable */, mLifecycle);
+ final ArrayList<SwitchBar.OnSwitchChangeListener> 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);
mController = new DevelopmentSwitchBarController(mSettings, mSwitchBar,
}
@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<SwitchBar.OnSwitchChangeListener> 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);
mController = new DevelopmentSwitchBarController(mSettings, mSwitchBar,