Initial Contribution
diff --git a/src/com/android/settings/BluetoothSettings.java b/src/com/android/settings/BluetoothSettings.java
new file mode 100644
index 0000000..6a19ec4
--- /dev/null
+++ b/src/com/android/settings/BluetoothSettings.java
@@ -0,0 +1,1035 @@
+/*
+ * 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;
+
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothIntent;
+import android.bluetooth.DeviceClass;
+import android.bluetooth.IBluetoothDeviceCallback;
+import android.bluetooth.IBluetoothHeadsetCallback;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.preference.CheckBoxPreference;
+import android.preference.EditTextPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.provider.Settings;
+import android.text.method.PasswordTransformationMethod;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View.OnKeyListener;
+import android.widget.AdapterView;
+import android.widget.EditText;
+import android.widget.Toast;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+import java.util.HashMap;
+
+public class BluetoothSettings
+ extends PreferenceActivity
+ implements OnSharedPreferenceChangeListener, OnKeyListener,
+ View.OnCreateContextMenuListener {
+
+ private static final String TAG = "BluetoothSettings";
+
+ private static final int MENU_SCAN_ID = Menu.FIRST;
+ private static final int MENU_CLEAR_ID = Menu.FIRST + 1;
+
+ private static final int MENU_CONNECT = ContextMenu.FIRST;
+ private static final int MENU_DISCONNECT = ContextMenu.FIRST + 1;
+ private static final int MENU_PAIR = ContextMenu.FIRST + 2;
+ private static final int MENU_UNPAIR = ContextMenu.FIRST + 3;
+
+ private static final String BT_ENABLE = "bt_checkbox";
+ private static final String BT_VISIBILITY = "bt_visibility";
+ private static final String BT_NAME = "bt_name";
+
+ private static final String BT_KEY_PREFIX = "bt_dev_";
+ private static final int BT_KEY_LENGTH = BT_KEY_PREFIX.length();
+ private static final String FREEZE_ADDRESSES = "addresses";
+ private static final String FREEZE_TYPES = "types";
+ private static final String FREEZE_PIN = "pinText";
+ private static final String FREEZE_PIN_ADDRESS = "pinAddress";
+ private static final String FREEZE_RSSI = "rssi";
+ private static final String FREEZE_DISCOVERABLE_START = "dstart";
+
+ private static final int HANDLE_FAILED_TO_CONNECT = 1;
+ private static final int HANDLE_CONNECTING = 2;
+ private static final int HANDLE_CONNECTED = 3;
+ private static final int HANDLE_DISCONNECTED = 4;
+ private static final int HANDLE_PIN_REQUEST = 5;
+ private static final int HANDLE_DISCOVERABLE_TIMEOUT = 6;
+ private static final int HANDLE_INITIAL_SCAN = 7;
+ private static final int HANDLE_PAIRING_FAILED = 8;
+ private static final int HANDLE_PAIRING_PASSED = 9;
+ private static final int HANDLE_PAUSE_TIMEOUT = 10;
+
+
+
+ private static String STR_CONNECTED;
+ private static String STR_PAIRED;
+ private static String STR_PAIRED_NOT_NEARBY;
+ private static String STR_NOT_CONNECTED;
+ private static String STR_CONNECTING;
+ private static String STR_PAIRING;
+
+ private static final int WEIGHT_CONNECTED = 1;
+ private static final int WEIGHT_PAIRED = 0;
+ private static final int WEIGHT_UNKNOWN = -1;
+
+ private CheckBoxPreference mBTToggle;
+ private CheckBoxPreference mBTVisibility;
+ private EditTextPreference mBTName;
+ private ProgressCategory mBTDeviceList;
+ private AlertDialog mPinDialog;
+ private String mPinAddress;
+ private EditText mPinEdit;
+ private View mPinButton1;
+ private String mDisconnectAddress;
+
+ private BluetoothDevice mBluetooth;
+ private BluetoothHeadset mBluetoothHeadset;
+ private boolean mIsEnabled;
+ private String mLastConnected;
+ private static boolean sIsRunning;
+ private static DeviceCallback sDeviceCallback;
+ private IntentFilter mIntentFilter;
+ private Resources mRes;
+ private long mDiscoverableStartTime;
+ private int mDiscoverableTime;
+ private static final String DISCOVERABLE_TIME = "debug.bt.discoverable_time";
+ private static final int DISCOVERABLE_TIME_DEFAULT = 120;
+ private boolean mAutoDiscovery;
+ // After a few seconds after a pause, if the user doesn't restart the
+ // BT settings, then we need to cleanup a few things in the message handler
+ private static final int PAUSE_TIMEOUT = 3000;
+
+ private boolean mStartScan;
+ private static final String AUTO_DISCOVERY = "debug.bt.auto_discovery";
+ private HashMap<String,Preference> mDeviceMap;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ addPreferencesFromResource(R.xml.bluetooth_settings);
+
+ // Deal with restarted activities by passing a static callback object to
+ // the Bluetooth service
+ if (sDeviceCallback == null) {
+ sDeviceCallback = new DeviceCallback();
+ }
+ sDeviceCallback.setHandler(mHandler);
+
+ mDiscoverableTime = SystemProperties.getInt(DISCOVERABLE_TIME, -1);
+ if (mDiscoverableTime <= 0) {
+ mDiscoverableTime= DISCOVERABLE_TIME_DEFAULT;
+ }
+ mAutoDiscovery = SystemProperties.getBoolean(AUTO_DISCOVERY, true);
+
+ if (!initBluetoothAPI()) {
+ finish();
+ return;
+ }
+ initUI();
+ getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
+
+ mDeviceMap = new HashMap<String,Preference>();
+ if (icicle != null && icicle.containsKey(FREEZE_ADDRESSES)) {
+ addDevices(icicle.getStringArray(FREEZE_ADDRESSES),
+ icicle.getStringArray(FREEZE_TYPES),
+ icicle.getIntArray(FREEZE_RSSI));
+ if (icicle.containsKey(FREEZE_PIN)) {
+ String savedPin = icicle.getString(FREEZE_PIN);
+ String pinAddress = icicle.getString(FREEZE_PIN_ADDRESS);
+ mPinDialog = showPinDialog(savedPin, pinAddress);
+ }
+ mDiscoverableStartTime = icicle.getLong(FREEZE_DISCOVERABLE_START);
+ } else {
+ mStartScan = true;
+ }
+ }
+
+ private void initUI() {
+ mBTToggle = (CheckBoxPreference) findPreference(BT_ENABLE);
+ mBTVisibility = (CheckBoxPreference) findPreference(BT_VISIBILITY);
+ mBTName = (EditTextPreference) findPreference(BT_NAME);
+ mBTDeviceList = (ProgressCategory) findPreference("bt_device_list");
+ mBTDeviceList.setOrderingAsAdded(false);
+ mRes = getResources();
+ if (mIsEnabled) {
+ String name = mBluetooth.getName();
+ if (name != null) {
+ mBTName.setSummary(name);
+ }
+ }
+ mBTVisibility.setEnabled(mIsEnabled);
+ mBTName.setEnabled(mIsEnabled);
+ STR_CONNECTED = mRes.getString(R.string.bluetooth_connected);
+ STR_PAIRED = mRes.getString(R.string.bluetooth_paired);
+ STR_PAIRED_NOT_NEARBY =
+ mRes.getString(R.string.bluetooth_paired_not_nearby);
+ STR_CONNECTING = mRes.getString(R.string.bluetooth_connecting);
+ STR_PAIRING = mRes.getString(R.string.bluetooth_pairing);
+ STR_NOT_CONNECTED = mRes.getString(R.string.bluetooth_not_connected);
+ getListView().setOnCreateContextMenuListener(this);
+ }
+
+ private boolean initBluetoothAPI() {
+ mIntentFilter =
+ new IntentFilter(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION);
+ mIntentFilter.addAction(BluetoothIntent.REMOTE_DEVICE_DISCONNECTED_ACTION);
+ mIntentFilter.addAction(BluetoothIntent.BONDING_CREATED_ACTION);
+ mIntentFilter.addAction(BluetoothIntent.ENABLED_ACTION);
+ mIntentFilter.addAction(BluetoothIntent.DISABLED_ACTION);
+ mIntentFilter.addAction(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION);
+ mIntentFilter.addAction(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION);
+ mIntentFilter.addAction(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION);
+ mIntentFilter.addAction(BluetoothIntent.PAIRING_REQUEST_ACTION);
+ mIntentFilter.addAction(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION);
+ mIntentFilter.addAction(BluetoothIntent.DISCOVERY_COMPLETED_ACTION);
+ mIntentFilter.addAction(BluetoothIntent.DISCOVERY_STARTED_ACTION);
+ mIntentFilter.addAction(BluetoothIntent.MODE_CHANGED_ACTION);
+
+ mBluetooth = (BluetoothDevice)getSystemService(BLUETOOTH_SERVICE);
+ mBluetoothHeadset = new BluetoothHeadset(this);
+ if (mBluetooth == null) { // If the environment doesn't support BT
+ return false;
+ }
+ mIsEnabled = mBluetooth.isEnabled();
+ return true;
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ sIsRunning = true;
+ mHandler.removeMessages(HANDLE_PAUSE_TIMEOUT);
+ registerReceiver(mReceiver, mIntentFilter);
+
+ mIsEnabled = mBluetooth.isEnabled();
+ updateStatus();
+ final boolean discoverable = mBluetooth.getMode() ==
+ BluetoothDevice.MODE_DISCOVERABLE;
+ mBTDeviceList.setProgress(mIsEnabled && mBluetooth.isDiscovering());
+ mBTVisibility.setChecked(mIsEnabled && discoverable);
+
+ if (discoverable) {
+ mHandler.sendMessage(
+ mHandler.obtainMessage(HANDLE_DISCOVERABLE_TIMEOUT));
+ }
+
+ if (mIsEnabled && mStartScan) {
+ // First attempt after 100ms
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(HANDLE_INITIAL_SCAN, 1), 100);
+ }
+ mStartScan = false;
+
+ // Check if headset status changed since we paused
+ String connected = mBluetoothHeadset.getHeadsetAddress();
+ if (connected != null) {
+ updateRemoteDeviceStatus(connected);
+ }
+ if (mLastConnected != null) {
+ updateRemoteDeviceStatus(mLastConnected);
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ sIsRunning = false;
+
+ unregisterReceiver(mReceiver);
+
+ // Wait for a few seconds and cleanup any pending requests, states
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(HANDLE_PAUSE_TIMEOUT,
+ new Object[] { mBluetooth, mPinAddress }),
+ PAUSE_TIMEOUT);
+ super.onPause();
+ }
+
+ @Override
+ protected void onDestroy() {
+ mBluetoothHeadset.close();
+ sDeviceCallback.setHandler(null);
+
+ super.onDestroy();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration c) {
+ super.onConfigurationChanged(c);
+ // Don't do anything on keyboardHidden/orientation change, as we need
+ // to make sure that we don't lose pairing request intents.
+ }
+
+ public static boolean isRunning() {
+ return sIsRunning;
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle icicle) {
+ int deviceCount = mBTDeviceList.getPreferenceCount();
+ String [] addresses = new String[deviceCount];
+ String [] states = new String[deviceCount];
+ int [] weights = new int[deviceCount];
+ for (int i = 0; i < deviceCount; i++) {
+ BluetoothListItem p = (BluetoothListItem) mBTDeviceList.getPreference(i);
+ CharSequence summary = p.getSummary();
+ if (summary != null) {
+ states[i] = summary.toString();
+ } else {
+ states[i] = STR_NOT_CONNECTED;
+ }
+ addresses[i] = getAddressFromKey(p.getKey());
+ weights[i] = p.getWeight();
+ }
+ icicle.putStringArray(FREEZE_ADDRESSES, addresses);
+ icicle.putStringArray(FREEZE_TYPES, states);
+ icicle.putIntArray(FREEZE_RSSI, weights);
+ icicle.putLong(FREEZE_DISCOVERABLE_START, mDiscoverableStartTime);
+ if (mPinDialog != null && mPinDialog.isShowing()) {
+ icicle.putString(FREEZE_PIN, mPinEdit.getText().toString());
+ icicle.putString(FREEZE_PIN_ADDRESS, mPinAddress);
+ }
+ super.onSaveInstanceState(icicle);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ menu.add(0, MENU_SCAN_ID, 0,
+ mRes.getString(R.string.bluetooth_scan_for_devices))
+ .setIcon(R.drawable.ic_menu_scan_bluetooth);
+ menu.add(0, MENU_CLEAR_ID, 0,
+ mRes.getString(R.string.bluetooth_clear_list))
+ .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_SCAN_ID:
+ startScanning();
+ return true;
+ case MENU_CLEAR_ID:
+ clearDevices();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
+ if (!(menuInfo instanceof AdapterContextMenuInfo)) {
+ return;
+ }
+ int position = ((AdapterContextMenuInfo)menuInfo).position;
+ Preference pref = (Preference) getPreferenceScreen().getRootAdapter().getItem(position);
+ if (!(pref instanceof BluetoothListItem)) {
+ return;
+ }
+ String address = getAddressFromKey(pref.getKey());
+ // Setup the menu header
+ String name = mBluetooth.getRemoteName(address);
+ menu.setHeaderTitle(name != null? name : address);
+ int n = 0;
+ if (mBluetoothHeadset.isConnected(address)) {
+ menu.add(0, MENU_DISCONNECT, n++, R.string.bluetooth_disconnect);
+ } else {
+ menu.add(0, MENU_CONNECT, n++, R.string.bluetooth_connect);
+ }
+ if (mBluetooth.hasBonding(address)) {
+ menu.add(0, MENU_UNPAIR, n++, R.string.bluetooth_unpair);
+ } else {
+ menu.add(0, MENU_PAIR, n++, R.string.bluetooth_pair);
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ AdapterView.AdapterContextMenuInfo info;
+ if (!(item.getMenuInfo() instanceof AdapterContextMenuInfo)) {
+ return false;
+ }
+ info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
+ Preference pref = (Preference) getPreferenceScreen().getRootAdapter().
+ getItem(info.position);
+ String address = getAddressFromKey(pref.getKey());
+ mBluetooth.cancelDiscovery();
+ switch (item.getItemId()) {
+ case MENU_DISCONNECT:
+ if (mBluetoothHeadset.isConnected(address)) {
+ mBluetoothHeadset.disconnectHeadset();
+ }
+ break;
+ case MENU_CONNECT:
+ if (!mBluetoothHeadset.isConnected(address)) {
+ updateRemoteDeviceStatus(address, STR_CONNECTING);
+ connect(pref, address);
+ }
+ break;
+ case MENU_UNPAIR:
+ if (mBluetooth.hasBonding(address)) {
+ mBluetooth.removeBonding(address);
+ updateRemoteDeviceStatus(address);
+ }
+ break;
+ case MENU_PAIR:
+ if (!mBluetooth.hasBonding(address)) {
+ pair(pref, address);
+ }
+ break;
+ }
+ return true;
+ }
+
+ private void startScanning() {
+ if (mIsEnabled && mBluetooth.isDiscovering()) {
+ return;
+ }
+ resetDeviceListUI();
+ if (mIsEnabled) {
+ mBluetooth.startDiscovery();
+ }
+ }
+
+ private void clearDevices() {
+ String [] addresses = mBluetooth.listBondings();
+ if (addresses != null) {
+ for (int i = 0; i < addresses.length; i++) {
+ unbond(addresses[i]);
+ }
+ }
+ resetDeviceListUI();
+ }
+
+ /* Update the Bluetooth toggle and visibility summary */
+ private void updateStatus() {
+ boolean started = mIsEnabled;
+ mBTToggle.setChecked(started);
+ }
+
+ private void updateRemoteDeviceStatus(String address) {
+ if (address != null) {
+ Preference device = mDeviceMap.get(address);
+ if (device == null) {
+ // This device is not in our discovered list
+ // Let's add the device, if BT is not shut down already
+ if (mIsEnabled) {
+ addDeviceToUI(address, null, null, WEIGHT_PAIRED);
+ }
+ return;
+ }
+ device.setEnabled(true);
+ if (address.equals(mBluetoothHeadset.getHeadsetAddress())) {
+ int state = mBluetoothHeadset.getState();
+ switch (state) {
+ case BluetoothHeadset.STATE_CONNECTED:
+ device.setSummary(STR_CONNECTED);
+ mLastConnected = address;
+ break;
+ case BluetoothHeadset.STATE_CONNECTING:
+ device.setSummary(STR_CONNECTING);
+ break;
+ case BluetoothHeadset.STATE_DISCONNECTED:
+ if (mBluetooth.hasBonding(address)) {
+ device.setSummary(STR_PAIRED);
+ }
+ break;
+ }
+ } else if (mBluetooth.hasBonding(address)) {
+ device.setSummary(STR_PAIRED);
+ } else {
+ device.setSummary(STR_NOT_CONNECTED);
+ }
+ }
+ }
+
+ private void updateRemoteDeviceStatus(String address, String summary) {
+ Preference device = mDeviceMap.get(address);
+ if (device != null) {
+ device.setEnabled(true);
+ device.setSummary(summary);
+ }
+ }
+
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (key.equals(BT_NAME)) {
+ String name = sharedPreferences.getString(key, null);
+ if (name == null) {
+ return;
+ }
+ if (mBluetooth.setName(name)) {
+ mBTName.setSummary(name);
+ }
+ }
+ }
+
+ private String getAddressFromKey(String key) {
+ if (key != null) {
+ return key.substring(BT_KEY_LENGTH);
+ }
+ return "";
+ }
+
+ private void sendPin(String pin) {
+ byte[] pinBytes = BluetoothDevice.convertPinToBytes(pin);
+ if (pinBytes == null) {
+ mBluetooth.cancelPin(mPinAddress);
+ } else {
+ mBluetooth.setPin(mPinAddress, pinBytes);
+ }
+ mPinAddress = null;
+ }
+
+ private AlertDialog showPinDialog(String savedPin, String pinAddress) {
+ if (mPinDialog != null) {
+ return mPinDialog;
+ }
+ View view = LayoutInflater.from(this).inflate(
+ R.layout.bluetooth_pin_entry, null);
+ mPinEdit = (EditText) view.findViewById(R.id.text);
+ mPinEdit.setTransformationMethod(PasswordTransformationMethod.getInstance());
+ mPinEdit.setOnKeyListener(this);
+ mPinAddress = pinAddress;
+
+ if (savedPin != null) {
+ mPinEdit.setText(savedPin);
+ }
+
+ String remoteName = mBluetooth.getRemoteName(mPinAddress);
+ if (remoteName == null) {
+ remoteName = mPinAddress;
+ }
+
+ AlertDialog ad = new AlertDialog.Builder(this)
+ .setTitle(getString(R.string.bluetooth_notif_title))
+ .setMessage(getString(R.string.bluetooth_enter_pin_msg) + remoteName)
+ .setView(view)
+ .setPositiveButton(android.R.string.ok, mDisconnectListener)
+ .setNegativeButton(android.R.string.cancel, mDisconnectListener)
+ .setOnCancelListener(mCancelListener)
+ .show();
+ ad.setCanceledOnTouchOutside(false);
+ // Making an assumption here that the dialog buttons have the ids starting
+ // with ...button1 as below
+ mPinButton1 = ad.findViewById(com.android.internal.R.id.button1);
+ if (mPinButton1 != null) {
+ mPinButton1.setEnabled(savedPin != null? savedPin.length() > 0 : false);
+ }
+ return ad;
+ }
+
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+ if (preference == mBTToggle) {
+ toggleBT();
+ return false;
+ } else if (preference == mBTVisibility) {
+ boolean vis = mBTVisibility.isChecked();
+ if (!vis) {
+ // Cancel discoverability
+ mBluetooth.setMode(BluetoothDevice.MODE_CONNECTABLE);
+ mHandler.removeMessages(HANDLE_DISCOVERABLE_TIMEOUT);
+ } else {
+ mBluetooth.setMode(BluetoothDevice.MODE_DISCOVERABLE);
+ mBTVisibility.setSummaryOn(
+ getResources().getString(R.string.bluetooth_is_discoverable,
+ String.valueOf(mDiscoverableTime)));
+ mDiscoverableStartTime = SystemClock.elapsedRealtime();
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(HANDLE_DISCOVERABLE_TIMEOUT), 1000);
+ }
+ } else {
+ String key = preference.getKey();
+ if (key.startsWith(BT_KEY_PREFIX)) {
+ // Extract the device address from the key
+ String address = getAddressFromKey(key);
+ if (mBluetoothHeadset.isConnected(address)) {
+ askDisconnect(address);
+ } else if (mBluetooth.hasBonding(address)) {
+ if (mIsEnabled) {
+ mBluetooth.cancelDiscovery();
+ }
+ updateRemoteDeviceStatus(address, STR_CONNECTING);
+ connect(preference, address);
+ } else {
+ if (mIsEnabled) {
+ mBluetooth.cancelDiscovery();
+ }
+ pair(preference, address);
+ }
+ }
+ }
+ return false;
+ }
+
+ /* Handle the key input to the PIN entry dialog */
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+
+ if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
+ || keyCode == KeyEvent.KEYCODE_ENTER) {
+ String pin = ((EditText)v).getText().toString();
+ if (pin != null && pin.length() > 0) {
+ sendPin(pin);
+ mPinDialog.dismiss();
+ return true;
+ }
+ } else if (mPinButton1 != null) {
+ boolean valid =
+ BluetoothDevice.convertPinToBytes(((EditText)v).getText().toString()) != null;
+ mPinButton1.setEnabled(valid);
+ }
+ return false;
+ }
+
+ private void askDisconnect(String address) {
+ String name = mBluetooth.getRemoteName(address);
+ if (name == null) {
+ name = mRes.getString(R.string.bluetooth_device);
+ }
+ String message = mRes.getString(R.string.bluetooth_disconnect_blank, name);
+
+ mDisconnectAddress = address;
+
+ AlertDialog ad = new AlertDialog.Builder(this)
+ .setTitle(message)
+ .setPositiveButton(android.R.string.ok, mDisconnectListener)
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ ad.setCanceledOnTouchOutside(false);
+
+ }
+
+ private void pairingDone(String address, boolean result) {
+ Preference pref = mDeviceMap.get(address);
+ if (pref != null) {
+ pref.setEnabled(true);
+ updateRemoteDeviceStatus(address);
+ } else if (result) {
+ // We've paired to a device that isn't in our list
+ addDeviceToUI(address, STR_PAIRED, mBluetooth.getRemoteName(address),
+ WEIGHT_PAIRED);
+ }
+ }
+
+ private void pair(Preference pref, String address) {
+ pref.setEnabled(false);
+ pref.setSummary(STR_PAIRING);
+ mBluetooth.createBonding(address, sDeviceCallback);
+ }
+
+ private void connect(Preference pref, String address) {
+ pref.setEnabled(false);
+ //TODO: Prompt the user to confirm they will disconnect current headset
+ disconnect();
+ mBluetoothHeadset.connectHeadset(address, mHeadsetCallback);
+ }
+
+ private void disconnect() {
+ int state = mBluetoothHeadset.getState();
+ if (state == BluetoothHeadset.STATE_CONNECTING ||
+ state == BluetoothHeadset.STATE_CONNECTED) {
+ mBluetoothHeadset.disconnectHeadset();
+ }
+ }
+
+ private void toggleBT() {
+ if (mIsEnabled) {
+ mBTToggle.setSummaryOn(mRes.getString(R.string.bluetooth_stopping));
+ mBTDeviceList.setProgress(false);
+ // Force shutdown.
+ mBluetooth.cancelDiscovery();
+ mBluetooth.disable();
+ } else {
+ mBTToggle.setSummaryOff(mRes.getString(R.string.bluetooth_enabling));
+ mBTToggle.setChecked(false);
+ mBTToggle.setEnabled(false);
+ if (!mBluetooth.enable()) {
+ mBTToggle.setEnabled(true);
+ }
+ }
+ }
+
+ private void addDeviceToUI(String address, String summary, String name,
+ int rssi) {
+
+ if (address == null) {
+ return;
+ }
+
+ BluetoothListItem p;
+ if (mDeviceMap.containsKey(address)) {
+ p = (BluetoothListItem) mDeviceMap.get(address);
+ if (summary != null && summary.equals(STR_NOT_CONNECTED)) {
+ if (mBluetooth.hasBonding(address)) {
+ summary = STR_PAIRED;
+ }
+ }
+ CharSequence oldSummary = p.getSummary();
+ if (oldSummary != null && oldSummary.equals(STR_CONNECTED)) {
+ summary = STR_CONNECTED; // Don't override connected with paired
+ mLastConnected = address;
+ }
+ } else {
+ p = new BluetoothListItem(this, null);
+ }
+ if (name == null) {
+ name = mBluetooth.getRemoteName(address);
+ }
+ if (name == null) {
+ name = address;
+ }
+
+ p.setTitle(name);
+ p.setSummary(summary);
+ p.setKey(BT_KEY_PREFIX + address);
+ // Enable the headset icon if it is most probably a headset class device
+ if (DeviceClass.getMajorClass(mBluetooth.getRemoteClass(address)) ==
+ DeviceClass.MAJOR_CLASS_AUDIO_VIDEO) {
+ p.setHeadset(true);
+ }
+ p.setWeight(rssi);
+ if (!mDeviceMap.containsKey(address)) {
+ mBTDeviceList.addPreference(p);
+ mDeviceMap.put(address, p);
+ }
+ }
+
+ private void addDevices(String [] addresses,
+ String[] deviceStatus, int[] rssi) {
+ for (int i = 0; i < addresses.length; i++) {
+ String status = deviceStatus[i];
+ String name = mBluetooth.getRemoteName(addresses[i]);
+ String address = addresses[i];
+ // Query the status if it's not known
+ if (status == null) {
+ if (mBluetoothHeadset.isConnected(addresses[i])) {
+ status = STR_CONNECTED;
+ mLastConnected = address;
+ } else if (mBluetooth.hasBonding(addresses[i])) {
+ status = STR_PAIRED;
+ } else {
+ status = STR_NOT_CONNECTED;
+ }
+ }
+ addDeviceToUI(address, status, name, rssi[i]);
+ }
+ }
+
+ private void removeDeviceFromUI(String address) {
+ Preference p = mDeviceMap.get(address);
+ if (p == null) {
+ return;
+ }
+ mBTDeviceList.removePreference(p);
+ mDeviceMap.remove(address);
+ }
+
+ private void updateDeviceName(String address, String name) {
+ Preference p = mDeviceMap.get(address);
+ if (p != null) {
+ p.setTitle(name);
+ }
+ }
+
+ private void resetDeviceListUI() {
+ mDeviceMap.clear();
+
+ while (mBTDeviceList.getPreferenceCount() > 0) {
+ mBTDeviceList.removePreference(mBTDeviceList.getPreference(0));
+ }
+ if (!mIsEnabled) {
+ return;
+ }
+
+ String connectedDevice = mBluetoothHeadset.getHeadsetAddress();
+ if (connectedDevice != null && mBluetoothHeadset.isConnected(connectedDevice)) {
+ addDeviceToUI(connectedDevice, STR_CONNECTED,
+ mBluetooth.getRemoteName(connectedDevice), WEIGHT_CONNECTED);
+ }
+ String [] bondedDevices = mBluetooth.listBondings();
+ if (bondedDevices != null) {
+ for (int i = 0; i < bondedDevices.length; i++) {
+ addDeviceToUI(bondedDevices[i], STR_PAIRED_NOT_NEARBY,
+ mBluetooth.getRemoteName(bondedDevices[i]), WEIGHT_PAIRED);
+ }
+ }
+ }
+
+ private void unbond(String address) {
+ mBluetooth.removeBonding(address);
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ String address = intent.getStringExtra(BluetoothIntent.ADDRESS);
+ if (action.equals(BluetoothIntent.ENABLED_ACTION)) {
+ mIsEnabled = true;
+ mBTToggle.setChecked(true);
+ mBTToggle.setSummaryOn(mRes.getString(R.string.bluetooth_enabled));
+ mBTToggle.setEnabled(true);
+ String name = mBluetooth.getName();
+ if (name != null) {
+ mBTName.setSummary(name);
+ }
+ // save the "enabled" setting to database, so we can
+ // remember it on startup.
+ Settings.System.putInt(getContentResolver(),
+ Settings.System.BLUETOOTH_ON, 1);
+ resetDeviceListUI();
+ if (mAutoDiscovery) {
+ mBluetooth.startDiscovery();
+ }
+ } else if (action.equals(BluetoothIntent.DISABLED_ACTION)) {
+ mIsEnabled = false;
+ mBTToggle.setSummaryOff(mRes.getString(R.string.bluetooth_disabled));
+ resetDeviceListUI();
+ mBTVisibility.setChecked(false);
+ // save the "disabled" setting to database
+ Settings.System.putInt(getContentResolver(),
+ Settings.System.BLUETOOTH_ON, 0);
+ } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION)) {
+ if (address != null) {
+ int rssi = intent.getShortExtra(BluetoothIntent.RSSI,
+ (short) WEIGHT_UNKNOWN);
+ addDeviceToUI(address, STR_NOT_CONNECTED, null, rssi);
+ }
+ } else if (action.equals(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION)) {
+ String name = intent.getStringExtra(BluetoothIntent.NAME);
+ updateDeviceName(address, name);
+ } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION)) {
+ removeDeviceFromUI(address);
+ } else if (action.equals(BluetoothIntent.PAIRING_REQUEST_ACTION)) {
+ mHandler.sendMessage(mHandler.obtainMessage(HANDLE_PIN_REQUEST, address));
+ } else if (action.equals(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION)) {
+ int state = intent.getIntExtra(BluetoothIntent.HEADSET_STATE,
+ BluetoothHeadset.STATE_ERROR);
+ if (state == BluetoothHeadset.STATE_CONNECTED) {
+ mHandler.sendMessage(mHandler.obtainMessage(HANDLE_CONNECTED, address));
+ } else if (state == BluetoothHeadset.STATE_DISCONNECTED) {
+ mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISCONNECTED, address));
+ } else if (state == BluetoothHeadset.STATE_CONNECTING) {
+ mHandler.sendMessage(mHandler.obtainMessage(HANDLE_CONNECTING, address));
+ }
+ } else if (action.equals(BluetoothIntent.DISCOVERY_STARTED_ACTION)) {
+ mBTDeviceList.setProgress(true);
+ } else if (action.equals(BluetoothIntent.DISCOVERY_COMPLETED_ACTION)) {
+ mBTDeviceList.setProgress(false);
+ } else if (action.equals(BluetoothIntent.MODE_CHANGED_ACTION)) {
+ mBTVisibility.setChecked(
+ mBluetooth.getMode() == BluetoothDevice.MODE_DISCOVERABLE);
+ } else if (action.equals(BluetoothIntent.BONDING_CREATED_ACTION)) {
+ mHandler.sendMessage(mHandler.obtainMessage(HANDLE_PAIRING_PASSED, address));
+ } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION)) {
+ mHandler.sendMessage(mHandler.obtainMessage(HANDLE_CONNECTED, address));
+ } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_DISCONNECTED_ACTION)) {
+ mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISCONNECTED, address));
+ }
+ }
+ };
+
+
+ static class DeviceCallback extends IBluetoothDeviceCallback.Stub {
+ Handler messageHandler;
+
+ public void setHandler(Handler handler) {
+ synchronized (this) {
+ messageHandler = handler;
+ }
+ }
+
+ public void onCreateBondingResult(String address, int result) {
+ synchronized (this) {
+ if (messageHandler != null) {
+ if (result == BluetoothDevice.RESULT_FAILURE) {
+ messageHandler.sendMessage(messageHandler.obtainMessage(
+ HANDLE_PAIRING_FAILED, address));
+ } else {
+ messageHandler.sendMessage(messageHandler.obtainMessage(
+ HANDLE_PAIRING_PASSED, address));
+ }
+ }
+ }
+ }
+
+ public void onEnableResult(int result) { }
+ public void onGetRemoteServiceChannelResult(String address, int channel) { }
+ };
+
+ private IBluetoothHeadsetCallback mHeadsetCallback = new IBluetoothHeadsetCallback.Stub() {
+ public void onConnectHeadsetResult(String address, int resultCode) {
+ if (resultCode == BluetoothHeadset.RESULT_SUCCESS) {
+ mHandler.sendMessage(mHandler.obtainMessage(HANDLE_CONNECTED, address));
+ } else {
+ // Make toast in UI thread
+ mHandler.sendMessage(mHandler.obtainMessage(HANDLE_FAILED_TO_CONNECT, resultCode,
+ -1, address));
+ }
+ }
+ };
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case HANDLE_CONNECTED:
+ case HANDLE_DISCONNECTED:
+ case HANDLE_CONNECTING:
+ updateRemoteDeviceStatus((String) msg.obj);
+ break;
+ case HANDLE_FAILED_TO_CONNECT:
+ updateRemoteDeviceStatus((String) msg.obj);
+ String name = mBluetooth.getRemoteName((String) msg.obj);
+ if (name == null) {
+ name = (String) msg.obj;
+ }
+ if (msg.arg1 == BluetoothHeadset.RESULT_FAILURE) {
+ Toast.makeText(BluetoothSettings.this,
+ mRes.getString(R.string.failed_to_connect, name),
+ Toast.LENGTH_SHORT).show();
+ }
+ break;
+ case HANDLE_PIN_REQUEST:
+ mPinDialog = showPinDialog(null, (String) msg.obj);
+ break;
+ case HANDLE_DISCOVERABLE_TIMEOUT:
+ long nowTime = SystemClock.elapsedRealtime();
+ int secondsLeft = mDiscoverableTime
+ - (int) (nowTime - mDiscoverableStartTime) / 1000;
+ if (secondsLeft > 0) {
+ mBTVisibility.setSummaryOn(
+ getResources().getString(R.string.bluetooth_is_discoverable,
+ String.valueOf(secondsLeft)));
+ sendMessageDelayed(obtainMessage(HANDLE_DISCOVERABLE_TIMEOUT), 1000);
+ } else {
+ mBluetooth.setMode(BluetoothDevice.MODE_CONNECTABLE);
+ mBTVisibility.setChecked(false);
+ }
+ break;
+ case HANDLE_INITIAL_SCAN:
+ if (mBluetoothHeadset.getState() == BluetoothHeadset.STATE_ERROR &&
+ ((Integer)msg.obj).intValue() < 2) {
+ // Second attempt after another 100ms
+ sendMessageDelayed(obtainMessage(HANDLE_INITIAL_SCAN, 2), 100);
+ } else {
+ resetDeviceListUI();
+ if (mAutoDiscovery) {
+ mBluetooth.cancelDiscovery();
+ mBluetooth.startDiscovery();
+ }
+ }
+ break;
+ case HANDLE_PAIRING_PASSED:
+ String addr = (String) msg.obj;
+ pairingDone(addr, true);
+ break;
+ case HANDLE_PAIRING_FAILED:
+ String address = (String) msg.obj;
+ pairingDone(address, false);
+ String pairName = mBluetooth.getRemoteName(address);
+ if (pairName == null) {
+ pairName = address;
+ }
+ Toast.makeText(BluetoothSettings.this,
+ mRes.getString(R.string.failed_to_pair, pairName),
+ Toast.LENGTH_SHORT).show();
+ break;
+ case HANDLE_PAUSE_TIMEOUT:
+ // Possibility of race condition, but not really harmful
+ if (!sIsRunning) {
+ Object[] params = (Object[]) msg.obj;
+ BluetoothDevice bluetooth = (BluetoothDevice) params[0];
+ if (bluetooth.isEnabled()) {
+ if (bluetooth.isDiscovering()) {
+ bluetooth.cancelDiscovery();
+ }
+ if (params[1] != null) {
+ bluetooth.cancelBondingProcess((String) params[1]);
+ }
+ bluetooth.setMode(BluetoothDevice.MODE_CONNECTABLE);
+ }
+ }
+ break;
+ }
+ }
+ };
+
+ private DialogInterface.OnClickListener mDisconnectListener =
+ new DialogInterface.OnClickListener() {
+
+ public void onClick(DialogInterface dialog, int which) {
+ if (dialog == mPinDialog) {
+ if (which == DialogInterface.BUTTON1) {
+ String pin = mPinEdit.getText().toString();
+ if (pin != null && pin.length() > 0) {
+ sendPin(pin);
+ } else {
+ sendPin(null);
+ }
+ } else {
+ sendPin(null);
+ }
+ mPinDialog = null;
+ mPinEdit = null;
+ } else {
+ if (which == DialogInterface.BUTTON1) {
+ disconnect();
+ }
+ }
+ }
+ };
+
+ private DialogInterface.OnCancelListener mCancelListener =
+ new DialogInterface.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ if (dialog == mPinDialog) {
+ sendPin(null);
+ }
+ mPinDialog = null;
+ mPinEdit = null;
+ }
+ };
+}
+