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