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