BluetoothDiscoverableEnabler.java revision 59a97f7a735fd2e47df8358cdd9276ada9de5386
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
17package com.android.settings.bluetooth;
18
19import android.bluetooth.BluetoothAdapter;
20import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.content.SharedPreferences;
25import android.os.Handler;
26import android.os.SystemProperties;
27import android.preference.Preference;
28import android.text.format.DateUtils;
29
30import com.android.settings.R;
31
32/* Required to handle timeout notification when phone is suspended */
33import android.app.AlarmManager;
34import android.app.PendingIntent;
35import android.text.format.Time;
36import android.util.Log;
37
38/**
39 * BluetoothDiscoverableEnabler is a helper to manage the "Discoverable"
40 * checkbox. It sets/unsets discoverability and keeps track of how much time
41 * until the the discoverability is automatically turned off.
42 */
43final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceClickListener {
44
45    private static final String TAG = "BluetoothDiscoverableEnabler";
46
47    private static final String SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT =
48            "debug.bt.discoverable_time";
49
50    private static final int DISCOVERABLE_TIMEOUT_TWO_MINUTES = 120;
51    private static final int DISCOVERABLE_TIMEOUT_FIVE_MINUTES = 300;
52    private static final int DISCOVERABLE_TIMEOUT_ONE_HOUR = 3600;
53    static final int DISCOVERABLE_TIMEOUT_NEVER = 0;
54    private static final String INTENT_DISCOVERABLE_TIMEOUT = "android.bluetooth.intent.DISCOVERABLE_TIMEOUT";
55
56    // Bluetooth advanced settings screen was replaced with action bar items.
57    // Use the same preference key for discoverable timeout as the old ListPreference.
58    private static final String KEY_DISCOVERABLE_TIMEOUT = "bt_discoverable_timeout";
59
60    private static final String VALUE_DISCOVERABLE_TIMEOUT_TWO_MINUTES = "twomin";
61    private static final String VALUE_DISCOVERABLE_TIMEOUT_FIVE_MINUTES = "fivemin";
62    private static final String VALUE_DISCOVERABLE_TIMEOUT_ONE_HOUR = "onehour";
63    private static final String VALUE_DISCOVERABLE_TIMEOUT_NEVER = "never";
64
65    static final int DEFAULT_DISCOVERABLE_TIMEOUT = DISCOVERABLE_TIMEOUT_TWO_MINUTES;
66
67    private final Context mContext;
68    private final Handler mUiHandler;
69    private final Preference mDiscoveryPreference;
70
71    private final LocalBluetoothAdapter mLocalAdapter;
72
73    private final SharedPreferences mSharedPreferences;
74
75    private boolean mDiscoverable;
76    private int mNumberOfPairedDevices;
77
78    private int mTimeoutSecs = -1;
79
80    private AlarmManager mAlarmManager = null;
81
82    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
83        @Override
84        public void onReceive(Context context, Intent intent) {
85            if (BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(intent.getAction())) {
86                int mode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE,
87                        BluetoothAdapter.ERROR);
88                if (mode != BluetoothAdapter.ERROR) {
89                    handleModeChanged(mode);
90                }
91            }
92        }
93    };
94
95    private final Runnable mUpdateCountdownSummaryRunnable = new Runnable() {
96        public void run() {
97            updateCountdownSummary();
98        }
99    };
100
101    BluetoothDiscoverableEnabler(Context context, LocalBluetoothAdapter adapter,
102            Preference discoveryPreference) {
103        mContext = context;
104        mUiHandler = new Handler();
105        mLocalAdapter = adapter;
106        mDiscoveryPreference = discoveryPreference;
107        mSharedPreferences = discoveryPreference.getSharedPreferences();
108        discoveryPreference.setPersistent(false);
109
110        mAlarmManager = (AlarmManager) mContext.getSystemService (Context.ALARM_SERVICE);
111    }
112
113    public void resume() {
114        if (mLocalAdapter == null) {
115            return;
116        }
117
118        IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
119        mContext.registerReceiver(mReceiver, filter);
120        mDiscoveryPreference.setOnPreferenceClickListener(this);
121        handleModeChanged(mLocalAdapter.getScanMode());
122    }
123
124    public void pause() {
125        if (mLocalAdapter == null) {
126            return;
127        }
128
129        mUiHandler.removeCallbacks(mUpdateCountdownSummaryRunnable);
130        mContext.unregisterReceiver(mReceiver);
131        mDiscoveryPreference.setOnPreferenceClickListener(null);
132    }
133
134    public boolean onPreferenceClick(Preference preference) {
135        // toggle discoverability
136        mDiscoverable = !mDiscoverable;
137        setEnabled(mDiscoverable);
138        return true;
139    }
140
141    private void setEnabled(boolean enable) {
142        if (enable) {
143            int timeout = getDiscoverableTimeout();
144            long endTimestamp = System.currentTimeMillis() + timeout * 1000L;
145            LocalBluetoothPreferences.persistDiscoverableEndTimestamp(mContext, endTimestamp);
146
147            mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, timeout);
148            updateCountdownSummary();
149
150            if (0 < timeout) {
151                setDiscoverableAlarm(endTimestamp);
152            }
153        } else {
154            mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
155            cancelDiscoverableAlarm();
156        }
157    }
158
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
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
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
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
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
251    void setNumberOfPairedDevices(int pairedDevices) {
252        mNumberOfPairedDevices = pairedDevices;
253        handleModeChanged(mLocalAdapter.getScanMode());
254    }
255
256    void handleModeChanged(int mode) {
257        if (mode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
258            mDiscoverable = true;
259            updateCountdownSummary();
260        } else {
261            mDiscoverable = false;
262            setSummaryNotDiscoverable();
263        }
264    }
265
266    private void setSummaryNotDiscoverable() {
267        if (mNumberOfPairedDevices != 0) {
268            mDiscoveryPreference.setSummary(R.string.bluetooth_only_visible_to_paired_devices);
269        } else {
270            mDiscoveryPreference.setSummary(R.string.bluetooth_not_visible_to_other_devices);
271        }
272    }
273
274    private void updateCountdownSummary() {
275        int mode = mLocalAdapter.getScanMode();
276        if (mode != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
277            return;
278        }
279
280        long currentTimestamp = System.currentTimeMillis();
281        long endTimestamp = LocalBluetoothPreferences.getDiscoverableEndTimestamp(mContext);
282
283        if (currentTimestamp > endTimestamp) {
284            // We're still in discoverable mode, but maybe there isn't a timeout.
285            updateTimerDisplay(0);
286            return;
287        }
288
289        int timeLeft = (int) ((endTimestamp - currentTimestamp) / 1000L);
290        updateTimerDisplay(timeLeft);
291
292        synchronized (this) {
293            mUiHandler.removeCallbacks(mUpdateCountdownSummaryRunnable);
294            mUiHandler.postDelayed(mUpdateCountdownSummaryRunnable, 1000);
295        }
296    }
297
298    private void setDiscoverableAlarm(long alarmTime) {
299        Log.d(TAG, "setDiscoverableAlarm(): alarmTime = " + alarmTime);
300
301        Intent intent = new Intent(INTENT_DISCOVERABLE_TIMEOUT);
302        intent.setClass(mContext, BluetoothDiscoverableTimeoutReceiver.class);
303        PendingIntent pending = PendingIntent.getBroadcast(
304            mContext, 0, intent, 0);
305        if (pending != null) {
306            // Cancel any previous alarms that do the same thing.
307            mAlarmManager.cancel(pending);
308        }
309        pending = PendingIntent.getBroadcast(
310            mContext, 0, intent, 0);
311
312        mAlarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pending);
313    }
314
315    private void cancelDiscoverableAlarm() {
316        Log.d(TAG, "cancelDiscoverableAlarm(): Enter");
317
318        Intent intent = new Intent(INTENT_DISCOVERABLE_TIMEOUT);
319        intent.setClass(mContext, BluetoothDiscoverableTimeoutReceiver.class);
320        PendingIntent pending = PendingIntent.getBroadcast(
321            mContext, 0, intent, PendingIntent.FLAG_NO_CREATE);
322        if (pending != null) {
323            // Cancel any previous alarms that do the same thing.
324            mAlarmManager.cancel(pending);
325        }
326    }
327}
328