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 android.bluetooth.BluetoothAdapter;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.os.UserManager;
25 import android.provider.Settings;
26 import android.widget.Switch;
27 import android.widget.Toast;
28 
29 import com.android.internal.annotations.VisibleForTesting;
30 import com.android.settings.R;
31 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
32 import com.android.settings.widget.SwitchWidgetController;
33 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
34 import com.android.settingslib.WirelessUtils;
35 import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
36 import com.android.settingslib.bluetooth.LocalBluetoothManager;
37 
38 /**
39  * BluetoothEnabler is a helper to manage the Bluetooth on/off checkbox
40  * preference. It turns on/off Bluetooth and ensures the summary of the
41  * preference reflects the current state.
42  */
43 public final class BluetoothEnabler implements SwitchWidgetController.OnSwitchChangeListener {
44     private final Switch mSwitch;
45     private final SwitchWidgetController mSwitchWidget;
46     private final MetricsFeatureProvider mMetricsFeatureProvider;
47     private Context mContext;
48     private boolean mValidListener;
49     private final LocalBluetoothAdapter mLocalAdapter;
50     private final IntentFilter mIntentFilter;
51     private final RestrictionUtils mRestrictionUtils;
52 
53     private static final String EVENT_DATA_IS_BT_ON = "is_bluetooth_on";
54     private static final int EVENT_UPDATE_INDEX = 0;
55     private final int mMetricsEvent;
56 
57     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
58         @Override
59         public void onReceive(Context context, Intent intent) {
60             // Broadcast receiver is always running on the UI thread here,
61             // so we don't need consider thread synchronization.
62             int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
63             handleStateChanged(state);
64         }
65     };
66 
BluetoothEnabler(Context context, SwitchWidgetController switchWidget, MetricsFeatureProvider metricsFeatureProvider, LocalBluetoothManager manager, int metricsEvent)67     public BluetoothEnabler(Context context, SwitchWidgetController switchWidget,
68             MetricsFeatureProvider metricsFeatureProvider, LocalBluetoothManager manager,
69             int metricsEvent) {
70         this(context, switchWidget, metricsFeatureProvider, manager, metricsEvent,
71                 new RestrictionUtils());
72     }
73 
BluetoothEnabler(Context context, SwitchWidgetController switchWidget, MetricsFeatureProvider metricsFeatureProvider, LocalBluetoothManager manager, int metricsEvent, RestrictionUtils restrictionUtils)74     public BluetoothEnabler(Context context, SwitchWidgetController switchWidget,
75             MetricsFeatureProvider metricsFeatureProvider, LocalBluetoothManager manager,
76             int metricsEvent, RestrictionUtils restrictionUtils) {
77         mContext = context;
78         mMetricsFeatureProvider = metricsFeatureProvider;
79         mSwitchWidget = switchWidget;
80         mSwitch = mSwitchWidget.getSwitch();
81         mSwitchWidget.setListener(this);
82         mValidListener = false;
83         mMetricsEvent = metricsEvent;
84 
85         if (manager == null) {
86             // Bluetooth is not supported
87             mLocalAdapter = null;
88             mSwitchWidget.setEnabled(false);
89         } else {
90             mLocalAdapter = manager.getBluetoothAdapter();
91         }
92         mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
93         mRestrictionUtils = restrictionUtils;
94     }
95 
setupSwitchController()96     public void setupSwitchController() {
97         mSwitchWidget.setupView();
98     }
99 
teardownSwitchController()100     public void teardownSwitchController() {
101         mSwitchWidget.teardownView();
102     }
103 
resume(Context context)104     public void resume(Context context) {
105         if (mContext != context) {
106             mContext = context;
107         }
108 
109         maybeEnforceRestrictions();
110 
111         if (mLocalAdapter == null) {
112             mSwitchWidget.setEnabled(false);
113             return;
114         }
115 
116         // Bluetooth state is not sticky, so set it manually
117         handleStateChanged(mLocalAdapter.getBluetoothState());
118 
119         mSwitchWidget.startListening();
120         mContext.registerReceiver(mReceiver, mIntentFilter);
121         mValidListener = true;
122     }
123 
pause()124     public void pause() {
125         if (mLocalAdapter == null) {
126             return;
127         }
128         if (mValidListener) {
129             mSwitchWidget.stopListening();
130             mContext.unregisterReceiver(mReceiver);
131             mValidListener = false;
132         }
133     }
134 
handleStateChanged(int state)135     void handleStateChanged(int state) {
136         switch (state) {
137             case BluetoothAdapter.STATE_TURNING_ON:
138                 mSwitchWidget.setEnabled(false);
139                 break;
140             case BluetoothAdapter.STATE_ON:
141                 setChecked(true);
142                 mSwitchWidget.setEnabled(true);
143                 break;
144             case BluetoothAdapter.STATE_TURNING_OFF:
145                 mSwitchWidget.setEnabled(false);
146                 break;
147             case BluetoothAdapter.STATE_OFF:
148                 setChecked(false);
149                 mSwitchWidget.setEnabled(true);
150                 break;
151             default:
152                 setChecked(false);
153                 mSwitchWidget.setEnabled(true);
154         }
155     }
156 
setChecked(boolean isChecked)157     private void setChecked(boolean isChecked) {
158         if (isChecked != mSwitchWidget.isChecked()) {
159             // set listener to null, so onCheckedChanged won't be called
160             // if the checked status on Switch isn't changed by user click
161             if (mValidListener) {
162                 mSwitchWidget.stopListening();
163             }
164             mSwitchWidget.setChecked(isChecked);
165             if (mValidListener) {
166                 mSwitchWidget.startListening();
167             }
168         }
169     }
170 
171     @Override
onSwitchToggled(boolean isChecked)172     public boolean onSwitchToggled(boolean isChecked) {
173         if (maybeEnforceRestrictions()) {
174             return true;
175         }
176 
177         // Show toast message if Bluetooth is not allowed in airplane mode
178         if (isChecked &&
179                 !WirelessUtils.isRadioAllowed(mContext, Settings.Global.RADIO_BLUETOOTH)) {
180             Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();
181             // Reset switch to off
182             mSwitch.setChecked(false);
183             return false;
184         }
185 
186         mMetricsFeatureProvider.action(mContext, mMetricsEvent, isChecked);
187 
188         if (mLocalAdapter != null) {
189             boolean status = mLocalAdapter.setBluetoothEnabled(isChecked);
190             // If we cannot toggle it ON then reset the UI assets:
191             // a) The switch should be OFF but it should still be togglable (enabled = True)
192             // b) The switch bar should have OFF text.
193             if (isChecked && !status) {
194                 mSwitch.setChecked(false);
195                 mSwitch.setEnabled(true);
196                 mSwitchWidget.updateTitle(false);
197                 return false;
198             }
199         }
200         mSwitchWidget.setEnabled(false);
201         return true;
202     }
203 
204     /**
205      * Enforces user restrictions disallowing Bluetooth (or its configuration) if there are any.
206      *
207      * @return if there was any user restriction to enforce.
208      */
209     @VisibleForTesting
maybeEnforceRestrictions()210     boolean maybeEnforceRestrictions() {
211         EnforcedAdmin admin = mRestrictionUtils.checkIfRestrictionEnforced(
212                 mContext, UserManager.DISALLOW_BLUETOOTH);
213         if (admin == null) {
214             admin = mRestrictionUtils.checkIfRestrictionEnforced(
215                     mContext, UserManager.DISALLOW_CONFIG_BLUETOOTH);
216         }
217         mSwitchWidget.setDisabledByAdmin(admin);
218         if (admin != null) {
219             mSwitchWidget.setChecked(false);
220             if (mSwitch != null) {
221                 mSwitch.setEnabled(false);
222                 mSwitch.setChecked(false);
223             }
224         }
225         return admin != null;
226     }
227 
228 }
229