diff --git a/res/xml/development_prefs.xml b/res/xml/development_prefs.xml index bec8863aad8..0089ed79590 100644 --- a/res/xml/development_prefs.xml +++ b/res/xml/development_prefs.xml @@ -138,7 +138,7 @@ android:title="@string/wait_for_debugger" android:summary="@string/wait_for_debugger_summary"/> - diff --git a/src/com/android/settings/development/DevelopmentSettings.java b/src/com/android/settings/development/DevelopmentSettings.java index ea0adb9190a..0b18987d496 100644 --- a/src/com/android/settings/development/DevelopmentSettings.java +++ b/src/com/android/settings/development/DevelopmentSettings.java @@ -40,7 +40,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.IShortcutService; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.hardware.usb.IUsbManager; import android.hardware.usb.UsbManager; @@ -140,7 +139,6 @@ public class DevelopmentSettings extends RestrictedSettingsFragment private static final String DEBUG_APP_KEY = "debug_app"; private static final String WAIT_FOR_DEBUGGER_KEY = "wait_for_debugger"; private static final String MOCK_LOCATION_APP_KEY = "mock_location_app"; - private static final String VERIFY_APPS_OVER_USB_KEY = "verify_apps_over_usb"; private static final String DEBUG_VIEW_ATTRIBUTES = "debug_view_attributes"; private static final String FORCE_ALLOW_ON_EXTERNAL_KEY = "force_allow_on_external"; private static final String STRICT_MODE_KEY = "strict_mode"; @@ -226,8 +224,6 @@ public class DevelopmentSettings extends RestrictedSettingsFragment private static final String SHOW_ALL_ANRS_KEY = "show_all_anrs"; - private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive"; - private static final String TERMINAL_APP_PACKAGE = "com.android.terminal"; private static final String KEY_CONVERT_FBE = "convert_to_file_encryption"; @@ -274,7 +270,7 @@ public class DevelopmentSettings extends RestrictedSettingsFragment private Preference mMockLocationAppPref; private SwitchPreference mWaitForDebugger; - private SwitchPreference mVerifyAppsOverUsb; + private VerifyAppsOverUsbPreferenceController mVerifyAppsOverUsbController; private SwitchPreference mWifiDisplayCertification; private SwitchPreference mWifiVerboseLogging; private SwitchPreference mWifiAggressiveHandover; @@ -391,6 +387,7 @@ public class DevelopmentSettings extends RestrictedSettingsFragment mBugReportInPowerController = new BugReportInPowerPreferenceController(getActivity()); mTelephonyMonitorController = new TelephonyMonitorPreferenceController(getActivity()); mWebViewAppPrefController = new WebViewAppPreferenceController(getActivity()); + mVerifyAppsOverUsbController = new VerifyAppsOverUsbPreferenceController(getActivity()); setIfOnlyAvailableForAdmins(true); if (isUiRestricted() || !Utils.isDeviceProvisioned(getActivity())) { @@ -451,14 +448,8 @@ public class DevelopmentSettings extends RestrictedSettingsFragment mMockLocationAppPref = findPreference(MOCK_LOCATION_APP_KEY); mAllPrefs.add(mMockLocationAppPref); - mVerifyAppsOverUsb = findAndInitSwitchPref(VERIFY_APPS_OVER_USB_KEY); - if (!showVerifierSetting()) { - if (debugDebuggingCategory != null) { - debugDebuggingCategory.removePreference(mVerifyAppsOverUsb); - } else { - mVerifyAppsOverUsb.setEnabled(false); - } - } + mVerifyAppsOverUsbController.displayPreference(getPreferenceScreen()); + mStrictMode = findAndInitSwitchPref(STRICT_MODE_KEY); mPointerLocation = findAndInitSwitchPref(POINTER_LOCATION_KEY); mShowTouches = findAndInitSwitchPref(SHOW_TOUCHES_KEY); @@ -793,7 +784,7 @@ public class DevelopmentSettings extends RestrictedSettingsFragment updateImmediatelyDestroyActivitiesOptions(); updateAppProcessLimitOptions(); updateShowAllANRsOptions(); - updateVerifyAppsOverUsbOptions(); + mVerifyAppsOverUsbController.updatePreference(); updateOtaDisableAutomaticUpdateOptions(); updateBugreportOptions(); updateForceRtlOptions(); @@ -993,19 +984,6 @@ public class DevelopmentSettings extends RestrictedSettingsFragment } } - private void updateVerifyAppsOverUsbOptions() { - updateSwitchPreference(mVerifyAppsOverUsb, - Settings.Global.getInt(getActivity().getContentResolver(), - Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1) != 0); - mVerifyAppsOverUsb.setEnabled(enableVerifierSetting()); - } - - private void writeVerifyAppsOverUsbOptions() { - Settings.Global.putInt(getActivity().getContentResolver(), - Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, - mVerifyAppsOverUsb.isChecked() ? 1 : 0); - } - private void updateOtaDisableAutomaticUpdateOptions() { // We use the "disabled status" in code, but show the opposite text // "Automatic system updates" on screen. So a value 0 indicates the @@ -1024,31 +1002,6 @@ public class DevelopmentSettings extends RestrictedSettingsFragment mOtaDisableAutomaticUpdate.isChecked() ? 0 : 1); } - private boolean enableVerifierSetting() { - final ContentResolver cr = getActivity().getContentResolver(); - if (Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) == 0) { - return false; - } - if (Settings.Global.getInt(cr, Settings.Global.PACKAGE_VERIFIER_ENABLE, 1) == 0) { - return false; - } else { - final PackageManager pm = getActivity().getPackageManager(); - final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION); - verification.setType(PACKAGE_MIME_TYPE); - verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - final List receivers = pm.queryBroadcastReceivers(verification, 0); - if (receivers.size() == 0) { - return false; - } - } - return true; - } - - private boolean showVerifierSetting() { - return Settings.Global.getInt(getActivity().getContentResolver(), - Settings.Global.PACKAGE_VERIFIER_SETTING_VISIBLE, 1) > 0; - } - private static boolean showEnableOemUnlockPreference(Context context) { return context.getSystemService(Context.OEM_LOCK_SERVICE) != null; } @@ -2414,6 +2367,10 @@ public class DevelopmentSettings extends RestrictedSettingsFragment return true; } + if (mVerifyAppsOverUsbController.handlePreferenceTreeClick(preference)) { + return true; + } + if (preference == mEnableAdb) { if (mEnableAdb.isChecked()) { mDialogClicked = false; @@ -2428,8 +2385,7 @@ public class DevelopmentSettings extends RestrictedSettingsFragment } else { Settings.Global.putInt(getActivity().getContentResolver(), Settings.Global.ADB_ENABLED, 0); - mVerifyAppsOverUsb.setEnabled(false); - mVerifyAppsOverUsb.setChecked(false); + mVerifyAppsOverUsbController.updatePreference(); updateBugreportOptions(); } } else if (preference == mClearAdbKeys) { @@ -2479,8 +2435,6 @@ public class DevelopmentSettings extends RestrictedSettingsFragment startActivityForResult(intent, RESULT_DEBUG_APP); } else if (preference == mWaitForDebugger) { writeDebuggerOptions(); - } else if (preference == mVerifyAppsOverUsb) { - writeVerifyAppsOverUsbOptions(); } else if (preference == mOtaDisableAutomaticUpdate) { writeOtaDisableAutomaticUpdateOptions(); } else if (preference == mStrictMode) { @@ -2628,8 +2582,7 @@ public class DevelopmentSettings extends RestrictedSettingsFragment mDialogClicked = true; Settings.Global.putInt(getActivity().getContentResolver(), Settings.Global.ADB_ENABLED, 1); - mVerifyAppsOverUsb.setEnabled(true); - updateVerifyAppsOverUsbOptions(); + mVerifyAppsOverUsbController.updatePreference(); updateBugreportOptions(); } else { // Reset the toggle diff --git a/src/com/android/settings/development/VerifyAppsOverUsbPreferenceController.java b/src/com/android/settings/development/VerifyAppsOverUsbPreferenceController.java new file mode 100644 index 00000000000..c0bb0c74030 --- /dev/null +++ b/src/com/android/settings/development/VerifyAppsOverUsbPreferenceController.java @@ -0,0 +1,149 @@ +/* + * 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.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.core.PreferenceController; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; +import com.android.settingslib.RestrictedSwitchPreference; + +import java.util.List; + +/** + * Controller to manage the state of "Verify apps over USB" toggle. + */ +public class VerifyAppsOverUsbPreferenceController extends PreferenceController { + private static final String VERIFY_APPS_OVER_USB_KEY = "verify_apps_over_usb"; + private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive"; + + private RestrictedSwitchPreference mPreference; + + /** + * Class for indirection of RestrictedLockUtils for testing purposes. It would be nice to mock + * the appropriate methods in UserManager instead but they aren't accessible. + */ + @VisibleForTesting + class RestrictedLockUtilsDelegate { + public EnforcedAdmin checkIfRestrictionEnforced( + Context context, String userRestriction, int userId) { + return RestrictedLockUtils.checkIfRestrictionEnforced(context, userRestriction, userId); + } + } + // NB: This field is accessed using reflection in the test, please keep name in sync. + private final RestrictedLockUtilsDelegate mRestrictedLockUtils = + new RestrictedLockUtilsDelegate(); + + VerifyAppsOverUsbPreferenceController(Context context) { + super(context); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + if (isAvailable()) { + mPreference = (RestrictedSwitchPreference) + screen.findPreference(VERIFY_APPS_OVER_USB_KEY); + } + } + + @Override + public boolean isAvailable() { + return Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.PACKAGE_VERIFIER_SETTING_VISIBLE, 1) > 0; + } + + @Override + public String getPreferenceKey() { + return VERIFY_APPS_OVER_USB_KEY; + } + + /** Saves the settings value when it is toggled. */ + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (VERIFY_APPS_OVER_USB_KEY.equals(preference.getKey())) { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, mPreference.isChecked() ? 1 : 0); + return true; + } + return false; + } + + /** + * Checks whether the toggle should be enabled depending on whether verify apps over USB is + * possible currently. If ADB is disabled or if package verifier does not exist, the toggle + * should be disabled. + */ + private boolean shouldBeEnabled() { + final ContentResolver cr = mContext.getContentResolver(); + if (Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) == 0) { + return false; + } + if (Settings.Global.getInt(cr, Settings.Global.PACKAGE_VERIFIER_ENABLE, 1) == 0) { + return false; + } else { + final PackageManager pm = mContext.getPackageManager(); + final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION); + verification.setType(PACKAGE_MIME_TYPE); + verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + final List receivers = pm.queryBroadcastReceivers(verification, 0); + if (receivers.size() == 0) { + return false; + } + } + return true; + } + + /** + * Updates position, enabled status and maybe admin message. + */ + public void updatePreference() { + if (!isAvailable()) { + return; + } + + if (!shouldBeEnabled()) { + mPreference.setChecked(false); + mPreference.setDisabledByAdmin(null); + mPreference.setEnabled(false); + return; + } + + final EnforcedAdmin enforcingAdmin = mRestrictedLockUtils.checkIfRestrictionEnforced( + mContext, UserManager.ENSURE_VERIFY_APPS, UserHandle.myUserId()); + if (enforcingAdmin != null) { + mPreference.setChecked(true); + mPreference.setDisabledByAdmin(enforcingAdmin); + return; + } + + mPreference.setEnabled(true); + final boolean checked = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1) != 0; + mPreference.setChecked(checked); + } +} diff --git a/tests/robotests/src/com/android/settings/development/VerifyAppsOverUsbPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/VerifyAppsOverUsbPreferenceControllerTest.java new file mode 100644 index 00000000000..d9c17bc49c6 --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/VerifyAppsOverUsbPreferenceControllerTest.java @@ -0,0 +1,179 @@ +/* + * 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 org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.admin.DevicePolicyManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.UserManager; +import android.provider.Settings.Global; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; +import com.android.settingslib.RestrictedSwitchPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; +import org.robolectric.util.ReflectionHelpers; + +import java.util.Collections; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class VerifyAppsOverUsbPreferenceControllerTest { + + @Mock + private PackageManager mPackageManager; + @Mock + private PreferenceScreen mScreen; + @Mock + private RestrictedSwitchPreference mPreference; + + @Mock + private VerifyAppsOverUsbPreferenceController.RestrictedLockUtilsDelegate + mRestrictedLockUtilsDelegate; + + private Context mContext; + private VerifyAppsOverUsbPreferenceController mController; + + /** Convenience class for setting global int settings. */ + class GlobalSetter { + public GlobalSetter set(String setting, int value) { + Global.putInt(mContext.getContentResolver(), setting, value); + return this; + } + } + private final GlobalSetter mGlobals = new GlobalSetter(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + final ShadowApplication shadowContext = ShadowApplication.getInstance(); + mContext = spy(shadowContext.getApplicationContext()); + when(mScreen.findPreference(anyString())).thenReturn(mPreference); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + mController = new VerifyAppsOverUsbPreferenceController(mContext); + ReflectionHelpers.setField( + mController, "mRestrictedLockUtils", mRestrictedLockUtilsDelegate); + } + + private void setupVerifyBroadcastReceivers(boolean nonEmpty) { + final List resolveInfos = nonEmpty + ? Collections.singletonList(mock(ResolveInfo.class)) + : Collections.emptyList(); + when(mPackageManager.queryBroadcastReceivers((Intent) any(), anyInt())) + .thenReturn(resolveInfos); + } + + private void setupEnforcedAdmin(EnforcedAdmin result) { + when(mRestrictedLockUtilsDelegate.checkIfRestrictionEnforced( + (Context) any(), anyString(), anyInt())).thenReturn(result); + } + + @Test + public void updateState_preferenceCheckedWhenSettingIsOn() { + setupVerifyBroadcastReceivers(true); + setupEnforcedAdmin(null); + mGlobals.set(Global.ADB_ENABLED, 1).set(Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1); + mController.displayPreference(mScreen); + mController.updatePreference(); + verify(mPreference).setChecked(true); + } + + @Test + public void updateState_preferenceUncheckedWhenSettingIsOff() { + setupVerifyBroadcastReceivers(true); + setupEnforcedAdmin(null); + mGlobals.set(Global.ADB_ENABLED, 1).set(Global.PACKAGE_VERIFIER_INCLUDE_ADB, 0); + mController.displayPreference(mScreen); + mController.updatePreference(); + verify(mPreference).setChecked(false); + } + + @Test + public void updateState_preferenceUncheckedWhenNoAdb() { + setupVerifyBroadcastReceivers(true); + setupEnforcedAdmin(null); + mGlobals.set(Global.ADB_ENABLED, 0).set(Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1); + mController.displayPreference(mScreen); + mController.updatePreference(); + verify(mPreference).setChecked(false); + } + + @Test + public void updateState_preferenceUncheckedWhenVerifierIsOff() { + setupVerifyBroadcastReceivers(true); + setupEnforcedAdmin(null); + mGlobals.set(Global.ADB_ENABLED, 1) + .set(Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1) + .set(Global.PACKAGE_VERIFIER_ENABLE, 0); + mController.displayPreference(mScreen); + mController.updatePreference(); + verify(mPreference).setChecked(false); + } + + @Test + public void updateState_preferenceUncheckedWhenNoVerifyBroadcastReceivers() { + setupVerifyBroadcastReceivers(false); + setupEnforcedAdmin(null); + mGlobals.set(Global.ADB_ENABLED, 1) + .set(Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1); + mController.displayPreference(mScreen); + mController.updatePreference(); + verify(mPreference).setChecked(false); + } + + @Test + public void updateState_preferenceDisabledWhenRestrictedByAdmin() { + setupVerifyBroadcastReceivers(true); + final EnforcedAdmin admin = new EnforcedAdmin(); + setupEnforcedAdmin(admin); + mGlobals.set(Global.ADB_ENABLED, 1) + .set(Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1); + mController.displayPreference(mScreen); + mController.updatePreference(); + verify(mPreference).setDisabledByAdmin(admin); + } + + @Test + public void updateState_preferenceRemovedWhenVerifierSettingsVisibleIsOff() { + setupVerifyBroadcastReceivers(true); + mGlobals.set(Global.PACKAGE_VERIFIER_SETTING_VISIBLE, 0); + when(mPreference.getKey()).thenReturn(mController.getPreferenceKey()); + when(mScreen.getPreferenceCount()).thenReturn(1); + when(mScreen.getPreference(anyInt())).thenReturn(mPreference); + mController.displayPreference(mScreen); + verify(mScreen).removePreference(mPreference); + } +} \ No newline at end of file