diff --git a/src/com/android/settings/development/AdbClearKeysDialogHost.java b/src/com/android/settings/development/AdbClearKeysDialogHost.java new file mode 100644 index 00000000000..7f69f76bee4 --- /dev/null +++ b/src/com/android/settings/development/AdbClearKeysDialogHost.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 AdbClearKeysDialogFragment callbacks. + */ +public interface AdbClearKeysDialogHost { + + /** + * Called when the user presses ok on the warning dialog. + */ + void onAdbClearKeysDialogConfirmed(); +} diff --git a/src/com/android/settings/development/ClearAdbKeysPreferenceController.java b/src/com/android/settings/development/ClearAdbKeysPreferenceController.java new file mode 100644 index 00000000000..9c93280e777 --- /dev/null +++ b/src/com/android/settings/development/ClearAdbKeysPreferenceController.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.hardware.usb.IUsbManager; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemProperties; +import android.os.UserManager; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.text.TextUtils; +import android.util.Log; + +public class ClearAdbKeysPreferenceController extends DeveloperOptionsPreferenceController { + + private static final String TAG = "ClearAdbPrefCtrl"; + private static final String CLEAR_ADB_KEYS = "clear_adb_keys"; + + @VisibleForTesting + static final String RO_ADB_SECURE_PROPERTY_KEY = "ro.adb.secure"; + + private final IUsbManager mUsbManager; + private final DevelopmentSettingsDashboardFragment mFragment; + + private Preference mPreference; + + public ClearAdbKeysPreferenceController(Context context, + DevelopmentSettingsDashboardFragment fragment) { + super(context); + + mFragment = fragment; + mUsbManager = IUsbManager.Stub.asInterface(ServiceManager.getService(Context.USB_SERVICE)); + } + + @Override + public boolean isAvailable() { + return SystemProperties.getBoolean(RO_ADB_SECURE_PROPERTY_KEY, false /* default */); + } + + @Override + public String getPreferenceKey() { + return CLEAR_ADB_KEYS; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + mPreference = screen.findPreference(getPreferenceKey()); + if (mPreference != null && !isAdminUser()) { + mPreference.setEnabled(false); + } + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (TextUtils.equals(preference.getKey(), getPreferenceKey())) { + ClearAdbKeysWarningDialog.show(mFragment); + return true; + } + return false; + } + + @Override + protected void onDeveloperOptionsSwitchEnabled() { + if (isAdminUser()) { + mPreference.setEnabled(true); + } + } + + @Override + protected void onDeveloperOptionsSwitchDisabled() { + mPreference.setEnabled(false); + } + + public void onClearAdbKeysConfirmed() { + try { + mUsbManager.clearUsbDebuggingKeys(); + } catch (RemoteException e) { + Log.e(TAG, "Unable to clear adb keys", e); + } + } + + @VisibleForTesting + boolean isAdminUser() { + return ((UserManager) mContext.getSystemService(Context.USER_SERVICE)).isAdminUser(); + } +} diff --git a/src/com/android/settings/development/ClearAdbKeysWarningDialog.java b/src/com/android/settings/development/ClearAdbKeysWarningDialog.java new file mode 100644 index 00000000000..61b55a02f62 --- /dev/null +++ b/src/com/android/settings/development/ClearAdbKeysWarningDialog.java @@ -0,0 +1,67 @@ +/* + * 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 ClearAdbKeysWarningDialog extends InstrumentedDialogFragment implements + DialogInterface.OnClickListener, DialogInterface.OnDismissListener { + + public static final String TAG = "ClearAdbKeysDlg"; + + public static void show(Fragment host) { + final FragmentManager manager = host.getActivity().getFragmentManager(); + if (manager.findFragmentByTag(TAG) == null) { + final ClearAdbKeysWarningDialog dialog = + new ClearAdbKeysWarningDialog(); + dialog.setTargetFragment(host, 0 /* requestCode */); + dialog.show(manager, TAG); + } + } + + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.DIALOG_CLEAR_ADB_KEYS; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return new AlertDialog.Builder(getActivity()) + .setMessage(R.string.adb_keys_warning_message) + .setPositiveButton(android.R.string.ok, this /* onClickListener */) + .setNegativeButton(android.R.string.cancel, null /* onClickListener */) + .create(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + final AdbClearKeysDialogHost host = (AdbClearKeysDialogHost) getTargetFragment(); + if (host == null) { + return; + } + host.onAdbClearKeysDialogConfirmed(); + } +} diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index 61f36b39523..1a788b5dad7 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -50,7 +50,8 @@ import java.util.Arrays; import java.util.List; public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFragment - implements SwitchBar.OnSwitchChangeListener, OemUnlockDialogHost, AdbDialogHost { + implements SwitchBar.OnSwitchChangeListener, OemUnlockDialogHost, AdbDialogHost, + AdbClearKeysDialogHost { private static final String TAG = "DevSettingsDashboard"; @@ -170,6 +171,13 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra controller.onAdbDialogDismissed(); } + @Override + public void onAdbClearKeysDialogConfirmed() { + final ClearAdbKeysPreferenceController controller = getDevelopmentOptionsController( + ClearAdbKeysPreferenceController.class); + controller.onClearAdbKeysConfirmed(); + } + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { boolean handledResult = false; @@ -252,7 +260,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra // system ui demo mode // quick settings developer tiles controllers.add(new AdbPreferenceController(context, fragment)); - // revoke usb debugging authorizations + controllers.add(new ClearAdbKeysPreferenceController(context, fragment)); controllers.add(new LocalTerminalPreferenceController(context)); controllers.add(new BugReportInPowerPreferenceControllerV2(context)); // select mock location app diff --git a/tests/robotests/src/com/android/settings/development/ClearAdbKeysPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/ClearAdbKeysPreferenceControllerTest.java new file mode 100644 index 00000000000..4bcdb9812a9 --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/ClearAdbKeysPreferenceControllerTest.java @@ -0,0 +1,190 @@ +/* + * 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.ClearAdbKeysPreferenceController + .RO_ADB_SECURE_PROPERTY_KEY; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Fragment; +import android.content.Context; +import android.hardware.usb.IUsbManager; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.support.v14.preference.SwitchPreference; +import android.support.v7.preference.PreferenceScreen; + +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.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, + sdk = TestConfig.SDK_VERSION, + shadows = {SettingsShadowSystemProperties.class}) +public class ClearAdbKeysPreferenceControllerTest { + + @Mock + private PreferenceScreen mScreen; + @Mock + private SwitchPreference mPreference; + @Mock + private IUsbManager mUsbManager; + @Mock + private DevelopmentSettingsDashboardFragment mFragment; + + private Context mContext; + + private ClearAdbKeysPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mController = spy(new ClearAdbKeysPreferenceController(mContext, mFragment)); + ReflectionHelpers.setField(mController, "mUsbManager", mUsbManager); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + } + + @After + public void tearDown() { + ShadowClearAdbKeysWarningDialog.resetDialog(); + SettingsShadowSystemProperties.clear(); + } + + @Test + public void isAvailable_roAdbSecureEnabled_shouldBeTrue() { + SystemProperties.set(RO_ADB_SECURE_PROPERTY_KEY, Boolean.toString(true)); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_roAdbSecureDisabled_shouldBeFalse() { + SystemProperties.set(RO_ADB_SECURE_PROPERTY_KEY, Boolean.toString(false)); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void displayPreference_isNotAdminUser_preferenceShouldBeDisabled() { + SystemProperties.set(RO_ADB_SECURE_PROPERTY_KEY, Boolean.toString(true)); + doReturn(false).when(mController).isAdminUser(); + + mController.displayPreference(mScreen); + + verify(mPreference).setEnabled(false); + } + + @Test + @Config(shadows = { + ShadowClearAdbKeysWarningDialog.class + }) + public void handlePreferenceTreeClick_clearAdbKeysPreference_shouldShowWarningDialog() { + SystemProperties.set(RO_ADB_SECURE_PROPERTY_KEY, Boolean.toString(true)); + doReturn(true).when(mController).isAdminUser(); + mController.displayPreference(mScreen); + final String preferenceKey = mController.getPreferenceKey(); + when(mPreference.getKey()).thenReturn(preferenceKey); + final boolean isHandled = mController.handlePreferenceTreeClick(mPreference); + + assertThat(ShadowClearAdbKeysWarningDialog.sIsShowing).isTrue(); + assertThat(isHandled).isTrue(); + } + + @Test + public void handlePreferenceTreeClick_notClearAdbKeysPreference_shouldReturnFalse() { + SystemProperties.set(RO_ADB_SECURE_PROPERTY_KEY, Boolean.toString(true)); + doReturn(true).when(mController).isAdminUser(); + mController.displayPreference(mScreen); + when(mPreference.getKey()).thenReturn("Some random key!!!"); + final boolean isHandled = mController.handlePreferenceTreeClick(mPreference); + + assertThat(isHandled).isFalse(); + } + + @Test + public void onDeveloperOptionsSwitchEnabled_isAdminUser_shouldEnablePreference() { + SystemProperties.set(RO_ADB_SECURE_PROPERTY_KEY, Boolean.toString(true)); + doReturn(true).when(mController).isAdminUser(); + mController.displayPreference(mScreen); + mController.onDeveloperOptionsSwitchEnabled(); + + verify(mPreference).setEnabled(true); + } + + @Test + public void onDeveloperOptionsSwitchEnabled_isNotAdminUser_shouldNotEnablePreference() { + SystemProperties.set(RO_ADB_SECURE_PROPERTY_KEY, Boolean.toString(true)); + doReturn(false).when(mController).isAdminUser(); + mController.displayPreference(mScreen); + mController.onDeveloperOptionsSwitchEnabled(); + + verify(mPreference, never()).setEnabled(true); + } + + @Test + public void onDeveloperOptionsSwitchDisabled_shouldDisablePreference() { + SystemProperties.set(RO_ADB_SECURE_PROPERTY_KEY, Boolean.toString(true)); + doReturn(true).when(mController).isAdminUser(); + mController.displayPreference(mScreen); + mController.onDeveloperOptionsSwitchDisabled(); + + verify(mPreference).setEnabled(false); + } + + @Test + public void onClearAdbKeysConfirmed_shouldClearKeys() throws RemoteException { + mController.onClearAdbKeysConfirmed(); + + verify(mUsbManager).clearUsbDebuggingKeys(); + } + + @Implements(ClearAdbKeysWarningDialog.class) + public static class ShadowClearAdbKeysWarningDialog { + + public static boolean sIsShowing; + + @Implementation + public static void show(Fragment host) { + sIsShowing = true; + } + + public static void resetDialog() { + sIsShowing = false; + } + } +} diff --git a/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java index 7a48120e870..ddf536544b7 100644 --- a/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java @@ -114,7 +114,8 @@ public class DevelopmentSettingsDashboardFragmentTest { @Config(shadows = { ShadowPictureColorModePreferenceController.class, ShadowAdbPreferenceController.class, - ShadowBluetoothInbandRingingPreferenceController.class + ShadowBluetoothInbandRingingPreferenceController.class, + ShadowClearAdbKeysPreferenceController.class }) public void searchIndex_pageEnabled_shouldNotAddKeysToNonIndexable() { final Context appContext = RuntimeEnvironment.application; @@ -207,6 +208,17 @@ public class DevelopmentSettingsDashboardFragmentTest { verify(controller).onAdbDialogDismissed(); } + @Test + public void onAdbClearKeysDialogConfirmed_shouldCallControllerDialogConfirmed() { + final ClearAdbKeysPreferenceController controller = mock( + ClearAdbKeysPreferenceController.class); + doReturn(controller).when(mDashboard).getDevelopmentOptionsController( + ClearAdbKeysPreferenceController.class); + mDashboard.onAdbClearKeysDialogConfirmed(); + + verify(controller).onClearAdbKeysConfirmed(); + } + @Implements(EnableDevelopmentSettingWarningDialog.class) public static class ShadowEnableDevelopmentSettingWarningDialog { @@ -247,4 +259,13 @@ public class DevelopmentSettingsDashboardFragmentTest { return true; } } + + @Implements(ClearAdbKeysPreferenceController.class) + public static class ShadowClearAdbKeysPreferenceController { + + @Implementation + public boolean isAvailable() { + return true; + } + } }