summaryrefslogtreecommitdiffstats
path: root/src/com/android/settings/bluetooth
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2008-12-17 18:06:01 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2008-12-17 18:06:01 -0800
commitabc48f80d8747b4fc051b7dd364355ee667a9bac (patch)
tree31ae577fe29d75963b071e738703e4db83ad6580 /src/com/android/settings/bluetooth
parentde2d9f5f109265873196f1615e1f3546b114aaa7 (diff)
downloadpackages_apps_settings-abc48f80d8747b4fc051b7dd364355ee667a9bac.zip
packages_apps_settings-abc48f80d8747b4fc051b7dd364355ee667a9bac.tar.gz
packages_apps_settings-abc48f80d8747b4fc051b7dd364355ee667a9bac.tar.bz2
Code drop from //branches/cupcake/...@124589
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.java192
-rw-r--r--src/com/android/settings/bluetooth/BluetoothEnabler.java149
-rw-r--r--src/com/android/settings/bluetooth/BluetoothEventRedirector.java159
-rw-r--r--src/com/android/settings/bluetooth/BluetoothNamePreference.java79
-rw-r--r--src/com/android/settings/bluetooth/BluetoothPinDialog.java112
-rw-r--r--src/com/android/settings/bluetooth/BluetoothPinRequest.java96
-rw-r--r--src/com/android/settings/bluetooth/BluetoothSettings.java258
-rw-r--r--src/com/android/settings/bluetooth/ConnectSpecificProfilesActivity.java297
-rw-r--r--src/com/android/settings/bluetooth/LocalBluetoothDevice.java558
-rw-r--r--src/com/android/settings/bluetooth/LocalBluetoothDeviceManager.java209
-rw-r--r--src/com/android/settings/bluetooth/LocalBluetoothManager.java260
-rw-r--r--src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java312
-rw-r--r--src/com/android/settings/bluetooth/SettingsBtStatus.java85
14 files changed, 2888 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 0000000..f0a8189
--- /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 0000000..f895696
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java
@@ -0,0 +1,192 @@
+/*
+ * 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.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) {
+ handleModeChanged(intent.getIntExtra(BluetoothIntent.MODE,
+ BluetoothDevice.MODE_UNKNOWN));
+ }
+ };
+
+ 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;
+ }
+
+ mContext.registerReceiver(mReceiver,
+ new IntentFilter(BluetoothIntent.MODE_CHANGED_ACTION));
+ mCheckBoxPreference.setOnPreferenceChangeListener(this);
+
+ handleModeChanged(mLocalManager.getBluetoothManager().getMode());
+ }
+
+ 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.setMode(BluetoothDevice.MODE_DISCOVERABLE);
+ handleModeChanged(BluetoothDevice.MODE_DISCOVERABLE);
+
+ } else {
+ manager.setMode(BluetoothDevice.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.MODE_DISCOVERABLE) {
+ mCheckBoxPreference.setChecked(true);
+ updateCountdownSummary();
+
+ } else {
+ mCheckBoxPreference.setChecked(false);
+ }
+ }
+
+ private void updateCountdownSummary() {
+ int mode = mLocalManager.getBluetoothManager().getMode();
+ if (mode != BluetoothDevice.MODE_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 0000000..661700f
--- /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 0000000..bcad206
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothEventRedirector.java
@@ -0,0 +1,159 @@
+/*
+ * 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.BluetoothIntent;
+import android.bluetooth.IBluetoothDeviceCallback;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+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 Handler mUiHandler = new Handler();
+
+ private IBluetoothDeviceCallback mBtDevCallback = new IBluetoothDeviceCallback.Stub() {
+ public void onCreateBondingResult(final String address, final int result) {
+ if (V) {
+ Log.v(TAG, "onCreateBondingResult(" + address + ", " + result + ")");
+ }
+
+ mUiHandler.post(new Runnable() {
+ public void run() {
+ boolean wasSuccess = result == BluetoothDevice.RESULT_SUCCESS;
+ LocalBluetoothDeviceManager deviceManager = mManager.getLocalDeviceManager();
+ deviceManager.onBondingStateChanged(address, wasSuccess);
+ if (!wasSuccess) {
+ deviceManager.onBondingError(address);
+ }
+ }
+ });
+ }
+
+ public void onEnableResult(int result) { }
+ public void onGetRemoteServiceChannelResult(String address, int channel) { }
+ };
+
+ 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.BONDING_CREATED_ACTION)) {
+ mManager.getLocalDeviceManager().onBondingStateChanged(address, true);
+ } else if (action.equals(BluetoothIntent.BONDING_REMOVED_ACTION)) {
+ mManager.getLocalDeviceManager().onBondingStateChanged(address, false);
+
+ } 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) {
+ mManager.getLocalDeviceManager().onConnectingError(address);
+ }
+
+ } 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) {
+ mManager.getLocalDeviceManager().onConnectingError(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.BONDING_CREATED_ACTION);
+ filter.addAction(BluetoothIntent.BONDING_REMOVED_ACTION);
+
+ // Fine-grained state broadcasts
+ filter.addAction(BluetoothA2dp.SINK_STATE_CHANGED_ACTION);
+ filter.addAction(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION);
+
+ mManager.getContext().registerReceiver(mBroadcastReceiver, filter);
+ }
+
+ public void stop() {
+ mManager.getContext().unregisterReceiver(mBroadcastReceiver);
+ }
+
+ public IBluetoothDeviceCallback getBluetoothDeviceCallback() {
+ return mBtDevCallback;
+ }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothNamePreference.java b/src/com/android/settings/bluetooth/BluetoothNamePreference.java
new file mode 100644
index 0000000..3065b26
--- /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 0000000..291d0c1
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothPinDialog.java
@@ -0,0 +1,112 @@
+/*
+ * 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.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.InputFilter;
+import android.text.method.DigitsKeyListener;
+import android.util.Log;
+import android.view.View;
+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 {
+ private static final String TAG = "BluetoothPinDialog";
+
+ private LocalBluetoothManager mLocalManager;
+ private String mAddress;
+ private EditText mPinView;
+
+ @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();
+ }
+
+ 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);
+
+ return view;
+ }
+
+ private void onPair(String pin) {
+ byte[] pinBytes = BluetoothDevice.convertPinToBytes(pin);
+
+ if (pinBytes == null) {
+ return;
+ }
+
+ mLocalManager.getBluetoothManager().setPin(mAddress, pinBytes);
+ }
+
+ private void onCancel() {
+ mLocalManager.getBluetoothManager().cancelPin(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;
+ }
+ }
+
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothPinRequest.java b/src/com/android/settings/bluetooth/BluetoothPinRequest.java
new file mode 100644
index 0000000..619052d
--- /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 0000000..316e831
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothSettings.java
@@ -0,0 +1,258 @@
+/*
+ * 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(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);
+ }
+ }
+}
diff --git a/src/com/android/settings/bluetooth/ConnectSpecificProfilesActivity.java b/src/com/android/settings/bluetooth/ConnectSpecificProfilesActivity.java
new file mode 100644
index 0000000..f29ec79
--- /dev/null
+++ b/src/com/android/settings/bluetooth/ConnectSpecificProfilesActivity.java
@@ -0,0 +1,297 @@
+/*
+ * 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.preference.PreferenceScreen;
+import android.text.TextUtils;
+import android.widget.ImageView;
+import android.widget.TextView;
+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(true);
+ }
+
+ @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) {
+ switchModes(checked, false);
+ }
+
+ private void onProfileCheckedStateChanged(Profile profile, boolean checked) {
+ if (mOnlineMode) {
+ if (checked) {
+ mDevice.connect(profile);
+ } else {
+ mDevice.disconnect(profile);
+ }
+ }
+
+ LocalBluetoothProfileManager.setPreferredProfile(this, mDevice.getAddress(), profile,
+ checked);
+ }
+
+ public void onDeviceAttributesChanged(LocalBluetoothDevice device) {
+ refresh(false);
+ }
+
+ private void refresh(boolean forceRefresh) {
+ // The online mode could have changed
+ updateOnlineMode(forceRefresh);
+ refreshProfiles();
+ refreshOnlineModePreference();
+ }
+
+ private void updateOnlineMode(boolean force) {
+ // Connected or Connecting (and Disconnecting, which is fine)
+ boolean onlineMode = mDevice.isConnected() || mDevice.isBusy();
+ switchModes(onlineMode, force);
+ }
+
+ /**
+ * Switches between online/offline mode.
+ *
+ * @param onlineMode Whether to be in online mode, or offline mode.
+ */
+ private void switchModes(boolean onlineMode, boolean force) {
+ if (mOnlineMode != onlineMode || force) {
+ mOnlineMode = onlineMode;
+
+ if (onlineMode) {
+ mDevice.connect();
+ } else {
+ mDevice.disconnect();
+ }
+
+ refreshOnlineModePreference();
+ }
+ }
+
+ private void refreshOnlineModePreference() {
+ mOnlineModePreference.setChecked(mOnlineMode);
+
+ /**
+ * 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) continue;
+
+ 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);
+
+ profilePref.setSummary(getProfileSummary(profileManager, profile, address,
+ connectionStatus, mOnlineMode));
+
+ profilePref.setChecked(
+ LocalBluetoothProfileManager.isPreferredProfile(this, address, profile));
+ }
+
+ 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 0000000..a8f79ff
--- /dev/null
+++ b/src/com/android/settings/bluetooth/LocalBluetoothDevice.java
@@ -0,0 +1,558 @@
+/*
+ * 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.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 int mPairingStatus;
+
+ 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 pairingStatus = getPairingStatus();
+
+ if (isConnected()) {
+ askDisconnect();
+ } else if (pairingStatus == SettingsBtStatus.PAIRING_STATUS_PAIRED) {
+ connect();
+ } else if (pairingStatus == SettingsBtStatus.PAIRING_STATUS_UNPAIRED) {
+ 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;
+
+ Context context = mLocalManager.getContext();
+ boolean hasAtLeastOnePreferredProfile = false;
+ for (Profile profile : mProfiles) {
+ if (LocalBluetoothProfileManager.isPreferredProfile(context, mAddress, profile)) {
+ hasAtLeastOnePreferredProfile = true;
+ connect(profile);
+ }
+ }
+
+ if (!hasAtLeastOnePreferredProfile) {
+ connectAndPreferAllProfiles();
+ }
+ }
+
+ private void connectAndPreferAllProfiles() {
+ if (!ensurePaired()) return;
+
+ Context context = mLocalManager.getContext();
+ for (Profile profile : mProfiles) {
+ LocalBluetoothProfileManager.setPreferredProfile(context, mAddress, profile, true);
+ connect(profile);
+ }
+ }
+
+ public void connect(Profile profile) {
+ if (!ensurePaired()) return;
+
+ LocalBluetoothProfileManager profileManager =
+ LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile);
+ int status = profileManager.getConnectionStatus(mAddress);
+ if (!SettingsBtStatus.isConnectionStatusConnected(status)) {
+ mIsConnectingErrorPossible = true;
+ if (profileManager.connect(mAddress) != BluetoothDevice.RESULT_SUCCESS) {
+ showConnectingError();
+ }
+ }
+ }
+
+ 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 (getPairingStatus() == SettingsBtStatus.PAIRING_STATUS_UNPAIRED) {
+ pair();
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ public void pair() {
+ BluetoothDevice manager = mLocalManager.getBluetoothManager();
+
+ // Pairing doesn't work if scanning, so cancel
+ if (manager.isDiscovering()) {
+ manager.cancelDiscovery();
+ }
+
+ if (mLocalManager.createBonding(mAddress)) {
+ setPairingStatus(SettingsBtStatus.PAIRING_STATUS_PAIRING);
+ }
+ }
+
+ public void unpair() {
+ BluetoothDevice manager = mLocalManager.getBluetoothManager();
+
+ switch (getPairingStatus()) {
+ case SettingsBtStatus.PAIRING_STATUS_PAIRED:
+ manager.removeBonding(mAddress);
+ break;
+
+ case SettingsBtStatus.PAIRING_STATUS_PAIRING:
+ manager.cancelBondingProcess(mAddress);
+ break;
+ }
+ }
+
+ private void fillData() {
+ BluetoothDevice manager = mLocalManager.getBluetoothManager();
+
+ fetchName();
+ mBtClass = manager.getRemoteClass(mAddress);
+
+ LocalBluetoothProfileManager.fill(mBtClass, mProfiles);
+
+ mPairingStatus = manager.hasBonding(mAddress)
+ ? SettingsBtStatus.PAIRING_STATUS_PAIRED
+ : SettingsBtStatus.PAIRING_STATUS_UNPAIRED;
+
+ 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 getPairingStatus() {
+ return mPairingStatus;
+ }
+
+ void setPairingStatus(int pairingStatus) {
+ if (mPairingStatus != pairingStatus) {
+ mPairingStatus = pairingStatus;
+ dispatchAttributesChanged();
+ }
+ }
+
+ 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 (getPairingStatus() == SettingsBtStatus.PAIRING_STATUS_PAIRING) {
+ 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;
+ }
+ }
+
+ 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);
+ }
+ }
+
+ int pairingStatus = getPairingStatus();
+ return SettingsBtStatus.getPairingStatusSummary(pairingStatus);
+ }
+
+ /**
+ * 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;
+
+ // No context menu if there are no profiles
+ if (mProfiles.size() == 0) return;
+
+ int pairingStatus = getPairingStatus();
+ boolean isConnected = isConnected();
+
+ menu.setHeaderTitle(getName());
+
+ if (isConnected) {
+ menu.add(0, CONTEXT_ITEM_DISCONNECT, 0, R.string.bluetooth_device_context_disconnect);
+ } else {
+ // For connection action, show either "Connect" or "Pair & connect"
+ int connectString = pairingStatus == SettingsBtStatus.PAIRING_STATUS_UNPAIRED
+ ? R.string.bluetooth_device_context_pair_connect
+ : R.string.bluetooth_device_context_connect;
+ menu.add(0, CONTEXT_ITEM_CONNECT, 0, connectString);
+ }
+
+ if (pairingStatus == SettingsBtStatus.PAIRING_STATUS_PAIRED) {
+ // 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:
+ 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.mPairingStatus == SettingsBtStatus.PAIRING_STATUS_PAIRED ? 1 : 0) -
+ (mPairingStatus == SettingsBtStatus.PAIRING_STATUS_PAIRED ? 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 0000000..48a41f1
--- /dev/null
+++ b/src/com/android/settings/bluetooth/LocalBluetoothDeviceManager.java
@@ -0,0 +1,209 @@
+/*
+ * 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 void readPairedDevices() {
+ BluetoothDevice manager = mLocalManager.getBluetoothManager();
+ String[] bondedAddresses = manager.listBondings();
+ if (bondedAddresses == null) return;
+
+ for (String address : bondedAddresses) {
+ LocalBluetoothDevice device = findDevice(address);
+ if (device == null) {
+ device = new LocalBluetoothDevice(mLocalManager.getContext(), address);
+ mDevices.add(device);
+ dispatchDeviceAdded(device);
+ }
+ }
+ }
+
+ 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.getPairingStatus() == SettingsBtStatus.PAIRING_STATUS_UNPAIRED &&
+ !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, boolean created) {
+ LocalBluetoothDevice device = findDevice(address);
+ if (device == null) {
+ Log.e(TAG, "Got bonding state changed for " + address +
+ ", but we have no record of that device.");
+ return;
+ }
+
+ device.setPairingStatus(created ? SettingsBtStatus.PAIRING_STATUS_PAIRED
+ : SettingsBtStatus.PAIRING_STATUS_UNPAIRED);
+ checkForDeviceRemoval(device);
+
+ if (created) {
+ // Auto-connect after pairing
+ device.connect();
+ }
+ }
+
+ public synchronized void onBondingError(String address) {
+ mLocalManager.showError(address, R.string.bluetooth_error_title,
+ 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);
+ }
+ }
+}
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothManager.java b/src/com/android/settings/bluetooth/LocalBluetoothManager.java
new file mode 100644
index 0000000..9db9e77
--- /dev/null
+++ b/src/com/android/settings/bluetooth/LocalBluetoothManager.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.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+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 BluetoothDevice mManager;
+
+ private LocalBluetoothDeviceManager mLocalDeviceManager;
+ private BluetoothEventRedirector mEventRedirector;
+
+ 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();
+
+ return true;
+ }
+
+ public BluetoothDevice getBluetoothManager() {
+ return mManager;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public Activity getForegroundActivity() {
+ return mForegroundActivity;
+ }
+
+ public void setForegroundActivity(Activity activity) {
+ 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 {
+
+ // Don't scan more than frequently than SCAN_EXPIRATION_MS, unless forced
+ if (!force && mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) 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 boolean createBonding(String address) {
+ return mManager.createBonding(address, mEventRedirector.getBluetoothDeviceCallback());
+ }
+
+ 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
+ AlertDialog ad = 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 0000000..b614712
--- /dev/null
+++ b/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java
@@ -0,0 +1,312 @@
+/*
+ * 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 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;
+ }
+
+ // TODO: remove once the framework has this API
+ public static boolean isPreferredProfile(Context context, String address, Profile profile) {
+ return getPreferredProfileSharedPreferences(context).getBoolean(
+ getPreferredProfileKey(address, profile), true);
+ }
+
+ public static void setPreferredProfile(Context context, String address, Profile profile,
+ boolean preferred) {
+ getPreferredProfileSharedPreferences(context).edit().putBoolean(
+ getPreferredProfileKey(address, profile), preferred).commit();
+ }
+
+ private static SharedPreferences getPreferredProfileSharedPreferences(Context context) {
+ return context.getSharedPreferences("bluetooth_preferred_profiles", Context.MODE_PRIVATE);
+ }
+
+ private static String getPreferredProfileKey(String address, Profile profile) {
+ return address + "_" + profile.toString();
+ }
+
+ /**
+ * 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 (A2dpProfileManager.doesClassMatch(btClass)) {
+ profiles.add(Profile.A2DP);
+ }
+
+ if (HeadsetProfileManager.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 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());
+ // TODO: block until connection?
+ }
+
+ @Override
+ public int connect(String address) {
+ return mService.connectSink(address);
+ }
+
+ @Override
+ public int disconnect(String address) {
+ return mService.disconnectSink(address);
+ }
+
+ static boolean doesClassMatch(int btClass) {
+ if (BluetoothClass.Service.hasService(btClass, BluetoothClass.Service.RENDER)) {
+ return true;
+ }
+
+ // By the specification A2DP sinks must indicate the RENDER service
+ // class, but some do not (Chordette). So match on a few more to be
+ // safe
+ switch (BluetoothClass.Device.getDevice(btClass)) {
+ case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
+ case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES:
+ case BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER:
+ case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ @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);
+ }
+ }
+
+ 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 {
+ private BluetoothHeadset mService;
+
+ public HeadsetProfileManager(LocalBluetoothManager localManager) {
+ super(localManager);
+
+// final boolean[] isServiceConnected = new boolean[1];
+// BluetoothHeadset.ServiceListener l = new BluetoothHeadset.ServiceListener() {
+// public void onServiceConnected() {
+// synchronized (this) {
+// isServiceConnected[0] = true;
+// notifyAll();
+// }
+// }
+// public void onServiceDisconnected() {
+// mService = null;
+// }
+// };
+
+ // TODO: block, but can't on UI thread
+ mService = new BluetoothHeadset(localManager.getContext(), null);
+
+// synchronized (l) {
+// while (!isServiceConnected[0]) {
+// try {
+// l.wait(100);
+// } catch (InterruptedException e) {
+// throw new IllegalStateException(e);
+// }
+// }
+// }
+ }
+
+ @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, null)
+ ? 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;
+ }
+ }
+
+ static boolean doesClassMatch(int btClass) {
+ switch (BluetoothClass.Device.getDevice(btClass)) {
+ case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
+ case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
+ case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ @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);
+ }
+ }
+
+ 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 0000000..051d666
--- /dev/null
+++ b/src/com/android/settings/bluetooth/SettingsBtStatus.java
@@ -0,0 +1,85 @@
+/*
+ * 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;
+
+/**
+ * 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;
+ }
+
+ // Pairing status
+
+ public static final int PAIRING_STATUS_UNPAIRED = 0;
+ public static final int PAIRING_STATUS_PAIRED = 1;
+ public static final int PAIRING_STATUS_PAIRING = 2;
+
+ public static final int getPairingStatusSummary(int pairingStatus) {
+ switch (pairingStatus) {
+ case PAIRING_STATUS_PAIRED:
+ return R.string.bluetooth_paired;
+ case PAIRING_STATUS_PAIRING:
+ return R.string.bluetooth_pairing;
+ case PAIRING_STATUS_UNPAIRED:
+ return R.string.bluetooth_not_connected;
+ default:
+ return 0;
+ }
+ }
+}