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