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