1 /*
2  * Copyright (C) 2008 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.content.SharedPreferences;
25 import android.os.Handler;
26 import android.os.SystemProperties;
27 import android.preference.Preference;
28 import android.text.format.DateUtils;
29 
30 import com.android.settings.R;
31 
32 import android.text.format.Time;
33 import android.util.Log;
34 
35 /**
36  * BluetoothDiscoverableEnabler is a helper to manage the "Discoverable"
37  * checkbox. It sets/unsets discoverability and keeps track of how much time
38  * until the the discoverability is automatically turned off.
39  */
40 final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceClickListener {
41 
42     private static final String TAG = "BluetoothDiscoverableEnabler";
43 
44     private static final String SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT =
45             "debug.bt.discoverable_time";
46 
47     private static final int DISCOVERABLE_TIMEOUT_TWO_MINUTES = 120;
48     private static final int DISCOVERABLE_TIMEOUT_FIVE_MINUTES = 300;
49     private static final int DISCOVERABLE_TIMEOUT_ONE_HOUR = 3600;
50     static final int DISCOVERABLE_TIMEOUT_NEVER = 0;
51 
52     // Bluetooth advanced settings screen was replaced with action bar items.
53     // Use the same preference key for discoverable timeout as the old ListPreference.
54     private static final String KEY_DISCOVERABLE_TIMEOUT = "bt_discoverable_timeout";
55 
56     private static final String VALUE_DISCOVERABLE_TIMEOUT_TWO_MINUTES = "twomin";
57     private static final String VALUE_DISCOVERABLE_TIMEOUT_FIVE_MINUTES = "fivemin";
58     private static final String VALUE_DISCOVERABLE_TIMEOUT_ONE_HOUR = "onehour";
59     private static final String VALUE_DISCOVERABLE_TIMEOUT_NEVER = "never";
60 
61     static final int DEFAULT_DISCOVERABLE_TIMEOUT = DISCOVERABLE_TIMEOUT_TWO_MINUTES;
62 
63     private Context mContext;
64     private final Handler mUiHandler;
65     private final Preference mDiscoveryPreference;
66 
67     private final LocalBluetoothAdapter mLocalAdapter;
68 
69     private final SharedPreferences mSharedPreferences;
70 
71     private boolean mDiscoverable;
72     private int mNumberOfPairedDevices;
73 
74     private int mTimeoutSecs = -1;
75 
76     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
77         @Override
78         public void onReceive(Context context, Intent intent) {
79             if (BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(intent.getAction())) {
80                 int mode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE,
81                         BluetoothAdapter.ERROR);
82                 if (mode != BluetoothAdapter.ERROR) {
83                     handleModeChanged(mode);
84                 }
85             }
86         }
87     };
88 
89     private final Runnable mUpdateCountdownSummaryRunnable = new Runnable() {
90         public void run() {
91             updateCountdownSummary();
92         }
93     };
94 
BluetoothDiscoverableEnabler(LocalBluetoothAdapter adapter, Preference discoveryPreference)95     BluetoothDiscoverableEnabler(LocalBluetoothAdapter adapter,
96             Preference discoveryPreference) {
97         mUiHandler = new Handler();
98         mLocalAdapter = adapter;
99         mDiscoveryPreference = discoveryPreference;
100         mSharedPreferences = discoveryPreference.getSharedPreferences();
101         discoveryPreference.setPersistent(false);
102     }
103 
resume(Context context)104     public void resume(Context context) {
105         if (mLocalAdapter == null) {
106             return;
107         }
108 
109         if (mContext != context) {
110             mContext = context;
111         }
112 
113         IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
114         mContext.registerReceiver(mReceiver, filter);
115         mDiscoveryPreference.setOnPreferenceClickListener(this);
116         handleModeChanged(mLocalAdapter.getScanMode());
117     }
118 
pause()119     public void pause() {
120         if (mLocalAdapter == null) {
121             return;
122         }
123 
124         mUiHandler.removeCallbacks(mUpdateCountdownSummaryRunnable);
125         mContext.unregisterReceiver(mReceiver);
126         mDiscoveryPreference.setOnPreferenceClickListener(null);
127     }
128 
onPreferenceClick(Preference preference)129     public boolean onPreferenceClick(Preference preference) {
130         // toggle discoverability
131         mDiscoverable = !mDiscoverable;
132         setEnabled(mDiscoverable);
133         return true;
134     }
135 
setEnabled(boolean enable)136     private void setEnabled(boolean enable) {
137         if (enable) {
138             int timeout = getDiscoverableTimeout();
139             long endTimestamp = System.currentTimeMillis() + timeout * 1000L;
140             LocalBluetoothPreferences.persistDiscoverableEndTimestamp(mContext, endTimestamp);
141 
142             mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, timeout);
143             updateCountdownSummary();
144 
145             Log.d(TAG, "setEnabled(): enabled = " + enable + "timeout = " + timeout);
146 
147             if (timeout > 0) {
148                 BluetoothDiscoverableTimeoutReceiver.setDiscoverableAlarm(mContext, endTimestamp);
149             } else {
150                 BluetoothDiscoverableTimeoutReceiver.cancelDiscoverableAlarm(mContext);
151             }
152 
153         } else {
154             mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
155             BluetoothDiscoverableTimeoutReceiver.cancelDiscoverableAlarm(mContext);
156         }
157     }
158 
updateTimerDisplay(int timeout)159     private void updateTimerDisplay(int timeout) {
160         if (getDiscoverableTimeout() == DISCOVERABLE_TIMEOUT_NEVER) {
161             mDiscoveryPreference.setSummary(R.string.bluetooth_is_discoverable_always);
162         } else {
163             String textTimeout = formatTimeRemaining(timeout);
164             mDiscoveryPreference.setSummary(mContext.getString(R.string.bluetooth_is_discoverable,
165                     textTimeout));
166         }
167     }
168 
formatTimeRemaining(int timeout)169     private static String formatTimeRemaining(int timeout) {
170         StringBuilder sb = new StringBuilder(6);    // "mmm:ss"
171         int min = timeout / 60;
172         sb.append(min).append(':');
173         int sec = timeout - (min * 60);
174         if (sec < 10) {
175             sb.append('0');
176         }
177         sb.append(sec);
178         return sb.toString();
179     }
180 
setDiscoverableTimeout(int index)181     void setDiscoverableTimeout(int index) {
182         String timeoutValue;
183         switch (index) {
184             case 0:
185             default:
186                 mTimeoutSecs = DISCOVERABLE_TIMEOUT_TWO_MINUTES;
187                 timeoutValue = VALUE_DISCOVERABLE_TIMEOUT_TWO_MINUTES;
188                 break;
189 
190             case 1:
191                 mTimeoutSecs = DISCOVERABLE_TIMEOUT_FIVE_MINUTES;
192                 timeoutValue = VALUE_DISCOVERABLE_TIMEOUT_FIVE_MINUTES;
193                 break;
194 
195             case 2:
196                 mTimeoutSecs = DISCOVERABLE_TIMEOUT_ONE_HOUR;
197                 timeoutValue = VALUE_DISCOVERABLE_TIMEOUT_ONE_HOUR;
198                 break;
199 
200             case 3:
201                 mTimeoutSecs = DISCOVERABLE_TIMEOUT_NEVER;
202                 timeoutValue = VALUE_DISCOVERABLE_TIMEOUT_NEVER;
203                 break;
204         }
205         mSharedPreferences.edit().putString(KEY_DISCOVERABLE_TIMEOUT, timeoutValue).apply();
206         setEnabled(true);   // enable discovery and reset timer
207     }
208 
getDiscoverableTimeout()209     private int getDiscoverableTimeout() {
210         if (mTimeoutSecs != -1) {
211             return mTimeoutSecs;
212         }
213 
214         int timeout = SystemProperties.getInt(SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT, -1);
215         if (timeout < 0) {
216             String timeoutValue = mSharedPreferences.getString(KEY_DISCOVERABLE_TIMEOUT,
217                     VALUE_DISCOVERABLE_TIMEOUT_TWO_MINUTES);
218 
219             if (timeoutValue.equals(VALUE_DISCOVERABLE_TIMEOUT_NEVER)) {
220                 timeout = DISCOVERABLE_TIMEOUT_NEVER;
221             } else if (timeoutValue.equals(VALUE_DISCOVERABLE_TIMEOUT_ONE_HOUR)) {
222                 timeout = DISCOVERABLE_TIMEOUT_ONE_HOUR;
223             } else if (timeoutValue.equals(VALUE_DISCOVERABLE_TIMEOUT_FIVE_MINUTES)) {
224                 timeout = DISCOVERABLE_TIMEOUT_FIVE_MINUTES;
225             } else {
226                 timeout = DISCOVERABLE_TIMEOUT_TWO_MINUTES;
227             }
228         }
229         mTimeoutSecs = timeout;
230         return timeout;
231     }
232 
getDiscoverableTimeoutIndex()233     int getDiscoverableTimeoutIndex() {
234         int timeout = getDiscoverableTimeout();
235         switch (timeout) {
236             case DISCOVERABLE_TIMEOUT_TWO_MINUTES:
237             default:
238                 return 0;
239 
240             case DISCOVERABLE_TIMEOUT_FIVE_MINUTES:
241                 return 1;
242 
243             case DISCOVERABLE_TIMEOUT_ONE_HOUR:
244                 return 2;
245 
246             case DISCOVERABLE_TIMEOUT_NEVER:
247                 return 3;
248         }
249     }
250 
setNumberOfPairedDevices(int pairedDevices)251     void setNumberOfPairedDevices(int pairedDevices) {
252         mNumberOfPairedDevices = pairedDevices;
253         handleModeChanged(mLocalAdapter.getScanMode());
254     }
255 
handleModeChanged(int mode)256     void handleModeChanged(int mode) {
257         Log.d(TAG, "handleModeChanged(): mode = " + mode);
258         if (mode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
259             mDiscoverable = true;
260             updateCountdownSummary();
261         } else {
262             mDiscoverable = false;
263             setSummaryNotDiscoverable();
264         }
265     }
266 
setSummaryNotDiscoverable()267     private void setSummaryNotDiscoverable() {
268         if (mNumberOfPairedDevices != 0) {
269             mDiscoveryPreference.setSummary(R.string.bluetooth_only_visible_to_paired_devices);
270         } else {
271             mDiscoveryPreference.setSummary(R.string.bluetooth_not_visible_to_other_devices);
272         }
273     }
274 
updateCountdownSummary()275     private void updateCountdownSummary() {
276         int mode = mLocalAdapter.getScanMode();
277         if (mode != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
278             return;
279         }
280 
281         long currentTimestamp = System.currentTimeMillis();
282         long endTimestamp = LocalBluetoothPreferences.getDiscoverableEndTimestamp(mContext);
283 
284         if (currentTimestamp > endTimestamp) {
285             // We're still in discoverable mode, but maybe there isn't a timeout.
286             updateTimerDisplay(0);
287             return;
288         }
289 
290         int timeLeft = (int) ((endTimestamp - currentTimestamp) / 1000L);
291         updateTimerDisplay(timeLeft);
292 
293         synchronized (this) {
294             mUiHandler.removeCallbacks(mUpdateCountdownSummaryRunnable);
295             mUiHandler.postDelayed(mUpdateCountdownSummaryRunnable, 1000);
296         }
297     }
298 }
299