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.EnterpriseUtils.getAvailabilityStatusRestricted; 24 25 import android.bluetooth.BluetoothAdapter; 26 import android.car.drivingstate.CarUxRestrictions; 27 import android.content.BroadcastReceiver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.pm.PackageManager; 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 = mContext.getSystemService(UserManager.class); 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 EnterpriseUtils.onClickWhileDisabled(mContext, getFragmentController(), 132 DISALLOW_CONFIG_BLUETOOTH); 133 } 134 }); 135 } 136 137 @Override getDefaultAvailabilityStatus()138 protected int getDefaultAvailabilityStatus() { 139 if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) { 140 return UNSUPPORTED_ON_DEVICE; 141 } 142 return getAvailabilityStatusRestricted(mContext, DISALLOW_CONFIG_BLUETOOTH); 143 } 144 145 @Override onStartInternal()146 protected void onStartInternal() { 147 mContext.registerReceiver(mReceiver, mIntentFilter); 148 mLocalBluetoothManager.setForegroundActivity(mContext); 149 handleStateChanged(mBluetoothAdapter.getState()); 150 } 151 152 @Override onResumeInternal()153 protected void onResumeInternal() { 154 mPowerPolicyListener.handleCurrentPolicy(); 155 } 156 157 @Override onStopInternal()158 protected void onStopInternal() { 159 mContext.unregisterReceiver(mReceiver); 160 mLocalBluetoothManager.setForegroundActivity(null); 161 } 162 163 @Override onDestroyInternal()164 protected void onDestroyInternal() { 165 mPowerPolicyListener.release(); 166 } 167 isUserRestricted()168 private boolean isUserRestricted() { 169 return mUserManager.hasUserRestriction(DISALLOW_BLUETOOTH); 170 } 171 172 @VisibleForTesting handleStateChanged(int state)173 void handleStateChanged(int state) { 174 // Set updating state to prevent additional updates while trying to reflect the new 175 // adapter state. 176 mUpdating = true; 177 switch (state) { 178 case BluetoothAdapter.STATE_TURNING_ON: 179 enableSwitchPreference(getPreference(), /* enabled= */ false); 180 updateSwitchPreference(true); 181 break; 182 case BluetoothAdapter.STATE_ON: 183 enableSwitchPreference(getPreference(), /* enabled= */ !isUserRestricted()); 184 updateSwitchPreference(true); 185 break; 186 case BluetoothAdapter.STATE_TURNING_OFF: 187 enableSwitchPreference(getPreference(), /* enabled= */ false); 188 updateSwitchPreference(false); 189 break; 190 case BluetoothAdapter.STATE_OFF: 191 default: 192 enableSwitchPreference(getPreference(), /* enabled= */ !isUserRestricted()); 193 updateSwitchPreference(false); 194 } 195 mUpdating = false; 196 } 197 updateSwitchPreference(boolean enabled)198 private void updateSwitchPreference(boolean enabled) { 199 String bluetoothName = BluetoothAdapter.getDefaultAdapter().getName(); 200 getPreference().setSummary(enabled ? getContext() 201 .getString(R.string.bluetooth_state_switch_summary, bluetoothName) : null); 202 getPreference().setChecked(enabled); 203 } 204 enableSwitchPreference(ColoredSwitchPreference preference, boolean enabled)205 private void enableSwitchPreference(ColoredSwitchPreference preference, boolean enabled) { 206 preference.setEnabled(enabled && getAvailabilityStatus() == AVAILABLE); 207 } 208 } 209