summaryrefslogtreecommitdiffstats
path: root/src/com/android/settings/bluetooth
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/settings/bluetooth')
-rw-r--r--src/com/android/settings/bluetooth/BluetoothDevicePreference.java122
-rw-r--r--src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java198
-rw-r--r--src/com/android/settings/bluetooth/BluetoothEnabler.java149
-rw-r--r--src/com/android/settings/bluetooth/BluetoothEventRedirector.java147
-rw-r--r--src/com/android/settings/bluetooth/BluetoothNamePreference.java79
-rw-r--r--src/com/android/settings/bluetooth/BluetoothPinDialog.java202
-rw-r--r--src/com/android/settings/bluetooth/BluetoothPinRequest.java96
-rw-r--r--src/com/android/settings/bluetooth/BluetoothSettings.java260
-rw-r--r--src/com/android/settings/bluetooth/ConnectSpecificProfilesActivity.java298
-rw-r--r--src/com/android/settings/bluetooth/LocalBluetoothDevice.java576
-rw-r--r--src/com/android/settings/bluetooth/LocalBluetoothDeviceManager.java229
-rw-r--r--src/com/android/settings/bluetooth/LocalBluetoothManager.java279
-rw-r--r--src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java280
-rw-r--r--src/com/android/settings/bluetooth/SettingsBtStatus.java81
14 files changed, 2996 insertions, 0 deletions
diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
new file mode 100644
index 000000000..f0a818909
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
@@ -0,0 +1,122 @@
+/*
+ * 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 com.android.settings.R;
+
+import android.content.Context;
+import android.preference.Preference;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.ImageView;
+
+/**
+ * BluetoothDevicePreference is the preference type used to display each remote
+ * Bluetooth device in the Bluetooth Settings screen.
+ */
+public class BluetoothDevicePreference extends Preference implements LocalBluetoothDevice.Callback {
+ private static final String TAG = "BluetoothDevicePreference";
+
+ private static int sDimAlpha = Integer.MIN_VALUE;
+
+ private LocalBluetoothDevice mLocalDevice;
+
+ /**
+ * Cached local copy of whether the device is busy. This is only updated
+ * from {@link #onDeviceAttributesChanged(LocalBluetoothDevice)}.
+ */
+ private boolean mIsBusy;
+
+ public BluetoothDevicePreference(Context context, LocalBluetoothDevice localDevice) {
+ super(context);
+
+ if (sDimAlpha == Integer.MIN_VALUE) {
+ TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true);
+ sDimAlpha = (int) (outValue.getFloat() * 255);
+ }
+
+ mLocalDevice = localDevice;
+
+ setLayoutResource(R.layout.preference_bluetooth);
+
+ localDevice.registerCallback(this);
+
+ onDeviceAttributesChanged(localDevice);
+ }
+
+ public LocalBluetoothDevice getDevice() {
+ return mLocalDevice;
+ }
+
+ @Override
+ protected void onPrepareForRemoval() {
+ super.onPrepareForRemoval();
+ mLocalDevice.unregisterCallback(this);
+ }
+
+ public void onDeviceAttributesChanged(LocalBluetoothDevice device) {
+
+ /*
+ * The preference framework takes care of making sure the value has
+ * changed before proceeding.
+ */
+
+ setTitle(mLocalDevice.getName());
+
+ /*
+ * TODO: Showed "Paired" even though it was "Connected". This may be
+ * related to BluetoothHeadset not bound to the actual
+ * BluetoothHeadsetService when we got here.
+ */
+ setSummary(mLocalDevice.getSummary());
+
+ // Used to gray out the item
+ mIsBusy = mLocalDevice.isBusy();
+
+ // Data has changed
+ notifyChanged();
+
+ // This could affect ordering, so notify that also
+ notifyHierarchyChanged();
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled() && !mIsBusy;
+ }
+
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+
+ ImageView btClass = (ImageView) view.findViewById(R.id.btClass);
+ btClass.setImageResource(mLocalDevice.getBtClassDrawable());
+ btClass.setAlpha(isEnabled() ? 255 : sDimAlpha);
+ }
+
+ @Override
+ public int compareTo(Preference another) {
+ if (!(another instanceof BluetoothDevicePreference)) {
+ // Put other preference types above us
+ return 1;
+ }
+
+ return mLocalDevice.compareTo(((BluetoothDevicePreference) another).mLocalDevice);
+ }
+
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java b/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java
new file mode 100644
index 000000000..a51f9b5ea
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java
@@ -0,0 +1,198 @@
+/*
+ * 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 com.android.settings.R;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothError;
+import android.bluetooth.BluetoothIntent;
+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.preference.Preference;
+import android.preference.CheckBoxPreference;
+import android.util.Log;
+
+/**
+ * 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.
+ */
+public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChangeListener {
+ private static final String TAG = "BluetoothDiscoverableEnabler";
+ private static final boolean V = LocalBluetoothManager.V;
+
+ private static final String SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT =
+ "debug.bt.discoverable_time";
+ private static final int DISCOVERABLE_TIMEOUT = 120;
+
+ private static final String SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP =
+ "discoverable_end_timestamp";
+
+ private final Context mContext;
+ private final Handler mUiHandler;
+ private final CheckBoxPreference mCheckBoxPreference;
+
+ private final LocalBluetoothManager mLocalManager;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (BluetoothIntent.SCAN_MODE_CHANGED_ACTION.equals(intent.getAction())) {
+ int mode = intent.getIntExtra(BluetoothIntent.SCAN_MODE, BluetoothError.ERROR);
+ if (mode != BluetoothError.ERROR) {
+ handleModeChanged(mode);
+ }
+ }
+ }
+ };
+
+ private final Runnable mUpdateCountdownSummaryRunnable = new Runnable() {
+ public void run() {
+ updateCountdownSummary();
+ }
+ };
+
+ public BluetoothDiscoverableEnabler(Context context, CheckBoxPreference checkBoxPreference) {
+ mContext = context;
+ mUiHandler = new Handler();
+ mCheckBoxPreference = checkBoxPreference;
+
+ checkBoxPreference.setPersistent(false);
+
+ mLocalManager = LocalBluetoothManager.getInstance(context);
+ if (mLocalManager == null) {
+ // Bluetooth not supported
+ checkBoxPreference.setEnabled(false);
+ }
+ }
+
+ public void resume() {
+ if (mLocalManager == null) {
+ return;
+ }
+
+ IntentFilter filter = new IntentFilter(BluetoothIntent.SCAN_MODE_CHANGED_ACTION);
+ filter.addAction(BluetoothIntent.DISABLED_ACTION);
+ mContext.registerReceiver(mReceiver, filter);
+ mCheckBoxPreference.setOnPreferenceChangeListener(this);
+
+ handleModeChanged(mLocalManager.getBluetoothManager().getScanMode());
+ }
+
+ public void pause() {
+ if (mLocalManager == null) {
+ return;
+ }
+
+ mUiHandler.removeCallbacks(mUpdateCountdownSummaryRunnable);
+ mCheckBoxPreference.setOnPreferenceChangeListener(null);
+ mContext.unregisterReceiver(mReceiver);
+ }
+
+ public boolean onPreferenceChange(Preference preference, Object value) {
+ if (V) {
+ Log.v(TAG, "Preference changed to " + value);
+ }
+
+ // Turn on/off BT discoverability
+ setEnabled((Boolean) value);
+
+ return true;
+ }
+
+ private void setEnabled(final boolean enable) {
+ BluetoothDevice manager = mLocalManager.getBluetoothManager();
+
+ if (enable) {
+
+ int timeout = getDiscoverableTimeout();
+ manager.setDiscoverableTimeout(timeout);
+
+ long endTimestamp = System.currentTimeMillis() + timeout * 1000;
+ persistDiscoverableEndTimestamp(endTimestamp);
+
+ manager.setScanMode(BluetoothDevice.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
+ } else {
+ manager.setScanMode(BluetoothDevice.SCAN_MODE_CONNECTABLE);
+ }
+ }
+
+ private int getDiscoverableTimeout() {
+ int timeout = SystemProperties.getInt(SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT, -1);
+ if (timeout <= 0) {
+ timeout = DISCOVERABLE_TIMEOUT;
+ }
+
+ return timeout;
+ }
+
+ private void persistDiscoverableEndTimestamp(long endTimestamp) {
+ SharedPreferences.Editor editor = mLocalManager.getSharedPreferences().edit();
+ editor.putLong(SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, endTimestamp);
+ editor.commit();
+ }
+
+ private void handleModeChanged(int mode) {
+ if (V) {
+ Log.v(TAG, "Got mode changed: " + mode);
+ }
+
+ if (mode == BluetoothDevice.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
+ mCheckBoxPreference.setChecked(true);
+ updateCountdownSummary();
+
+ } else {
+ mCheckBoxPreference.setChecked(false);
+ }
+ }
+
+ private void updateCountdownSummary() {
+ int mode = mLocalManager.getBluetoothManager().getScanMode();
+ if (mode != BluetoothDevice.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
+ return;
+ }
+
+ long currentTimestamp = System.currentTimeMillis();
+ long endTimestamp = mLocalManager.getSharedPreferences().getLong(
+ SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, 0);
+
+ if (currentTimestamp > endTimestamp) {
+ // We're still in discoverable mode, but maybe there isn't a timeout.
+ mCheckBoxPreference.setSummaryOn(null);
+ return;
+ }
+
+ String formattedTimeLeft = String.valueOf((endTimestamp - currentTimestamp) / 1000);
+
+ mCheckBoxPreference.setSummaryOn(
+ mContext.getResources().getString(R.string.bluetooth_is_discoverable,
+ formattedTimeLeft));
+
+ synchronized (this) {
+ mUiHandler.removeCallbacks(mUpdateCountdownSummaryRunnable);
+ mUiHandler.postDelayed(mUpdateCountdownSummaryRunnable, 1000);
+ }
+ }
+
+
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothEnabler.java b/src/com/android/settings/bluetooth/BluetoothEnabler.java
new file mode 100644
index 000000000..661700fd3
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothEnabler.java
@@ -0,0 +1,149 @@
+/*
+ * 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 com.android.settings.R;
+import com.android.settings.bluetooth.LocalBluetoothManager.ExtendedBluetoothState;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.preference.Preference;
+import android.preference.CheckBoxPreference;
+import android.text.TextUtils;
+import android.util.Config;
+
+/**
+ * BluetoothEnabler is a helper to manage the Bluetooth on/off checkbox
+ * preference. It is turns on/off Bluetooth and ensures the summary of the
+ * preference reflects the current state.
+ */
+public class BluetoothEnabler implements Preference.OnPreferenceChangeListener {
+
+ private static final boolean LOCAL_LOGD = Config.LOGD || false;
+ private static final String TAG = "BluetoothEnabler";
+
+ private final Context mContext;
+ private final CheckBoxPreference mCheckBoxPreference;
+ private final CharSequence mOriginalSummary;
+
+ private final LocalBluetoothManager mLocalManager;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ handleStateChanged(mLocalManager.getBluetoothState());
+ }
+ };
+
+ public BluetoothEnabler(Context context, CheckBoxPreference checkBoxPreference) {
+ mContext = context;
+ mCheckBoxPreference = checkBoxPreference;
+
+ mOriginalSummary = checkBoxPreference.getSummary();
+ checkBoxPreference.setPersistent(false);
+
+ mLocalManager = LocalBluetoothManager.getInstance(context);
+ if (mLocalManager == null) {
+ // Bluetooth not supported
+ checkBoxPreference.setEnabled(false);
+ }
+ }
+
+ public void resume() {
+ if (mLocalManager == null) {
+ return;
+ }
+
+ ExtendedBluetoothState state = mLocalManager.getBluetoothState();
+ // This is the widget enabled state, not the preference toggled state
+ mCheckBoxPreference.setEnabled(state == ExtendedBluetoothState.ENABLED ||
+ state == ExtendedBluetoothState.DISABLED);
+ // BT state is not a sticky broadcast, so set it manually
+ handleStateChanged(state);
+
+ mContext.registerReceiver(mReceiver,
+ new IntentFilter(LocalBluetoothManager.EXTENDED_BLUETOOTH_STATE_CHANGED_ACTION));
+ mCheckBoxPreference.setOnPreferenceChangeListener(this);
+ }
+
+ public void pause() {
+ if (mLocalManager == null) {
+ return;
+ }
+
+ mContext.unregisterReceiver(mReceiver);
+ mCheckBoxPreference.setOnPreferenceChangeListener(null);
+ }
+
+ public boolean onPreferenceChange(Preference preference, Object value) {
+ // Turn on/off BT
+ setEnabled((Boolean) value);
+
+ // Don't update UI to opposite state until we're sure
+ return false;
+ }
+
+ private void setEnabled(final boolean enable) {
+ // Disable preference
+ mCheckBoxPreference.setEnabled(false);
+
+ mLocalManager.setBluetoothEnabled(enable);
+ }
+
+ private void handleStateChanged(ExtendedBluetoothState state) {
+
+ if (state == ExtendedBluetoothState.DISABLED || state == ExtendedBluetoothState.ENABLED) {
+ mCheckBoxPreference.setChecked(state == ExtendedBluetoothState.ENABLED);
+ mCheckBoxPreference
+ .setSummary(state == ExtendedBluetoothState.DISABLED ? mOriginalSummary : null);
+
+ mCheckBoxPreference.setEnabled(isEnabledByDependency());
+
+ } else if (state == ExtendedBluetoothState.ENABLING ||
+ state == ExtendedBluetoothState.DISABLING) {
+ mCheckBoxPreference.setSummary(state == ExtendedBluetoothState.ENABLING
+ ? R.string.wifi_starting
+ : R.string.wifi_stopping);
+
+ } else if (state == ExtendedBluetoothState.UNKNOWN) {
+ mCheckBoxPreference.setChecked(false);
+ mCheckBoxPreference.setSummary(R.string.wifi_error);
+ mCheckBoxPreference.setEnabled(true);
+ }
+ }
+
+ private boolean isEnabledByDependency() {
+ Preference dep = getDependencyPreference();
+ if (dep == null) {
+ return true;
+ }
+
+ return !dep.shouldDisableDependents();
+ }
+
+ private Preference getDependencyPreference() {
+ String depKey = mCheckBoxPreference.getDependency();
+ if (TextUtils.isEmpty(depKey)) {
+ return null;
+ }
+
+ return mCheckBoxPreference.getPreferenceManager().findPreference(depKey);
+ }
+
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothEventRedirector.java b/src/com/android/settings/bluetooth/BluetoothEventRedirector.java
new file mode 100644
index 000000000..2ad5726dd
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothEventRedirector.java
@@ -0,0 +1,147 @@
+/*
+ * 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 com.android.settings.bluetooth.LocalBluetoothManager.ExtendedBluetoothState;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothError;
+import android.bluetooth.BluetoothIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+/**
+ * BluetoothEventRedirector receives broadcasts and callbacks from the Bluetooth
+ * API and dispatches the event on the UI thread to the right class in the
+ * Settings.
+ */
+public class BluetoothEventRedirector {
+ private static final String TAG = "BluetoothEventRedirector";
+ private static final boolean V = LocalBluetoothManager.V;
+
+ private LocalBluetoothManager mManager;
+
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (V) {
+ Log.v(TAG, "Received " + intent.getAction());
+ }
+
+ String action = intent.getAction();
+ String address = intent.getStringExtra(BluetoothIntent.ADDRESS);
+
+ if (action.equals(BluetoothIntent.ENABLED_ACTION)) {
+ mManager.setBluetoothStateInt(ExtendedBluetoothState.ENABLED);
+
+ } else if (action.equals(BluetoothIntent.DISABLED_ACTION)) {
+ mManager.setBluetoothStateInt(ExtendedBluetoothState.DISABLED);
+
+ } else if (action.equals(BluetoothIntent.DISCOVERY_STARTED_ACTION)) {
+ mManager.onScanningStateChanged(true);
+
+ } else if (action.equals(BluetoothIntent.DISCOVERY_COMPLETED_ACTION)) {
+ mManager.onScanningStateChanged(false);
+
+ } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION)) {
+ short rssi = intent.getShortExtra(BluetoothIntent.RSSI, Short.MIN_VALUE);
+ mManager.getLocalDeviceManager().onDeviceAppeared(address, rssi);
+
+ } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION)) {
+ mManager.getLocalDeviceManager().onDeviceDisappeared(address);
+
+ } else if (action.equals(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION)) {
+ mManager.getLocalDeviceManager().onDeviceNameUpdated(address);
+
+ } else if (action.equals(BluetoothIntent.BOND_STATE_CHANGED_ACTION)) {
+ int bondState = intent.getIntExtra(BluetoothIntent.BOND_STATE,
+ BluetoothError.ERROR);
+ mManager.getLocalDeviceManager().onBondingStateChanged(address, bondState);
+ if (bondState == BluetoothDevice.BOND_NOT_BONDED) {
+ int reason = intent.getIntExtra(BluetoothIntent.REASON, BluetoothError.ERROR);
+ if (reason == BluetoothDevice.UNBOND_REASON_AUTH_FAILED ||
+ reason == BluetoothDevice.UNBOND_REASON_AUTH_REJECTED ||
+ reason == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN) {
+ mManager.getLocalDeviceManager().onBondingError(address, reason);
+ }
+ }
+
+ } else if (action.equals(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION)) {
+ mManager.getLocalDeviceManager().onProfileStateChanged(address);
+
+ int newState = intent.getIntExtra(BluetoothIntent.HEADSET_STATE, 0);
+ int oldState = intent.getIntExtra(BluetoothIntent.HEADSET_PREVIOUS_STATE, 0);
+ if (newState == BluetoothHeadset.STATE_DISCONNECTED &&
+ oldState == BluetoothHeadset.STATE_CONNECTING) {
+ Log.i(TAG, "Failed to connect BT headset");
+ }
+
+ } else if (action.equals(BluetoothA2dp.SINK_STATE_CHANGED_ACTION)) {
+ mManager.getLocalDeviceManager().onProfileStateChanged(address);
+
+ int newState = intent.getIntExtra(BluetoothA2dp.SINK_STATE, 0);
+ int oldState = intent.getIntExtra(BluetoothA2dp.SINK_PREVIOUS_STATE, 0);
+ if (newState == BluetoothA2dp.STATE_DISCONNECTED &&
+ oldState == BluetoothA2dp.STATE_CONNECTING) {
+ Log.i(TAG, "Failed to connect BT A2DP");
+ }
+
+ } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_CLASS_UPDATED_ACTION)) {
+ mManager.getLocalDeviceManager().onBtClassChanged(address);
+
+ }
+ }
+ };
+
+ public BluetoothEventRedirector(LocalBluetoothManager localBluetoothManager) {
+ mManager = localBluetoothManager;
+ }
+
+ public void start() {
+ IntentFilter filter = new IntentFilter();
+
+ // Bluetooth on/off broadcasts
+ filter.addAction(BluetoothIntent.ENABLED_ACTION);
+ filter.addAction(BluetoothIntent.DISABLED_ACTION);
+
+ // Discovery broadcasts
+ filter.addAction(BluetoothIntent.DISCOVERY_STARTED_ACTION);
+ filter.addAction(BluetoothIntent.DISCOVERY_COMPLETED_ACTION);
+ filter.addAction(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION);
+ filter.addAction(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION);
+ filter.addAction(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION);
+
+ // Pairing broadcasts
+ filter.addAction(BluetoothIntent.BOND_STATE_CHANGED_ACTION);
+
+ // Fine-grained state broadcasts
+ filter.addAction(BluetoothA2dp.SINK_STATE_CHANGED_ACTION);
+ filter.addAction(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION);
+ filter.addAction(BluetoothIntent.REMOTE_DEVICE_CLASS_UPDATED_ACTION);
+
+ mManager.getContext().registerReceiver(mBroadcastReceiver, filter);
+ }
+
+ public void stop() {
+ mManager.getContext().unregisterReceiver(mBroadcastReceiver);
+ }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothNamePreference.java b/src/com/android/settings/bluetooth/BluetoothNamePreference.java
new file mode 100644
index 000000000..3065b26c5
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothNamePreference.java
@@ -0,0 +1,79 @@
+/*
+ * 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.BluetoothDevice;
+import android.bluetooth.BluetoothIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.preference.EditTextPreference;
+import android.preference.PreferenceManager;
+import android.util.AttributeSet;
+
+/**
+ * BluetoothNamePreference is the preference type for editing the device's
+ * Bluetooth name. It asks the user for a name, and persists it via the
+ * Bluetooth API.
+ */
+public class BluetoothNamePreference extends EditTextPreference {
+ private static final String TAG = "BluetoothNamePreference";
+
+ private LocalBluetoothManager mLocalManager;
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ setSummaryToName();
+ }
+ };
+
+ public BluetoothNamePreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ mLocalManager = LocalBluetoothManager.getInstance(context);
+
+ setSummaryToName();
+ }
+
+ public void resume() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothIntent.ENABLED_ACTION);
+ filter.addAction(BluetoothIntent.NAME_CHANGED_ACTION);
+ getContext().registerReceiver(mReceiver, filter);
+ }
+
+ public void pause() {
+ getContext().unregisterReceiver(mReceiver);
+ }
+
+ private void setSummaryToName() {
+ BluetoothDevice manager = mLocalManager.getBluetoothManager();
+ if (manager.isEnabled()) {
+ setSummary(manager.getName());
+ }
+ }
+
+ @Override
+ protected boolean persistString(String value) {
+ BluetoothDevice manager = mLocalManager.getBluetoothManager();
+ manager.setName(value);
+ return true;
+ }
+
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothPinDialog.java b/src/com/android/settings/bluetooth/BluetoothPinDialog.java
new file mode 100644
index 000000000..5e289f756
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothPinDialog.java
@@ -0,0 +1,202 @@
+/*
+ * 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.BluetoothDevice;
+import android.bluetooth.BluetoothIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.TextWatcher;
+import android.text.InputFilter.LengthFilter;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+import com.android.settings.R;
+
+/**
+ * BluetoothPinDialog asks the user to enter a PIN for pairing with a remote
+ * Bluetooth device. It is an activity that appears as a dialog.
+ */
+public class BluetoothPinDialog extends AlertActivity implements DialogInterface.OnClickListener,
+ TextWatcher {
+ private static final String TAG = "BluetoothPinDialog";
+
+ private LocalBluetoothManager mLocalManager;
+ private String mAddress;
+ private EditText mPinView;
+ private Button mOkButton;
+
+ private static final String INSTANCE_KEY_PAIRING_CANCELED = "received_pairing_canceled";
+ private boolean mReceivedPairingCanceled;
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!BluetoothIntent.PAIRING_CANCEL_ACTION.equals(intent.getAction())) {
+ return;
+ }
+
+ String address = intent.getStringExtra(BluetoothIntent.ADDRESS);
+ if (address == null || address.equals(mAddress)) {
+ onReceivedPairingCanceled();
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ if (!intent.getAction().equals(BluetoothIntent.PAIRING_REQUEST_ACTION))
+ {
+ Log.e(TAG,
+ "Error: this activity may be started only with intent " +
+ BluetoothIntent.PAIRING_REQUEST_ACTION);
+ finish();
+ }
+
+ mLocalManager = LocalBluetoothManager.getInstance(this);
+ mAddress = intent.getStringExtra(BluetoothIntent.ADDRESS);
+
+ // Set up the "dialog"
+ final AlertController.AlertParams p = mAlertParams;
+ p.mIconId = android.R.drawable.ic_dialog_info;
+ p.mTitle = getString(R.string.bluetooth_pin_entry);
+ p.mView = createView();
+ p.mPositiveButtonText = getString(android.R.string.ok);
+ p.mPositiveButtonListener = this;
+ p.mNegativeButtonText = getString(android.R.string.cancel);
+ p.mNegativeButtonListener = this;
+ setupAlert();
+
+ mOkButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
+ mOkButton.setEnabled(false);
+
+ /*
+ * Leave this registered through pause/resume since we still want to
+ * finish the activity in the background if pairing is canceled.
+ */
+ registerReceiver(mReceiver, new IntentFilter(BluetoothIntent.PAIRING_CANCEL_ACTION));
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+
+ mReceivedPairingCanceled = savedInstanceState.getBoolean(INSTANCE_KEY_PAIRING_CANCELED);
+ if (mReceivedPairingCanceled) {
+ onReceivedPairingCanceled();
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putBoolean(INSTANCE_KEY_PAIRING_CANCELED, mReceivedPairingCanceled);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ unregisterReceiver(mReceiver);
+ }
+
+ private View createView() {
+ View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_entry, null);
+
+ String name = mLocalManager.getLocalDeviceManager().getName(mAddress);
+ TextView messageView = (TextView) view.findViewById(R.id.message);
+ messageView.setText(getString(R.string.bluetooth_enter_pin_msg, name));
+
+ mPinView = (EditText) view.findViewById(R.id.text);
+ mPinView.addTextChangedListener(this);
+ // Maximum of 10 characters in a PIN
+ mPinView.setFilters(new InputFilter[] { new LengthFilter(10) });
+
+ return view;
+ }
+
+ public void afterTextChanged(Editable s) {
+ if (s.length() > 0) {
+ mOkButton.setEnabled(true);
+ }
+ }
+
+ private void onReceivedPairingCanceled() {
+ mReceivedPairingCanceled = true;
+
+ TextView messageView = (TextView) findViewById(R.id.message);
+ messageView.setText(getString(R.string.bluetooth_pairing_error_message,
+ mLocalManager.getLocalDeviceManager().getName(mAddress)));
+
+ mPinView.setVisibility(View.GONE);
+ mPinView.clearFocus();
+ mPinView.removeTextChangedListener(this);
+
+ mOkButton.setEnabled(true);
+ mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
+ }
+
+ private void onPair(String pin) {
+ byte[] pinBytes = BluetoothDevice.convertPinToBytes(pin);
+
+ if (pinBytes == null) {
+ return;
+ }
+
+ mLocalManager.getBluetoothManager().setPin(mAddress, pinBytes);
+ }
+
+ private void onCancel() {
+ mLocalManager.getBluetoothManager().cancelBondProcess(mAddress);
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ switch (which) {
+ case DialogInterface.BUTTON_POSITIVE:
+ onPair(mPinView.getText().toString());
+ break;
+
+ case DialogInterface.BUTTON_NEGATIVE:
+ onCancel();
+ break;
+ }
+ }
+
+ /* Not used */
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ /* Not used */
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothPinRequest.java b/src/com/android/settings/bluetooth/BluetoothPinRequest.java
new file mode 100644
index 000000000..619052d87
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothPinRequest.java
@@ -0,0 +1,96 @@
+/*
+ * 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 com.android.settings.R;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.text.TextUtils;
+
+/**
+ * BluetoothPinRequest is a receiver for any Bluetooth pairing PIN request. It
+ * checks if the Bluetooth Settings is currently visible and brings up the PIN
+ * entry dialog. Otherwise it puts a Notification in the status bar, which can
+ * be clicked to bring up the PIN entry dialog.
+ */
+public class BluetoothPinRequest extends BroadcastReceiver {
+
+ public static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(BluetoothIntent.PAIRING_REQUEST_ACTION)) {
+
+ LocalBluetoothManager localManager = LocalBluetoothManager.getInstance(context);
+
+ String address = intent.getStringExtra(BluetoothIntent.ADDRESS);
+ Intent pinIntent = new Intent();
+ pinIntent.setClass(context, BluetoothPinDialog.class);
+ pinIntent.putExtra(BluetoothIntent.ADDRESS, address);
+ pinIntent.setAction(BluetoothIntent.PAIRING_REQUEST_ACTION);
+ pinIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ if (localManager.getForegroundActivity() != null) {
+ // Since the BT-related activity is in the foreground, just open the dialog
+ context.startActivity(pinIntent);
+
+ } else {
+
+ // Put up a notification that leads to the dialog
+ Resources res = context.getResources();
+ Notification notification = new Notification(
+ android.R.drawable.stat_sys_data_bluetooth,
+ res.getString(R.string.bluetooth_notif_ticker),
+ System.currentTimeMillis());
+
+ PendingIntent pending = PendingIntent.getActivity(context, 0,
+ pinIntent, PendingIntent.FLAG_ONE_SHOT);
+
+ String name = intent.getStringExtra(BluetoothIntent.NAME);
+ if (TextUtils.isEmpty(name)) {
+ name = localManager.getLocalDeviceManager().getName(address);
+ }
+
+ notification.setLatestEventInfo(context,
+ res.getString(R.string.bluetooth_notif_title),
+ res.getString(R.string.bluetooth_notif_message) + name,
+ pending);
+ notification.flags |= Notification.FLAG_AUTO_CANCEL;
+
+ NotificationManager manager = (NotificationManager)
+ context.getSystemService(Context.NOTIFICATION_SERVICE);
+ manager.notify(NOTIFICATION_ID, notification);
+ }
+
+ } else if (action.equals(BluetoothIntent.PAIRING_CANCEL_ACTION)) {
+
+ // Remove the notification
+ NotificationManager manager = (NotificationManager) context
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ manager.cancel(NOTIFICATION_ID);
+ }
+ }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothSettings.java b/src/com/android/settings/bluetooth/BluetoothSettings.java
new file mode 100644
index 000000000..5adada3c4
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothSettings.java
@@ -0,0 +1,260 @@
+/*
+ * 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 com.android.settings.ProgressCategory;
+import com.android.settings.R;
+import com.android.settings.bluetooth.LocalBluetoothManager.ExtendedBluetoothState;
+
+import java.util.List;
+import java.util.WeakHashMap;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+/**
+ * BluetoothSettings is the Settings screen for Bluetooth configuration and
+ * connection management.
+ */
+public class BluetoothSettings extends PreferenceActivity
+ implements LocalBluetoothManager.Callback {
+
+ private static final String TAG = "BluetoothSettings";
+
+ private static final int MENU_SCAN = Menu.FIRST;
+
+ private static final String KEY_BT_CHECKBOX = "bt_checkbox";
+ private static final String KEY_BT_DISCOVERABLE = "bt_discoverable";
+ private static final String KEY_BT_DEVICE_LIST = "bt_device_list";
+ private static final String KEY_BT_NAME = "bt_name";
+ private static final String KEY_BT_SCAN = "bt_scan";
+
+ private LocalBluetoothManager mLocalManager;
+
+ private BluetoothEnabler mEnabler;
+ private BluetoothDiscoverableEnabler mDiscoverableEnabler;
+
+ private BluetoothNamePreference mNamePreference;
+
+ private ProgressCategory mDeviceList;
+
+ private WeakHashMap<LocalBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap =
+ new WeakHashMap<LocalBluetoothDevice, BluetoothDevicePreference>();
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // TODO: put this in callback instead of receiving
+ onBluetoothStateChanged(mLocalManager.getBluetoothState());
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mLocalManager = LocalBluetoothManager.getInstance(this);
+ if (mLocalManager == null) finish();
+
+ addPreferencesFromResource(R.xml.bluetooth_settings);
+
+ mEnabler = new BluetoothEnabler(
+ this,
+ (CheckBoxPreference) findPreference(KEY_BT_CHECKBOX));
+
+ mDiscoverableEnabler = new BluetoothDiscoverableEnabler(
+ this,
+ (CheckBoxPreference) findPreference(KEY_BT_DISCOVERABLE));
+
+ mNamePreference = (BluetoothNamePreference) findPreference(KEY_BT_NAME);
+
+ mDeviceList = (ProgressCategory) findPreference(KEY_BT_DEVICE_LIST);
+
+ registerForContextMenu(getListView());
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ // Repopulate (which isn't too bad since it's cached in the settings
+ // bluetooth manager
+ mDevicePreferenceMap.clear();
+ mDeviceList.removeAll();
+ addDevices();
+
+ mEnabler.resume();
+ mDiscoverableEnabler.resume();
+ mNamePreference.resume();
+ mLocalManager.registerCallback(this);
+
+ mLocalManager.startScanning(false);
+
+ registerReceiver(mReceiver,
+ new IntentFilter(LocalBluetoothManager.EXTENDED_BLUETOOTH_STATE_CHANGED_ACTION));
+
+ mLocalManager.setForegroundActivity(this);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ mLocalManager.setForegroundActivity(null);
+
+ unregisterReceiver(mReceiver);
+
+ mLocalManager.unregisterCallback(this);
+ mNamePreference.pause();
+ mDiscoverableEnabler.pause();
+ mEnabler.pause();
+ }
+
+ private void addDevices() {
+ List<LocalBluetoothDevice> devices = mLocalManager.getLocalDeviceManager().getDevicesCopy();
+ for (LocalBluetoothDevice device : devices) {
+ onDeviceAdded(device);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, MENU_SCAN, 0, R.string.bluetooth_scan_for_devices)
+ .setIcon(com.android.internal.R.drawable.ic_menu_refresh)
+ .setAlphabeticShortcut('r');
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ menu.findItem(MENU_SCAN).setEnabled(mLocalManager.getBluetoothManager().isEnabled());
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+
+ case MENU_SCAN:
+ mLocalManager.startScanning(true);
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
+ Preference preference) {
+
+ if (KEY_BT_SCAN.equals(preference.getKey())) {
+ mLocalManager.startScanning(true);
+ return true;
+ }
+
+ if (preference instanceof BluetoothDevicePreference) {
+ BluetoothDevicePreference btPreference = (BluetoothDevicePreference) preference;
+ btPreference.getDevice().onClicked();
+ return true;
+ }
+
+ return super.onPreferenceTreeClick(preferenceScreen, preference);
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenuInfo menuInfo) {
+ LocalBluetoothDevice device = getDeviceFromMenuInfo(menuInfo);
+ if (device == null) return;
+
+ device.onCreateContextMenu(menu);
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ LocalBluetoothDevice device = getDeviceFromMenuInfo(item.getMenuInfo());
+ if (device == null) return false;
+
+ device.onContextItemSelected(item);
+ return true;
+ }
+
+ private LocalBluetoothDevice getDeviceFromMenuInfo(ContextMenuInfo menuInfo) {
+ if ((menuInfo == null) || !(menuInfo instanceof AdapterContextMenuInfo)) {
+ return null;
+ }
+
+ AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) menuInfo;
+ Preference pref = (Preference) getPreferenceScreen().getRootAdapter().getItem(
+ adapterMenuInfo.position);
+ if (pref == null || !(pref instanceof BluetoothDevicePreference)) {
+ return null;
+ }
+
+ return ((BluetoothDevicePreference) pref).getDevice();
+ }
+
+ public void onDeviceAdded(LocalBluetoothDevice device) {
+
+ if (mDevicePreferenceMap.get(device) != null) {
+ throw new IllegalStateException("Got onDeviceAdded, but device already exists");
+ }
+
+ createDevicePreference(device);
+ }
+
+ private void createDevicePreference(LocalBluetoothDevice device) {
+ BluetoothDevicePreference preference = new BluetoothDevicePreference(this, device);
+ mDeviceList.addPreference(preference);
+ mDevicePreferenceMap.put(device, preference);
+ }
+
+ public void onDeviceDeleted(LocalBluetoothDevice device) {
+ BluetoothDevicePreference preference = mDevicePreferenceMap.remove(device);
+ if (preference != null) {
+ mDeviceList.removePreference(preference);
+ }
+ }
+
+ public void onScanningStateChanged(boolean started) {
+ mDeviceList.setProgress(started);
+ }
+
+ private void onBluetoothStateChanged(ExtendedBluetoothState bluetoothState) {
+ // When bluetooth is enabled (and we are in the activity, which we are),
+ // we should start a scan
+ if (bluetoothState == ExtendedBluetoothState.ENABLED) {
+ mLocalManager.startScanning(false);
+ } else if (bluetoothState == ExtendedBluetoothState.DISABLED) {
+ mDeviceList.setProgress(false);
+ }
+ }
+}
diff --git a/src/com/android/settings/bluetooth/ConnectSpecificProfilesActivity.java b/src/com/android/settings/bluetooth/ConnectSpecificProfilesActivity.java
new file mode 100644
index 000000000..7dd1b706d
--- /dev/null
+++ b/src/com/android/settings/bluetooth/ConnectSpecificProfilesActivity.java
@@ -0,0 +1,298 @@
+/*
+ * 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 com.android.settings.R;
+import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceGroup;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * ConnectSpecificProfilesActivity presents the user with all of the profiles
+ * for a particular device, and allows him to choose which should be connected
+ * (or disconnected).
+ */
+public class ConnectSpecificProfilesActivity extends PreferenceActivity
+ implements LocalBluetoothDevice.Callback, Preference.OnPreferenceChangeListener {
+ private static final String TAG = "ConnectSpecificProfilesActivity";
+
+ private static final String KEY_ONLINE_MODE = "online_mode";
+ private static final String KEY_TITLE = "title";
+ private static final String KEY_PROFILE_CONTAINER = "profile_container";
+
+ public static final String EXTRA_ADDRESS = "address";
+
+ private LocalBluetoothManager mManager;
+ private LocalBluetoothDevice mDevice;
+
+ private PreferenceGroup mProfileContainer;
+ private CheckBoxPreference mOnlineModePreference;
+
+ /**
+ * The current mode of this activity and its checkboxes (either online mode
+ * or offline mode). In online mode, user interactions with the profile
+ * checkboxes will also toggle the profile's connectivity. In offline mode,
+ * they will not, and only the preferred state will be saved for the
+ * profile.
+ */
+ private boolean mOnlineMode;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ String address;
+ if (savedInstanceState != null) {
+ address = savedInstanceState.getString(EXTRA_ADDRESS);
+ } else {
+ Intent intent = getIntent();
+ address = intent.getStringExtra(EXTRA_ADDRESS);
+ }
+
+ if (TextUtils.isEmpty(address)) {
+ Log.w(TAG, "Activity started without address");
+ finish();
+ }
+
+ mManager = LocalBluetoothManager.getInstance(this);
+ mDevice = mManager.getLocalDeviceManager().findDevice(address);
+ if (mDevice == null) {
+ Log.w(TAG, "Device not found, cannot connect to it");
+ finish();
+ }
+
+ addPreferencesFromResource(R.xml.bluetooth_device_advanced);
+ mProfileContainer = (PreferenceGroup) findPreference(KEY_PROFILE_CONTAINER);
+
+ // Set the title of the screen
+ findPreference(KEY_TITLE).setTitle(
+ getString(R.string.bluetooth_device_advanced_title, mDevice.getName()));
+
+ // Listen for check/uncheck of the online mode checkbox
+ mOnlineModePreference = (CheckBoxPreference) findPreference(KEY_ONLINE_MODE);
+ mOnlineModePreference.setOnPreferenceChangeListener(this);
+
+ // Add a preference for each profile
+ addPreferencesForProfiles();
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putString(EXTRA_ADDRESS, mDevice.getAddress());
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ mManager.setForegroundActivity(this);
+ mDevice.registerCallback(this);
+
+ refresh();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ mDevice.unregisterCallback(this);
+ mManager.setForegroundActivity(null);
+ }
+
+ private void addPreferencesForProfiles() {
+ for (Profile profile : mDevice.getProfiles()) {
+ Preference pref = createProfilePreference(profile);
+ mProfileContainer.addPreference(pref);
+ }
+ }
+
+ /**
+ * Creates a checkbox preference for the particular profile. The key will be
+ * the profile's name.
+ *
+ * @param profile The profile for which the preference controls.
+ * @return A preference that allows the user to choose whether this profile
+ * will be connected to.
+ */
+ private CheckBoxPreference createProfilePreference(Profile profile) {
+ CheckBoxPreference pref = new CheckBoxPreference(this);
+ pref.setKey(profile.toString());
+ pref.setTitle(profile.localizedString);
+ pref.setPersistent(false);
+ pref.setOnPreferenceChangeListener(this);
+
+ refreshProfilePreference(pref, profile);
+
+ return pref;
+ }
+
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ String key = preference.getKey();
+ if (TextUtils.isEmpty(key) || newValue == null) return true;
+
+ if (key.equals(KEY_ONLINE_MODE)) {
+ onOnlineModeCheckedStateChanged((Boolean) newValue);
+
+ } else {
+ Profile profile = getProfileOf(preference);
+ if (profile == null) return false;
+ onProfileCheckedStateChanged(profile, (Boolean) newValue);
+ }
+
+ return true;
+ }
+
+ private void onOnlineModeCheckedStateChanged(boolean checked) {
+ setOnlineMode(checked, true);
+ }
+
+ private void onProfileCheckedStateChanged(Profile profile, boolean checked) {
+ if (mOnlineMode) {
+ if (checked) {
+ mDevice.connect(profile);
+ } else {
+ mDevice.disconnect(profile);
+ }
+ }
+
+ LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
+ .getProfileManager(mManager, profile);
+ profileManager.setPreferred(mDevice.getAddress(), checked);
+ }
+
+ public void onDeviceAttributesChanged(LocalBluetoothDevice device) {
+ refresh();
+ }
+
+ private void refresh() {
+ // We are in 'online mode' if we are connected, connecting, or disconnecting
+ setOnlineMode(mDevice.isConnected() || mDevice.isBusy(), false);
+ refreshProfiles();
+ }
+
+ /**
+ * Switches between online/offline mode.
+ *
+ * @param onlineMode Whether to be in online mode, or offline mode.
+ * @param takeAction Whether to take action (i.e., connect or disconnect)
+ * based on the new online mode.
+ */
+ private void setOnlineMode(boolean onlineMode, boolean takeAction) {
+ mOnlineMode = onlineMode;
+
+ if (takeAction) {
+ if (onlineMode) {
+ mDevice.connect();
+ } else {
+ mDevice.disconnect();
+ }
+ }
+
+ refreshOnlineModePreference();
+ }
+
+ private void refreshOnlineModePreference() {
+ mOnlineModePreference.setChecked(mOnlineMode);
+
+ /* Gray out checkbox while connecting and disconnecting */
+ mOnlineModePreference.setEnabled(!mDevice.isBusy());
+
+ /**
+ * If the device is online, show status. Otherwise, show a summary that
+ * describes what the checkbox does.
+ */
+ mOnlineModePreference.setSummary(mOnlineMode ? mDevice.getSummary()
+ : R.string.bluetooth_device_advanced_online_mode_summary);
+ }
+
+ private void refreshProfiles() {
+ for (Profile profile : mDevice.getProfiles()) {
+ CheckBoxPreference profilePref =
+ (CheckBoxPreference) findPreference(profile.toString());
+ if (profilePref == null) {
+ profilePref = createProfilePreference(profile);
+ mProfileContainer.addPreference(profilePref);
+ } else {
+ refreshProfilePreference(profilePref, profile);
+ }
+ }
+ }
+
+ private void refreshProfilePreference(CheckBoxPreference profilePref, Profile profile) {
+ String address = mDevice.getAddress();
+ LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
+ .getProfileManager(mManager, profile);
+
+ int connectionStatus = profileManager.getConnectionStatus(address);
+
+ /* Gray out checkbox while connecting and disconnecting */
+ profilePref.setEnabled(!mDevice.isBusy());
+
+ profilePref.setSummary(getProfileSummary(profileManager, profile, address,
+ connectionStatus, mOnlineMode));
+
+ profilePref.setChecked(profileManager.isPreferred(address));
+ }
+
+ private Profile getProfileOf(Preference pref) {
+ if (!(pref instanceof CheckBoxPreference)) return null;
+ String key = pref.getKey();
+ if (TextUtils.isEmpty(key)) return null;
+
+ try {
+ return Profile.valueOf(pref.getKey());
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+ private static int getProfileSummary(LocalBluetoothProfileManager profileManager,
+ Profile profile, String address, int connectionStatus, boolean onlineMode) {
+ if (!onlineMode || connectionStatus == SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED) {
+ return getProfileSummaryForSettingPreference(profile);
+ } else {
+ return profileManager.getSummary(address);
+ }
+ }
+
+ /**
+ * Gets the summary that describes when checked, it will become a preferred profile.
+ *
+ * @param profile The profile to get the summary for.
+ * @return The summary.
+ */
+ private static final int getProfileSummaryForSettingPreference(Profile profile) {
+ switch (profile) {
+ case A2DP:
+ return R.string.bluetooth_a2dp_profile_summary_use_for;
+ case HEADSET:
+ return R.string.bluetooth_headset_profile_summary_use_for;
+ default:
+ return 0;
+ }
+ }
+
+}
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothDevice.java b/src/com/android/settings/bluetooth/LocalBluetoothDevice.java
new file mode 100644
index 000000000..a4885401e
--- /dev/null
+++ b/src/com/android/settings/bluetooth/LocalBluetoothDevice.java
@@ -0,0 +1,576 @@
+/*
+ * 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 com.android.settings.R;
+import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile;
+
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.IBluetoothDeviceCallback;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * LocalBluetoothDevice represents a remote Bluetooth device. It contains
+ * attributes of the device (such as the address, name, RSSI, etc.) and
+ * functionality that can be performed on the device (connect, pair, disconnect,
+ * etc.).
+ */
+public class LocalBluetoothDevice implements Comparable<LocalBluetoothDevice> {
+ private static final String TAG = "LocalBluetoothDevice";
+
+ private static final int CONTEXT_ITEM_CONNECT = Menu.FIRST + 1;
+ private static final int CONTEXT_ITEM_DISCONNECT = Menu.FIRST + 2;
+ private static final int CONTEXT_ITEM_UNPAIR = Menu.FIRST + 3;
+ private static final int CONTEXT_ITEM_CONNECT_ADVANCED = Menu.FIRST + 4;
+
+ private final String mAddress;
+ private String mName;
+ private short mRssi;
+ private int mBtClass = BluetoothClass.ERROR;
+
+ private List<Profile> mProfiles = new ArrayList<Profile>();
+
+ private boolean mVisible;
+
+ private final LocalBluetoothManager mLocalManager;
+
+ private List<Callback> mCallbacks = new ArrayList<Callback>();
+
+ /**
+ * When we connect to multiple profiles, we only want to display a single
+ * error even if they all fail. This tracks that state.
+ */
+ private boolean mIsConnectingErrorPossible;
+
+ LocalBluetoothDevice(Context context, String address) {
+ mLocalManager = LocalBluetoothManager.getInstance(context);
+ if (mLocalManager == null) {
+ throw new IllegalStateException(
+ "Cannot use LocalBluetoothDevice without Bluetooth hardware");
+ }
+
+ mAddress = address;
+
+ fillData();
+ }
+
+ public void onClicked() {
+ int bondState = getBondState();
+
+ if (isConnected()) {
+ askDisconnect();
+ } else if (bondState == BluetoothDevice.BOND_BONDED) {
+ connect();
+ } else if (bondState == BluetoothDevice.BOND_NOT_BONDED) {
+ pair();
+ }
+ }
+
+ public void disconnect() {
+ for (Profile profile : mProfiles) {
+ disconnect(profile);
+ }
+ }
+
+ public void disconnect(Profile profile) {
+ LocalBluetoothProfileManager profileManager =
+ LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile);
+ int status = profileManager.getConnectionStatus(mAddress);
+ if (SettingsBtStatus.isConnectionStatusConnected(status)) {
+ profileManager.disconnect(mAddress);
+ }
+ }
+
+ public void askDisconnect() {
+ Context context = mLocalManager.getForegroundActivity();
+ if (context == null) {
+ // Cannot ask, since we need an activity context
+ disconnect();
+ return;
+ }
+
+ Resources res = context.getResources();
+
+ String name = getName();
+ if (TextUtils.isEmpty(name)) {
+ name = res.getString(R.string.bluetooth_device);
+ }
+ String message = res.getString(R.string.bluetooth_disconnect_blank, name);
+
+ DialogInterface.OnClickListener disconnectListener = new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ disconnect();
+ }
+ };
+
+ AlertDialog ad = new AlertDialog.Builder(context)
+ .setTitle(getName())
+ .setMessage(message)
+ .setPositiveButton(android.R.string.ok, disconnectListener)
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ }
+
+ public void connect() {
+ if (!ensurePaired()) return;
+
+ // Reset the only-show-one-error-dialog tracking variable
+ mIsConnectingErrorPossible = true;
+
+ Context context = mLocalManager.getContext();
+ boolean hasAtLeastOnePreferredProfile = false;
+ for (Profile profile : mProfiles) {
+ LocalBluetoothProfileManager profileManager =
+ LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile);
+ if (profileManager.isPreferred(mAddress)) {
+ hasAtLeastOnePreferredProfile = true;
+ connectInt(profile);
+ }
+ }
+
+ if (!hasAtLeastOnePreferredProfile) {
+ connectAndPreferAllProfiles();
+ }
+ }
+
+ private void connectAndPreferAllProfiles() {
+ if (!ensurePaired()) return;
+
+ // Reset the only-show-one-error-dialog tracking variable
+ mIsConnectingErrorPossible = true;
+
+ Context context = mLocalManager.getContext();
+ for (Profile profile : mProfiles) {
+ LocalBluetoothProfileManager profileManager =
+ LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile);
+ profileManager.setPreferred(mAddress, true);
+ connectInt(profile);
+ }
+ }
+
+ public void connect(Profile profile) {
+ // Reset the only-show-one-error-dialog tracking variable
+ mIsConnectingErrorPossible = true;
+ connectInt(profile);
+ }
+
+ public void connectInt(Profile profile) {
+ if (!ensurePaired()) return;
+
+ LocalBluetoothProfileManager profileManager =
+ LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile);
+ int status = profileManager.getConnectionStatus(mAddress);
+ if (!SettingsBtStatus.isConnectionStatusConnected(status)) {
+ if (profileManager.connect(mAddress) != BluetoothDevice.RESULT_SUCCESS) {
+ Log.i(TAG, "Failed to connect " + profile.toString() + " to " + mName);
+ }
+ }
+ }
+
+ public void showConnectingError() {
+ if (!mIsConnectingErrorPossible) return;
+ mIsConnectingErrorPossible = false;
+
+ mLocalManager.showError(mAddress, R.string.bluetooth_error_title,
+ R.string.bluetooth_connecting_error_message);
+ }
+
+ private boolean ensurePaired() {
+ if (getBondState() == BluetoothDevice.BOND_NOT_BONDED) {
+ pair();
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ public void pair() {
+ BluetoothDevice manager = mLocalManager.getBluetoothManager();
+
+ // Pairing is unreliable while scanning, so cancel discovery
+ if (manager.isDiscovering()) {
+ manager.cancelDiscovery();
+ }
+
+ if (!mLocalManager.getBluetoothManager().createBond(mAddress)) {
+ mLocalManager.showError(mAddress, R.string.bluetooth_error_title,
+ R.string.bluetooth_pairing_error_message);
+ }
+ }
+
+ public void unpair() {
+ BluetoothDevice manager = mLocalManager.getBluetoothManager();
+
+ switch (getBondState()) {
+ case BluetoothDevice.BOND_BONDED:
+ manager.removeBond(mAddress);
+ break;
+
+ case BluetoothDevice.BOND_BONDING:
+ manager.cancelBondProcess(mAddress);
+ break;
+ }
+ }
+
+ private void fillData() {
+ BluetoothDevice manager = mLocalManager.getBluetoothManager();
+
+ fetchName();
+ fetchBtClass();
+
+ mVisible = false;
+
+ dispatchAttributesChanged();
+ }
+
+ public String getAddress() {
+ return mAddress;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public void refreshName() {
+ fetchName();
+ dispatchAttributesChanged();
+ }
+
+ private void fetchName() {
+ mName = mLocalManager.getBluetoothManager().getRemoteName(mAddress);
+
+ if (TextUtils.isEmpty(mName)) {
+ mName = mAddress;
+ }
+ }
+
+ public void refresh() {
+ dispatchAttributesChanged();
+ }
+
+ public boolean isVisible() {
+ return mVisible;
+ }
+
+ void setVisible(boolean visible) {
+ if (mVisible != visible) {
+ mVisible = visible;
+ dispatchAttributesChanged();
+ }
+ }
+
+ public int getBondState() {
+ return mLocalManager.getBluetoothManager().getBondState(mAddress);
+ }
+
+ void setRssi(short rssi) {
+ if (mRssi != rssi) {
+ mRssi = rssi;
+ dispatchAttributesChanged();
+ }
+ }
+
+ /**
+ * Checks whether we are connected to this device (any profile counts).
+ *
+ * @return Whether it is connected.
+ */
+ public boolean isConnected() {
+ for (Profile profile : mProfiles) {
+ int status = LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile)
+ .getConnectionStatus(mAddress);
+ if (SettingsBtStatus.isConnectionStatusConnected(status)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public boolean isBusy() {
+ for (Profile profile : mProfiles) {
+ int status = LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile)
+ .getConnectionStatus(mAddress);
+ if (SettingsBtStatus.isConnectionStatusBusy(status)) {
+ return true;
+ }
+ }
+
+ if (getBondState() == BluetoothDevice.BOND_BONDING) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public int getBtClassDrawable() {
+
+ // First try looking at profiles
+ if (mProfiles.contains(Profile.A2DP)) {
+ return R.drawable.ic_bt_headphones_a2dp;
+ } else if (mProfiles.contains(Profile.HEADSET)) {
+ return R.drawable.ic_bt_headset_hfp;
+ }
+
+ // Fallback on class
+ switch (BluetoothClass.Device.Major.getDeviceMajor(mBtClass)) {
+ case BluetoothClass.Device.Major.COMPUTER:
+ return R.drawable.ic_bt_laptop;
+
+ case BluetoothClass.Device.Major.PHONE:
+ return R.drawable.ic_bt_cellphone;
+
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * Fetches a new value for the cached BT class.
+ */
+ private void fetchBtClass() {
+ mBtClass = mLocalManager.getBluetoothManager().getRemoteClass(mAddress);
+ mProfiles.clear();
+ LocalBluetoothProfileManager.fill(mBtClass, mProfiles);
+ }
+
+ /**
+ * Refreshes the UI for the BT class, including fetching the latest value
+ * for the class.
+ */
+ public void refreshBtClass() {
+ fetchBtClass();
+ dispatchAttributesChanged();
+ }
+
+ public int getSummary() {
+ // TODO: clean up
+ int oneOffSummary = getOneOffSummary();
+ if (oneOffSummary != 0) {
+ return oneOffSummary;
+ }
+
+ for (Profile profile : mProfiles) {
+ LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
+ .getProfileManager(mLocalManager, profile);
+ int connectionStatus = profileManager.getConnectionStatus(mAddress);
+
+ if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus) ||
+ connectionStatus == SettingsBtStatus.CONNECTION_STATUS_CONNECTING ||
+ connectionStatus == SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING) {
+ return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
+ }
+ }
+
+ return SettingsBtStatus.getPairingStatusSummary(getBondState());
+ }
+
+ /**
+ * We have special summaries when particular profiles are connected. This
+ * checks for those states and returns an applicable summary.
+ *
+ * @return A one-off summary that is applicable for the current state, or 0.
+ */
+ private int getOneOffSummary() {
+ boolean isA2dpConnected = false, isHeadsetConnected = false, isConnecting = false;
+
+ if (mProfiles.contains(Profile.A2DP)) {
+ LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
+ .getProfileManager(mLocalManager, Profile.A2DP);
+ isConnecting = profileManager.getConnectionStatus(mAddress) ==
+ SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
+ isA2dpConnected = profileManager.isConnected(mAddress);
+ }
+
+ if (mProfiles.contains(Profile.HEADSET)) {
+ LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
+ .getProfileManager(mLocalManager, Profile.HEADSET);
+ isConnecting |= profileManager.getConnectionStatus(mAddress) ==
+ SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
+ isHeadsetConnected = profileManager.isConnected(mAddress);
+ }
+
+ if (isConnecting) {
+ // If any of these important profiles is connecting, prefer that
+ return SettingsBtStatus.getConnectionStatusSummary(
+ SettingsBtStatus.CONNECTION_STATUS_CONNECTING);
+ } else if (isA2dpConnected && isHeadsetConnected) {
+ return R.string.bluetooth_summary_connected_to_a2dp_headset;
+ } else if (isA2dpConnected) {
+ return R.string.bluetooth_summary_connected_to_a2dp;
+ } else if (isHeadsetConnected) {
+ return R.string.bluetooth_summary_connected_to_headset;
+ } else {
+ return 0;
+ }
+ }
+
+ public List<Profile> getProfiles() {
+ return new ArrayList<Profile>(mProfiles);
+ }
+
+ public void onCreateContextMenu(ContextMenu menu) {
+ // No context menu if it is busy (none of these items are applicable if busy)
+ if (isBusy()) return;
+
+ int bondState = getBondState();
+ boolean isConnected = isConnected();
+ boolean hasProfiles = mProfiles.size() > 0;
+
+ menu.setHeaderTitle(getName());
+
+ if (isConnected) {
+ menu.add(0, CONTEXT_ITEM_DISCONNECT, 0, R.string.bluetooth_device_context_disconnect);
+ } else if (hasProfiles) {
+ // For connection action, show either "Connect" or "Pair & connect"
+ int connectString = (bondState == BluetoothDevice.BOND_NOT_BONDED)
+ ? R.string.bluetooth_device_context_pair_connect
+ : R.string.bluetooth_device_context_connect;
+ menu.add(0, CONTEXT_ITEM_CONNECT, 0, connectString);
+ }
+
+ if (bondState == BluetoothDevice.BOND_BONDED) {
+ // For unpair action, show either "Unpair" or "Disconnect & unpair"
+ int unpairString = isConnected
+ ? R.string.bluetooth_device_context_disconnect_unpair
+ : R.string.bluetooth_device_context_unpair;
+ menu.add(0, CONTEXT_ITEM_UNPAIR, 0, unpairString);
+
+ // Show the connection options item
+ menu.add(0, CONTEXT_ITEM_CONNECT_ADVANCED, 0,
+ R.string.bluetooth_device_context_connect_advanced);
+ }
+ }
+
+ /**
+ * Called when a context menu item is clicked.
+ *
+ * @param item The item that was clicked.
+ */
+ public void onContextItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case CONTEXT_ITEM_DISCONNECT:
+ disconnect();
+ break;
+
+ case CONTEXT_ITEM_CONNECT:
+ connect();
+ break;
+
+ case CONTEXT_ITEM_UNPAIR:
+ mLocalManager.getBluetoothManager().disconnectRemoteDeviceAcl(mAddress);
+ unpair();
+ break;
+
+ case CONTEXT_ITEM_CONNECT_ADVANCED:
+ Intent intent = new Intent();
+ // Need an activity context to open this in our task
+ Context context = mLocalManager.getForegroundActivity();
+ if (context == null) {
+ // Fallback on application context, and open in a new task
+ context = mLocalManager.getContext();
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
+ intent.setClass(context, ConnectSpecificProfilesActivity.class);
+ intent.putExtra(ConnectSpecificProfilesActivity.EXTRA_ADDRESS, mAddress);
+ context.startActivity(intent);
+ break;
+ }
+ }
+
+ public void registerCallback(Callback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.add(callback);
+ }
+ }
+
+ public void unregisterCallback(Callback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.remove(callback);
+ }
+ }
+
+ private void dispatchAttributesChanged() {
+ synchronized (mCallbacks) {
+ for (Callback callback : mCallbacks) {
+ callback.onDeviceAttributesChanged(this);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return mAddress;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ((o == null) || !(o instanceof LocalBluetoothDevice)) {
+ throw new ClassCastException();
+ }
+
+ return mAddress.equals(((LocalBluetoothDevice) o).mAddress);
+ }
+
+ @Override
+ public int hashCode() {
+ return mAddress.hashCode();
+ }
+
+ public int compareTo(LocalBluetoothDevice another) {
+ int comparison;
+
+ // Connected above not connected
+ comparison = (another.isConnected() ? 1 : 0) - (isConnected() ? 1 : 0);
+ if (comparison != 0) return comparison;
+
+ // Paired above not paired
+ comparison = (another.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0) -
+ (getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0);
+ if (comparison != 0) return comparison;
+
+ // Visible above not visible
+ comparison = (another.mVisible ? 1 : 0) - (mVisible ? 1 : 0);
+ if (comparison != 0) return comparison;
+
+ // Stronger signal above weaker signal
+ comparison = another.mRssi - mRssi;
+ if (comparison != 0) return comparison;
+
+ // Fallback on name
+ return getName().compareTo(another.getName());
+ }
+
+ public interface Callback {
+ void onDeviceAttributesChanged(LocalBluetoothDevice device);
+ }
+}
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothDeviceManager.java b/src/com/android/settings/bluetooth/LocalBluetoothDeviceManager.java
new file mode 100644
index 000000000..6bb2b4afe
--- /dev/null
+++ b/src/com/android/settings/bluetooth/LocalBluetoothDeviceManager.java
@@ -0,0 +1,229 @@
+/*
+ * 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.app.AlertDialog;
+import android.bluetooth.BluetoothDevice;
+import android.util.Log;
+import android.widget.Toast;
+import android.content.Context;
+
+import com.android.settings.R;
+import com.android.settings.bluetooth.LocalBluetoothManager.Callback;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * LocalBluetoothDeviceManager manages the set of remote Bluetooth devices.
+ */
+public class LocalBluetoothDeviceManager {
+ private static final String TAG = "LocalBluetoothDeviceManager";
+
+ final LocalBluetoothManager mLocalManager;
+ final List<Callback> mCallbacks;
+
+ final List<LocalBluetoothDevice> mDevices = new ArrayList<LocalBluetoothDevice>();
+
+ public LocalBluetoothDeviceManager(LocalBluetoothManager localManager) {
+ mLocalManager = localManager;
+ mCallbacks = localManager.getCallbacks();
+ readPairedDevices();
+ }
+
+ private synchronized boolean readPairedDevices() {
+ BluetoothDevice manager = mLocalManager.getBluetoothManager();
+ String[] bondedAddresses = manager.listBonds();
+ if (bondedAddresses == null) return false;
+
+ boolean deviceAdded = false;
+ for (String address : bondedAddresses) {
+ LocalBluetoothDevice device = findDevice(address);
+ if (device == null) {
+ device = new LocalBluetoothDevice(mLocalManager.getContext(), address);
+ mDevices.add(device);
+ dispatchDeviceAdded(device);
+ deviceAdded = true;
+ }
+ }
+
+ return deviceAdded;
+ }
+
+ public synchronized List<LocalBluetoothDevice> getDevicesCopy() {
+ return new ArrayList<LocalBluetoothDevice>(mDevices);
+ }
+
+ void onBluetoothStateChanged(boolean enabled) {
+ if (enabled) {
+ readPairedDevices();
+ }
+ }
+
+ public synchronized void onDeviceAppeared(String address, short rssi) {
+ boolean deviceAdded = false;
+
+ LocalBluetoothDevice device = findDevice(address);
+ if (device == null) {
+ device = new LocalBluetoothDevice(mLocalManager.getContext(), address);
+ mDevices.add(device);
+ deviceAdded = true;
+ }
+
+ device.setRssi(rssi);
+ device.setVisible(true);
+
+ if (deviceAdded) {
+ dispatchDeviceAdded(device);
+ }
+ }
+
+ public synchronized void onDeviceDisappeared(String address) {
+ LocalBluetoothDevice device = findDevice(address);
+ if (device == null) return;
+
+ device.setVisible(false);
+ checkForDeviceRemoval(device);
+ }
+
+ private void checkForDeviceRemoval(LocalBluetoothDevice device) {
+ if (device.getBondState() == BluetoothDevice.BOND_NOT_BONDED &&
+ !device.isVisible()) {
+ // If device isn't paired, remove it altogether
+ mDevices.remove(device);
+ dispatchDeviceDeleted(device);
+ }
+ }
+
+ public synchronized void onDeviceNameUpdated(String address) {
+ LocalBluetoothDevice device = findDevice(address);
+ if (device != null) {
+ device.refreshName();
+ }
+ }
+
+ public synchronized LocalBluetoothDevice findDevice(String address) {
+
+ for (int i = mDevices.size() - 1; i >= 0; i--) {
+ LocalBluetoothDevice device = mDevices.get(i);
+
+ if (device.getAddress().equals(address)) {
+ return device;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Attempts to get the name of a remote device, otherwise returns the address.
+ *
+ * @param address The address.
+ * @return The name, or if unavailable, the address.
+ */
+ public String getName(String address) {
+ LocalBluetoothDevice device = findDevice(address);
+ return device != null ? device.getName() : address;
+ }
+
+ private void dispatchDeviceAdded(LocalBluetoothDevice device) {
+ synchronized (mCallbacks) {
+ for (Callback callback : mCallbacks) {
+ callback.onDeviceAdded(device);
+ }
+ }
+
+ // TODO: divider between prev paired/connected and scanned
+ }
+
+ private void dispatchDeviceDeleted(LocalBluetoothDevice device) {
+ synchronized (mCallbacks) {
+ for (Callback callback : mCallbacks) {
+ callback.onDeviceDeleted(device);
+ }
+ }
+ }
+
+ public synchronized void onBondingStateChanged(String address, int bondState) {
+ LocalBluetoothDevice device = findDevice(address);
+ if (device == null) {
+ if (!readPairedDevices()) {
+ Log.e(TAG, "Got bonding state changed for " + address +
+ ", but we have no record of that device.");
+ }
+ return;
+ }
+
+ device.refresh();
+
+ if (bondState == BluetoothDevice.BOND_BONDED) {
+ // Auto-connect after pairing
+ device.connect();
+ }
+ }
+
+ /**
+ * Called when there is a bonding error.
+ *
+ * @param address The address of the remote device.
+ * @param reason The reason, one of the error reasons from
+ * BluetoothDevice.UNBOND_REASON_*
+ */
+ public synchronized void onBondingError(String address, int reason) {
+ mLocalManager.showError(address, R.string.bluetooth_error_title,
+ (reason == BluetoothDevice.UNBOND_REASON_AUTH_FAILED) ?
+ R.string.bluetooth_pairing_pin_error_message :
+ R.string.bluetooth_pairing_error_message);
+ }
+
+ public synchronized void onProfileStateChanged(String address) {
+ LocalBluetoothDevice device = findDevice(address);
+ if (device == null) return;
+
+ device.refresh();
+ }
+
+ public synchronized void onConnectingError(String address) {
+ LocalBluetoothDevice device = findDevice(address);
+ if (device == null) return;
+
+ /*
+ * Go through the device's delegate so we don't spam the user with
+ * errors connecting to different profiles, and instead make sure the
+ * user sees a single error for his single 'connect' action.
+ */
+ device.showConnectingError();
+ }
+
+ public synchronized void onScanningStateChanged(boolean started) {
+ if (!started) return;
+
+ // If starting a new scan, clear old visibility
+ for (int i = mDevices.size() - 1; i >= 0; i--) {
+ LocalBluetoothDevice device = mDevices.get(i);
+ device.setVisible(false);
+ checkForDeviceRemoval(device);
+ }
+ }
+
+ public synchronized void onBtClassChanged(String address) {
+ LocalBluetoothDevice device = findDevice(address);
+ if (device != null) {
+ device.refreshBtClass();
+ }
+ }
+}
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothManager.java b/src/com/android/settings/bluetooth/LocalBluetoothManager.java
new file mode 100644
index 000000000..4671fac6e
--- /dev/null
+++ b/src/com/android/settings/bluetooth/LocalBluetoothManager.java
@@ -0,0 +1,279 @@
+/*
+ * 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 com.android.settings.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.util.Log;
+import android.widget.Toast;
+
+// TODO: have some notion of shutting down. Maybe a minute after they leave BT settings?
+/**
+ * LocalBluetoothManager provides a simplified interface on top of a subset of
+ * the Bluetooth API.
+ */
+public class LocalBluetoothManager {
+ private static final String TAG = "LocalBluetoothManager";
+ static final boolean V = true;
+
+ public static final String EXTENDED_BLUETOOTH_STATE_CHANGED_ACTION =
+ "com.android.settings.bluetooth.intent.action.EXTENDED_BLUETOOTH_STATE_CHANGED";
+ private static final String SHARED_PREFERENCES_NAME = "bluetooth_settings";
+
+ private static LocalBluetoothManager INSTANCE;
+ /** Used when obtaining a reference to the singleton instance. */
+ private static Object INSTANCE_LOCK = new Object();
+ private boolean mInitialized;
+
+ private Context mContext;
+ /** If a BT-related activity is in the foreground, this will be it. */
+ private Activity mForegroundActivity;
+ private AlertDialog mErrorDialog = null;
+
+ private BluetoothDevice mManager;
+
+ private LocalBluetoothDeviceManager mLocalDeviceManager;
+ private BluetoothEventRedirector mEventRedirector;
+ private BluetoothA2dp mBluetoothA2dp;
+
+ public static enum ExtendedBluetoothState { ENABLED, ENABLING, DISABLED, DISABLING, UNKNOWN }
+ private ExtendedBluetoothState mState = ExtendedBluetoothState.UNKNOWN;
+
+ private List<Callback> mCallbacks = new ArrayList<Callback>();
+
+ private static final int SCAN_EXPIRATION_MS = 5 * 60 * 1000; // 5 mins
+ private long mLastScan;
+
+ public static LocalBluetoothManager getInstance(Context context) {
+ synchronized (INSTANCE_LOCK) {
+ if (INSTANCE == null) {
+ INSTANCE = new LocalBluetoothManager();
+ }
+
+ if (!INSTANCE.init(context)) {
+ return null;
+ }
+
+ return INSTANCE;
+ }
+ }
+
+ private boolean init(Context context) {
+ if (mInitialized) return true;
+ mInitialized = true;
+
+ // This will be around as long as this process is
+ mContext = context.getApplicationContext();
+
+ mManager = (BluetoothDevice) context.getSystemService(Context.BLUETOOTH_SERVICE);
+ if (mManager == null) {
+ return false;
+ }
+
+ mLocalDeviceManager = new LocalBluetoothDeviceManager(this);
+
+ mEventRedirector = new BluetoothEventRedirector(this);
+ mEventRedirector.start();
+
+ mBluetoothA2dp = new BluetoothA2dp(context);
+
+ return true;
+ }
+
+ public BluetoothDevice getBluetoothManager() {
+ return mManager;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public Activity getForegroundActivity() {
+ return mForegroundActivity;
+ }
+
+ public void setForegroundActivity(Activity activity) {
+ if (mErrorDialog != null) {
+ mErrorDialog.dismiss();
+ mErrorDialog = null;
+ }
+ mForegroundActivity = activity;
+ }
+
+ public SharedPreferences getSharedPreferences() {
+ return mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
+ }
+
+ public LocalBluetoothDeviceManager getLocalDeviceManager() {
+ return mLocalDeviceManager;
+ }
+
+ List<Callback> getCallbacks() {
+ return mCallbacks;
+ }
+
+ public void registerCallback(Callback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.add(callback);
+ }
+ }
+
+ public void unregisterCallback(Callback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.remove(callback);
+ }
+ }
+
+ public void startScanning(boolean force) {
+ if (mManager.isDiscovering()) {
+ /*
+ * Already discovering, but give the callback that information.
+ * Note: we only call the callbacks, not the same path as if the
+ * scanning state had really changed (in that case the device
+ * manager would clear its list of unpaired scanned devices).
+ */
+ dispatchScanningStateChanged(true);
+ } else {
+ if (!force) {
+ // Don't scan more than frequently than SCAN_EXPIRATION_MS,
+ // unless forced
+ if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
+ return;
+ }
+
+ // If we are playing music, don't scan unless forced.
+ List<String> sinks = mBluetoothA2dp.listConnectedSinks();
+ if (sinks != null) {
+ for (String address : sinks) {
+ if (mBluetoothA2dp.getSinkState(address) == BluetoothA2dp.STATE_PLAYING) {
+ return;
+ }
+ }
+ }
+ }
+
+ if (mManager.startDiscovery(true)) {
+ mLastScan = System.currentTimeMillis();
+ }
+ }
+ }
+
+ public ExtendedBluetoothState getBluetoothState() {
+
+ if (mState == ExtendedBluetoothState.UNKNOWN) {
+ syncBluetoothState();
+ }
+
+ return mState;
+ }
+
+ void setBluetoothStateInt(ExtendedBluetoothState state) {
+ mState = state;
+
+ /*
+ * TODO: change to callback method. originally it was broadcast to
+ * parallel the framework's method, but it just complicates things here.
+ */
+ // If this were a real API, I'd add as an extra
+ mContext.sendBroadcast(new Intent(EXTENDED_BLUETOOTH_STATE_CHANGED_ACTION));
+
+ if (state == ExtendedBluetoothState.ENABLED || state == ExtendedBluetoothState.DISABLED) {
+ mLocalDeviceManager.onBluetoothStateChanged(state == ExtendedBluetoothState.ENABLED);
+ }
+ }
+
+ private void syncBluetoothState() {
+ setBluetoothStateInt(mManager.isEnabled()
+ ? ExtendedBluetoothState.ENABLED
+ : ExtendedBluetoothState.DISABLED);
+ }
+
+ public void setBluetoothEnabled(boolean enabled) {
+ boolean wasSetStateSuccessful = enabled
+ ? mManager.enable()
+ : mManager.disable();
+
+ if (wasSetStateSuccessful) {
+ setBluetoothStateInt(enabled
+ ? ExtendedBluetoothState.ENABLING
+ : ExtendedBluetoothState.DISABLING);
+ } else {
+ if (V) {
+ Log.v(TAG,
+ "setBluetoothEnabled call, manager didn't return success for enabled: "
+ + enabled);
+ }
+
+ syncBluetoothState();
+ }
+ }
+
+ /**
+ * @param started True if scanning started, false if scanning finished.
+ */
+ void onScanningStateChanged(boolean started) {
+ // TODO: have it be a callback (once we switch bluetooth state changed to callback)
+ mLocalDeviceManager.onScanningStateChanged(started);
+ dispatchScanningStateChanged(started);
+ }
+
+ private void dispatchScanningStateChanged(boolean started) {
+ synchronized (mCallbacks) {
+ for (Callback callback : mCallbacks) {
+ callback.onScanningStateChanged(started);
+ }
+ }
+ }
+
+ public void showError(String address, int titleResId, int messageResId) {
+ LocalBluetoothDevice device = mLocalDeviceManager.findDevice(address);
+ if (device == null) return;
+
+ String name = device.getName();
+ String message = mContext.getString(messageResId, name);
+
+ if (mForegroundActivity != null) {
+ // Need an activity context to show a dialog
+ mErrorDialog = new AlertDialog.Builder(mForegroundActivity)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setTitle(titleResId)
+ .setMessage(message)
+ .setPositiveButton(android.R.string.ok, null)
+ .show();
+ } else {
+ // Fallback on a toast
+ Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ public interface Callback {
+ void onScanningStateChanged(boolean started);
+ void onDeviceAdded(LocalBluetoothDevice device);
+ void onDeviceDeleted(LocalBluetoothDevice device);
+ }
+
+}
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java b/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java
new file mode 100644
index 000000000..a1a2af60c
--- /dev/null
+++ b/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java
@@ -0,0 +1,280 @@
+/*
+ * 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 com.android.settings.R;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothError;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothClass;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.text.TextUtils;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * LocalBluetoothProfileManager is an abstract class defining the basic
+ * functionality related to a profile.
+ */
+public abstract class LocalBluetoothProfileManager {
+
+ // TODO: close profiles when we're shutting down
+ private static Map<Profile, LocalBluetoothProfileManager> sProfileMap =
+ new HashMap<Profile, LocalBluetoothProfileManager>();
+
+ protected LocalBluetoothManager mLocalManager;
+
+ public static LocalBluetoothProfileManager getProfileManager(LocalBluetoothManager localManager,
+ Profile profile) {
+
+ LocalBluetoothProfileManager profileManager;
+
+ synchronized (sProfileMap) {
+ profileManager = sProfileMap.get(profile);
+
+ if (profileManager == null) {
+ switch (profile) {
+ case A2DP:
+ profileManager = new A2dpProfileManager(localManager);
+ break;
+
+ case HEADSET:
+ profileManager = new HeadsetProfileManager(localManager);
+ break;
+ }
+
+ sProfileMap.put(profile, profileManager);
+ }
+ }
+
+ return profileManager;
+ }
+
+ /**
+ * Temporary method to fill profiles based on a device's class.
+ *
+ * @param btClass The class
+ * @param profiles The list of profiles to fill
+ */
+ public static void fill(int btClass, List<Profile> profiles) {
+ profiles.clear();
+
+ if (BluetoothA2dp.doesClassMatchSink(btClass)) {
+ profiles.add(Profile.A2DP);
+ }
+
+ if (BluetoothHeadset.doesClassMatch(btClass)) {
+ profiles.add(Profile.HEADSET);
+ }
+ }
+
+ protected LocalBluetoothProfileManager(LocalBluetoothManager localManager) {
+ mLocalManager = localManager;
+ }
+
+ public abstract int connect(String address);
+
+ public abstract int disconnect(String address);
+
+ public abstract int getConnectionStatus(String address);
+
+ public abstract int getSummary(String address);
+
+ public abstract boolean isPreferred(String address);
+
+ public abstract void setPreferred(String address, boolean preferred);
+
+ public boolean isConnected(String address) {
+ return SettingsBtStatus.isConnectionStatusConnected(getConnectionStatus(address));
+ }
+
+ // TODO: int instead of enum
+ public enum Profile {
+ HEADSET(R.string.bluetooth_profile_headset),
+ A2DP(R.string.bluetooth_profile_a2dp);
+
+ public final int localizedString;
+
+ private Profile(int localizedString) {
+ this.localizedString = localizedString;
+ }
+ }
+
+ /**
+ * A2dpProfileManager is an abstraction for the {@link BluetoothA2dp} service.
+ */
+ private static class A2dpProfileManager extends LocalBluetoothProfileManager {
+ private BluetoothA2dp mService;
+
+ public A2dpProfileManager(LocalBluetoothManager localManager) {
+ super(localManager);
+ mService = new BluetoothA2dp(localManager.getContext());
+ }
+
+ @Override
+ public int connect(String address) {
+ return mService.connectSink(address);
+ }
+
+ @Override
+ public int disconnect(String address) {
+ return mService.disconnectSink(address);
+ }
+
+ @Override
+ public int getConnectionStatus(String address) {
+ return convertState(mService.getSinkState(address));
+ }
+
+ @Override
+ public int getSummary(String address) {
+ int connectionStatus = getConnectionStatus(address);
+
+ if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
+ return R.string.bluetooth_a2dp_profile_summary_connected;
+ } else {
+ return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
+ }
+ }
+
+ @Override
+ public boolean isPreferred(String address) {
+ return mService.getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF;
+ }
+
+ @Override
+ public void setPreferred(String address, boolean preferred) {
+ mService.setSinkPriority(address,
+ preferred ? BluetoothA2dp.PRIORITY_AUTO : BluetoothA2dp.PRIORITY_OFF);
+ }
+
+ private static int convertState(int a2dpState) {
+ switch (a2dpState) {
+ case BluetoothA2dp.STATE_CONNECTED:
+ return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
+ case BluetoothA2dp.STATE_CONNECTING:
+ return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
+ case BluetoothA2dp.STATE_DISCONNECTED:
+ return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
+ case BluetoothA2dp.STATE_DISCONNECTING:
+ return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING;
+ case BluetoothA2dp.STATE_PLAYING:
+ return SettingsBtStatus.CONNECTION_STATUS_ACTIVE;
+ default:
+ return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
+ }
+ }
+ }
+
+ /**
+ * HeadsetProfileManager is an abstraction for the {@link BluetoothHeadset} service.
+ */
+ private static class HeadsetProfileManager extends LocalBluetoothProfileManager
+ implements BluetoothHeadset.ServiceListener {
+ private BluetoothHeadset mService;
+ private Handler mUiHandler = new Handler();
+
+ public HeadsetProfileManager(LocalBluetoothManager localManager) {
+ super(localManager);
+ mService = new BluetoothHeadset(localManager.getContext(), this);
+ }
+
+ public void onServiceConnected() {
+ // This could be called on a non-UI thread, funnel to UI thread.
+ mUiHandler.post(new Runnable() {
+ public void run() {
+ /*
+ * We just bound to the service, so refresh the UI of the
+ * headset device.
+ */
+ String address = mService.getHeadsetAddress();
+ if (TextUtils.isEmpty(address)) return;
+ mLocalManager.getLocalDeviceManager().onProfileStateChanged(address);
+ }
+ });
+ }
+
+ public void onServiceDisconnected() {
+ }
+
+ @Override
+ public int connect(String address) {
+ // Since connectHeadset fails if already connected to a headset, we
+ // disconnect from any headset first
+ mService.disconnectHeadset();
+ return mService.connectHeadset(address)
+ ? BluetoothError.SUCCESS : BluetoothError.ERROR;
+ }
+
+ @Override
+ public int disconnect(String address) {
+ if (mService.getHeadsetAddress().equals(address)) {
+ return mService.disconnectHeadset() ? BluetoothError.SUCCESS : BluetoothError.ERROR;
+ } else {
+ return BluetoothError.SUCCESS;
+ }
+ }
+
+ @Override
+ public int getConnectionStatus(String address) {
+ String headsetAddress = mService.getHeadsetAddress();
+ return headsetAddress != null && headsetAddress.equals(address)
+ ? convertState(mService.getState())
+ : SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
+ }
+
+ @Override
+ public int getSummary(String address) {
+ int connectionStatus = getConnectionStatus(address);
+
+ if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
+ return R.string.bluetooth_headset_profile_summary_connected;
+ } else {
+ return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
+ }
+ }
+
+ @Override
+ public boolean isPreferred(String address) {
+ return mService.getPriority(address) > BluetoothHeadset.PRIORITY_OFF;
+ }
+
+ @Override
+ public void setPreferred(String address, boolean preferred) {
+ mService.setPriority(address,
+ preferred ? BluetoothHeadset.PRIORITY_AUTO : BluetoothHeadset.PRIORITY_OFF);
+ }
+
+ private static int convertState(int headsetState) {
+ switch (headsetState) {
+ case BluetoothHeadset.STATE_CONNECTED:
+ return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
+ case BluetoothHeadset.STATE_CONNECTING:
+ return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
+ case BluetoothHeadset.STATE_DISCONNECTED:
+ return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
+ default:
+ return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/bluetooth/SettingsBtStatus.java b/src/com/android/settings/bluetooth/SettingsBtStatus.java
new file mode 100644
index 000000000..d2cbef5c5
--- /dev/null
+++ b/src/com/android/settings/bluetooth/SettingsBtStatus.java
@@ -0,0 +1,81 @@
+/*
+ * 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.BluetoothDevice;
+
+import com.android.settings.R;
+
+/**
+ * SettingsBtStatus is a helper class that contains constants for various status
+ * codes.
+ */
+public class SettingsBtStatus {
+ private static final String TAG = "SettingsBtStatus";
+
+ // Connection status
+
+ public static final int CONNECTION_STATUS_UNKNOWN = 0;
+ public static final int CONNECTION_STATUS_ACTIVE = 1;
+ /** Use {@link #isConnected} to check for the connected state */
+ public static final int CONNECTION_STATUS_CONNECTED = 2;
+ public static final int CONNECTION_STATUS_CONNECTING = 3;
+ public static final int CONNECTION_STATUS_DISCONNECTED = 4;
+ public static final int CONNECTION_STATUS_DISCONNECTING = 5;
+
+ public static final int getConnectionStatusSummary(int connectionStatus) {
+ switch (connectionStatus) {
+ case CONNECTION_STATUS_ACTIVE:
+ return R.string.bluetooth_connected;
+ case CONNECTION_STATUS_CONNECTED:
+ return R.string.bluetooth_connected;
+ case CONNECTION_STATUS_CONNECTING:
+ return R.string.bluetooth_connecting;
+ case CONNECTION_STATUS_DISCONNECTED:
+ return R.string.bluetooth_disconnected;
+ case CONNECTION_STATUS_DISCONNECTING:
+ return R.string.bluetooth_disconnecting;
+ case CONNECTION_STATUS_UNKNOWN:
+ return R.string.bluetooth_unknown;
+ default:
+ return 0;
+ }
+ }
+
+ public static final boolean isConnectionStatusConnected(int connectionStatus) {
+ return connectionStatus == CONNECTION_STATUS_ACTIVE
+ || connectionStatus == CONNECTION_STATUS_CONNECTED;
+ }
+
+ public static final boolean isConnectionStatusBusy(int connectionStatus) {
+ return connectionStatus == CONNECTION_STATUS_CONNECTING
+ || connectionStatus == CONNECTION_STATUS_DISCONNECTING;
+ }
+
+ public static final int getPairingStatusSummary(int bondState) {
+ switch (bondState) {
+ case BluetoothDevice.BOND_BONDED:
+ return R.string.bluetooth_paired;
+ case BluetoothDevice.BOND_BONDING:
+ return R.string.bluetooth_pairing;
+ case BluetoothDevice.BOND_NOT_BONDED:
+ return R.string.bluetooth_not_connected;
+ default:
+ return 0;
+ }
+ }
+}