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 final 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 95 BluetoothDiscoverableEnabler(Context context, LocalBluetoothAdapter adapter, 96 Preference discoveryPreference) { 97 mContext = context; 98 mUiHandler = new Handler(); 99 mLocalAdapter = adapter; 100 mDiscoveryPreference = discoveryPreference; 101 mSharedPreferences = discoveryPreference.getSharedPreferences(); 102 discoveryPreference.setPersistent(false); 103 } 104 105 public void resume() { 106 if (mLocalAdapter == null) { 107 return; 108 } 109 110 IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); 111 mContext.registerReceiver(mReceiver, filter); 112 mDiscoveryPreference.setOnPreferenceClickListener(this); 113 handleModeChanged(mLocalAdapter.getScanMode()); 114 } 115 116 public void pause() { 117 if (mLocalAdapter == null) { 118 return; 119 } 120 121 mUiHandler.removeCallbacks(mUpdateCountdownSummaryRunnable); 122 mContext.unregisterReceiver(mReceiver); 123 mDiscoveryPreference.setOnPreferenceClickListener(null); 124 } 125 126 public boolean onPreferenceClick(Preference preference) { 127 // toggle discoverability 128 mDiscoverable = !mDiscoverable; 129 setEnabled(mDiscoverable); 130 return true; 131 } 132 133 private void setEnabled(boolean enable) { 134 if (enable) { 135 int timeout = getDiscoverableTimeout(); 136 long endTimestamp = System.currentTimeMillis() + timeout * 1000L; 137 LocalBluetoothPreferences.persistDiscoverableEndTimestamp(mContext, endTimestamp); 138 139 mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, timeout); 140 updateCountdownSummary(); 141 142 Log.d(TAG, "setEnabled(): enabled = " + enable + "timeout = " + timeout); 143 144 if (timeout > 0) { 145 BluetoothDiscoverableTimeoutReceiver.setDiscoverableAlarm(mContext, endTimestamp); 146 } 147 } else { 148 mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE); 149 BluetoothDiscoverableTimeoutReceiver.cancelDiscoverableAlarm(mContext); 150 } 151 } 152 153 private void updateTimerDisplay(int timeout) { 154 if (getDiscoverableTimeout() == DISCOVERABLE_TIMEOUT_NEVER) { 155 mDiscoveryPreference.setSummary(R.string.bluetooth_is_discoverable_always); 156 } else { 157 String textTimeout = formatTimeRemaining(timeout); 158 mDiscoveryPreference.setSummary(mContext.getString(R.string.bluetooth_is_discoverable, 159 textTimeout)); 160 } 161 } 162 163 private static String formatTimeRemaining(int timeout) { 164 StringBuilder sb = new StringBuilder(6); // "mmm:ss" 165 int min = timeout / 60; 166 sb.append(min).append(':'); 167 int sec = timeout - (min * 60); 168 if (sec < 10) { 169 sb.append('0'); 170 } 171 sb.append(sec); 172 return sb.toString(); 173 } 174 175 void setDiscoverableTimeout(int index) { 176 String timeoutValue; 177 switch (index) { 178 case 0: 179 default: 180 mTimeoutSecs = DISCOVERABLE_TIMEOUT_TWO_MINUTES; 181 timeoutValue = VALUE_DISCOVERABLE_TIMEOUT_TWO_MINUTES; 182 break; 183 184 case 1: 185 mTimeoutSecs = DISCOVERABLE_TIMEOUT_FIVE_MINUTES; 186 timeoutValue = VALUE_DISCOVERABLE_TIMEOUT_FIVE_MINUTES; 187 break; 188 189 case 2: 190 mTimeoutSecs = DISCOVERABLE_TIMEOUT_ONE_HOUR; 191 timeoutValue = VALUE_DISCOVERABLE_TIMEOUT_ONE_HOUR; 192 break; 193 194 case 3: 195 mTimeoutSecs = DISCOVERABLE_TIMEOUT_NEVER; 196 timeoutValue = VALUE_DISCOVERABLE_TIMEOUT_NEVER; 197 break; 198 } 199 mSharedPreferences.edit().putString(KEY_DISCOVERABLE_TIMEOUT, timeoutValue).apply(); 200 setEnabled(true); // enable discovery and reset timer 201 } 202 203 private int getDiscoverableTimeout() { 204 if (mTimeoutSecs != -1) { 205 return mTimeoutSecs; 206 } 207 208 int timeout = SystemProperties.getInt(SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT, -1); 209 if (timeout < 0) { 210 String timeoutValue = mSharedPreferences.getString(KEY_DISCOVERABLE_TIMEOUT, 211 VALUE_DISCOVERABLE_TIMEOUT_TWO_MINUTES); 212 213 if (timeoutValue.equals(VALUE_DISCOVERABLE_TIMEOUT_NEVER)) { 214 timeout = DISCOVERABLE_TIMEOUT_NEVER; 215 } else if (timeoutValue.equals(VALUE_DISCOVERABLE_TIMEOUT_ONE_HOUR)) { 216 timeout = DISCOVERABLE_TIMEOUT_ONE_HOUR; 217 } else if (timeoutValue.equals(VALUE_DISCOVERABLE_TIMEOUT_FIVE_MINUTES)) { 218 timeout = DISCOVERABLE_TIMEOUT_FIVE_MINUTES; 219 } else { 220 timeout = DISCOVERABLE_TIMEOUT_TWO_MINUTES; 221 } 222 } 223 mTimeoutSecs = timeout; 224 return timeout; 225 } 226 227 int getDiscoverableTimeoutIndex() { 228 int timeout = getDiscoverableTimeout(); 229 switch (timeout) { 230 case DISCOVERABLE_TIMEOUT_TWO_MINUTES: 231 default: 232 return 0; 233 234 case DISCOVERABLE_TIMEOUT_FIVE_MINUTES: 235 return 1; 236 237 case DISCOVERABLE_TIMEOUT_ONE_HOUR: 238 return 2; 239 240 case DISCOVERABLE_TIMEOUT_NEVER: 241 return 3; 242 } 243 } 244 245 void setNumberOfPairedDevices(int pairedDevices) { 246 mNumberOfPairedDevices = pairedDevices; 247 handleModeChanged(mLocalAdapter.getScanMode()); 248 } 249 250 void handleModeChanged(int mode) { 251 Log.d(TAG, "handleModeChanged(): mode = " + mode); 252 if (mode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { 253 mDiscoverable = true; 254 updateCountdownSummary(); 255 } else { 256 mDiscoverable = false; 257 setSummaryNotDiscoverable(); 258 } 259 } 260 261 private void setSummaryNotDiscoverable() { 262 if (mNumberOfPairedDevices != 0) { 263 mDiscoveryPreference.setSummary(R.string.bluetooth_only_visible_to_paired_devices); 264 } else { 265 mDiscoveryPreference.setSummary(R.string.bluetooth_not_visible_to_other_devices); 266 } 267 } 268 269 private void updateCountdownSummary() { 270 int mode = mLocalAdapter.getScanMode(); 271 if (mode != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { 272 return; 273 } 274 275 long currentTimestamp = System.currentTimeMillis(); 276 long endTimestamp = LocalBluetoothPreferences.getDiscoverableEndTimestamp(mContext); 277 278 if (currentTimestamp > endTimestamp) { 279 // We're still in discoverable mode, but maybe there isn't a timeout. 280 updateTimerDisplay(0); 281 return; 282 } 283 284 int timeLeft = (int) ((endTimestamp - currentTimestamp) / 1000L); 285 updateTimerDisplay(timeLeft); 286 287 synchronized (this) { 288 mUiHandler.removeCallbacks(mUpdateCountdownSummaryRunnable); 289 mUiHandler.postDelayed(mUpdateCountdownSummaryRunnable, 1000); 290 } 291 } 292 } 293