diff --git a/src/com/android/settings/bluetooth/BluetoothEnabler.java b/src/com/android/settings/bluetooth/BluetoothEnabler.java index 22b21e4ceb8..6c41f830a12 100644 --- a/src/com/android/settings/bluetooth/BluetoothEnabler.java +++ b/src/com/android/settings/bluetooth/BluetoothEnabler.java @@ -21,13 +21,16 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.UserManager; import android.provider.Settings; import android.widget.Switch; import android.widget.Toast; +import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.widget.SwitchWidgetController; +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.WirelessUtils; import com.android.settingslib.bluetooth.LocalBluetoothAdapter; import com.android.settingslib.bluetooth.LocalBluetoothManager; @@ -45,6 +48,7 @@ public final class BluetoothEnabler implements SwitchWidgetController.OnSwitchCh private boolean mValidListener; private final LocalBluetoothAdapter mLocalAdapter; private final IntentFilter mIntentFilter; + private final RestrictionUtils mRestrictionUtils; private static final String EVENT_DATA_IS_BT_ON = "is_bluetooth_on"; private static final int EVENT_UPDATE_INDEX = 0; @@ -63,6 +67,13 @@ public final class BluetoothEnabler implements SwitchWidgetController.OnSwitchCh public BluetoothEnabler(Context context, SwitchWidgetController switchWidget, MetricsFeatureProvider metricsFeatureProvider, LocalBluetoothManager manager, int metricsEvent) { + this(context, switchWidget, metricsFeatureProvider, manager, metricsEvent, + new RestrictionUtils()); + } + + public BluetoothEnabler(Context context, SwitchWidgetController switchWidget, + MetricsFeatureProvider metricsFeatureProvider, LocalBluetoothManager manager, + int metricsEvent, RestrictionUtils restrictionUtils) { mContext = context; mMetricsFeatureProvider = metricsFeatureProvider; mSwitchWidget = switchWidget; @@ -79,6 +90,7 @@ public final class BluetoothEnabler implements SwitchWidgetController.OnSwitchCh mLocalAdapter = manager.getBluetoothAdapter(); } mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); + mRestrictionUtils = restrictionUtils; } public void setupSwitchController() { @@ -90,15 +102,17 @@ public final class BluetoothEnabler implements SwitchWidgetController.OnSwitchCh } public void resume(Context context) { + if (mContext != context) { + mContext = context; + } + + maybeEnforceRestrictions(); + if (mLocalAdapter == null) { mSwitchWidget.setEnabled(false); return; } - if (mContext != context) { - mContext = context; - } - // Bluetooth state is not sticky, so set it manually handleStateChanged(mLocalAdapter.getBluetoothState()); @@ -156,6 +170,10 @@ public final class BluetoothEnabler implements SwitchWidgetController.OnSwitchCh @Override public boolean onSwitchToggled(boolean isChecked) { + if (maybeEnforceRestrictions()) { + return true; + } + // Show toast message if Bluetooth is not allowed in airplane mode if (isChecked && !WirelessUtils.isRadioAllowed(mContext, Settings.Global.RADIO_BLUETOOTH)) { @@ -182,4 +200,29 @@ public final class BluetoothEnabler implements SwitchWidgetController.OnSwitchCh mSwitchWidget.setEnabled(false); return true; } + + /** + * Enforces user restrictions disallowing Bluetooth (or its configuration) if there are any. + * + * @return if there was any user restriction to enforce. + */ + @VisibleForTesting + boolean maybeEnforceRestrictions() { + EnforcedAdmin admin = mRestrictionUtils.checkIfRestrictionEnforced( + mContext, UserManager.DISALLOW_BLUETOOTH); + if (admin == null) { + admin = mRestrictionUtils.checkIfRestrictionEnforced( + mContext, UserManager.DISALLOW_CONFIG_BLUETOOTH); + } + mSwitchWidget.setDisabledByAdmin(admin); + if (admin != null) { + mSwitchWidget.setChecked(false); + if (mSwitch != null) { + mSwitch.setEnabled(false); + mSwitch.setChecked(false); + } + } + return admin != null; + } + } diff --git a/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceController.java b/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceController.java index f98a2096fd5..c0715706e23 100644 --- a/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceController.java +++ b/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceController.java @@ -18,6 +18,7 @@ package com.android.settings.bluetooth; import android.content.Context; import android.support.v7.preference.PreferenceScreen; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.core.PreferenceController; import com.android.settings.core.lifecycle.LifecycleObserver; @@ -26,9 +27,9 @@ import com.android.settings.core.lifecycle.events.OnResume; import com.android.settings.core.lifecycle.events.OnStart; import com.android.settings.core.lifecycle.events.OnStop; import com.android.settings.overlay.FeatureFactory; -import com.android.settings.widget.SummaryUpdater.OnSummaryChangeListener; -import com.android.settings.widget.MasterSwitchPreference; import com.android.settings.widget.MasterSwitchController; +import com.android.settings.widget.MasterSwitchPreference; +import com.android.settings.widget.SummaryUpdater.OnSummaryChangeListener; import com.android.settingslib.bluetooth.LocalBluetoothManager; public class BluetoothMasterSwitchPreferenceController extends PreferenceController @@ -41,12 +42,20 @@ public class BluetoothMasterSwitchPreferenceController extends PreferenceControl private MasterSwitchPreference mBtPreference; private BluetoothEnabler mBluetoothEnabler; private BluetoothSummaryUpdater mSummaryUpdater; + private RestrictionUtils mRestrictionUtils; public BluetoothMasterSwitchPreferenceController(Context context, LocalBluetoothManager bluetoothManager) { + this(context, bluetoothManager, new RestrictionUtils()); + } + + @VisibleForTesting + public BluetoothMasterSwitchPreferenceController(Context context, + LocalBluetoothManager bluetoothManager, RestrictionUtils restrictionUtils) { super(context); mBluetoothManager = bluetoothManager; mSummaryUpdater = new BluetoothSummaryUpdater(mContext, this, mBluetoothManager); + mRestrictionUtils = restrictionUtils; } @Override @@ -56,7 +65,8 @@ public class BluetoothMasterSwitchPreferenceController extends PreferenceControl mBluetoothEnabler = new BluetoothEnabler(mContext, new MasterSwitchController(mBtPreference), FeatureFactory.getFactory(mContext).getMetricsFeatureProvider(), mBluetoothManager, - MetricsEvent.ACTION_SETTINGS_MASTER_SWITCH_BLUETOOTH_TOGGLE); + MetricsEvent.ACTION_SETTINGS_MASTER_SWITCH_BLUETOOTH_TOGGLE, + mRestrictionUtils); } @Override diff --git a/src/com/android/settings/bluetooth/RestrictionUtils.java b/src/com/android/settings/bluetooth/RestrictionUtils.java new file mode 100644 index 00000000000..9c0c4813245 --- /dev/null +++ b/src/com/android/settings/bluetooth/RestrictionUtils.java @@ -0,0 +1,43 @@ +/* + * 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.bluetooth; + +import android.content.Context; +import android.os.UserHandle; + +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; + +/** + * A utility class to aid testing. + */ +public class RestrictionUtils { + + public RestrictionUtils() {} + + /** + * Utility method to check if user restriction is enforced on the current user. + * + *
It helps with testing - override it to avoid calling static method which calls system + * API. + */ + public EnforcedAdmin checkIfRestrictionEnforced(Context context, String restriction) { + return RestrictedLockUtils.checkIfRestrictionEnforced( + context, restriction, UserHandle.myUserId()); + } + +} diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothEnablerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothEnablerTest.java index 7761afc1076..074bef2beed 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothEnablerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothEnablerTest.java @@ -15,12 +15,15 @@ */ package com.android.settings.bluetooth; +import android.content.ComponentName; import android.content.Context; +import android.os.UserManager; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.widget.MasterSwitchController; +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.bluetooth.LocalBluetoothManager; import org.junit.Before; @@ -30,30 +33,108 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.annotation.Config; +import static com.google.common.truth.Truth.assertThat; +import static junit.framework.TestCase.assertNotNull; +import static junit.framework.TestCase.assertEquals; import static org.mockito.Mockito.mock; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class BluetoothEnablerTest { + private static final EnforcedAdmin FAKE_ENFORCED_ADMIN = + new EnforcedAdmin(new ComponentName("test.package", "test.Class"), 10); + @Mock private MetricsFeatureProvider mMetricsFeatureProvider; @Mock private Context mContext; + @Mock + private MasterSwitchController mMasterSwitchController; + @Mock + private RestrictionUtils mRestrictionUtils; + + private BluetoothEnabler mBluetoothEnabler; @Before public void setUp() { MockitoAnnotations.initMocks(this); + mBluetoothEnabler = new BluetoothEnabler( + mContext, + mMasterSwitchController, + mMetricsFeatureProvider, + mock(LocalBluetoothManager.class), + 123, + mRestrictionUtils); } @Test public void onSwitchToggled_shouldLogActionWithSuppliedEvent() { - BluetoothEnabler bluetoothEnabler = new BluetoothEnabler(mContext, - mock(MasterSwitchController.class), mMetricsFeatureProvider, - mock(LocalBluetoothManager.class), 123); - bluetoothEnabler.onSwitchToggled(false); + // WHEN the switch is toggled... + mBluetoothEnabler.onSwitchToggled(false); + // THEN the corresponding metrics action is logged. verify(mMetricsFeatureProvider).action(mContext, 123, false); } + + @Test + public void maybeEnforceRestrictions_noRestrictions() { + // GIVEN there are no restrictions set... + when(mRestrictionUtils.checkIfRestrictionEnforced(any(Context.class), any(String.class))) + .thenReturn(null); + + // WHEN the maybeEnforceRestrictions is called... + // THEN false is returned to indicate there was no restriction to enforce + assertThat(mBluetoothEnabler.maybeEnforceRestrictions()).isFalse(); + + // THEN a null EnfoceAdmin is set. + verify(mMasterSwitchController).setDisabledByAdmin(null); + // THEN the state of the switch isn't changed. + verify(mMasterSwitchController, never()).setChecked(anyBoolean()); + } + + @Test + public void maybeEnforceRestrictions_disallowBluetoothRestrictionSet() { + // GIVEN Bluetooth has been disallowed... + when(mRestrictionUtils.checkIfRestrictionEnforced( + mContext, UserManager.DISALLOW_BLUETOOTH)).thenReturn(FAKE_ENFORCED_ADMIN); + when(mRestrictionUtils.checkIfRestrictionEnforced( + mContext, UserManager.DISALLOW_CONFIG_BLUETOOTH)).thenReturn(null); + + // WHEN the maybeEnforceRestrictions is called... + // THEN true is returned to indicate there was a restriction to enforce. + assertThat(mBluetoothEnabler.maybeEnforceRestrictions()).isTrue(); + + // THEN the expected EnfoceAdmin is set. + verify(mMasterSwitchController).setDisabledByAdmin(FAKE_ENFORCED_ADMIN); + + // THEN the switch is unchecked. + verify(mMasterSwitchController).setChecked(false); + } + + @Test + public void maybeEnforceRestrictions_disallowConfigBluetoothRestrictionSet() { + // GIVEN configuring Bluetooth has been disallowed... + when(mRestrictionUtils.checkIfRestrictionEnforced( + mContext, UserManager.DISALLOW_BLUETOOTH)).thenReturn(null); + when(mRestrictionUtils.checkIfRestrictionEnforced( + mContext, UserManager.DISALLOW_CONFIG_BLUETOOTH)).thenReturn(FAKE_ENFORCED_ADMIN); + + // WHEN the maybeEnforceRestrictions is called... + // THEN true is returned to indicate there was a restriction to enforce. + assertThat(mBluetoothEnabler.maybeEnforceRestrictions()).isTrue(); + + // THEN the expected EnfoceAdmin is set. + verify(mMasterSwitchController).setDisabledByAdmin(FAKE_ENFORCED_ADMIN); + + // THEN the switch is unchecked. + verify(mMasterSwitchController).setChecked(false); + } + } diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceControllerTest.java index 0e39c5df6e6..177130e714b 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceControllerTest.java @@ -52,6 +52,8 @@ public class BluetoothMasterSwitchPreferenceControllerTest { private PreferenceScreen mScreen; @Mock private MasterSwitchPreference mPreference; + @Mock + private RestrictionUtils mRestrictionUtils; private Context mContext; private BluetoothMasterSwitchPreferenceController mController; @@ -60,7 +62,8 @@ public class BluetoothMasterSwitchPreferenceControllerTest { public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application.getApplicationContext(); - mController = new BluetoothMasterSwitchPreferenceController(mContext, mBluetoothManager); + mController = new BluetoothMasterSwitchPreferenceController( + mContext, mBluetoothManager, mRestrictionUtils); when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); }