From 8d8f1663b4fabf4de640e4530a9c9298d86549c8 Mon Sep 17 00:00:00 2001 From: jeffreyhuang Date: Thu, 21 Sep 2017 11:14:22 -0700 Subject: [PATCH] Introduce AdbPreferenceController - Create new AdbPreferenceController - Create AdbOnChangeListener interface for adb change callbacks - Create EnableAdbSettingWarningDialog - Create controller inside the DashboardFragment - Port logic from DevelopmentSettings into the controller - Add deprecated annotation to EnableAdbPreferenceController Bug: 34203528 Test: make RunSettingsRoboTests -j40 Change-Id: Id57146149943d770bf5ccc9ed5ecc6ea9d8ae66c --- .../settings/development/AdbDialogHost.java | 33 ++++ .../development/AdbOnChangeListener.java | 28 ++++ .../development/AdbPreferenceController.java | 116 +++++++++++++ .../DevelopmentSettingsDashboardFragment.java | 59 ++++++- .../EnableAdbPreferenceController.java | 4 + .../development/EnableAdbWarningDialog.java | 81 ++++++++++ .../AdbPreferenceControllerTest.java | 152 ++++++++++++++++++ ...elopmentSettingsDashboardFragmentTest.java | 30 +++- .../EnableAdbPreferenceControllerTest.java | 4 + 9 files changed, 504 insertions(+), 3 deletions(-) create mode 100644 src/com/android/settings/development/AdbDialogHost.java create mode 100644 src/com/android/settings/development/AdbOnChangeListener.java create mode 100644 src/com/android/settings/development/AdbPreferenceController.java create mode 100644 src/com/android/settings/development/EnableAdbWarningDialog.java create mode 100644 tests/robotests/src/com/android/settings/development/AdbPreferenceControllerTest.java diff --git a/src/com/android/settings/development/AdbDialogHost.java b/src/com/android/settings/development/AdbDialogHost.java new file mode 100644 index 00000000000..3288ab3af0b --- /dev/null +++ b/src/com/android/settings/development/AdbDialogHost.java @@ -0,0 +1,33 @@ +/* + * 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; + +/** + * Interface for EnableAdbWarningDialog callbacks. + */ +public interface AdbDialogHost { + /** + * Called when the user presses enable on the warning dialog. + */ + void onEnableAdbDialogConfirmed(); + + /** + * Called when the user dismisses or cancels the warning dialog. + */ + void onEnableAdbDialogDismissed(); +} diff --git a/src/com/android/settings/development/AdbOnChangeListener.java b/src/com/android/settings/development/AdbOnChangeListener.java new file mode 100644 index 00000000000..80692706596 --- /dev/null +++ b/src/com/android/settings/development/AdbOnChangeListener.java @@ -0,0 +1,28 @@ +/* + * 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; + +/** + * Interface for callbacks in Adb setting changes. + */ +public interface AdbOnChangeListener { + + /** + * Called when the global Adb setting changes + */ + void onAdbSettingChanged(); +} diff --git a/src/com/android/settings/development/AdbPreferenceController.java b/src/com/android/settings/development/AdbPreferenceController.java new file mode 100644 index 00000000000..ed13b7339c5 --- /dev/null +++ b/src/com/android/settings/development/AdbPreferenceController.java @@ -0,0 +1,116 @@ +/* + * 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.Intent; +import android.os.UserManager; +import android.provider.Settings; +import android.support.annotation.VisibleForTesting; +import android.support.v14.preference.SwitchPreference; +import android.support.v4.content.LocalBroadcastManager; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +public class AdbPreferenceController extends DeveloperOptionsPreferenceController implements + Preference.OnPreferenceChangeListener { + + public static final String ADB_STATE_CHANGED = + "com.android.settings.development.AdbPreferenceController.ADB_STATE_CHANGED"; + public static final int ADB_SETTING_ON = 1; + public static final int ADB_SETTING_OFF = 0; + + private static final String KEY_ENABLE_ADB = "enable_adb"; + + private final DevelopmentSettingsDashboardFragment mFragment; + private SwitchPreference mPreference; + + public AdbPreferenceController(Context context, DevelopmentSettingsDashboardFragment fragment) { + super(context); + mFragment = fragment; + } + + @Override + public boolean isAvailable() { + return mContext.getSystemService(UserManager.class).isAdminUser(); + } + + @Override + public String getPreferenceKey() { + return KEY_ENABLE_ADB; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + mPreference = (SwitchPreference) screen.findPreference(getPreferenceKey()); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final boolean isAdbEnabled = (Boolean) newValue; + if (isAdbEnabled) { + EnableAdbWarningDialog.show(mFragment); + } else { + writeAdbSetting(isAdbEnabled); + notifyStateChanged(); + } + return true; + } + + @Override + public void updateState(Preference preference) { + final int adbMode = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.ADB_ENABLED, 0 /* default */); + mPreference.setChecked(adbMode != ADB_SETTING_OFF); + } + + @Override + protected void onDeveloperOptionsSwitchEnabled() { + mPreference.setEnabled(true); + } + + @Override + protected void onDeveloperOptionsSwitchDisabled() { + writeAdbSetting(false); + notifyStateChanged(); + mPreference.setEnabled(false); + mPreference.setChecked(false); + } + + public void onAdbDialogConfirmed() { + writeAdbSetting(true); + notifyStateChanged(); + } + + public void onAdbDialogDismissed() { + updateState(mPreference); + } + + private void writeAdbSetting(boolean enabled) { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.ADB_ENABLED, enabled ? ADB_SETTING_ON : ADB_SETTING_OFF); + } + + @VisibleForTesting + void notifyStateChanged() { + LocalBroadcastManager.getInstance(mContext) + .sendBroadcast(new Intent(ADB_STATE_CHANGED)); + } +} diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index 2324a4a0300..59f80c4ec61 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -17,13 +17,19 @@ package com.android.settings.development; import android.app.Activity; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.os.Bundle; import android.os.UserManager; import android.provider.SearchIndexableResource; import android.support.annotation.VisibleForTesting; +import android.support.v4.content.LocalBroadcastManager; import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; import android.widget.Switch; import com.android.internal.logging.nano.MetricsProto; @@ -43,7 +49,7 @@ import java.util.Arrays; import java.util.List; public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFragment - implements SwitchBar.OnSwitchChangeListener, OemUnlockDialogHost { + implements SwitchBar.OnSwitchChangeListener, OemUnlockDialogHost, AdbDialogHost { private static final String TAG = "DevSettingsDashboard"; @@ -52,6 +58,17 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra private DevelopmentSwitchBarController mSwitchBarController; private List mPreferenceControllers = new ArrayList<>(); + private final BroadcastReceiver mEnableAdbReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + for (AbstractPreferenceController controller : mPreferenceControllers) { + if (controller instanceof AdbOnChangeListener) { + ((AdbOnChangeListener) controller).onAdbSettingChanged(); + } + } + } + }; + public DevelopmentSettingsDashboardFragment() { super(UserManager.DISALLOW_DEBUGGING_FEATURES); } @@ -79,6 +96,19 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra mSwitchBar.show(); } + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + registerReceivers(); + return super.onCreateView(inflater, container, savedInstanceState); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unregisterReceivers(); + } + @Override public int getMetricsCategory() { return MetricsProto.MetricsEvent.DEVELOPMENT; @@ -120,6 +150,21 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra controller.onOemUnlockDismissed(); } + @Override + public void onEnableAdbDialogConfirmed() { + final AdbPreferenceController controller = getDevelopmentOptionsController( + AdbPreferenceController.class); + controller.onAdbDialogConfirmed(); + + } + + @Override + public void onEnableAdbDialogDismissed() { + final AdbPreferenceController controller = getDevelopmentOptionsController( + AdbPreferenceController.class); + controller.onAdbDialogDismissed(); + } + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { boolean handledResult = false; @@ -160,6 +205,16 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra return mPreferenceControllers; } + private void registerReceivers() { + LocalBroadcastManager.getInstance(getContext()) + .registerReceiver(mEnableAdbReceiver, new IntentFilter( + AdbPreferenceController.ADB_STATE_CHANGED)); + } + + private void unregisterReceivers() { + LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mEnableAdbReceiver); + } + void onEnableDevelopmentOptionsConfirmed() { DevelopmentSettingsEnabler.setDevelopmentSettingsEnabled(getContext(), true); for (AbstractPreferenceController controller : mPreferenceControllers) { @@ -191,7 +246,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra controllers.add(new DisableAutomaticUpdatesPreferenceController(context)); // system ui demo mode // quick settings developer tiles - // usb debugging + controllers.add(new AdbPreferenceController(context, fragment)); // revoke usb debugging authorizations controllers.add(new LocalTerminalPreferenceController(context)); // bug report shortcut diff --git a/src/com/android/settings/development/EnableAdbPreferenceController.java b/src/com/android/settings/development/EnableAdbPreferenceController.java index bd6267badf1..a866353fecd 100644 --- a/src/com/android/settings/development/EnableAdbPreferenceController.java +++ b/src/com/android/settings/development/EnableAdbPreferenceController.java @@ -27,6 +27,10 @@ import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; import com.android.settingslib.development.AbstractEnableAdbPreferenceController; +/** + * @deprecated in favor of {@link AdbPreferenceController} + */ +@Deprecated public class EnableAdbPreferenceController extends AbstractEnableAdbPreferenceController implements PreferenceControllerMixin { diff --git a/src/com/android/settings/development/EnableAdbWarningDialog.java b/src/com/android/settings/development/EnableAdbWarningDialog.java new file mode 100644 index 00000000000..9829f70d1e3 --- /dev/null +++ b/src/com/android/settings/development/EnableAdbWarningDialog.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; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.Fragment; +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 EnableAdbWarningDialog extends InstrumentedDialogFragment implements + DialogInterface.OnClickListener, DialogInterface.OnDismissListener { + + public static final String TAG = "EnableAdbDialog"; + + public static void show(Fragment host) { + final FragmentManager manager = host.getActivity().getFragmentManager(); + if (manager.findFragmentByTag(TAG) == null) { + final EnableAdbWarningDialog dialog = new EnableAdbWarningDialog(); + dialog.setTargetFragment(host, 0 /* requestCode */); + dialog.show(manager, TAG); + } + } + + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.DIALOG_ENABLE_ADB; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.adb_warning_title) + .setMessage(R.string.adb_warning_message) + .setPositiveButton(android.R.string.yes, this /* onClickListener */) + .setNegativeButton(android.R.string.no, this /* onClickListener */) + .create(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + final AdbDialogHost host = (AdbDialogHost) getTargetFragment(); + if (host == null) { + return; + } + if (which == DialogInterface.BUTTON_POSITIVE) { + host.onEnableAdbDialogConfirmed(); + } else { + host.onEnableAdbDialogDismissed(); + } + } + + @Override + public void onDismiss(DialogInterface dialog) { + super.onDismiss(dialog); + final AdbDialogHost host = (AdbDialogHost) getTargetFragment(); + if (host == null) { + return; + } + host.onEnableAdbDialogDismissed(); + } +} diff --git a/tests/robotests/src/com/android/settings/development/AdbPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/AdbPreferenceControllerTest.java new file mode 100644 index 00000000000..2404f677d0b --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/AdbPreferenceControllerTest.java @@ -0,0 +1,152 @@ +/* + * 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.ContentResolver; +import android.content.Context; +import android.os.UserManager; +import android.provider.Settings; +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; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class AdbPreferenceControllerTest { + @Mock + private Context mContext; + @Mock + private SwitchPreference mPreference; + @Mock + private PreferenceScreen mPreferenceScreen; + @Mock + private UserManager mUserManager; + @Mock + private DevelopmentSettingsDashboardFragment mFragment; + + private ContentResolver mContentResolver; + private AdbPreferenceController mController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContentResolver = RuntimeEnvironment.application.getContentResolver(); + mController = new AdbPreferenceController(mContext, mFragment); + when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); + when(mContext.getContentResolver()).thenReturn(mContentResolver); + when(mPreferenceScreen.findPreference(mController.getPreferenceKey())).thenReturn( + mPreference); + mController.displayPreference(mPreferenceScreen); + } + + @Test + public void isAvailable_notAdmin_shouldBeFalse() { + when(mUserManager.isAdminUser()).thenReturn(false); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_isAdmin_shouldBeTrue() { + when(mUserManager.isAdminUser()).thenReturn(true); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void onPreferenceChanged_settingDisabled_shouldTurnOffAdb() { + when(mContext.getApplicationContext()).thenReturn(RuntimeEnvironment.application); + mController.onPreferenceChange(null, false); + + final int mode = Settings.System.getInt(mContentResolver, + Settings.Global.ADB_ENABLED, -1); + + assertThat(mode).isEqualTo(AdbPreferenceController.ADB_SETTING_OFF); + } + + @Test + public void updateState_settingEnabled_preferenceShouldBeChecked() { + Settings.System.putInt(mContentResolver, Settings.Global.ADB_ENABLED, + AdbPreferenceController.ADB_SETTING_ON); + mController.updateState(mPreference); + + verify(mPreference).setChecked(true); + } + + @Test + public void updateState_settingDisabled_preferenceShouldNotBeChecked() { + Settings.System.putInt(mContentResolver, Settings.Global.ADB_ENABLED, + AdbPreferenceController.ADB_SETTING_OFF); + mController.updateState(mPreference); + + verify(mPreference).setChecked(false); + } + + @Test + public void onDeveloperOptionsDisabled_shouldDisablePreference() { + when(mContext.getApplicationContext()).thenReturn(RuntimeEnvironment.application); + when(mUserManager.isAdminUser()).thenReturn(true); + mController.onDeveloperOptionsDisabled(); + final int mode = Settings.System.getInt(mContentResolver, + Settings.Global.ADB_ENABLED, -1); + + assertThat(mode).isEqualTo(AdbPreferenceController.ADB_SETTING_OFF); + verify(mPreference).setEnabled(false); + verify(mPreference).setChecked(false); + } + + @Test + public void onDeveloperOptionsEnabled_shouldEnablePreference() { + when(mUserManager.isAdminUser()).thenReturn(true); + mController.onDeveloperOptionsEnabled(); + + verify(mPreference).setEnabled(true); + } + + @Test + public void onAdbDialogConfirmed_shouldEnableAdbSetting() { + mController.onAdbDialogConfirmed(); + final int mode = Settings.System.getInt(mContentResolver, + Settings.Global.ADB_ENABLED, -1); + + assertThat(mode).isEqualTo(AdbPreferenceController.ADB_SETTING_ON); + } + + @Test + public void onAdbDialogDismissed_preferenceShouldNotBeChecked() { + Settings.System.putInt(mContentResolver, Settings.Global.ADB_ENABLED, + AdbPreferenceController.ADB_SETTING_OFF); + mController.onAdbDialogDismissed(); + + verify(mPreference).setChecked(false); + } +} diff --git a/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java index 13f73742d65..7121a30514f 100644 --- a/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java @@ -113,7 +113,8 @@ public class DevelopmentSettingsDashboardFragmentTest { @Test @Config(shadows = { - ShadowPictureColorModePreferenceController.class + ShadowPictureColorModePreferenceController.class, + ShadowAdbPreferenceController.class }) public void searchIndex_pageEnabled_shouldNotAddKeysToNonIndexable() { final Context appContext = RuntimeEnvironment.application; @@ -186,6 +187,26 @@ public class DevelopmentSettingsDashboardFragmentTest { verify(controller).onOemUnlockDismissed(); } + @Test + public void onAdbDialogConfirmed_shouldCallControllerDialogConfirmed() { + final AdbPreferenceController controller = mock(AdbPreferenceController.class); + doReturn(controller).when(mDashboard).getDevelopmentOptionsController( + AdbPreferenceController.class); + mDashboard.onEnableAdbDialogConfirmed(); + + verify(controller).onAdbDialogConfirmed(); + } + + @Test + public void onAdbDialogDismissed_shouldCallControllerOemDismissed() { + final AdbPreferenceController controller = mock(AdbPreferenceController.class); + doReturn(controller).when(mDashboard).getDevelopmentOptionsController( + AdbPreferenceController.class); + mDashboard.onEnableAdbDialogDismissed(); + + verify(controller).onAdbDialogDismissed(); + } + @Implements(EnableDevelopmentSettingWarningDialog.class) public static class ShadowEnableDevelopmentSettingWarningDialog { @@ -204,7 +225,14 @@ public class DevelopmentSettingsDashboardFragmentTest { @Implements(PictureColorModePreferenceController.class) public static class ShadowPictureColorModePreferenceController { + @Implementation + public boolean isAvailable() { + return true; + } + } + @Implements(AdbPreferenceController.class) + public static class ShadowAdbPreferenceController { @Implementation public boolean isAvailable() { return true; diff --git a/tests/robotests/src/com/android/settings/development/EnableAdbPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/EnableAdbPreferenceControllerTest.java index 6dcfbf2bc3d..e7080ebb79f 100644 --- a/tests/robotests/src/com/android/settings/development/EnableAdbPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/EnableAdbPreferenceControllerTest.java @@ -31,6 +31,10 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +/** + * @deprecated in favor of {@link AdbPreferenceController} + */ +@Deprecated @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class EnableAdbPreferenceControllerTest {