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