1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.settings.bluetooth; 18 19 import static android.car.hardware.power.PowerComponent.BLUETOOTH; 20 import static android.os.UserManager.DISALLOW_BLUETOOTH; 21 import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; 22 23 import static com.android.car.settings.enterprise.ActionDisabledByAdminDialogFragment.DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG; 24 import static com.android.car.settings.enterprise.EnterpriseUtils.hasUserRestrictionByDpm; 25 26 import android.bluetooth.BluetoothAdapter; 27 import android.car.drivingstate.CarUxRestrictions; 28 import android.content.BroadcastReceiver; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.os.UserManager; 33 import android.widget.Toast; 34 35 import androidx.annotation.VisibleForTesting; 36 37 import com.android.car.settings.R; 38 import com.android.car.settings.common.ColoredSwitchPreference; 39 import com.android.car.settings.common.FragmentController; 40 import com.android.car.settings.common.PowerPolicyListener; 41 import com.android.car.settings.common.PreferenceController; 42 import com.android.car.settings.enterprise.EnterpriseUtils; 43 import com.android.settingslib.bluetooth.LocalBluetoothManager; 44 45 /** 46 * Enables/disables bluetooth state via SwitchPreference. 47 */ 48 public class BluetoothStateSwitchPreferenceController extends 49 PreferenceController<ColoredSwitchPreference> { 50 51 private final Context mContext; 52 private final IntentFilter mIntentFilter = new IntentFilter( 53 BluetoothAdapter.ACTION_STATE_CHANGED); 54 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 55 @Override 56 public void onReceive(Context context, Intent intent) { 57 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); 58 handleStateChanged(state); 59 } 60 }; 61 private final BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 62 private LocalBluetoothManager mLocalBluetoothManager; 63 private UserManager mUserManager; 64 private boolean mUpdating = false; 65 private boolean mIsPowerOn = true; 66 67 @VisibleForTesting 68 final PowerPolicyListener mPowerPolicyListener; 69 BluetoothStateSwitchPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)70 public BluetoothStateSwitchPreferenceController(Context context, 71 String preferenceKey, 72 FragmentController fragmentController, 73 CarUxRestrictions uxRestrictions) { 74 super(context, preferenceKey, fragmentController, uxRestrictions); 75 mContext = context; 76 mPowerPolicyListener = new PowerPolicyListener(context, BLUETOOTH, 77 isPowerOn -> { 78 mIsPowerOn = isPowerOn; 79 enableSwitchPreference(getPreference(), /* enabled= */ mIsPowerOn); 80 }); 81 } 82 83 @Override getPreferenceType()84 protected Class<ColoredSwitchPreference> getPreferenceType() { 85 return ColoredSwitchPreference.class; 86 } 87 88 @Override updateState(ColoredSwitchPreference preference)89 protected void updateState(ColoredSwitchPreference preference) { 90 updateSwitchPreference(mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON 91 || mBluetoothAdapter.getState() == BluetoothAdapter.STATE_TURNING_ON); 92 } 93 94 @Override handlePreferenceChanged(ColoredSwitchPreference preference, Object newValue)95 protected boolean handlePreferenceChanged(ColoredSwitchPreference preference, 96 Object newValue) { 97 if (mUpdating) { 98 return false; 99 } 100 enableSwitchPreference(preference, /* enabled= */ false); 101 boolean bluetoothEnabled = (Boolean) newValue; 102 if (bluetoothEnabled) { 103 mBluetoothAdapter.enable(); 104 } else { 105 mBluetoothAdapter.disable(); 106 } 107 return true; 108 } 109 110 @Override onCreateInternal()111 protected void onCreateInternal() { 112 mUserManager = UserManager.get(mContext); 113 mLocalBluetoothManager = BluetoothUtils.getLocalBtManager(mContext); 114 if (mLocalBluetoothManager == null) { 115 getFragmentController().goBack(); 116 } 117 ColoredSwitchPreference preference = getPreference(); 118 preference.setContentDescription( 119 mContext.getString(R.string.bluetooth_state_switch_content_description)); 120 preference.setClickableWhileDisabled(true); 121 preference.setDisabledClickListener(p -> { 122 // This is logic when clicking while disabled: 123 // 1. If power is off, then show a toast with the related error message; 124 // 2. If restricted by DPM, show a dialog message with the related restriction message; 125 // 3. Do nothing otherwise. 126 if (!mIsPowerOn) { 127 Toast.makeText(getContext(), 128 getContext().getString(R.string.power_component_disabled), 129 Toast.LENGTH_LONG).show(); 130 } else if (getAvailabilityStatus() == AVAILABLE_FOR_VIEWING) { 131 showActionDisabledByAdminDialog(); 132 } 133 }); 134 } 135 136 @Override getAvailabilityStatus()137 protected int getAvailabilityStatus() { 138 return hasUserRestrictionByDpm(getContext(), DISALLOW_CONFIG_BLUETOOTH) 139 ? AVAILABLE_FOR_VIEWING : AVAILABLE; 140 } 141 showActionDisabledByAdminDialog()142 private void showActionDisabledByAdminDialog() { 143 getFragmentController().showDialog( 144 EnterpriseUtils.getActionDisabledByAdminDialog(getContext(), 145 DISALLOW_CONFIG_BLUETOOTH), 146 DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG); 147 } 148 149 @Override onStartInternal()150 protected void onStartInternal() { 151 mContext.registerReceiver(mReceiver, mIntentFilter); 152 mLocalBluetoothManager.setForegroundActivity(mContext); 153 handleStateChanged(mBluetoothAdapter.getState()); 154 } 155 156 @Override onResumeInternal()157 protected void onResumeInternal() { 158 mPowerPolicyListener.handleCurrentPolicy(); 159 } 160 161 @Override onStopInternal()162 protected void onStopInternal() { 163 mContext.unregisterReceiver(mReceiver); 164 mLocalBluetoothManager.setForegroundActivity(null); 165 } 166 167 @Override onDestroyInternal()168 protected void onDestroyInternal() { 169 mPowerPolicyListener.release(); 170 } 171 isUserRestricted()172 private boolean isUserRestricted() { 173 return mUserManager.hasUserRestriction(DISALLOW_BLUETOOTH); 174 } 175 176 @VisibleForTesting handleStateChanged(int state)177 void handleStateChanged(int state) { 178 // Set updating state to prevent additional updates while trying to reflect the new 179 // adapter state. 180 mUpdating = true; 181 switch (state) { 182 case BluetoothAdapter.STATE_TURNING_ON: 183 enableSwitchPreference(getPreference(), /* enabled= */ false); 184 updateSwitchPreference(true); 185 break; 186 case BluetoothAdapter.STATE_ON: 187 enableSwitchPreference(getPreference(), /* enabled= */ !isUserRestricted()); 188 updateSwitchPreference(true); 189 break; 190 case BluetoothAdapter.STATE_TURNING_OFF: 191 enableSwitchPreference(getPreference(), /* enabled= */ false); 192 updateSwitchPreference(false); 193 break; 194 case BluetoothAdapter.STATE_OFF: 195 default: 196 enableSwitchPreference(getPreference(), /* enabled= */ !isUserRestricted()); 197 updateSwitchPreference(false); 198 } 199 mUpdating = false; 200 } 201 202 @VisibleForTesting setUserManager(UserManager userManager)203 void setUserManager(UserManager userManager) { 204 mUserManager = userManager; 205 } 206 updateSwitchPreference(boolean enabled)207 private void updateSwitchPreference(boolean enabled) { 208 String bluetoothName = BluetoothAdapter.getDefaultAdapter().getName(); 209 getPreference().setSummary(enabled ? getContext() 210 .getString(R.string.bluetooth_state_switch_summary, bluetoothName) : null); 211 getPreference().setChecked(enabled); 212 } 213 enableSwitchPreference(ColoredSwitchPreference preference, boolean enabled)214 private void enableSwitchPreference(ColoredSwitchPreference preference, boolean enabled) { 215 preference.setEnabled(enabled && getAvailabilityStatus() == AVAILABLE); 216 } 217 } 218