| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.settings.bluetooth; |
| |
| import android.bluetooth.BluetoothAdapter; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.SharedPreferences; |
| import android.os.Handler; |
| import android.os.SystemProperties; |
| import android.util.Log; |
| |
| import androidx.preference.Preference; |
| |
| import com.android.settings.R; |
| import com.android.settingslib.bluetooth.BluetoothDiscoverableTimeoutReceiver; |
| |
| import java.time.Duration; |
| |
| /** |
| * BluetoothDiscoverableEnabler is a helper to manage the "Discoverable" |
| * checkbox. It sets/unsets discoverability and keeps track of how much time |
| * until the the discoverability is automatically turned off. |
| */ |
| final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceClickListener { |
| |
| private static final String TAG = "BluetoothDiscoverableEnabler"; |
| |
| private static final String SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT = |
| "debug.bt.discoverable_time"; |
| |
| private static final int DISCOVERABLE_TIMEOUT_TWO_MINUTES = 120; |
| private static final int DISCOVERABLE_TIMEOUT_FIVE_MINUTES = 300; |
| private static final int DISCOVERABLE_TIMEOUT_ONE_HOUR = 3600; |
| static final int DISCOVERABLE_TIMEOUT_NEVER = 0; |
| |
| // Bluetooth advanced settings screen was replaced with action bar items. |
| // Use the same preference key for discoverable timeout as the old ListPreference. |
| private static final String KEY_DISCOVERABLE_TIMEOUT = "bt_discoverable_timeout"; |
| |
| private static final String VALUE_DISCOVERABLE_TIMEOUT_TWO_MINUTES = "twomin"; |
| private static final String VALUE_DISCOVERABLE_TIMEOUT_FIVE_MINUTES = "fivemin"; |
| private static final String VALUE_DISCOVERABLE_TIMEOUT_ONE_HOUR = "onehour"; |
| private static final String VALUE_DISCOVERABLE_TIMEOUT_NEVER = "never"; |
| |
| static final int DEFAULT_DISCOVERABLE_TIMEOUT = DISCOVERABLE_TIMEOUT_TWO_MINUTES; |
| |
| private Context mContext; |
| private final Handler mUiHandler; |
| private final Preference mDiscoveryPreference; |
| |
| private final BluetoothAdapter mBluetoothAdapter; |
| |
| private final SharedPreferences mSharedPreferences; |
| |
| private boolean mDiscoverable; |
| private int mNumberOfPairedDevices; |
| |
| private int mTimeoutSecs = -1; |
| |
| private final BroadcastReceiver mReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(intent.getAction())) { |
| int mode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, |
| BluetoothAdapter.ERROR); |
| if (mode != BluetoothAdapter.ERROR) { |
| handleModeChanged(mode); |
| } |
| } |
| } |
| }; |
| |
| private final Runnable mUpdateCountdownSummaryRunnable = new Runnable() { |
| public void run() { |
| updateCountdownSummary(); |
| } |
| }; |
| |
| BluetoothDiscoverableEnabler(Preference discoveryPreference) { |
| mUiHandler = new Handler(); |
| mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); |
| mDiscoveryPreference = discoveryPreference; |
| mSharedPreferences = discoveryPreference.getSharedPreferences(); |
| discoveryPreference.setPersistent(false); |
| } |
| |
| public void resume(Context context) { |
| if (mBluetoothAdapter == null) { |
| return; |
| } |
| |
| if (mContext != context) { |
| mContext = context; |
| } |
| |
| IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); |
| mContext.registerReceiver(mReceiver, filter); |
| mDiscoveryPreference.setOnPreferenceClickListener(this); |
| handleModeChanged(mBluetoothAdapter.getScanMode()); |
| } |
| |
| public void pause() { |
| if (mBluetoothAdapter == null) { |
| return; |
| } |
| |
| mUiHandler.removeCallbacks(mUpdateCountdownSummaryRunnable); |
| mContext.unregisterReceiver(mReceiver); |
| mDiscoveryPreference.setOnPreferenceClickListener(null); |
| } |
| |
| public boolean onPreferenceClick(Preference preference) { |
| // toggle discoverability |
| mDiscoverable = !mDiscoverable; |
| setEnabled(mDiscoverable); |
| return true; |
| } |
| |
| private void setEnabled(boolean enable) { |
| if (enable) { |
| int timeout = getDiscoverableTimeout(); |
| long endTimestamp = System.currentTimeMillis() + timeout * 1000L; |
| LocalBluetoothPreferences.persistDiscoverableEndTimestamp(mContext, endTimestamp); |
| mBluetoothAdapter.setDiscoverableTimeout(Duration.ofSeconds(timeout)); |
| mBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE); |
| updateCountdownSummary(); |
| |
| Log.d(TAG, "setEnabled(): enabled = " + enable + "timeout = " + timeout); |
| |
| if (timeout > 0) { |
| BluetoothDiscoverableTimeoutReceiver.setDiscoverableAlarm(mContext, endTimestamp); |
| } else { |
| BluetoothDiscoverableTimeoutReceiver.cancelDiscoverableAlarm(mContext); |
| } |
| |
| } else { |
| mBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE); |
| BluetoothDiscoverableTimeoutReceiver.cancelDiscoverableAlarm(mContext); |
| } |
| } |
| |
| private void updateTimerDisplay(int timeout) { |
| if (getDiscoverableTimeout() == DISCOVERABLE_TIMEOUT_NEVER) { |
| mDiscoveryPreference.setSummary(R.string.bluetooth_is_discoverable_always); |
| } else { |
| String textTimeout = formatTimeRemaining(timeout); |
| mDiscoveryPreference.setSummary(mContext.getString(R.string.bluetooth_is_discoverable, |
| textTimeout)); |
| } |
| } |
| |
| private static String formatTimeRemaining(int timeout) { |
| StringBuilder sb = new StringBuilder(6); // "mmm:ss" |
| int min = timeout / 60; |
| sb.append(min).append(':'); |
| int sec = timeout - (min * 60); |
| if (sec < 10) { |
| sb.append('0'); |
| } |
| sb.append(sec); |
| return sb.toString(); |
| } |
| |
| void setDiscoverableTimeout(int index) { |
| String timeoutValue; |
| switch (index) { |
| case 0: |
| default: |
| mTimeoutSecs = DISCOVERABLE_TIMEOUT_TWO_MINUTES; |
| timeoutValue = VALUE_DISCOVERABLE_TIMEOUT_TWO_MINUTES; |
| break; |
| |
| case 1: |
| mTimeoutSecs = DISCOVERABLE_TIMEOUT_FIVE_MINUTES; |
| timeoutValue = VALUE_DISCOVERABLE_TIMEOUT_FIVE_MINUTES; |
| break; |
| |
| case 2: |
| mTimeoutSecs = DISCOVERABLE_TIMEOUT_ONE_HOUR; |
| timeoutValue = VALUE_DISCOVERABLE_TIMEOUT_ONE_HOUR; |
| break; |
| |
| case 3: |
| mTimeoutSecs = DISCOVERABLE_TIMEOUT_NEVER; |
| timeoutValue = VALUE_DISCOVERABLE_TIMEOUT_NEVER; |
| break; |
| } |
| mSharedPreferences.edit().putString(KEY_DISCOVERABLE_TIMEOUT, timeoutValue).apply(); |
| setEnabled(true); // enable discovery and reset timer |
| } |
| |
| private int getDiscoverableTimeout() { |
| if (mTimeoutSecs != -1) { |
| return mTimeoutSecs; |
| } |
| |
| int timeout = SystemProperties.getInt(SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT, -1); |
| if (timeout < 0) { |
| String timeoutValue = mSharedPreferences.getString(KEY_DISCOVERABLE_TIMEOUT, |
| VALUE_DISCOVERABLE_TIMEOUT_TWO_MINUTES); |
| |
| if (timeoutValue.equals(VALUE_DISCOVERABLE_TIMEOUT_NEVER)) { |
| timeout = DISCOVERABLE_TIMEOUT_NEVER; |
| } else if (timeoutValue.equals(VALUE_DISCOVERABLE_TIMEOUT_ONE_HOUR)) { |
| timeout = DISCOVERABLE_TIMEOUT_ONE_HOUR; |
| } else if (timeoutValue.equals(VALUE_DISCOVERABLE_TIMEOUT_FIVE_MINUTES)) { |
| timeout = DISCOVERABLE_TIMEOUT_FIVE_MINUTES; |
| } else { |
| timeout = DISCOVERABLE_TIMEOUT_TWO_MINUTES; |
| } |
| } |
| mTimeoutSecs = timeout; |
| return timeout; |
| } |
| |
| int getDiscoverableTimeoutIndex() { |
| int timeout = getDiscoverableTimeout(); |
| switch (timeout) { |
| case DISCOVERABLE_TIMEOUT_TWO_MINUTES: |
| default: |
| return 0; |
| |
| case DISCOVERABLE_TIMEOUT_FIVE_MINUTES: |
| return 1; |
| |
| case DISCOVERABLE_TIMEOUT_ONE_HOUR: |
| return 2; |
| |
| case DISCOVERABLE_TIMEOUT_NEVER: |
| return 3; |
| } |
| } |
| |
| void setNumberOfPairedDevices(int pairedDevices) { |
| mNumberOfPairedDevices = pairedDevices; |
| handleModeChanged(mBluetoothAdapter.getScanMode()); |
| } |
| |
| void handleModeChanged(int mode) { |
| Log.d(TAG, "handleModeChanged(): mode = " + mode); |
| if (mode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { |
| mDiscoverable = true; |
| updateCountdownSummary(); |
| } else { |
| mDiscoverable = false; |
| setSummaryNotDiscoverable(); |
| } |
| } |
| |
| private void setSummaryNotDiscoverable() { |
| if (mNumberOfPairedDevices != 0) { |
| mDiscoveryPreference.setSummary(R.string.bluetooth_only_visible_to_paired_devices); |
| } else { |
| mDiscoveryPreference.setSummary(R.string.bluetooth_not_visible_to_other_devices); |
| } |
| } |
| |
| private void updateCountdownSummary() { |
| int mode = mBluetoothAdapter.getScanMode(); |
| if (mode != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { |
| return; |
| } |
| |
| long currentTimestamp = System.currentTimeMillis(); |
| long endTimestamp = LocalBluetoothPreferences.getDiscoverableEndTimestamp(mContext); |
| |
| if (currentTimestamp > endTimestamp) { |
| // We're still in discoverable mode, but maybe there isn't a timeout. |
| updateTimerDisplay(0); |
| return; |
| } |
| |
| int timeLeft = (int) ((endTimestamp - currentTimestamp) / 1000L); |
| updateTimerDisplay(timeLeft); |
| |
| synchronized (this) { |
| mUiHandler.removeCallbacks(mUpdateCountdownSummaryRunnable); |
| mUiHandler.postDelayed(mUpdateCountdownSummaryRunnable, 1000); |
| } |
| } |
| } |