• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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.settings.bluetooth;
18 
19 import static com.android.settings.network.SatelliteWarningDialogActivity.EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG;
20 import static com.android.settings.network.SatelliteWarningDialogActivity.TYPE_IS_BLUETOOTH;
21 
22 import android.bluetooth.BluetoothAdapter;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.os.UserManager;
28 import android.provider.Settings;
29 import android.util.Log;
30 import android.widget.Toast;
31 
32 import androidx.annotation.VisibleForTesting;
33 
34 import com.android.settings.R;
35 import com.android.settings.network.SatelliteRepository;
36 import com.android.settings.network.SatelliteWarningDialogActivity;
37 import com.android.settings.widget.SwitchWidgetController;
38 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
39 import com.android.settingslib.WirelessUtils;
40 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
41 
42 import java.util.concurrent.ExecutionException;
43 import java.util.concurrent.Executors;
44 import java.util.concurrent.TimeUnit;
45 import java.util.concurrent.TimeoutException;
46 import java.util.concurrent.atomic.AtomicBoolean;
47 
48 /**
49  * BluetoothEnabler is a helper to manage the Bluetooth on/off checkbox
50  * preference. It turns on/off Bluetooth and ensures the summary of the
51  * preference reflects the current state.
52  */
53 public final class BluetoothEnabler implements SwitchWidgetController.OnSwitchChangeListener {
54     private static final String TAG = BluetoothEnabler.class.getSimpleName();
55     private final SwitchWidgetController mSwitchController;
56     private final MetricsFeatureProvider mMetricsFeatureProvider;
57     private Context mContext;
58     private boolean mValidListener;
59     private final BluetoothAdapter mBluetoothAdapter;
60     private final IntentFilter mIntentFilter;
61     private final RestrictionUtils mRestrictionUtils;
62     private SwitchWidgetController.OnSwitchChangeListener mCallback;
63 
64     private static final String EVENT_DATA_IS_BT_ON = "is_bluetooth_on";
65     private static final int EVENT_UPDATE_INDEX = 0;
66     private final int mMetricsEvent;
67     private SatelliteRepository mSatelliteRepository;
68     @VisibleForTesting
69     AtomicBoolean mIsSatelliteOn = new AtomicBoolean(false);
70 
71     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
72         @Override
73         public void onReceive(Context context, Intent intent) {
74             // Broadcast receiver is always running on the UI thread here,
75             // so we don't need consider thread synchronization.
76             int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
77             handleStateChanged(state);
78         }
79     };
80 
BluetoothEnabler(Context context, SwitchWidgetController switchController, MetricsFeatureProvider metricsFeatureProvider, int metricsEvent, RestrictionUtils restrictionUtils)81     public BluetoothEnabler(Context context, SwitchWidgetController switchController,
82             MetricsFeatureProvider metricsFeatureProvider, int metricsEvent,
83             RestrictionUtils restrictionUtils) {
84         mContext = context;
85         mMetricsFeatureProvider = metricsFeatureProvider;
86         mSwitchController = switchController;
87         mSwitchController.setListener(this);
88         mSwitchController.setTitle(context.getString(R.string.bluetooth_main_switch_title));
89 
90         mValidListener = false;
91         mMetricsEvent = metricsEvent;
92 
93         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
94         if (mBluetoothAdapter == null) {
95             // Bluetooth is not supported
96             mSwitchController.setEnabled(false);
97         }
98         mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
99         mRestrictionUtils = restrictionUtils;
100         mSatelliteRepository = new SatelliteRepository(context);
101     }
102 
setupSwitchController()103     public void setupSwitchController() {
104         mSwitchController.setupView();
105     }
106 
teardownSwitchController()107     public void teardownSwitchController() {
108         mSwitchController.teardownView();
109     }
110 
resume(Context context)111     public void resume(Context context) {
112         if (mContext != context) {
113             mContext = context;
114         }
115 
116         final boolean restricted = maybeEnforceRestrictions();
117 
118         if (mBluetoothAdapter == null) {
119             mSwitchController.setEnabled(false);
120             return;
121         }
122 
123         // Bluetooth state is not sticky, so set it manually
124         if (!restricted) {
125             handleStateChanged(mBluetoothAdapter.getState());
126         }
127 
128         mSwitchController.startListening();
129         mContext.registerReceiver(mReceiver, mIntentFilter,
130                 Context.RECEIVER_EXPORTED_UNAUDITED);
131         mValidListener = true;
132 
133         new Thread(() -> {
134             try {
135                 mIsSatelliteOn.set(mSatelliteRepository.requestIsSessionStarted(
136                         Executors.newSingleThreadExecutor()).get(3000, TimeUnit.MILLISECONDS));
137             } catch (InterruptedException | ExecutionException | TimeoutException e) {
138                 Log.e(TAG, "Error to get satellite status : " + e);
139             }
140         }).start();
141     }
142 
pause()143     public void pause() {
144         if (mBluetoothAdapter == null) {
145             return;
146         }
147         if (mValidListener) {
148             mSwitchController.stopListening();
149             mContext.unregisterReceiver(mReceiver);
150             mValidListener = false;
151         }
152     }
153 
handleStateChanged(int state)154     void handleStateChanged(int state) {
155         switch (state) {
156             case BluetoothAdapter.STATE_TURNING_ON:
157                 mSwitchController.setEnabled(false);
158                 break;
159             case BluetoothAdapter.STATE_ON:
160                 setChecked(true);
161                 mSwitchController.setEnabled(true);
162                 break;
163             case BluetoothAdapter.STATE_TURNING_OFF:
164                 mSwitchController.setEnabled(false);
165                 break;
166             case BluetoothAdapter.STATE_OFF:
167                 setChecked(false);
168                 mSwitchController.setEnabled(true);
169                 break;
170             default:
171                 setChecked(false);
172                 mSwitchController.setEnabled(true);
173         }
174     }
175 
setChecked(boolean isChecked)176     private void setChecked(boolean isChecked) {
177         if (isChecked != mSwitchController.isChecked()) {
178             // set listener to null, so onCheckedChanged won't be called
179             // if the checked status on Switch isn't changed by user click
180             if (mValidListener) {
181                 mSwitchController.stopListening();
182             }
183             mSwitchController.setChecked(isChecked);
184             if (mValidListener) {
185                 mSwitchController.startListening();
186             }
187         }
188     }
189 
190     @Override
onSwitchToggled(boolean isChecked)191     public boolean onSwitchToggled(boolean isChecked) {
192         if (maybeEnforceRestrictions()) {
193             triggerParentPreferenceCallback(isChecked);
194             return true;
195         }
196 
197         if (mIsSatelliteOn.get()) {
198             mContext.startActivity(
199                     new Intent(mContext, SatelliteWarningDialogActivity.class)
200                             .putExtra(
201                                     EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG,
202                                     TYPE_IS_BLUETOOTH)
203             );
204             mSwitchController.setChecked(!isChecked);
205             return false;
206         }
207 
208         // Show toast message if Bluetooth is not allowed in airplane mode
209         if (isChecked &&
210                 !WirelessUtils.isRadioAllowed(mContext, Settings.Global.RADIO_BLUETOOTH)) {
211             Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();
212             // Reset switch to off
213             mSwitchController.setChecked(false);
214             triggerParentPreferenceCallback(false);
215             return false;
216         }
217 
218         mMetricsFeatureProvider.action(mContext, mMetricsEvent, isChecked);
219 
220         if (mBluetoothAdapter != null) {
221             boolean status = setBluetoothEnabled(isChecked);
222             // If we cannot toggle it ON then reset the UI assets:
223             // a) The switch should be OFF but it should still be togglable (enabled = True)
224             // b) The switch bar should have OFF text.
225             if (isChecked && !status) {
226                 mSwitchController.setChecked(false);
227                 mSwitchController.setEnabled(true);
228                 triggerParentPreferenceCallback(false);
229                 return false;
230             }
231         }
232         mSwitchController.setEnabled(false);
233         triggerParentPreferenceCallback(isChecked);
234         return true;
235     }
236 
237     /**
238      * Sets a callback back that this enabler will trigger in case the preference using the enabler
239      * still needed the callback on the SwitchController (which we now use).
240      * @param listener The listener with a callback to trigger.
241      */
setToggleCallback(SwitchWidgetController.OnSwitchChangeListener listener)242     public void setToggleCallback(SwitchWidgetController.OnSwitchChangeListener listener) {
243         mCallback = listener;
244     }
245 
246     /**
247      * Enforces user restrictions disallowing Bluetooth (or its configuration) if there are any.
248      *
249      * @return if there was any user restriction to enforce.
250      */
251     @VisibleForTesting
maybeEnforceRestrictions()252     boolean maybeEnforceRestrictions() {
253         EnforcedAdmin admin = getEnforcedAdmin(mRestrictionUtils, mContext);
254         mSwitchController.setDisabledByAdmin(admin);
255         if (admin != null) {
256             mSwitchController.setChecked(false);
257         }
258         return admin != null;
259     }
260 
getEnforcedAdmin(RestrictionUtils mRestrictionUtils, Context mContext)261     public static EnforcedAdmin getEnforcedAdmin(RestrictionUtils mRestrictionUtils,
262             Context mContext) {
263         EnforcedAdmin admin = mRestrictionUtils.checkIfRestrictionEnforced(
264                 mContext, UserManager.DISALLOW_BLUETOOTH);
265         if (admin == null) {
266             admin = mRestrictionUtils.checkIfRestrictionEnforced(
267                     mContext, UserManager.DISALLOW_CONFIG_BLUETOOTH);
268         }
269         return admin;
270     }
271 
272     // This triggers the callback which was manually set for this enabler since the enabler will
273     // take over the switch controller callback
triggerParentPreferenceCallback(boolean isChecked)274     private void triggerParentPreferenceCallback(boolean isChecked) {
275         if (mCallback != null) {
276             mCallback.onSwitchToggled(isChecked);
277         }
278     }
279 
setBluetoothEnabled(boolean isEnabled)280     private boolean setBluetoothEnabled(boolean isEnabled) {
281         return isEnabled ? mBluetoothAdapter.enable() : mBluetoothAdapter.disable();
282     }
283 }
284