tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AppRTCBluetoothManager.java (22460B)


      1 /*
      2 *  Copyright 2016 The WebRTC Project Authors. All rights reserved.
      3 *
      4 *  Use of this source code is governed by a BSD-style license
      5 *  that can be found in the LICENSE file in the root of the source
      6 *  tree. An additional intellectual property rights grant can be found
      7 *  in the file PATENTS.  All contributing project authors may
      8 *  be found in the AUTHORS file in the root of the source tree.
      9 */
     10 
     11 package org.appspot.apprtc;
     12 
     13 import android.annotation.SuppressLint;
     14 import android.bluetooth.BluetoothAdapter;
     15 import android.bluetooth.BluetoothDevice;
     16 import android.bluetooth.BluetoothHeadset;
     17 import android.bluetooth.BluetoothProfile;
     18 import android.content.BroadcastReceiver;
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.content.IntentFilter;
     22 import android.content.pm.PackageManager;
     23 import android.media.AudioManager;
     24 import android.os.Handler;
     25 import android.os.Looper;
     26 import android.os.Process;
     27 import android.util.Log;
     28 import androidx.annotation.Nullable;
     29 import java.util.List;
     30 import java.util.Set;
     31 import org.appspot.apprtc.util.AppRTCUtils;
     32 import org.webrtc.ThreadUtils;
     33 
     34 /**
     35 * AppRTCProximitySensor manages functions related to Bluetoth devices in the
     36 * AppRTC demo.
     37 */
     38 public class AppRTCBluetoothManager {
     39  private static final String TAG = "AppRTCBluetoothManager";
     40 
     41  // Timeout interval for starting or stopping audio to a Bluetooth SCO device.
     42  private static final int BLUETOOTH_SCO_TIMEOUT_MS = 4000;
     43  // Maximum number of SCO connection attempts.
     44  private static final int MAX_SCO_CONNECTION_ATTEMPTS = 2;
     45 
     46  // Bluetooth connection state.
     47  public enum State {
     48    // Bluetooth is not available; no adapter or Bluetooth is off.
     49    UNINITIALIZED,
     50    // Bluetooth error happened when trying to start Bluetooth.
     51    ERROR,
     52    // Bluetooth proxy object for the Headset profile exists, but no connected headset devices,
     53    // SCO is not started or disconnected.
     54    HEADSET_UNAVAILABLE,
     55    // Bluetooth proxy object for the Headset profile connected, connected Bluetooth headset
     56    // present, but SCO is not started or disconnected.
     57    HEADSET_AVAILABLE,
     58    // Bluetooth audio SCO connection with remote device is closing.
     59    SCO_DISCONNECTING,
     60    // Bluetooth audio SCO connection with remote device is initiated.
     61    SCO_CONNECTING,
     62    // Bluetooth audio SCO connection with remote device is established.
     63    SCO_CONNECTED
     64  }
     65 
     66  private final Context apprtcContext;
     67  private final AppRTCAudioManager apprtcAudioManager;
     68  @Nullable
     69  private final AudioManager audioManager;
     70  private final Handler handler;
     71 
     72  int scoConnectionAttempts;
     73  private State bluetoothState;
     74  private final BluetoothProfile.ServiceListener bluetoothServiceListener;
     75  @Nullable
     76  private BluetoothAdapter bluetoothAdapter;
     77  @Nullable
     78  private BluetoothHeadset bluetoothHeadset;
     79  @Nullable
     80  private BluetoothDevice bluetoothDevice;
     81  private final BroadcastReceiver bluetoothHeadsetReceiver;
     82 
     83  // Runs when the Bluetooth timeout expires. We use that timeout after calling
     84  // startScoAudio() or stopScoAudio() because we're not guaranteed to get a
     85  // callback after those calls.
     86  private final Runnable bluetoothTimeoutRunnable = new Runnable() {
     87    @Override
     88    public void run() {
     89      bluetoothTimeout();
     90    }
     91  };
     92 
     93  /**
     94   * Implementation of an interface that notifies BluetoothProfile IPC clients when they have been
     95   * connected to or disconnected from the service.
     96   */
     97  private class BluetoothServiceListener implements BluetoothProfile.ServiceListener {
     98    @Override
     99    // Called to notify the client when the proxy object has been connected to the service.
    100    // Once we have the profile proxy object, we can use it to monitor the state of the
    101    // connection and perform other operations that are relevant to the headset profile.
    102    public void onServiceConnected(int profile, BluetoothProfile proxy) {
    103      if (profile != BluetoothProfile.HEADSET || bluetoothState == State.UNINITIALIZED) {
    104        return;
    105      }
    106      Log.d(TAG, "BluetoothServiceListener.onServiceConnected: BT state=" + bluetoothState);
    107      // Android only supports one connected Bluetooth Headset at a time.
    108      bluetoothHeadset = (BluetoothHeadset) proxy;
    109      updateAudioDeviceState();
    110      Log.d(TAG, "onServiceConnected done: BT state=" + bluetoothState);
    111    }
    112 
    113    @Override
    114    // Notifies the client when the proxy object has been disconnected from the service.
    115    public void onServiceDisconnected(int profile) {
    116      if (profile != BluetoothProfile.HEADSET || bluetoothState == State.UNINITIALIZED) {
    117        return;
    118      }
    119      Log.d(TAG, "BluetoothServiceListener.onServiceDisconnected: BT state=" + bluetoothState);
    120      stopScoAudio();
    121      bluetoothHeadset = null;
    122      bluetoothDevice = null;
    123      bluetoothState = State.HEADSET_UNAVAILABLE;
    124      updateAudioDeviceState();
    125      Log.d(TAG, "onServiceDisconnected done: BT state=" + bluetoothState);
    126    }
    127  }
    128 
    129  // Intent broadcast receiver which handles changes in Bluetooth device availability.
    130  // Detects headset changes and Bluetooth SCO state changes.
    131  private class BluetoothHeadsetBroadcastReceiver extends BroadcastReceiver {
    132    @Override
    133    public void onReceive(Context context, Intent intent) {
    134      if (bluetoothState == State.UNINITIALIZED) {
    135        return;
    136      }
    137      final String action = intent.getAction();
    138      // Change in connection state of the Headset profile. Note that the
    139      // change does not tell us anything about whether we're streaming
    140      // audio to BT over SCO. Typically received when user turns on a BT
    141      // headset while audio is active using another audio device.
    142      if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
    143        final int state =
    144            intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_DISCONNECTED);
    145        Log.d(TAG, "BluetoothHeadsetBroadcastReceiver.onReceive: "
    146                + "a=ACTION_CONNECTION_STATE_CHANGED, "
    147                + "s=" + stateToString(state) + ", "
    148                + "sb=" + isInitialStickyBroadcast() + ", "
    149                + "BT state: " + bluetoothState);
    150        if (state == BluetoothHeadset.STATE_CONNECTED) {
    151          scoConnectionAttempts = 0;
    152          updateAudioDeviceState();
    153        } else if (state == BluetoothHeadset.STATE_CONNECTING) {
    154          // No action needed.
    155        } else if (state == BluetoothHeadset.STATE_DISCONNECTING) {
    156          // No action needed.
    157        } else if (state == BluetoothHeadset.STATE_DISCONNECTED) {
    158          // Bluetooth is probably powered off during the call.
    159          stopScoAudio();
    160          updateAudioDeviceState();
    161        }
    162        // Change in the audio (SCO) connection state of the Headset profile.
    163        // Typically received after call to startScoAudio() has finalized.
    164      } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
    165        final int state = intent.getIntExtra(
    166            BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
    167        Log.d(TAG, "BluetoothHeadsetBroadcastReceiver.onReceive: "
    168                + "a=ACTION_AUDIO_STATE_CHANGED, "
    169                + "s=" + stateToString(state) + ", "
    170                + "sb=" + isInitialStickyBroadcast() + ", "
    171                + "BT state: " + bluetoothState);
    172        if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
    173          cancelTimer();
    174          if (bluetoothState == State.SCO_CONNECTING) {
    175            Log.d(TAG, "+++ Bluetooth audio SCO is now connected");
    176            bluetoothState = State.SCO_CONNECTED;
    177            scoConnectionAttempts = 0;
    178            updateAudioDeviceState();
    179          } else {
    180            Log.w(TAG, "Unexpected state BluetoothHeadset.STATE_AUDIO_CONNECTED");
    181          }
    182        } else if (state == BluetoothHeadset.STATE_AUDIO_CONNECTING) {
    183          Log.d(TAG, "+++ Bluetooth audio SCO is now connecting...");
    184        } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
    185          Log.d(TAG, "+++ Bluetooth audio SCO is now disconnected");
    186          if (isInitialStickyBroadcast()) {
    187            Log.d(TAG, "Ignore STATE_AUDIO_DISCONNECTED initial sticky broadcast.");
    188            return;
    189          }
    190          updateAudioDeviceState();
    191        }
    192      }
    193      Log.d(TAG, "onReceive done: BT state=" + bluetoothState);
    194    }
    195  }
    196 
    197  /** Construction. */
    198  static AppRTCBluetoothManager create(Context context, AppRTCAudioManager audioManager) {
    199    Log.d(TAG, "create" + AppRTCUtils.getThreadInfo());
    200    return new AppRTCBluetoothManager(context, audioManager);
    201  }
    202 
    203  protected AppRTCBluetoothManager(Context context, AppRTCAudioManager audioManager) {
    204    Log.d(TAG, "ctor");
    205    ThreadUtils.checkIsOnMainThread();
    206    apprtcContext = context;
    207    apprtcAudioManager = audioManager;
    208    this.audioManager = getAudioManager(context);
    209    bluetoothState = State.UNINITIALIZED;
    210    bluetoothServiceListener = new BluetoothServiceListener();
    211    bluetoothHeadsetReceiver = new BluetoothHeadsetBroadcastReceiver();
    212    handler = new Handler(Looper.getMainLooper());
    213  }
    214 
    215  /** Returns the internal state. */
    216  public State getState() {
    217    ThreadUtils.checkIsOnMainThread();
    218    return bluetoothState;
    219  }
    220 
    221  /**
    222   * Activates components required to detect Bluetooth devices and to enable
    223   * BT SCO (audio is routed via BT SCO) for the headset profile. The end
    224   * state will be HEADSET_UNAVAILABLE but a state machine has started which
    225   * will start a state change sequence where the final outcome depends on
    226   * if/when the BT headset is enabled.
    227   * Example of state change sequence when start() is called while BT device
    228   * is connected and enabled:
    229   *   UNINITIALIZED --> HEADSET_UNAVAILABLE --> HEADSET_AVAILABLE -->
    230   *   SCO_CONNECTING --> SCO_CONNECTED <==> audio is now routed via BT SCO.
    231   * Note that the AppRTCAudioManager is also involved in driving this state
    232   * change.
    233   */
    234  public void start() {
    235    ThreadUtils.checkIsOnMainThread();
    236    Log.d(TAG, "start");
    237    if (!hasPermission(apprtcContext, android.Manifest.permission.BLUETOOTH)) {
    238      Log.w(TAG, "Process (pid=" + Process.myPid() + ") lacks BLUETOOTH permission");
    239      return;
    240    }
    241    if (bluetoothState != State.UNINITIALIZED) {
    242      Log.w(TAG, "Invalid BT state");
    243      return;
    244    }
    245    bluetoothHeadset = null;
    246    bluetoothDevice = null;
    247    scoConnectionAttempts = 0;
    248    // Get a handle to the default local Bluetooth adapter.
    249    bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    250    if (bluetoothAdapter == null) {
    251      Log.w(TAG, "Device does not support Bluetooth");
    252      return;
    253    }
    254    // Ensure that the device supports use of BT SCO audio for off call use cases.
    255    if (!audioManager.isBluetoothScoAvailableOffCall()) {
    256      Log.e(TAG, "Bluetooth SCO audio is not available off call");
    257      return;
    258    }
    259    logBluetoothAdapterInfo(bluetoothAdapter);
    260    // Establish a connection to the HEADSET profile (includes both Bluetooth Headset and
    261    // Hands-Free) proxy object and install a listener.
    262    if (!getBluetoothProfileProxy(
    263            apprtcContext, bluetoothServiceListener, BluetoothProfile.HEADSET)) {
    264      Log.e(TAG, "BluetoothAdapter.getProfileProxy(HEADSET) failed");
    265      return;
    266    }
    267    // Register receivers for BluetoothHeadset change notifications.
    268    IntentFilter bluetoothHeadsetFilter = new IntentFilter();
    269    // Register receiver for change in connection state of the Headset profile.
    270    bluetoothHeadsetFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
    271    // Register receiver for change in audio connection state of the Headset profile.
    272    bluetoothHeadsetFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
    273    registerReceiver(bluetoothHeadsetReceiver, bluetoothHeadsetFilter);
    274    Log.d(TAG, "HEADSET profile state: "
    275            + stateToString(bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET)));
    276    Log.d(TAG, "Bluetooth proxy for headset profile has started");
    277    bluetoothState = State.HEADSET_UNAVAILABLE;
    278    Log.d(TAG, "start done: BT state=" + bluetoothState);
    279  }
    280 
    281  /** Stops and closes all components related to Bluetooth audio. */
    282  public void stop() {
    283    ThreadUtils.checkIsOnMainThread();
    284    Log.d(TAG, "stop: BT state=" + bluetoothState);
    285    if (bluetoothAdapter == null) {
    286      return;
    287    }
    288    // Stop BT SCO connection with remote device if needed.
    289    stopScoAudio();
    290    // Close down remaining BT resources.
    291    if (bluetoothState == State.UNINITIALIZED) {
    292      return;
    293    }
    294    unregisterReceiver(bluetoothHeadsetReceiver);
    295    cancelTimer();
    296    if (bluetoothHeadset != null) {
    297      bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, bluetoothHeadset);
    298      bluetoothHeadset = null;
    299    }
    300    bluetoothAdapter = null;
    301    bluetoothDevice = null;
    302    bluetoothState = State.UNINITIALIZED;
    303    Log.d(TAG, "stop done: BT state=" + bluetoothState);
    304  }
    305 
    306  /**
    307   * Starts Bluetooth SCO connection with remote device.
    308   * Note that the phone application always has the priority on the usage of the SCO connection
    309   * for telephony. If this method is called while the phone is in call it will be ignored.
    310   * Similarly, if a call is received or sent while an application is using the SCO connection,
    311   * the connection will be lost for the application and NOT returned automatically when the call
    312   * ends. Also note that: up to and including API version JELLY_BEAN_MR1, this method initiates a
    313   * virtual voice call to the Bluetooth headset. After API version JELLY_BEAN_MR2 only a raw SCO
    314   * audio connection is established.
    315   * TODO(henrika): should we add support for virtual voice call to BT headset also for JBMR2 and
    316   * higher. It might be required to initiates a virtual voice call since many devices do not
    317   * accept SCO audio without a "call".
    318   */
    319  public boolean startScoAudio() {
    320    ThreadUtils.checkIsOnMainThread();
    321    Log.d(TAG, "startSco: BT state=" + bluetoothState + ", "
    322            + "attempts: " + scoConnectionAttempts + ", "
    323            + "SCO is on: " + isScoOn());
    324    if (scoConnectionAttempts >= MAX_SCO_CONNECTION_ATTEMPTS) {
    325      Log.e(TAG, "BT SCO connection fails - no more attempts");
    326      return false;
    327    }
    328    if (bluetoothState != State.HEADSET_AVAILABLE) {
    329      Log.e(TAG, "BT SCO connection fails - no headset available");
    330      return false;
    331    }
    332    // Start BT SCO channel and wait for ACTION_AUDIO_STATE_CHANGED.
    333    Log.d(TAG, "Starting Bluetooth SCO and waits for ACTION_AUDIO_STATE_CHANGED...");
    334    // The SCO connection establishment can take several seconds, hence we cannot rely on the
    335    // connection to be available when the method returns but instead register to receive the
    336    // intent ACTION_SCO_AUDIO_STATE_UPDATED and wait for the state to be SCO_AUDIO_STATE_CONNECTED.
    337    bluetoothState = State.SCO_CONNECTING;
    338    audioManager.startBluetoothSco();
    339    audioManager.setBluetoothScoOn(true);
    340    scoConnectionAttempts++;
    341    startTimer();
    342    Log.d(TAG, "startScoAudio done: BT state=" + bluetoothState + ", "
    343            + "SCO is on: " + isScoOn());
    344    return true;
    345  }
    346 
    347  /** Stops Bluetooth SCO connection with remote device. */
    348  public void stopScoAudio() {
    349    ThreadUtils.checkIsOnMainThread();
    350    Log.d(TAG, "stopScoAudio: BT state=" + bluetoothState + ", "
    351            + "SCO is on: " + isScoOn());
    352    if (bluetoothState != State.SCO_CONNECTING && bluetoothState != State.SCO_CONNECTED) {
    353      return;
    354    }
    355    cancelTimer();
    356    audioManager.stopBluetoothSco();
    357    audioManager.setBluetoothScoOn(false);
    358    bluetoothState = State.SCO_DISCONNECTING;
    359    Log.d(TAG, "stopScoAudio done: BT state=" + bluetoothState + ", "
    360            + "SCO is on: " + isScoOn());
    361  }
    362 
    363  /**
    364   * Use the BluetoothHeadset proxy object (controls the Bluetooth Headset
    365   * Service via IPC) to update the list of connected devices for the HEADSET
    366   * profile. The internal state will change to HEADSET_UNAVAILABLE or to
    367   * HEADSET_AVAILABLE and `bluetoothDevice` will be mapped to the connected
    368   * device if available.
    369   */
    370  public void updateDevice() {
    371    if (bluetoothState == State.UNINITIALIZED || bluetoothHeadset == null) {
    372      return;
    373    }
    374    Log.d(TAG, "updateDevice");
    375    // Get connected devices for the headset profile. Returns the set of
    376    // devices which are in state STATE_CONNECTED. The BluetoothDevice class
    377    // is just a thin wrapper for a Bluetooth hardware address.
    378    List<BluetoothDevice> devices = bluetoothHeadset.getConnectedDevices();
    379    if (devices.isEmpty()) {
    380      bluetoothDevice = null;
    381      bluetoothState = State.HEADSET_UNAVAILABLE;
    382      Log.d(TAG, "No connected bluetooth headset");
    383    } else {
    384      // Always use first device in list. Android only supports one device.
    385      bluetoothDevice = devices.get(0);
    386      bluetoothState = State.HEADSET_AVAILABLE;
    387      Log.d(TAG, "Connected bluetooth headset: "
    388              + "name=" + bluetoothDevice.getName() + ", "
    389              + "state=" + stateToString(bluetoothHeadset.getConnectionState(bluetoothDevice))
    390              + ", SCO audio=" + bluetoothHeadset.isAudioConnected(bluetoothDevice));
    391    }
    392    Log.d(TAG, "updateDevice done: BT state=" + bluetoothState);
    393  }
    394 
    395  /**
    396   * Stubs for test mocks.
    397   */
    398  @Nullable
    399  protected AudioManager getAudioManager(Context context) {
    400    return (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    401  }
    402 
    403  protected void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
    404    apprtcContext.registerReceiver(receiver, filter);
    405  }
    406 
    407  protected void unregisterReceiver(BroadcastReceiver receiver) {
    408    apprtcContext.unregisterReceiver(receiver);
    409  }
    410 
    411  protected boolean getBluetoothProfileProxy(
    412      Context context, BluetoothProfile.ServiceListener listener, int profile) {
    413    return bluetoothAdapter.getProfileProxy(context, listener, profile);
    414  }
    415 
    416  protected boolean hasPermission(Context context, String permission) {
    417    return apprtcContext.checkPermission(permission, Process.myPid(), Process.myUid())
    418        == PackageManager.PERMISSION_GRANTED;
    419  }
    420 
    421  /** Logs the state of the local Bluetooth adapter. */
    422  @SuppressLint("HardwareIds")
    423  protected void logBluetoothAdapterInfo(BluetoothAdapter localAdapter) {
    424    Log.d(TAG, "BluetoothAdapter: "
    425            + "enabled=" + localAdapter.isEnabled() + ", "
    426            + "state=" + stateToString(localAdapter.getState()) + ", "
    427            + "name=" + localAdapter.getName() + ", "
    428            + "address=" + localAdapter.getAddress());
    429    // Log the set of BluetoothDevice objects that are bonded (paired) to the local adapter.
    430    Set<BluetoothDevice> pairedDevices = localAdapter.getBondedDevices();
    431    if (!pairedDevices.isEmpty()) {
    432      Log.d(TAG, "paired devices:");
    433      for (BluetoothDevice device : pairedDevices) {
    434        Log.d(TAG, " name=" + device.getName() + ", address=" + device.getAddress());
    435      }
    436    }
    437  }
    438 
    439  /** Ensures that the audio manager updates its list of available audio devices. */
    440  private void updateAudioDeviceState() {
    441    ThreadUtils.checkIsOnMainThread();
    442    Log.d(TAG, "updateAudioDeviceState");
    443    apprtcAudioManager.updateAudioDeviceState();
    444  }
    445 
    446  /** Starts timer which times out after BLUETOOTH_SCO_TIMEOUT_MS milliseconds. */
    447  private void startTimer() {
    448    ThreadUtils.checkIsOnMainThread();
    449    Log.d(TAG, "startTimer");
    450    handler.postDelayed(bluetoothTimeoutRunnable, BLUETOOTH_SCO_TIMEOUT_MS);
    451  }
    452 
    453  /** Cancels any outstanding timer tasks. */
    454  private void cancelTimer() {
    455    ThreadUtils.checkIsOnMainThread();
    456    Log.d(TAG, "cancelTimer");
    457    handler.removeCallbacks(bluetoothTimeoutRunnable);
    458  }
    459 
    460  /**
    461   * Called when start of the BT SCO channel takes too long time. Usually
    462   * happens when the BT device has been turned on during an ongoing call.
    463   */
    464  private void bluetoothTimeout() {
    465    ThreadUtils.checkIsOnMainThread();
    466    if (bluetoothState == State.UNINITIALIZED || bluetoothHeadset == null) {
    467      return;
    468    }
    469    Log.d(TAG, "bluetoothTimeout: BT state=" + bluetoothState + ", "
    470            + "attempts: " + scoConnectionAttempts + ", "
    471            + "SCO is on: " + isScoOn());
    472    if (bluetoothState != State.SCO_CONNECTING) {
    473      return;
    474    }
    475    // Bluetooth SCO should be connecting; check the latest result.
    476    boolean scoConnected = false;
    477    List<BluetoothDevice> devices = bluetoothHeadset.getConnectedDevices();
    478    if (devices.size() > 0) {
    479      bluetoothDevice = devices.get(0);
    480      if (bluetoothHeadset.isAudioConnected(bluetoothDevice)) {
    481        Log.d(TAG, "SCO connected with " + bluetoothDevice.getName());
    482        scoConnected = true;
    483      } else {
    484        Log.d(TAG, "SCO is not connected with " + bluetoothDevice.getName());
    485      }
    486    }
    487    if (scoConnected) {
    488      // We thought BT had timed out, but it's actually on; updating state.
    489      bluetoothState = State.SCO_CONNECTED;
    490      scoConnectionAttempts = 0;
    491    } else {
    492      // Give up and "cancel" our request by calling stopBluetoothSco().
    493      Log.w(TAG, "BT failed to connect after timeout");
    494      stopScoAudio();
    495    }
    496    updateAudioDeviceState();
    497    Log.d(TAG, "bluetoothTimeout done: BT state=" + bluetoothState);
    498  }
    499 
    500  /** Checks whether audio uses Bluetooth SCO. */
    501  private boolean isScoOn() {
    502    return audioManager.isBluetoothScoOn();
    503  }
    504 
    505  /** Converts BluetoothAdapter states into local string representations. */
    506  private String stateToString(int state) {
    507    switch (state) {
    508      case BluetoothAdapter.STATE_DISCONNECTED:
    509        return "DISCONNECTED";
    510      case BluetoothAdapter.STATE_CONNECTED:
    511        return "CONNECTED";
    512      case BluetoothAdapter.STATE_CONNECTING:
    513        return "CONNECTING";
    514      case BluetoothAdapter.STATE_DISCONNECTING:
    515        return "DISCONNECTING";
    516      case BluetoothAdapter.STATE_OFF:
    517        return "OFF";
    518      case BluetoothAdapter.STATE_ON:
    519        return "ON";
    520      case BluetoothAdapter.STATE_TURNING_OFF:
    521        // Indicates the local Bluetooth adapter is turning off. Local clients should immediately
    522        // attempt graceful disconnection of any remote links.
    523        return "TURNING_OFF";
    524      case BluetoothAdapter.STATE_TURNING_ON:
    525        // Indicates the local Bluetooth adapter is turning on. However local clients should wait
    526        // for STATE_ON before attempting to use the adapter.
    527        return  "TURNING_ON";
    528      default:
    529        return "INVALID";
    530    }
    531  }
    532 }