/* * Copyright (C) 2010 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.bluetooth.BluetoothAdapter; 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.Toast; import androidx.annotation.VisibleForTesting; import com.android.settings.R; import com.android.settings.widget.SwitchWidgetController; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.WirelessUtils; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; /** * BluetoothEnabler is a helper to manage the Bluetooth on/off checkbox * preference. It turns on/off Bluetooth and ensures the summary of the * preference reflects the current state. */ public final class BluetoothEnabler implements SwitchWidgetController.OnSwitchChangeListener { private final SwitchWidgetController mSwitchController; private final MetricsFeatureProvider mMetricsFeatureProvider; private Context mContext; private boolean mValidListener; private final BluetoothAdapter mBluetoothAdapter; private final IntentFilter mIntentFilter; private final RestrictionUtils mRestrictionUtils; private SwitchWidgetController.OnSwitchChangeListener mCallback; private static final String EVENT_DATA_IS_BT_ON = "is_bluetooth_on"; private static final int EVENT_UPDATE_INDEX = 0; private final int mMetricsEvent; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // Broadcast receiver is always running on the UI thread here, // so we don't need consider thread synchronization. int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); handleStateChanged(state); } }; public BluetoothEnabler(Context context, SwitchWidgetController switchController, MetricsFeatureProvider metricsFeatureProvider, int metricsEvent) { this(context, switchController, metricsFeatureProvider, metricsEvent, new RestrictionUtils()); } public BluetoothEnabler(Context context, SwitchWidgetController switchController, MetricsFeatureProvider metricsFeatureProvider, int metricsEvent, RestrictionUtils restrictionUtils) { mContext = context; mMetricsFeatureProvider = metricsFeatureProvider; mSwitchController = switchController; mSwitchController.setListener(this); mSwitchController.setTitle(context.getString(R.string.bluetooth_main_switch_title)); mValidListener = false; mMetricsEvent = metricsEvent; mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (mBluetoothAdapter == null) { // Bluetooth is not supported mSwitchController.setEnabled(false); } mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); mRestrictionUtils = restrictionUtils; } public void setupSwitchController() { mSwitchController.setupView(); } public void teardownSwitchController() { mSwitchController.teardownView(); } public void resume(Context context) { if (mContext != context) { mContext = context; } final boolean restricted = maybeEnforceRestrictions(); if (mBluetoothAdapter == null) { mSwitchController.setEnabled(false); return; } // Bluetooth state is not sticky, so set it manually if (!restricted) { handleStateChanged(mBluetoothAdapter.getState()); } mSwitchController.startListening(); mContext.registerReceiver(mReceiver, mIntentFilter); mValidListener = true; } public void pause() { if (mBluetoothAdapter == null) { return; } if (mValidListener) { mSwitchController.stopListening(); mContext.unregisterReceiver(mReceiver); mValidListener = false; } } void handleStateChanged(int state) { switch (state) { case BluetoothAdapter.STATE_TURNING_ON: mSwitchController.setEnabled(false); break; case BluetoothAdapter.STATE_ON: setChecked(true); mSwitchController.setEnabled(true); break; case BluetoothAdapter.STATE_TURNING_OFF: mSwitchController.setEnabled(false); break; case BluetoothAdapter.STATE_OFF: setChecked(false); mSwitchController.setEnabled(true); break; default: setChecked(false); mSwitchController.setEnabled(true); } } private void setChecked(boolean isChecked) { if (isChecked != mSwitchController.isChecked()) { // set listener to null, so onCheckedChanged won't be called // if the checked status on Switch isn't changed by user click if (mValidListener) { mSwitchController.stopListening(); } mSwitchController.setChecked(isChecked); if (mValidListener) { mSwitchController.startListening(); } } } @Override public boolean onSwitchToggled(boolean isChecked) { if (maybeEnforceRestrictions()) { triggerParentPreferenceCallback(isChecked); return true; } // Show toast message if Bluetooth is not allowed in airplane mode if (isChecked && !WirelessUtils.isRadioAllowed(mContext, Settings.Global.RADIO_BLUETOOTH)) { Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show(); // Reset switch to off mSwitchController.setChecked(false); triggerParentPreferenceCallback(false); return false; } mMetricsFeatureProvider.action(mContext, mMetricsEvent, isChecked); if (mBluetoothAdapter != null) { boolean status = setBluetoothEnabled(isChecked); // If we cannot toggle it ON then reset the UI assets: // a) The switch should be OFF but it should still be togglable (enabled = True) // b) The switch bar should have OFF text. if (isChecked && !status) { mSwitchController.setChecked(false); mSwitchController.setEnabled(true); triggerParentPreferenceCallback(false); return false; } } mSwitchController.setEnabled(false); triggerParentPreferenceCallback(isChecked); return true; } /** * Sets a callback back that this enabler will trigger in case the preference using the enabler * still needed the callback on the SwitchController (which we now use). * @param listener The listener with a callback to trigger. */ public void setToggleCallback(SwitchWidgetController.OnSwitchChangeListener listener) { mCallback = listener; } /** * 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 = getEnforcedAdmin(mRestrictionUtils, mContext); mSwitchController.setDisabledByAdmin(admin); if (admin != null) { mSwitchController.setChecked(false); mSwitchController.setEnabled(false); } return admin != null; } public static EnforcedAdmin getEnforcedAdmin(RestrictionUtils mRestrictionUtils, Context mContext) { EnforcedAdmin admin = mRestrictionUtils.checkIfRestrictionEnforced( mContext, UserManager.DISALLOW_BLUETOOTH); if (admin == null) { admin = mRestrictionUtils.checkIfRestrictionEnforced( mContext, UserManager.DISALLOW_CONFIG_BLUETOOTH); } return admin; } // This triggers the callback which was manually set for this enabler since the enabler will // take over the switch controller callback private void triggerParentPreferenceCallback(boolean isChecked) { if (mCallback != null) { mCallback.onSwitchToggled(isChecked); } } private boolean setBluetoothEnabled(boolean isEnabled) { return isEnabled ? mBluetoothAdapter.enable() : mBluetoothAdapter.disable(); } }