Merge "Move non-ui bt settings code to SettingsLib"

This commit is contained in:
Jason Monk
2015-02-24 13:23:29 +00:00
committed by Android (Google) Code Review
57 changed files with 3803 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 764 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 993 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 947 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

View File

@@ -67,4 +67,87 @@
<!-- Status message of Wi-Fi when it is connected by a Wi-Fi assistant application. [CHAR LIMIT=NONE] -->
<string name="connected_via_wfa">Connected via Wi\u2011Fi assistant</string>
<!-- Bluetooth settings. Message when a device is disconnected -->
<string name="bluetooth_disconnected">Disconnected</string>
<!-- Bluetooth settings. Message when disconnecting from a device -->
<string name="bluetooth_disconnecting">Disconnecting\u2026</string>
<!-- Bluetooth settings. Message when connecting to a device -->
<string name="bluetooth_connecting">Connecting\u2026</string>
<!-- Bluetooth settings. Message when connected to a device. [CHAR LIMIT=40] -->
<string name="bluetooth_connected">Connected</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the A2DP profile. -->
<string name="bluetooth_profile_a2dp">Media audio</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the headset or handsfree profile. -->
<string name="bluetooth_profile_headset">Phone audio</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the OPP profile. -->
<string name="bluetooth_profile_opp">File transfer</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the HID profile. -->
<string name="bluetooth_profile_hid">Input device</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (accessing Internet through remote device). [CHAR LIMIT=40] -->
<string name="bluetooth_profile_pan">Internet access</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the PBAP profile. [CHAR LIMIT=40] -->
<string name="bluetooth_profile_pbap">Contact sharing</string>
<!-- Bluetooth settings. The user-visible summary string that is used whenever referring to the PBAP profile (sharing contacts). [CHAR LIMIT=60] -->
<string name="bluetooth_profile_pbap_summary">Use for contact sharing</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (sharing this device's Internet connection). [CHAR LIMIT=40] -->
<string name="bluetooth_profile_pan_nap">Internet connection sharing</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the map profile. -->
<string name="bluetooth_profile_map">Message Access</string>
<!-- Bluetooth settings. Connection options screen. The summary for the A2DP checkbox preference when A2DP is connected. -->
<string name="bluetooth_a2dp_profile_summary_connected">Connected to media audio</string>
<!-- Bluetooth settings. Connection options screen. The summary for the headset checkbox preference when headset is connected. -->
<string name="bluetooth_headset_profile_summary_connected">Connected to phone audio</string>
<!-- Bluetooth settings. Connection options screen. The summary for the OPP checkbox preference when OPP is connected. -->
<string name="bluetooth_opp_profile_summary_connected">Connected to file transfer server</string>
<!-- Bluetooth settings. Connection options screen. The summary for the map checkbox preference when map is connected. -->
<string name="bluetooth_map_profile_summary_connected">Connected to map</string>
<!-- Bluetooth settings. Connection options screen. The summary for the OPP checkbox preference when OPP is not connected. -->
<string name="bluetooth_opp_profile_summary_not_connected">Not connected to file transfer server</string>
<!-- Bluetooth settings. Connection options screen. The summary for the HID checkbox preference when HID is connected. -->
<string name="bluetooth_hid_profile_summary_connected">Connected to input device</string>
<!-- Bluetooth settings. Connection options screen. The summary for the checkbox preference when PAN is connected (user role). [CHAR LIMIT=25]-->
<string name="bluetooth_pan_user_profile_summary_connected">Connected to device for Internet access</string>
<!-- Bluetooth settings. Connection options screen. The summary for the checkbox preference when PAN is connected (NAP role). [CHAR LIMIT=25]-->
<string name="bluetooth_pan_nap_profile_summary_connected">Sharing local Internet connection with device</string>
<!-- Bluetooth settings. Connection options screen. The summary
for the PAN checkbox preference that describes how checking it
will set the PAN profile as preferred. -->
<string name="bluetooth_pan_profile_summary_use_for">Use for Internet access</string>
<!-- Bluetooth settings. Connection options screen. The summary for the map checkbox preference that describes how checking it will set the map profile as preferred. -->
<string name="bluetooth_map_profile_summary_use_for">Use for map</string>
<!-- Bluetooth settings. Connection options screen. The summary for the A2DP checkbox preference that describes how checking it will set the A2DP profile as preferred. -->
<string name="bluetooth_a2dp_profile_summary_use_for">Use for media audio</string>
<!-- Bluetooth settings. Connection options screen. The summary for the headset checkbox preference that describes how checking it will set the headset profile as preferred. -->
<string name="bluetooth_headset_profile_summary_use_for">Use for phone audio</string>
<!-- Bluetooth settings. Connection options screen. The summary for the OPP checkbox preference that describes how checking it will set the OPP profile as preferred. -->
<string name="bluetooth_opp_profile_summary_use_for">Use for file transfer</string>
<!-- Bluetooth settings. Connection options screen. The summary
for the HID checkbox preference that describes how checking it
will set the HID profile as preferred. -->
<string name="bluetooth_hid_profile_summary_use_for">Use for input</string>
<!-- Button text for accepting an incoming pairing request. [CHAR LIMIT=20] -->
<string name="bluetooth_pairing_accept">Pair</string>
<!-- Button text for accepting an incoming pairing request in all caps. [CHAR LIMIT=20] -->
<string name="bluetooth_pairing_accept_all_caps">PAIR</string>
<!-- Button text for declining an incoming pairing request. [CHAR LIMIT=20] -->
<string name="bluetooth_pairing_decline">Cancel</string>
<!-- Message in pairing dialogs. [CHAR LIMIT=NONE] -->
<string name="bluetooth_pairing_will_share_phonebook">Pairing grants access to your contacts and call history when connected.</string>
<!-- Message for the error dialog when BT pairing fails generically. -->
<string name="bluetooth_pairing_error_message">Couldn\'t pair with <xliff:g id="device_name">%1$s</xliff:g>.</string>
<!-- Message for the error dialog when BT pairing fails because the PIN /
Passkey entered is incorrect. -->
<string name="bluetooth_pairing_pin_error_message">Couldn\'t pair with <xliff:g id="device_name">%1$s</xliff:g> because of an incorrect PIN or passkey.</string>
<!-- Message for the error dialog when BT pairing fails because the other device is down. -->
<string name="bluetooth_pairing_device_down_error_message">Can\'t communicate with <xliff:g id="device_name">%1$s</xliff:g>.</string>
<!-- Message for the error dialog when BT pairing fails because the other device rejected the pairing. -->
<string name="bluetooth_pairing_rejected_error_message">Pairing rejected by <xliff:g id="device_name">%1$s</xliff:g>.</string>
</resources>

View File

@@ -0,0 +1,214 @@
/*
* Copyright (C) 2011 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.settingslib.bluetooth;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.content.Context;
import android.os.ParcelUuid;
import android.util.Log;
import com.android.settingslib.R;
import java.util.ArrayList;
import java.util.List;
public final class A2dpProfile implements LocalBluetoothProfile {
private static final String TAG = "A2dpProfile";
private static boolean V = false;
private BluetoothA2dp mService;
private boolean mIsProfileReady;
private final LocalBluetoothAdapter mLocalAdapter;
private final CachedBluetoothDeviceManager mDeviceManager;
static final ParcelUuid[] SINK_UUIDS = {
BluetoothUuid.AudioSink,
BluetoothUuid.AdvAudioDist,
};
static final String NAME = "A2DP";
private final LocalBluetoothProfileManager mProfileManager;
// Order of this profile in device profiles list
private static final int ORDINAL = 1;
// These callbacks run on the main thread.
private final class A2dpServiceListener
implements BluetoothProfile.ServiceListener {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (V) Log.d(TAG,"Bluetooth service connected");
mService = (BluetoothA2dp) proxy;
// We just bound to the service, so refresh the UI for any connected A2DP devices.
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
while (!deviceList.isEmpty()) {
BluetoothDevice nextDevice = deviceList.remove(0);
CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
// we may add a new device here, but generally this should not happen
if (device == null) {
Log.w(TAG, "A2dpProfile found new device: " + nextDevice);
device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
}
device.onProfileStateChanged(A2dpProfile.this, BluetoothProfile.STATE_CONNECTED);
device.refresh();
}
mIsProfileReady=true;
}
public void onServiceDisconnected(int profile) {
if (V) Log.d(TAG,"Bluetooth service disconnected");
mIsProfileReady=false;
}
}
public boolean isProfileReady() {
return mIsProfileReady;
}
A2dpProfile(Context context, LocalBluetoothAdapter adapter,
CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mProfileManager = profileManager;
mLocalAdapter.getProfileProxy(context, new A2dpServiceListener(),
BluetoothProfile.A2DP);
}
public boolean isConnectable() {
return true;
}
public boolean isAutoConnectable() {
return true;
}
public List<BluetoothDevice> getConnectedDevices() {
if (mService == null) return new ArrayList<BluetoothDevice>(0);
return mService.getDevicesMatchingConnectionStates(
new int[] {BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTING});
}
public boolean connect(BluetoothDevice device) {
if (mService == null) return false;
List<BluetoothDevice> sinks = getConnectedDevices();
if (sinks != null) {
for (BluetoothDevice sink : sinks) {
mService.disconnect(sink);
}
}
return mService.connect(device);
}
public boolean disconnect(BluetoothDevice device) {
if (mService == null) return false;
// Downgrade priority as user is disconnecting the headset.
if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON){
mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
}
return mService.disconnect(device);
}
public int getConnectionStatus(BluetoothDevice device) {
if (mService == null) {
return BluetoothProfile.STATE_DISCONNECTED;
}
return mService.getConnectionState(device);
}
public boolean isPreferred(BluetoothDevice device) {
if (mService == null) return false;
return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
}
public int getPreferred(BluetoothDevice device) {
if (mService == null) return BluetoothProfile.PRIORITY_OFF;
return mService.getPriority(device);
}
public void setPreferred(BluetoothDevice device, boolean preferred) {
if (mService == null) return;
if (preferred) {
if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
}
} else {
mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
}
}
boolean isA2dpPlaying() {
if (mService == null) return false;
List<BluetoothDevice> sinks = mService.getConnectedDevices();
if (!sinks.isEmpty()) {
if (mService.isA2dpPlaying(sinks.get(0))) {
return true;
}
}
return false;
}
public String toString() {
return NAME;
}
public int getOrdinal() {
return ORDINAL;
}
public int getNameResource(BluetoothDevice device) {
return R.string.bluetooth_profile_a2dp;
}
public int getSummaryResourceForDevice(BluetoothDevice device) {
int state = getConnectionStatus(device);
switch (state) {
case BluetoothProfile.STATE_DISCONNECTED:
return R.string.bluetooth_a2dp_profile_summary_use_for;
case BluetoothProfile.STATE_CONNECTED:
return R.string.bluetooth_a2dp_profile_summary_connected;
default:
return Utils.getConnectionStateSummary(state);
}
}
public int getDrawableResource(BluetoothClass btClass) {
return R.drawable.ic_bt_headphones_a2dp;
}
protected void finalize() {
if (V) Log.d(TAG, "finalize()");
if (mService != null) {
try {
BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.A2DP,
mService);
mService = null;
}catch (Throwable t) {
Log.w(TAG, "Error cleaning up A2DP proxy", t);
}
}
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2011 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.settingslib.bluetooth;
/**
* BluetoothCallback provides a callback interface for the settings
* UI to receive events from {@link BluetoothEventManager}.
*/
public interface BluetoothCallback {
void onBluetoothStateChanged(int bluetoothState);
void onScanningStateChanged(boolean started);
void onDeviceAdded(CachedBluetoothDevice cachedDevice);
void onDeviceDeleted(CachedBluetoothDevice cachedDevice);
void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState);
}

View File

@@ -0,0 +1,169 @@
/*
* Copyright (C) 2011 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.settingslib.bluetooth;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothUuid;
import android.os.ParcelUuid;
import android.util.Log;
/**
* BluetoothDeviceFilter contains a static method that returns a
* Filter object that returns whether or not the BluetoothDevice
* passed to it matches the specified filter type constant from
* {@link android.bluetooth.BluetoothDevicePicker}.
*/
public final class BluetoothDeviceFilter {
private static final String TAG = "BluetoothDeviceFilter";
/** The filter interface to external classes. */
public interface Filter {
boolean matches(BluetoothDevice device);
}
/** All filter singleton (referenced directly). */
public static final Filter ALL_FILTER = new AllFilter();
/** Bonded devices only filter (referenced directly). */
public static final Filter BONDED_DEVICE_FILTER = new BondedDeviceFilter();
/** Unbonded devices only filter (referenced directly). */
public static final Filter UNBONDED_DEVICE_FILTER = new UnbondedDeviceFilter();
/** Table of singleton filter objects. */
private static final Filter[] FILTERS = {
ALL_FILTER, // FILTER_TYPE_ALL
new AudioFilter(), // FILTER_TYPE_AUDIO
new TransferFilter(), // FILTER_TYPE_TRANSFER
new PanuFilter(), // FILTER_TYPE_PANU
new NapFilter() // FILTER_TYPE_NAP
};
/** Private constructor. */
private BluetoothDeviceFilter() {
}
/**
* Returns the singleton {@link Filter} object for the specified type,
* or {@link #ALL_FILTER} if the type value is out of range.
*
* @param filterType a constant from BluetoothDevicePicker
* @return a singleton object implementing the {@link Filter} interface.
*/
public static Filter getFilter(int filterType) {
if (filterType >= 0 && filterType < FILTERS.length) {
return FILTERS[filterType];
} else {
Log.w(TAG, "Invalid filter type " + filterType + " for device picker");
return ALL_FILTER;
}
}
/** Filter that matches all devices. */
private static final class AllFilter implements Filter {
public boolean matches(BluetoothDevice device) {
return true;
}
}
/** Filter that matches only bonded devices. */
private static final class BondedDeviceFilter implements Filter {
public boolean matches(BluetoothDevice device) {
return device.getBondState() == BluetoothDevice.BOND_BONDED;
}
}
/** Filter that matches only unbonded devices. */
private static final class UnbondedDeviceFilter implements Filter {
public boolean matches(BluetoothDevice device) {
return device.getBondState() != BluetoothDevice.BOND_BONDED;
}
}
/** Parent class of filters based on UUID and/or Bluetooth class. */
private abstract static class ClassUuidFilter implements Filter {
abstract boolean matches(ParcelUuid[] uuids, BluetoothClass btClass);
public boolean matches(BluetoothDevice device) {
return matches(device.getUuids(), device.getBluetoothClass());
}
}
/** Filter that matches devices that support AUDIO profiles. */
private static final class AudioFilter extends ClassUuidFilter {
@Override
boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) {
if (uuids != null) {
if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS)) {
return true;
}
if (BluetoothUuid.containsAnyUuid(uuids, HeadsetProfile.UUIDS)) {
return true;
}
} else if (btClass != null) {
if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP) ||
btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
return true;
}
}
return false;
}
}
/** Filter that matches devices that support Object Transfer. */
private static final class TransferFilter extends ClassUuidFilter {
@Override
boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) {
if (uuids != null) {
if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) {
return true;
}
}
return btClass != null
&& btClass.doesClassMatch(BluetoothClass.PROFILE_OPP);
}
}
/** Filter that matches devices that support PAN User (PANU) profile. */
private static final class PanuFilter extends ClassUuidFilter {
@Override
boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) {
if (uuids != null) {
if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.PANU)) {
return true;
}
}
return btClass != null
&& btClass.doesClassMatch(BluetoothClass.PROFILE_PANU);
}
}
/** Filter that matches devices that support NAP profile. */
private static final class NapFilter extends ClassUuidFilter {
@Override
boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) {
if (uuids != null) {
if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP)) {
return true;
}
}
return btClass != null
&& btClass.doesClassMatch(BluetoothClass.PROFILE_NAP);
}
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (C) 2012 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.settingslib.bluetooth;
/* Required to handle timeout notification when phone is suspended */
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class BluetoothDiscoverableTimeoutReceiver extends BroadcastReceiver {
private static final String TAG = "BluetoothDiscoverableTimeoutReceiver";
private static final String INTENT_DISCOVERABLE_TIMEOUT =
"android.bluetooth.intent.DISCOVERABLE_TIMEOUT";
public static void setDiscoverableAlarm(Context context, long alarmTime) {
Log.d(TAG, "setDiscoverableAlarm(): alarmTime = " + alarmTime);
Intent intent = new Intent(INTENT_DISCOVERABLE_TIMEOUT);
intent.setClass(context, BluetoothDiscoverableTimeoutReceiver.class);
PendingIntent pending = PendingIntent.getBroadcast(
context, 0, intent, 0);
AlarmManager alarmManager =
(AlarmManager) context.getSystemService (Context.ALARM_SERVICE);
if (pending != null) {
// Cancel any previous alarms that do the same thing.
alarmManager.cancel(pending);
Log.d(TAG, "setDiscoverableAlarm(): cancel prev alarm");
}
pending = PendingIntent.getBroadcast(
context, 0, intent, 0);
alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pending);
}
public static void cancelDiscoverableAlarm(Context context) {
Log.d(TAG, "cancelDiscoverableAlarm(): Enter");
Intent intent = new Intent(INTENT_DISCOVERABLE_TIMEOUT);
intent.setClass(context, BluetoothDiscoverableTimeoutReceiver.class);
PendingIntent pending = PendingIntent.getBroadcast(
context, 0, intent, PendingIntent.FLAG_NO_CREATE);
if (pending != null) {
// Cancel any previous alarms that do the same thing.
AlarmManager alarmManager =
(AlarmManager) context.getSystemService (Context.ALARM_SERVICE);
alarmManager.cancel(pending);
}
}
@Override
public void onReceive(Context context, Intent intent) {
LocalBluetoothAdapter localBluetoothAdapter = LocalBluetoothAdapter.getInstance();
if(localBluetoothAdapter != null &&
localBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
Log.d(TAG, "Disable discoverable...");
localBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
} else {
Log.e(TAG, "localBluetoothAdapter is NULL!!");
}
}
};

View File

@@ -0,0 +1,362 @@
/*
* Copyright (C) 2011 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.settingslib.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;
import com.android.settingslib.R;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* BluetoothEventManager receives broadcasts and callbacks from the Bluetooth
* API and dispatches the event on the UI thread to the right class in the
* Settings.
*/
public final class BluetoothEventManager {
private static final String TAG = "BluetoothEventManager";
private final LocalBluetoothAdapter mLocalAdapter;
private final CachedBluetoothDeviceManager mDeviceManager;
private LocalBluetoothProfileManager mProfileManager;
private final IntentFilter mAdapterIntentFilter, mProfileIntentFilter;
private final Map<String, Handler> mHandlerMap;
private Context mContext;
private final Collection<BluetoothCallback> mCallbacks =
new ArrayList<BluetoothCallback>();
interface Handler {
void onReceive(Context context, Intent intent, BluetoothDevice device);
}
void addHandler(String action, Handler handler) {
mHandlerMap.put(action, handler);
mAdapterIntentFilter.addAction(action);
}
void addProfileHandler(String action, Handler handler) {
mHandlerMap.put(action, handler);
mProfileIntentFilter.addAction(action);
}
// Set profile manager after construction due to circular dependency
void setProfileManager(LocalBluetoothProfileManager manager) {
mProfileManager = manager;
}
BluetoothEventManager(LocalBluetoothAdapter adapter,
CachedBluetoothDeviceManager deviceManager, Context context) {
mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mAdapterIntentFilter = new IntentFilter();
mProfileIntentFilter = new IntentFilter();
mHandlerMap = new HashMap<String, Handler>();
mContext = context;
// Bluetooth on/off broadcasts
addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());
// Discovery broadcasts
addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true));
addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false));
addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());
addHandler(BluetoothDevice.ACTION_DISAPPEARED, new DeviceDisappearedHandler());
addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler());
// Pairing broadcasts
addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler());
addHandler(BluetoothDevice.ACTION_PAIRING_CANCEL, new PairingCancelHandler());
// Fine-grained state broadcasts
addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler());
addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler());
// Dock event broadcasts
addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler());
mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter);
}
void registerProfileIntentReceiver() {
mContext.registerReceiver(mBroadcastReceiver, mProfileIntentFilter);
}
/** Register to start receiving callbacks for Bluetooth events. */
public void registerCallback(BluetoothCallback callback) {
synchronized (mCallbacks) {
mCallbacks.add(callback);
}
}
/** Unregister to stop receiving callbacks for Bluetooth events. */
public void unregisterCallback(BluetoothCallback callback) {
synchronized (mCallbacks) {
mCallbacks.remove(callback);
}
}
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
BluetoothDevice device = intent
.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Handler handler = mHandlerMap.get(action);
if (handler != null) {
handler.onReceive(context, intent, device);
}
}
};
private class AdapterStateChangedHandler implements Handler {
public void onReceive(Context context, Intent intent,
BluetoothDevice device) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
BluetoothAdapter.ERROR);
// update local profiles and get paired devices
mLocalAdapter.setBluetoothStateInt(state);
// send callback to update UI and possibly start scanning
synchronized (mCallbacks) {
for (BluetoothCallback callback : mCallbacks) {
callback.onBluetoothStateChanged(state);
}
}
// Inform CachedDeviceManager that the adapter state has changed
mDeviceManager.onBluetoothStateChanged(state);
}
}
private class ScanningStateChangedHandler implements Handler {
private final boolean mStarted;
ScanningStateChangedHandler(boolean started) {
mStarted = started;
}
public void onReceive(Context context, Intent intent,
BluetoothDevice device) {
synchronized (mCallbacks) {
for (BluetoothCallback callback : mCallbacks) {
callback.onScanningStateChanged(mStarted);
}
}
mDeviceManager.onScanningStateChanged(mStarted);
}
}
private class DeviceFoundHandler implements Handler {
public void onReceive(Context context, Intent intent,
BluetoothDevice device) {
short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS);
String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
// TODO Pick up UUID. They should be available for 2.1 devices.
// Skip for now, there's a bluez problem and we are not getting uuids even for 2.1.
CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
if (cachedDevice == null) {
cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: "
+ cachedDevice);
// callback to UI to create Preference for new device
dispatchDeviceAdded(cachedDevice);
}
cachedDevice.setRssi(rssi);
cachedDevice.setBtClass(btClass);
cachedDevice.setNewName(name);
cachedDevice.setVisible(true);
}
}
private void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
synchronized (mCallbacks) {
for (BluetoothCallback callback : mCallbacks) {
callback.onDeviceAdded(cachedDevice);
}
}
}
private class DeviceDisappearedHandler implements Handler {
public void onReceive(Context context, Intent intent,
BluetoothDevice device) {
CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
if (cachedDevice == null) {
Log.w(TAG, "received ACTION_DISAPPEARED for an unknown device: " + device);
return;
}
if (CachedBluetoothDeviceManager.onDeviceDisappeared(cachedDevice)) {
synchronized (mCallbacks) {
for (BluetoothCallback callback : mCallbacks) {
callback.onDeviceDeleted(cachedDevice);
}
}
}
}
}
private class NameChangedHandler implements Handler {
public void onReceive(Context context, Intent intent,
BluetoothDevice device) {
mDeviceManager.onDeviceNameUpdated(device);
}
}
private class BondStateChangedHandler implements Handler {
public void onReceive(Context context, Intent intent,
BluetoothDevice device) {
if (device == null) {
Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
return;
}
int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
BluetoothDevice.ERROR);
CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
if (cachedDevice == null) {
Log.w(TAG, "CachedBluetoothDevice for device " + device +
" not found, calling readPairedDevices().");
if (!readPairedDevices()) {
Log.e(TAG, "Got bonding state changed for " + device +
", but we have no record of that device.");
return;
}
cachedDevice = mDeviceManager.findDevice(device);
if (cachedDevice == null) {
Log.e(TAG, "Got bonding state changed for " + device +
", but device not added in cache.");
return;
}
}
synchronized (mCallbacks) {
for (BluetoothCallback callback : mCallbacks) {
callback.onDeviceBondStateChanged(cachedDevice, bondState);
}
}
cachedDevice.onBondingStateChanged(bondState);
if (bondState == BluetoothDevice.BOND_NONE) {
int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON,
BluetoothDevice.ERROR);
showUnbondMessage(context, cachedDevice.getName(), reason);
}
}
/**
* Called when we have reached the unbonded state.
*
* @param reason one of the error reasons from
* BluetoothDevice.UNBOND_REASON_*
*/
private void showUnbondMessage(Context context, String name, int reason) {
int errorMsg;
switch(reason) {
case BluetoothDevice.UNBOND_REASON_AUTH_FAILED:
errorMsg = R.string.bluetooth_pairing_pin_error_message;
break;
case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED:
errorMsg = R.string.bluetooth_pairing_rejected_error_message;
break;
case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN:
errorMsg = R.string.bluetooth_pairing_device_down_error_message;
break;
case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS:
case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT:
case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS:
case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED:
errorMsg = R.string.bluetooth_pairing_error_message;
break;
default:
Log.w(TAG, "showUnbondMessage: Not displaying any message for reason: " + reason);
return;
}
Utils.showError(context, name, errorMsg);
}
}
private class ClassChangedHandler implements Handler {
public void onReceive(Context context, Intent intent,
BluetoothDevice device) {
mDeviceManager.onBtClassChanged(device);
}
}
private class UuidChangedHandler implements Handler {
public void onReceive(Context context, Intent intent,
BluetoothDevice device) {
mDeviceManager.onUuidChanged(device);
}
}
private class PairingCancelHandler implements Handler {
public void onReceive(Context context, Intent intent, BluetoothDevice device) {
if (device == null) {
Log.e(TAG, "ACTION_PAIRING_CANCEL with no EXTRA_DEVICE");
return;
}
int errorMsg = R.string.bluetooth_pairing_error_message;
CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
Utils.showError(context, cachedDevice.getName(), errorMsg);
}
}
private class DockEventHandler implements Handler {
public void onReceive(Context context, Intent intent, BluetoothDevice device) {
// Remove if unpair device upon undocking
int anythingButUnDocked = Intent.EXTRA_DOCK_STATE_UNDOCKED + 1;
int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, anythingButUnDocked);
if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) {
CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
if (cachedDevice != null) {
cachedDevice.setVisible(false);
}
}
}
}
}
boolean readPairedDevices() {
Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices();
if (bondedDevices == null) {
return false;
}
boolean deviceAdded = false;
for (BluetoothDevice device : bondedDevices) {
CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
if (cachedDevice == null) {
cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
dispatchDeviceAdded(cachedDevice);
deviceAdded = true;
}
}
return deviceAdded;
}
}

View File

@@ -0,0 +1,787 @@
/*
* 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.settingslib.bluetooth;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.ParcelUuid;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
import android.bluetooth.BluetoothAdapter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
/**
* CachedBluetoothDevice 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 final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
private static final String TAG = "CachedBluetoothDevice";
private static final boolean DEBUG = Utils.V;
private final Context mContext;
private final LocalBluetoothAdapter mLocalAdapter;
private final LocalBluetoothProfileManager mProfileManager;
private final BluetoothDevice mDevice;
private String mName;
private short mRssi;
private BluetoothClass mBtClass;
private HashMap<LocalBluetoothProfile, Integer> mProfileConnectionState;
private final List<LocalBluetoothProfile> mProfiles =
new ArrayList<LocalBluetoothProfile>();
// List of profiles that were previously in mProfiles, but have been removed
private final List<LocalBluetoothProfile> mRemovedProfiles =
new ArrayList<LocalBluetoothProfile>();
// Device supports PANU but not NAP: remove PanProfile after device disconnects from NAP
private boolean mLocalNapRoleConnected;
private boolean mVisible;
private int mPhonebookPermissionChoice;
private int mMessagePermissionChoice;
private int mMessageRejectionCount;
private final Collection<Callback> mCallbacks = new ArrayList<Callback>();
// Following constants indicate the user's choices of Phone book/message access settings
// User hasn't made any choice or settings app has wiped out the memory
public final static int ACCESS_UNKNOWN = 0;
// User has accepted the connection and let Settings app remember the decision
public final static int ACCESS_ALLOWED = 1;
// User has rejected the connection and let Settings app remember the decision
public final static int ACCESS_REJECTED = 2;
// How many times user should reject the connection to make the choice persist.
private final static int MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST = 2;
private final static String MESSAGE_REJECTION_COUNT_PREFS_NAME = "bluetooth_message_reject";
/**
* 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;
/**
* Last time a bt profile auto-connect was attempted.
* If an ACTION_UUID intent comes in within
* MAX_UUID_DELAY_FOR_AUTO_CONNECT milliseconds, we will try auto-connect
* again with the new UUIDs
*/
private long mConnectAttempted;
// See mConnectAttempted
private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000;
/** Auto-connect after pairing only if locally initiated. */
private boolean mConnectAfterPairing;
/**
* Describes the current device and profile for logging.
*
* @param profile Profile to describe
* @return Description of the device and profile
*/
private String describe(LocalBluetoothProfile profile) {
StringBuilder sb = new StringBuilder();
sb.append("Address:").append(mDevice);
if (profile != null) {
sb.append(" Profile:").append(profile);
}
return sb.toString();
}
void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) {
if (Utils.D) {
Log.d(TAG, "onProfileStateChanged: profile " + profile +
" newProfileState " + newProfileState);
}
if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF)
{
if (Utils.D) Log.d(TAG, " BT Turninig Off...Profile conn state change ignored...");
return;
}
mProfileConnectionState.put(profile, newProfileState);
if (newProfileState == BluetoothProfile.STATE_CONNECTED) {
if (profile instanceof MapProfile) {
profile.setPreferred(mDevice, true);
} else if (!mProfiles.contains(profile)) {
mRemovedProfiles.remove(profile);
mProfiles.add(profile);
if (profile instanceof PanProfile &&
((PanProfile) profile).isLocalRoleNap(mDevice)) {
// Device doesn't support NAP, so remove PanProfile on disconnect
mLocalNapRoleConnected = true;
}
}
} else if (profile instanceof MapProfile &&
newProfileState == BluetoothProfile.STATE_DISCONNECTED) {
profile.setPreferred(mDevice, false);
} else if (mLocalNapRoleConnected && profile instanceof PanProfile &&
((PanProfile) profile).isLocalRoleNap(mDevice) &&
newProfileState == BluetoothProfile.STATE_DISCONNECTED) {
Log.d(TAG, "Removing PanProfile from device after NAP disconnect");
mProfiles.remove(profile);
mRemovedProfiles.add(profile);
mLocalNapRoleConnected = false;
}
}
CachedBluetoothDevice(Context context,
LocalBluetoothAdapter adapter,
LocalBluetoothProfileManager profileManager,
BluetoothDevice device) {
mContext = context;
mLocalAdapter = adapter;
mProfileManager = profileManager;
mDevice = device;
mProfileConnectionState = new HashMap<LocalBluetoothProfile, Integer>();
fillData();
}
public void disconnect() {
for (LocalBluetoothProfile profile : mProfiles) {
disconnect(profile);
}
// Disconnect PBAP server in case its connected
// This is to ensure all the profiles are disconnected as some CK/Hs do not
// disconnect PBAP connection when HF connection is brought down
PbapServerProfile PbapProfile = mProfileManager.getPbapProfile();
if (PbapProfile.getConnectionStatus(mDevice) == BluetoothProfile.STATE_CONNECTED)
{
PbapProfile.disconnect(mDevice);
}
}
public void disconnect(LocalBluetoothProfile profile) {
if (profile.disconnect(mDevice)) {
if (Utils.D) {
Log.d(TAG, "Command sent successfully:DISCONNECT " + describe(profile));
}
}
}
public void connect(boolean connectAllProfiles) {
if (!ensurePaired()) {
return;
}
mConnectAttempted = SystemClock.elapsedRealtime();
connectWithoutResettingTimer(connectAllProfiles);
}
void onBondingDockConnect() {
// Attempt to connect if UUIDs are available. Otherwise,
// we will connect when the ACTION_UUID intent arrives.
connect(false);
}
private void connectWithoutResettingTimer(boolean connectAllProfiles) {
// Try to initialize the profiles if they were not.
if (mProfiles.isEmpty()) {
// if mProfiles is empty, then do not invoke updateProfiles. This causes a race
// condition with carkits during pairing, wherein RemoteDevice.UUIDs have been updated
// from bluetooth stack but ACTION.uuid is not sent yet.
// Eventually ACTION.uuid will be received which shall trigger the connection of the
// various profiles
// If UUIDs are not available yet, connect will be happen
// upon arrival of the ACTION_UUID intent.
Log.d(TAG, "No profiles. Maybe we will connect later");
return;
}
// Reset the only-show-one-error-dialog tracking variable
mIsConnectingErrorPossible = true;
int preferredProfiles = 0;
for (LocalBluetoothProfile profile : mProfiles) {
if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) {
if (profile.isPreferred(mDevice)) {
++preferredProfiles;
connectInt(profile);
}
}
}
if (DEBUG) Log.d(TAG, "Preferred profiles = " + preferredProfiles);
if (preferredProfiles == 0) {
connectAutoConnectableProfiles();
}
}
private void connectAutoConnectableProfiles() {
if (!ensurePaired()) {
return;
}
// Reset the only-show-one-error-dialog tracking variable
mIsConnectingErrorPossible = true;
for (LocalBluetoothProfile profile : mProfiles) {
if (profile.isAutoConnectable()) {
profile.setPreferred(mDevice, true);
connectInt(profile);
}
}
}
/**
* Connect this device to the specified profile.
*
* @param profile the profile to use with the remote device
*/
public void connectProfile(LocalBluetoothProfile profile) {
mConnectAttempted = SystemClock.elapsedRealtime();
// Reset the only-show-one-error-dialog tracking variable
mIsConnectingErrorPossible = true;
connectInt(profile);
// Refresh the UI based on profile.connect() call
refresh();
}
synchronized void connectInt(LocalBluetoothProfile profile) {
if (!ensurePaired()) {
return;
}
if (profile.connect(mDevice)) {
if (Utils.D) {
Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile));
}
return;
}
Log.i(TAG, "Failed to connect " + profile.toString() + " to " + mName);
}
private boolean ensurePaired() {
if (getBondState() == BluetoothDevice.BOND_NONE) {
startPairing();
return false;
} else {
return true;
}
}
public boolean startPairing() {
// Pairing is unreliable while scanning, so cancel discovery
if (mLocalAdapter.isDiscovering()) {
mLocalAdapter.cancelDiscovery();
}
if (!mDevice.createBond()) {
return false;
}
mConnectAfterPairing = true; // auto-connect after pairing
return true;
}
/**
* Return true if user initiated pairing on this device. The message text is
* slightly different for local vs. remote initiated pairing dialogs.
*/
boolean isUserInitiatedPairing() {
return mConnectAfterPairing;
}
public void unpair() {
int state = getBondState();
if (state == BluetoothDevice.BOND_BONDING) {
mDevice.cancelBondProcess();
}
if (state != BluetoothDevice.BOND_NONE) {
final BluetoothDevice dev = mDevice;
if (dev != null) {
final boolean successful = dev.removeBond();
if (successful) {
if (Utils.D) {
Log.d(TAG, "Command sent successfully:REMOVE_BOND " + describe(null));
}
} else if (Utils.V) {
Log.v(TAG, "Framework rejected command immediately:REMOVE_BOND " +
describe(null));
}
}
}
}
public int getProfileConnectionState(LocalBluetoothProfile profile) {
if (mProfileConnectionState == null ||
mProfileConnectionState.get(profile) == null) {
// If cache is empty make the binder call to get the state
int state = profile.getConnectionStatus(mDevice);
mProfileConnectionState.put(profile, state);
}
return mProfileConnectionState.get(profile);
}
public void clearProfileConnectionState ()
{
if (Utils.D) {
Log.d(TAG," Clearing all connection state for dev:" + mDevice.getName());
}
for (LocalBluetoothProfile profile :getProfiles()) {
mProfileConnectionState.put(profile, BluetoothProfile.STATE_DISCONNECTED);
}
}
// TODO: do any of these need to run async on a background thread?
private void fillData() {
fetchName();
fetchBtClass();
updateProfiles();
migratePhonebookPermissionChoice();
migrateMessagePermissionChoice();
fetchMessageRejectionCount();
mVisible = false;
dispatchAttributesChanged();
}
public BluetoothDevice getDevice() {
return mDevice;
}
public String getName() {
return mName;
}
/**
* Populate name from BluetoothDevice.ACTION_FOUND intent
*/
void setNewName(String name) {
if (mName == null) {
mName = name;
if (mName == null || TextUtils.isEmpty(mName)) {
mName = mDevice.getAddress();
}
dispatchAttributesChanged();
}
}
/**
* user changes the device name
*/
public void setName(String name) {
if (!mName.equals(name)) {
mName = name;
mDevice.setAlias(name);
dispatchAttributesChanged();
}
}
void refreshName() {
fetchName();
dispatchAttributesChanged();
}
private void fetchName() {
mName = mDevice.getAliasName();
if (TextUtils.isEmpty(mName)) {
mName = mDevice.getAddress();
if (DEBUG) Log.d(TAG, "Device has no name (yet), use address: " + mName);
}
}
void refresh() {
dispatchAttributesChanged();
}
public boolean isVisible() {
return mVisible;
}
public void setVisible(boolean visible) {
if (mVisible != visible) {
mVisible = visible;
dispatchAttributesChanged();
}
}
public int getBondState() {
return mDevice.getBondState();
}
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 (LocalBluetoothProfile profile : mProfiles) {
int status = getProfileConnectionState(profile);
if (status == BluetoothProfile.STATE_CONNECTED) {
return true;
}
}
return false;
}
public boolean isConnectedProfile(LocalBluetoothProfile profile) {
int status = getProfileConnectionState(profile);
return status == BluetoothProfile.STATE_CONNECTED;
}
public boolean isBusy() {
for (LocalBluetoothProfile profile : mProfiles) {
int status = getProfileConnectionState(profile);
if (status == BluetoothProfile.STATE_CONNECTING
|| status == BluetoothProfile.STATE_DISCONNECTING) {
return true;
}
}
return getBondState() == BluetoothDevice.BOND_BONDING;
}
/**
* Fetches a new value for the cached BT class.
*/
private void fetchBtClass() {
mBtClass = mDevice.getBluetoothClass();
}
private boolean updateProfiles() {
ParcelUuid[] uuids = mDevice.getUuids();
if (uuids == null) return false;
ParcelUuid[] localUuids = mLocalAdapter.getUuids();
if (localUuids == null) return false;
/**
* Now we know if the device supports PBAP, update permissions...
*/
processPhonebookAccess();
mProfileManager.updateProfiles(uuids, localUuids, mProfiles, mRemovedProfiles,
mLocalNapRoleConnected, mDevice);
if (DEBUG) {
Log.e(TAG, "updating profiles for " + mDevice.getAliasName());
BluetoothClass bluetoothClass = mDevice.getBluetoothClass();
if (bluetoothClass != null) Log.v(TAG, "Class: " + bluetoothClass.toString());
Log.v(TAG, "UUID:");
for (ParcelUuid uuid : uuids) {
Log.v(TAG, " " + uuid);
}
}
return true;
}
/**
* Refreshes the UI for the BT class, including fetching the latest value
* for the class.
*/
void refreshBtClass() {
fetchBtClass();
dispatchAttributesChanged();
}
/**
* Refreshes the UI when framework alerts us of a UUID change.
*/
void onUuidChanged() {
updateProfiles();
if (DEBUG) {
Log.e(TAG, "onUuidChanged: Time since last connect"
+ (SystemClock.elapsedRealtime() - mConnectAttempted));
}
/*
* If a connect was attempted earlier without any UUID, we will do the
* connect now.
*/
if (!mProfiles.isEmpty()
&& (mConnectAttempted + MAX_UUID_DELAY_FOR_AUTO_CONNECT) > SystemClock
.elapsedRealtime()) {
connectWithoutResettingTimer(false);
}
dispatchAttributesChanged();
}
void onBondingStateChanged(int bondState) {
if (bondState == BluetoothDevice.BOND_NONE) {
mProfiles.clear();
mConnectAfterPairing = false; // cancel auto-connect
setPhonebookPermissionChoice(ACCESS_UNKNOWN);
setMessagePermissionChoice(ACCESS_UNKNOWN);
mMessageRejectionCount = 0;
saveMessageRejectionCount();
}
refresh();
if (bondState == BluetoothDevice.BOND_BONDED) {
if (mDevice.isBluetoothDock()) {
onBondingDockConnect();
} else if (mConnectAfterPairing) {
connect(false);
}
mConnectAfterPairing = false;
}
}
void setBtClass(BluetoothClass btClass) {
if (btClass != null && mBtClass != btClass) {
mBtClass = btClass;
dispatchAttributesChanged();
}
}
public BluetoothClass getBtClass() {
return mBtClass;
}
public List<LocalBluetoothProfile> getProfiles() {
return Collections.unmodifiableList(mProfiles);
}
public List<LocalBluetoothProfile> getConnectableProfiles() {
List<LocalBluetoothProfile> connectableProfiles =
new ArrayList<LocalBluetoothProfile>();
for (LocalBluetoothProfile profile : mProfiles) {
if (profile.isConnectable()) {
connectableProfiles.add(profile);
}
}
return connectableProfiles;
}
public List<LocalBluetoothProfile> getRemovedProfiles() {
return mRemovedProfiles;
}
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();
}
}
}
@Override
public String toString() {
return mDevice.toString();
}
@Override
public boolean equals(Object o) {
if ((o == null) || !(o instanceof CachedBluetoothDevice)) {
return false;
}
return mDevice.equals(((CachedBluetoothDevice) o).mDevice);
}
@Override
public int hashCode() {
return mDevice.getAddress().hashCode();
}
// This comparison uses non-final fields so the sort order may change
// when device attributes change (such as bonding state). Settings
// will completely refresh the device list when this happens.
public int compareTo(CachedBluetoothDevice another) {
// Connected above not connected
int comparison = (another.isConnected() ? 1 : 0) - (isConnected() ? 1 : 0);
if (comparison != 0) return comparison;
// Paired above not paired
comparison = (another.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0) -
(getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0);
if (comparison != 0) return comparison;
// Visible above not visible
comparison = (another.mVisible ? 1 : 0) - (mVisible ? 1 : 0);
if (comparison != 0) return comparison;
// Stronger signal above weaker signal
comparison = another.mRssi - mRssi;
if (comparison != 0) return comparison;
// Fallback on name
return mName.compareTo(another.mName);
}
public interface Callback {
void onDeviceAttributesChanged();
}
public int getPhonebookPermissionChoice() {
int permission = mDevice.getPhonebookAccessPermission();
if (permission == BluetoothDevice.ACCESS_ALLOWED) {
return ACCESS_ALLOWED;
} else if (permission == BluetoothDevice.ACCESS_REJECTED) {
return ACCESS_REJECTED;
}
return ACCESS_UNKNOWN;
}
public void setPhonebookPermissionChoice(int permissionChoice) {
int permission = BluetoothDevice.ACCESS_UNKNOWN;
if (permissionChoice == ACCESS_ALLOWED) {
permission = BluetoothDevice.ACCESS_ALLOWED;
} else if (permissionChoice == ACCESS_REJECTED) {
permission = BluetoothDevice.ACCESS_REJECTED;
}
mDevice.setPhonebookAccessPermission(permission);
}
// Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth
// app's shared preferences).
private void migratePhonebookPermissionChoice() {
SharedPreferences preferences = mContext.getSharedPreferences(
"bluetooth_phonebook_permission", Context.MODE_PRIVATE);
if (!preferences.contains(mDevice.getAddress())) {
return;
}
if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) {
int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN);
if (oldPermission == ACCESS_ALLOWED) {
mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
} else if (oldPermission == ACCESS_REJECTED) {
mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
}
}
SharedPreferences.Editor editor = preferences.edit();
editor.remove(mDevice.getAddress());
editor.commit();
}
public int getMessagePermissionChoice() {
int permission = mDevice.getMessageAccessPermission();
if (permission == BluetoothDevice.ACCESS_ALLOWED) {
return ACCESS_ALLOWED;
} else if (permission == BluetoothDevice.ACCESS_REJECTED) {
return ACCESS_REJECTED;
}
return ACCESS_UNKNOWN;
}
public void setMessagePermissionChoice(int permissionChoice) {
int permission = BluetoothDevice.ACCESS_UNKNOWN;
if (permissionChoice == ACCESS_ALLOWED) {
permission = BluetoothDevice.ACCESS_ALLOWED;
} else if (permissionChoice == ACCESS_REJECTED) {
permission = BluetoothDevice.ACCESS_REJECTED;
}
mDevice.setMessageAccessPermission(permission);
}
// Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth
// app's shared preferences).
private void migrateMessagePermissionChoice() {
SharedPreferences preferences = mContext.getSharedPreferences(
"bluetooth_message_permission", Context.MODE_PRIVATE);
if (!preferences.contains(mDevice.getAddress())) {
return;
}
if (mDevice.getMessageAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) {
int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN);
if (oldPermission == ACCESS_ALLOWED) {
mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
} else if (oldPermission == ACCESS_REJECTED) {
mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_REJECTED);
}
}
SharedPreferences.Editor editor = preferences.edit();
editor.remove(mDevice.getAddress());
editor.commit();
}
/**
* @return Whether this rejection should persist.
*/
public boolean checkAndIncreaseMessageRejectionCount() {
if (mMessageRejectionCount < MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST) {
mMessageRejectionCount++;
saveMessageRejectionCount();
}
return mMessageRejectionCount >= MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST;
}
private void fetchMessageRejectionCount() {
SharedPreferences preference = mContext.getSharedPreferences(
MESSAGE_REJECTION_COUNT_PREFS_NAME, Context.MODE_PRIVATE);
mMessageRejectionCount = preference.getInt(mDevice.getAddress(), 0);
}
private void saveMessageRejectionCount() {
SharedPreferences.Editor editor = mContext.getSharedPreferences(
MESSAGE_REJECTION_COUNT_PREFS_NAME, Context.MODE_PRIVATE).edit();
if (mMessageRejectionCount == 0) {
editor.remove(mDevice.getAddress());
} else {
editor.putInt(mDevice.getAddress(), mMessageRejectionCount);
}
editor.commit();
}
private void processPhonebookAccess() {
if (mDevice.getBondState() != BluetoothDevice.BOND_BONDED) return;
ParcelUuid[] uuids = mDevice.getUuids();
if (BluetoothUuid.containsAnyUuid(uuids, PbapServerProfile.PBAB_CLIENT_UUIDS)) {
// The pairing dialog now warns of phone-book access for paired devices.
// No separate prompt is displayed after pairing.
setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
}
}
}

View File

@@ -0,0 +1,172 @@
/*
* 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.settingslib.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.util.Log;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* CachedBluetoothDeviceManager manages the set of remote Bluetooth devices.
*/
public final class CachedBluetoothDeviceManager {
private static final String TAG = "CachedBluetoothDeviceManager";
private static final boolean DEBUG = Utils.D;
private Context mContext;
private final List<CachedBluetoothDevice> mCachedDevices =
new ArrayList<CachedBluetoothDevice>();
CachedBluetoothDeviceManager(Context context) {
mContext = context;
}
public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() {
return new ArrayList<CachedBluetoothDevice>(mCachedDevices);
}
public static boolean onDeviceDisappeared(CachedBluetoothDevice cachedDevice) {
cachedDevice.setVisible(false);
return cachedDevice.getBondState() == BluetoothDevice.BOND_NONE;
}
public void onDeviceNameUpdated(BluetoothDevice device) {
CachedBluetoothDevice cachedDevice = findDevice(device);
if (cachedDevice != null) {
cachedDevice.refreshName();
}
}
/**
* Search for existing {@link CachedBluetoothDevice} or return null
* if this device isn't in the cache. Use {@link #addDevice}
* to create and return a new {@link CachedBluetoothDevice} for
* a newly discovered {@link BluetoothDevice}.
*
* @param device the address of the Bluetooth device
* @return the cached device object for this device, or null if it has
* not been previously seen
*/
public CachedBluetoothDevice findDevice(BluetoothDevice device) {
for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
if (cachedDevice.getDevice().equals(device)) {
return cachedDevice;
}
}
return null;
}
/**
* Create and return a new {@link CachedBluetoothDevice}. This assumes
* that {@link #findDevice} has already been called and returned null.
* @param device the address of the new Bluetooth device
* @return the newly created CachedBluetoothDevice object
*/
public CachedBluetoothDevice addDevice(LocalBluetoothAdapter adapter,
LocalBluetoothProfileManager profileManager,
BluetoothDevice device) {
CachedBluetoothDevice newDevice = new CachedBluetoothDevice(mContext, adapter,
profileManager, device);
synchronized (mCachedDevices) {
mCachedDevices.add(newDevice);
}
return newDevice;
}
/**
* Attempts to get the name of a remote device, otherwise returns the address.
*
* @param device The remote device.
* @return The name, or if unavailable, the address.
*/
public String getName(BluetoothDevice device) {
CachedBluetoothDevice cachedDevice = findDevice(device);
if (cachedDevice != null) {
return cachedDevice.getName();
}
String name = device.getAliasName();
if (name != null) {
return name;
}
return device.getAddress();
}
public synchronized void clearNonBondedDevices() {
for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
mCachedDevices.remove(i);
}
}
}
public synchronized void onScanningStateChanged(boolean started) {
if (!started) return;
// If starting a new scan, clear old visibility
// Iterate in reverse order since devices may be removed.
for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
cachedDevice.setVisible(false);
}
}
public synchronized void onBtClassChanged(BluetoothDevice device) {
CachedBluetoothDevice cachedDevice = findDevice(device);
if (cachedDevice != null) {
cachedDevice.refreshBtClass();
}
}
public synchronized void onUuidChanged(BluetoothDevice device) {
CachedBluetoothDevice cachedDevice = findDevice(device);
if (cachedDevice != null) {
cachedDevice.onUuidChanged();
}
}
public synchronized void onBluetoothStateChanged(int bluetoothState) {
// When Bluetooth is turning off, we need to clear the non-bonded devices
// Otherwise, they end up showing up on the next BT enable
if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) {
for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
cachedDevice.setVisible(false);
mCachedDevices.remove(i);
} else {
// For bonded devices, we need to clear the connection status so that
// when BT is enabled next time, device connection status shall be retrieved
// by making a binder call.
cachedDevice.clearProfileConnectionState();
}
}
}
}
private void log(String msg) {
if (DEBUG) {
Log.d(TAG, msg);
}
}
}

View File

@@ -0,0 +1,227 @@
/*
* Copyright (C) 2012 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.settingslib.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.content.Context;
import android.os.ParcelUuid;
import android.util.Log;
import com.android.settingslib.R;
import java.util.ArrayList;
import java.util.List;
/**
* HeadsetProfile handles Bluetooth HFP and Headset profiles.
*/
public final class HeadsetProfile implements LocalBluetoothProfile {
private static final String TAG = "HeadsetProfile";
private static boolean V = true;
private BluetoothHeadset mService;
private boolean mIsProfileReady;
private final LocalBluetoothAdapter mLocalAdapter;
private final CachedBluetoothDeviceManager mDeviceManager;
private final LocalBluetoothProfileManager mProfileManager;
static final ParcelUuid[] UUIDS = {
BluetoothUuid.HSP,
BluetoothUuid.Handsfree,
};
static final String NAME = "HEADSET";
// Order of this profile in device profiles list
private static final int ORDINAL = 0;
// These callbacks run on the main thread.
private final class HeadsetServiceListener
implements BluetoothProfile.ServiceListener {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (V) Log.d(TAG,"Bluetooth service connected");
mService = (BluetoothHeadset) proxy;
// We just bound to the service, so refresh the UI for any connected HFP devices.
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
while (!deviceList.isEmpty()) {
BluetoothDevice nextDevice = deviceList.remove(0);
CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
// we may add a new device here, but generally this should not happen
if (device == null) {
Log.w(TAG, "HeadsetProfile found new device: " + nextDevice);
device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
}
device.onProfileStateChanged(HeadsetProfile.this,
BluetoothProfile.STATE_CONNECTED);
device.refresh();
}
mProfileManager.callServiceConnectedListeners();
mIsProfileReady=true;
}
public void onServiceDisconnected(int profile) {
if (V) Log.d(TAG,"Bluetooth service disconnected");
mProfileManager.callServiceDisconnectedListeners();
mIsProfileReady=false;
}
}
public boolean isProfileReady() {
return mIsProfileReady;
}
HeadsetProfile(Context context, LocalBluetoothAdapter adapter,
CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mProfileManager = profileManager;
mLocalAdapter.getProfileProxy(context, new HeadsetServiceListener(),
BluetoothProfile.HEADSET);
}
public boolean isConnectable() {
return true;
}
public boolean isAutoConnectable() {
return true;
}
public boolean connect(BluetoothDevice device) {
if (mService == null) return false;
List<BluetoothDevice> sinks = mService.getConnectedDevices();
if (sinks != null) {
for (BluetoothDevice sink : sinks) {
Log.d(TAG,"Not disconnecting device = " + sink);
}
}
return mService.connect(device);
}
public boolean disconnect(BluetoothDevice device) {
if (mService == null) return false;
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
if (!deviceList.isEmpty()) {
for (BluetoothDevice dev : deviceList) {
if (dev.equals(device)) {
if (V) Log.d(TAG,"Downgrade priority as user" +
"is disconnecting the headset");
// Downgrade priority as user is disconnecting the headset.
if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
}
return mService.disconnect(device);
}
}
}
return false;
}
public int getConnectionStatus(BluetoothDevice device) {
if (mService == null) return BluetoothProfile.STATE_DISCONNECTED;
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
if (!deviceList.isEmpty()){
for (BluetoothDevice dev : deviceList) {
if (dev.equals(device)) {
return mService.getConnectionState(device);
}
}
}
return BluetoothProfile.STATE_DISCONNECTED;
}
public boolean isPreferred(BluetoothDevice device) {
if (mService == null) return false;
return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
}
public int getPreferred(BluetoothDevice device) {
if (mService == null) return BluetoothProfile.PRIORITY_OFF;
return mService.getPriority(device);
}
public void setPreferred(BluetoothDevice device, boolean preferred) {
if (mService == null) return;
if (preferred) {
if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
}
} else {
mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
}
}
public List<BluetoothDevice> getConnectedDevices() {
if (mService == null) return new ArrayList<BluetoothDevice>(0);
return mService.getDevicesMatchingConnectionStates(
new int[] {BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTING});
}
public String toString() {
return NAME;
}
public int getOrdinal() {
return ORDINAL;
}
public int getNameResource(BluetoothDevice device) {
return R.string.bluetooth_profile_headset;
}
public int getSummaryResourceForDevice(BluetoothDevice device) {
int state = getConnectionStatus(device);
switch (state) {
case BluetoothProfile.STATE_DISCONNECTED:
return R.string.bluetooth_headset_profile_summary_use_for;
case BluetoothProfile.STATE_CONNECTED:
return R.string.bluetooth_headset_profile_summary_connected;
default:
return Utils.getConnectionStateSummary(state);
}
}
public int getDrawableResource(BluetoothClass btClass) {
return R.drawable.ic_bt_headset_hfp;
}
protected void finalize() {
if (V) Log.d(TAG, "finalize()");
if (mService != null) {
try {
BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.HEADSET,
mService);
mService = null;
}catch (Throwable t) {
Log.w(TAG, "Error cleaning up HID proxy", t);
}
}
}
}

View File

@@ -0,0 +1,201 @@
/*
* Copyright (C) 2012 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.settingslib.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothInputDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.util.Log;
import com.android.settingslib.R;
import java.util.List;
/**
* HidProfile handles Bluetooth HID profile.
*/
public final class HidProfile implements LocalBluetoothProfile {
private static final String TAG = "HidProfile";
private static boolean V = true;
private BluetoothInputDevice mService;
private boolean mIsProfileReady;
private final LocalBluetoothAdapter mLocalAdapter;
private final CachedBluetoothDeviceManager mDeviceManager;
private final LocalBluetoothProfileManager mProfileManager;
static final String NAME = "HID";
// Order of this profile in device profiles list
private static final int ORDINAL = 3;
// These callbacks run on the main thread.
private final class InputDeviceServiceListener
implements BluetoothProfile.ServiceListener {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (V) Log.d(TAG,"Bluetooth service connected");
mService = (BluetoothInputDevice) proxy;
// We just bound to the service, so refresh the UI for any connected HID devices.
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
while (!deviceList.isEmpty()) {
BluetoothDevice nextDevice = deviceList.remove(0);
CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
// we may add a new device here, but generally this should not happen
if (device == null) {
Log.w(TAG, "HidProfile found new device: " + nextDevice);
device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
}
device.onProfileStateChanged(HidProfile.this, BluetoothProfile.STATE_CONNECTED);
device.refresh();
}
mIsProfileReady=true;
}
public void onServiceDisconnected(int profile) {
if (V) Log.d(TAG,"Bluetooth service disconnected");
mIsProfileReady=false;
}
}
public boolean isProfileReady() {
return mIsProfileReady;
}
HidProfile(Context context, LocalBluetoothAdapter adapter,
CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mProfileManager = profileManager;
adapter.getProfileProxy(context, new InputDeviceServiceListener(),
BluetoothProfile.INPUT_DEVICE);
}
public boolean isConnectable() {
return true;
}
public boolean isAutoConnectable() {
return true;
}
public boolean connect(BluetoothDevice device) {
if (mService == null) return false;
return mService.connect(device);
}
public boolean disconnect(BluetoothDevice device) {
if (mService == null) return false;
return mService.disconnect(device);
}
public int getConnectionStatus(BluetoothDevice device) {
if (mService == null) {
return BluetoothProfile.STATE_DISCONNECTED;
}
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
return !deviceList.isEmpty() && deviceList.get(0).equals(device)
? mService.getConnectionState(device)
: BluetoothProfile.STATE_DISCONNECTED;
}
public boolean isPreferred(BluetoothDevice device) {
if (mService == null) return false;
return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
}
public int getPreferred(BluetoothDevice device) {
if (mService == null) return BluetoothProfile.PRIORITY_OFF;
return mService.getPriority(device);
}
public void setPreferred(BluetoothDevice device, boolean preferred) {
if (mService == null) return;
if (preferred) {
if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
}
} else {
mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
}
}
public String toString() {
return NAME;
}
public int getOrdinal() {
return ORDINAL;
}
public int getNameResource(BluetoothDevice device) {
// TODO: distinguish between keyboard and mouse?
return R.string.bluetooth_profile_hid;
}
public int getSummaryResourceForDevice(BluetoothDevice device) {
int state = getConnectionStatus(device);
switch (state) {
case BluetoothProfile.STATE_DISCONNECTED:
return R.string.bluetooth_hid_profile_summary_use_for;
case BluetoothProfile.STATE_CONNECTED:
return R.string.bluetooth_hid_profile_summary_connected;
default:
return Utils.getConnectionStateSummary(state);
}
}
public int getDrawableResource(BluetoothClass btClass) {
if (btClass == null) {
return R.drawable.ic_lockscreen_ime;
}
return getHidClassDrawable(btClass);
}
public static int getHidClassDrawable(BluetoothClass btClass) {
switch (btClass.getDeviceClass()) {
case BluetoothClass.Device.PERIPHERAL_KEYBOARD:
case BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING:
return R.drawable.ic_lockscreen_ime;
case BluetoothClass.Device.PERIPHERAL_POINTING:
return R.drawable.ic_bt_pointing_hid;
default:
return R.drawable.ic_bt_misc_hid;
}
}
protected void finalize() {
if (V) Log.d(TAG, "finalize()");
if (mService != null) {
try {
BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.INPUT_DEVICE,
mService);
mService = null;
}catch (Throwable t) {
Log.w(TAG, "Error cleaning up HID proxy", t);
}
}
}
}

View File

@@ -0,0 +1,216 @@
/*
* Copyright (C) 2011 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.settingslib.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.os.ParcelUuid;
import android.util.Log;
import java.util.Set;
/**
* LocalBluetoothAdapter provides an interface between the Settings app
* and the functionality of the local {@link BluetoothAdapter}, specifically
* those related to state transitions of the adapter itself.
*
* <p>Connection and bonding state changes affecting specific devices
* are handled by {@link CachedBluetoothDeviceManager},
* {@link BluetoothEventManager}, and {@link LocalBluetoothProfileManager}.
*/
public final class LocalBluetoothAdapter {
private static final String TAG = "LocalBluetoothAdapter";
/** This class does not allow direct access to the BluetoothAdapter. */
private final BluetoothAdapter mAdapter;
private LocalBluetoothProfileManager mProfileManager;
private static LocalBluetoothAdapter sInstance;
private int mState = BluetoothAdapter.ERROR;
private static final int SCAN_EXPIRATION_MS = 5 * 60 * 1000; // 5 mins
private long mLastScan;
private LocalBluetoothAdapter(BluetoothAdapter adapter) {
mAdapter = adapter;
}
void setProfileManager(LocalBluetoothProfileManager manager) {
mProfileManager = manager;
}
/**
* Get the singleton instance of the LocalBluetoothAdapter. If this device
* doesn't support Bluetooth, then null will be returned. Callers must be
* prepared to handle a null return value.
* @return the LocalBluetoothAdapter object, or null if not supported
*/
static synchronized LocalBluetoothAdapter getInstance() {
if (sInstance == null) {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter != null) {
sInstance = new LocalBluetoothAdapter(adapter);
}
}
return sInstance;
}
// Pass-through BluetoothAdapter methods that we can intercept if necessary
public void cancelDiscovery() {
mAdapter.cancelDiscovery();
}
public boolean enable() {
return mAdapter.enable();
}
public boolean disable() {
return mAdapter.disable();
}
void getProfileProxy(Context context,
BluetoothProfile.ServiceListener listener, int profile) {
mAdapter.getProfileProxy(context, listener, profile);
}
public Set<BluetoothDevice> getBondedDevices() {
return mAdapter.getBondedDevices();
}
public String getName() {
return mAdapter.getName();
}
public int getScanMode() {
return mAdapter.getScanMode();
}
public int getState() {
return mAdapter.getState();
}
public ParcelUuid[] getUuids() {
return mAdapter.getUuids();
}
public boolean isDiscovering() {
return mAdapter.isDiscovering();
}
public boolean isEnabled() {
return mAdapter.isEnabled();
}
public void setDiscoverableTimeout(int timeout) {
mAdapter.setDiscoverableTimeout(timeout);
}
public void setName(String name) {
mAdapter.setName(name);
}
public void setScanMode(int mode) {
mAdapter.setScanMode(mode);
}
public boolean setScanMode(int mode, int duration) {
return mAdapter.setScanMode(mode, duration);
}
public void startScanning(boolean force) {
// Only start if we're not already scanning
if (!mAdapter.isDiscovering()) {
if (!force) {
// Don't scan more than frequently than SCAN_EXPIRATION_MS,
// unless forced
if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
return;
}
// If we are playing music, don't scan unless forced.
A2dpProfile a2dp = mProfileManager.getA2dpProfile();
if (a2dp != null && a2dp.isA2dpPlaying()) {
return;
}
}
if (mAdapter.startDiscovery()) {
mLastScan = System.currentTimeMillis();
}
}
}
public void stopScanning() {
if (mAdapter.isDiscovering()) {
mAdapter.cancelDiscovery();
}
}
public synchronized int getBluetoothState() {
// Always sync state, in case it changed while paused
syncBluetoothState();
return mState;
}
synchronized void setBluetoothStateInt(int state) {
mState = state;
if (state == BluetoothAdapter.STATE_ON) {
// if mProfileManager hasn't been constructed yet, it will
// get the adapter UUIDs in its constructor when it is.
if (mProfileManager != null) {
mProfileManager.setBluetoothStateOn();
}
}
}
// Returns true if the state changed; false otherwise.
boolean syncBluetoothState() {
int currentState = mAdapter.getState();
if (currentState != mState) {
setBluetoothStateInt(mAdapter.getState());
return true;
}
return false;
}
public void setBluetoothEnabled(boolean enabled) {
boolean success = enabled
? mAdapter.enable()
: mAdapter.disable();
if (success) {
setBluetoothStateInt(enabled
? BluetoothAdapter.STATE_TURNING_ON
: BluetoothAdapter.STATE_TURNING_OFF);
} else {
if (Utils.V) {
Log.v(TAG, "setBluetoothEnabled call, manager didn't return " +
"success for enabled: " + enabled);
}
syncBluetoothState();
}
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright (C) 2011 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.settingslib.bluetooth;
import android.content.Context;
import android.util.Log;
/**
* LocalBluetoothManager provides a simplified interface on top of a subset of
* the Bluetooth API. Note that {@link #getInstance} will return null
* if there is no Bluetooth adapter on this device, and callers must be
* prepared to handle this case.
*/
public final class LocalBluetoothManager {
private static final String TAG = "LocalBluetoothManager";
/** Singleton instance. */
private static LocalBluetoothManager sInstance;
private final Context mContext;
/** If a BT-related activity is in the foreground, this will be it. */
private Context mForegroundActivity;
private final LocalBluetoothAdapter mLocalAdapter;
private final CachedBluetoothDeviceManager mCachedDeviceManager;
/** The Bluetooth profile manager. */
private final LocalBluetoothProfileManager mProfileManager;
/** The broadcast receiver event manager. */
private final BluetoothEventManager mEventManager;
public static synchronized LocalBluetoothManager getInstance(Context context,
BluetoothManagerCallback onInitCallback) {
if (sInstance == null) {
LocalBluetoothAdapter adapter = LocalBluetoothAdapter.getInstance();
if (adapter == null) {
return null;
}
// This will be around as long as this process is
Context appContext = context.getApplicationContext();
sInstance = new LocalBluetoothManager(adapter, appContext);
if (onInitCallback != null) {
onInitCallback.onBluetoothManagerInitialized(appContext, sInstance);
}
}
return sInstance;
}
private LocalBluetoothManager(LocalBluetoothAdapter adapter, Context context) {
mContext = context;
mLocalAdapter = adapter;
mCachedDeviceManager = new CachedBluetoothDeviceManager(context);
mEventManager = new BluetoothEventManager(mLocalAdapter,
mCachedDeviceManager, context);
mProfileManager = new LocalBluetoothProfileManager(context,
mLocalAdapter, mCachedDeviceManager, mEventManager);
}
public LocalBluetoothAdapter getBluetoothAdapter() {
return mLocalAdapter;
}
public Context getContext() {
return mContext;
}
public Context getForegroundActivity() {
return mForegroundActivity;
}
public boolean isForegroundActivity() {
return mForegroundActivity != null;
}
public synchronized void setForegroundActivity(Context context) {
if (context != null) {
Log.d(TAG, "setting foreground activity to non-null context");
mForegroundActivity = context;
} else {
if (mForegroundActivity != null) {
Log.d(TAG, "setting foreground activity to null");
mForegroundActivity = null;
}
}
}
public CachedBluetoothDeviceManager getCachedDeviceManager() {
return mCachedDeviceManager;
}
public BluetoothEventManager getEventManager() {
return mEventManager;
}
public LocalBluetoothProfileManager getProfileManager() {
return mProfileManager;
}
public interface BluetoothManagerCallback {
void onBluetoothManagerInitialized(Context appContext,
LocalBluetoothManager bluetoothManager);
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2011 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.settingslib.bluetooth;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
/**
* LocalBluetoothProfile is an interface defining the basic
* functionality related to a Bluetooth profile.
*/
public interface LocalBluetoothProfile {
/**
* Returns true if the user can initiate a connection, false otherwise.
*/
boolean isConnectable();
/**
* Returns true if the user can enable auto connection for this profile.
*/
boolean isAutoConnectable();
boolean connect(BluetoothDevice device);
boolean disconnect(BluetoothDevice device);
int getConnectionStatus(BluetoothDevice device);
boolean isPreferred(BluetoothDevice device);
int getPreferred(BluetoothDevice device);
void setPreferred(BluetoothDevice device, boolean preferred);
boolean isProfileReady();
/** Display order for device profile settings. */
int getOrdinal();
/**
* Returns the string resource ID for the localized name for this profile.
* @param device the Bluetooth device (to distinguish between PAN roles)
*/
int getNameResource(BluetoothDevice device);
/**
* Returns the string resource ID for the summary text for this profile
* for the specified device, e.g. "Use for media audio" or
* "Connected to media audio".
* @param device the device to query for profile connection status
* @return a string resource ID for the profile summary text
*/
int getSummaryResourceForDevice(BluetoothDevice device);
int getDrawableResource(BluetoothClass btClass);
}

View File

@@ -0,0 +1,383 @@
/*
* Copyright (C) 2011 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.settingslib.bluetooth;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothMap;
import android.bluetooth.BluetoothInputDevice;
import android.bluetooth.BluetoothPan;
import android.bluetooth.BluetoothPbap;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.content.Context;
import android.content.Intent;
import android.os.ParcelUuid;
import android.util.Log;
import android.os.Handler;
import android.os.Message;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.List;
/**
* LocalBluetoothProfileManager provides access to the LocalBluetoothProfile
* objects for the available Bluetooth profiles.
*/
public final class LocalBluetoothProfileManager {
private static final String TAG = "LocalBluetoothProfileManager";
private static final boolean DEBUG = Utils.D;
/** Singleton instance. */
private static LocalBluetoothProfileManager sInstance;
/**
* An interface for notifying BluetoothHeadset IPC clients when they have
* been connected to the BluetoothHeadset service.
* Only used by com.android.settings.bluetooth.DockService.
*/
public interface ServiceListener {
/**
* Called to notify the client when this proxy object has been
* connected to the BluetoothHeadset service. Clients must wait for
* this callback before making IPC calls on the BluetoothHeadset
* service.
*/
void onServiceConnected();
/**
* Called to notify the client that this proxy object has been
* disconnected from the BluetoothHeadset service. Clients must not
* make IPC calls on the BluetoothHeadset service after this callback.
* This callback will currently only occur if the application hosting
* the BluetoothHeadset service, but may be called more often in future.
*/
void onServiceDisconnected();
}
private final Context mContext;
private final LocalBluetoothAdapter mLocalAdapter;
private final CachedBluetoothDeviceManager mDeviceManager;
private final BluetoothEventManager mEventManager;
private A2dpProfile mA2dpProfile;
private HeadsetProfile mHeadsetProfile;
private MapProfile mMapProfile;
private final HidProfile mHidProfile;
private OppProfile mOppProfile;
private final PanProfile mPanProfile;
private final PbapServerProfile mPbapProfile;
/**
* Mapping from profile name, e.g. "HEADSET" to profile object.
*/
private final Map<String, LocalBluetoothProfile>
mProfileNameMap = new HashMap<String, LocalBluetoothProfile>();
LocalBluetoothProfileManager(Context context,
LocalBluetoothAdapter adapter,
CachedBluetoothDeviceManager deviceManager,
BluetoothEventManager eventManager) {
mContext = context;
mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mEventManager = eventManager;
// pass this reference to adapter and event manager (circular dependency)
mLocalAdapter.setProfileManager(this);
mEventManager.setProfileManager(this);
ParcelUuid[] uuids = adapter.getUuids();
// uuids may be null if Bluetooth is turned off
if (uuids != null) {
updateLocalProfiles(uuids);
}
// Always add HID and PAN profiles
mHidProfile = new HidProfile(context, mLocalAdapter, mDeviceManager, this);
addProfile(mHidProfile, HidProfile.NAME,
BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
mPanProfile = new PanProfile(context);
addPanProfile(mPanProfile, PanProfile.NAME,
BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
if(DEBUG) Log.d(TAG, "Adding local MAP profile");
mMapProfile = new MapProfile(mContext, mLocalAdapter,
mDeviceManager, this);
addProfile(mMapProfile, MapProfile.NAME,
BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
//Create PBAP server profile, but do not add it to list of profiles
// as we do not need to monitor the profile as part of profile list
mPbapProfile = new PbapServerProfile(context);
if (DEBUG) Log.d(TAG, "LocalBluetoothProfileManager construction complete");
}
/**
* Initialize or update the local profile objects. If a UUID was previously
* present but has been removed, we print a warning but don't remove the
* profile object as it might be referenced elsewhere, or the UUID might
* come back and we don't want multiple copies of the profile objects.
* @param uuids
*/
void updateLocalProfiles(ParcelUuid[] uuids) {
// A2DP
if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) {
if (mA2dpProfile == null) {
if(DEBUG) Log.d(TAG, "Adding local A2DP profile");
mA2dpProfile = new A2dpProfile(mContext, mLocalAdapter, mDeviceManager, this);
addProfile(mA2dpProfile, A2dpProfile.NAME,
BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
}
} else if (mA2dpProfile != null) {
Log.w(TAG, "Warning: A2DP profile was previously added but the UUID is now missing.");
}
// Headset / Handsfree
if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) ||
BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) {
if (mHeadsetProfile == null) {
if (DEBUG) Log.d(TAG, "Adding local HEADSET profile");
mHeadsetProfile = new HeadsetProfile(mContext, mLocalAdapter,
mDeviceManager, this);
addProfile(mHeadsetProfile, HeadsetProfile.NAME,
BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
}
} else if (mHeadsetProfile != null) {
Log.w(TAG, "Warning: HEADSET profile was previously added but the UUID is now missing.");
}
// OPP
if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) {
if (mOppProfile == null) {
if(DEBUG) Log.d(TAG, "Adding local OPP profile");
mOppProfile = new OppProfile();
// Note: no event handler for OPP, only name map.
mProfileNameMap.put(OppProfile.NAME, mOppProfile);
}
} else if (mOppProfile != null) {
Log.w(TAG, "Warning: OPP profile was previously added but the UUID is now missing.");
}
mEventManager.registerProfileIntentReceiver();
// There is no local SDP record for HID and Settings app doesn't control PBAP
}
private final Collection<ServiceListener> mServiceListeners =
new ArrayList<ServiceListener>();
private void addProfile(LocalBluetoothProfile profile,
String profileName, String stateChangedAction) {
mEventManager.addProfileHandler(stateChangedAction, new StateChangedHandler(profile));
mProfileNameMap.put(profileName, profile);
}
private void addPanProfile(LocalBluetoothProfile profile,
String profileName, String stateChangedAction) {
mEventManager.addProfileHandler(stateChangedAction,
new PanStateChangedHandler(profile));
mProfileNameMap.put(profileName, profile);
}
public LocalBluetoothProfile getProfileByName(String name) {
return mProfileNameMap.get(name);
}
// Called from LocalBluetoothAdapter when state changes to ON
void setBluetoothStateOn() {
ParcelUuid[] uuids = mLocalAdapter.getUuids();
if (uuids != null) {
updateLocalProfiles(uuids);
}
mEventManager.readPairedDevices();
}
/**
* Generic handler for connection state change events for the specified profile.
*/
private class StateChangedHandler implements BluetoothEventManager.Handler {
final LocalBluetoothProfile mProfile;
StateChangedHandler(LocalBluetoothProfile profile) {
mProfile = profile;
}
public void onReceive(Context context, Intent intent, BluetoothDevice device) {
CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
if (cachedDevice == null) {
Log.w(TAG, "StateChangedHandler found new device: " + device);
cachedDevice = mDeviceManager.addDevice(mLocalAdapter,
LocalBluetoothProfileManager.this, device);
}
int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
if (newState == BluetoothProfile.STATE_DISCONNECTED &&
oldState == BluetoothProfile.STATE_CONNECTING) {
Log.i(TAG, "Failed to connect " + mProfile + " device");
}
cachedDevice.onProfileStateChanged(mProfile, newState);
cachedDevice.refresh();
}
}
/** State change handler for NAP and PANU profiles. */
private class PanStateChangedHandler extends StateChangedHandler {
PanStateChangedHandler(LocalBluetoothProfile profile) {
super(profile);
}
@Override
public void onReceive(Context context, Intent intent, BluetoothDevice device) {
PanProfile panProfile = (PanProfile) mProfile;
int role = intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, 0);
panProfile.setLocalRole(device, role);
super.onReceive(context, intent, device);
}
}
// called from DockService
public void addServiceListener(ServiceListener l) {
mServiceListeners.add(l);
}
// called from DockService
public void removeServiceListener(ServiceListener l) {
mServiceListeners.remove(l);
}
// not synchronized: use only from UI thread! (TODO: verify)
void callServiceConnectedListeners() {
for (ServiceListener l : mServiceListeners) {
l.onServiceConnected();
}
}
// not synchronized: use only from UI thread! (TODO: verify)
void callServiceDisconnectedListeners() {
for (ServiceListener listener : mServiceListeners) {
listener.onServiceDisconnected();
}
}
// This is called by DockService, so check Headset and A2DP.
public synchronized boolean isManagerReady() {
// Getting just the headset profile is fine for now. Will need to deal with A2DP
// and others if they aren't always in a ready state.
LocalBluetoothProfile profile = mHeadsetProfile;
if (profile != null) {
return profile.isProfileReady();
}
profile = mA2dpProfile;
if (profile != null) {
return profile.isProfileReady();
}
return false;
}
public A2dpProfile getA2dpProfile() {
return mA2dpProfile;
}
public HeadsetProfile getHeadsetProfile() {
return mHeadsetProfile;
}
public PbapServerProfile getPbapProfile(){
return mPbapProfile;
}
public MapProfile getMapProfile(){
return mMapProfile;
}
/**
* Fill in a list of LocalBluetoothProfile objects that are supported by
* the local device and the remote device.
*
* @param uuids of the remote device
* @param localUuids UUIDs of the local device
* @param profiles The list of profiles to fill
* @param removedProfiles list of profiles that were removed
*/
synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids,
Collection<LocalBluetoothProfile> profiles,
Collection<LocalBluetoothProfile> removedProfiles,
boolean isPanNapConnected, BluetoothDevice device) {
// Copy previous profile list into removedProfiles
removedProfiles.clear();
removedProfiles.addAll(profiles);
profiles.clear();
if (uuids == null) {
return;
}
if (mHeadsetProfile != null) {
if ((BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG) &&
BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)) ||
(BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG) &&
BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree))) {
profiles.add(mHeadsetProfile);
removedProfiles.remove(mHeadsetProfile);
}
}
if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) &&
mA2dpProfile != null) {
profiles.add(mA2dpProfile);
removedProfiles.remove(mA2dpProfile);
}
if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) &&
mOppProfile != null) {
profiles.add(mOppProfile);
removedProfiles.remove(mOppProfile);
}
if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid) ||
BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hogp)) &&
mHidProfile != null) {
profiles.add(mHidProfile);
removedProfiles.remove(mHidProfile);
}
if(isPanNapConnected)
if(DEBUG) Log.d(TAG, "Valid PAN-NAP connection exists.");
if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP) &&
mPanProfile != null) || isPanNapConnected) {
profiles.add(mPanProfile);
removedProfiles.remove(mPanProfile);
}
if ((mMapProfile != null) &&
(mMapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) {
profiles.add(mMapProfile);
removedProfiles.remove(mMapProfile);
mMapProfile.setPreferred(device, true);
}
}
}

View File

@@ -0,0 +1,213 @@
/*
* Copyright (C) 2012 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.settingslib.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothMap;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.content.Context;
import android.os.ParcelUuid;
import android.util.Log;
import com.android.settingslib.R;
import java.util.ArrayList;
import java.util.List;
/**
* MapProfile handles Bluetooth MAP profile.
*/
public final class MapProfile implements LocalBluetoothProfile {
private static final String TAG = "MapProfile";
private static boolean V = true;
private BluetoothMap mService;
private boolean mIsProfileReady;
private final LocalBluetoothAdapter mLocalAdapter;
private final CachedBluetoothDeviceManager mDeviceManager;
private final LocalBluetoothProfileManager mProfileManager;
static final ParcelUuid[] UUIDS = {
BluetoothUuid.MAP,
BluetoothUuid.MNS,
BluetoothUuid.MAS,
};
static final String NAME = "MAP";
// Order of this profile in device profiles list
// These callbacks run on the main thread.
private final class MapServiceListener
implements BluetoothProfile.ServiceListener {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (V) Log.d(TAG,"Bluetooth service connected");
mService = (BluetoothMap) proxy;
// We just bound to the service, so refresh the UI for any connected MAP devices.
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
while (!deviceList.isEmpty()) {
BluetoothDevice nextDevice = deviceList.remove(0);
CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
// we may add a new device here, but generally this should not happen
if (device == null) {
Log.w(TAG, "MapProfile found new device: " + nextDevice);
device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
}
device.onProfileStateChanged(MapProfile.this,
BluetoothProfile.STATE_CONNECTED);
device.refresh();
}
mProfileManager.callServiceConnectedListeners();
mIsProfileReady=true;
}
public void onServiceDisconnected(int profile) {
if (V) Log.d(TAG,"Bluetooth service disconnected");
mProfileManager.callServiceDisconnectedListeners();
mIsProfileReady=false;
}
}
public boolean isProfileReady() {
if(V) Log.d(TAG,"isProfileReady(): "+ mIsProfileReady);
return mIsProfileReady;
}
MapProfile(Context context, LocalBluetoothAdapter adapter,
CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mProfileManager = profileManager;
mLocalAdapter.getProfileProxy(context, new MapServiceListener(),
BluetoothProfile.MAP);
}
public boolean isConnectable() {
return true;
}
public boolean isAutoConnectable() {
return true;
}
public boolean connect(BluetoothDevice device) {
if(V)Log.d(TAG,"connect() - should not get called");
return false; // MAP never connects out
}
public boolean disconnect(BluetoothDevice device) {
if (mService == null) return false;
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
if (!deviceList.isEmpty() && deviceList.get(0).equals(device)) {
if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
}
return mService.disconnect(device);
} else {
return false;
}
}
public int getConnectionStatus(BluetoothDevice device) {
if (mService == null) return BluetoothProfile.STATE_DISCONNECTED;
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
if(V) Log.d(TAG,"getConnectionStatus: status is: "+ mService.getConnectionState(device));
return !deviceList.isEmpty() && deviceList.get(0).equals(device)
? mService.getConnectionState(device)
: BluetoothProfile.STATE_DISCONNECTED;
}
public boolean isPreferred(BluetoothDevice device) {
if (mService == null) return false;
return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
}
public int getPreferred(BluetoothDevice device) {
if (mService == null) return BluetoothProfile.PRIORITY_OFF;
return mService.getPriority(device);
}
public void setPreferred(BluetoothDevice device, boolean preferred) {
if (mService == null) return;
if (preferred) {
if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
}
} else {
mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
}
}
public List<BluetoothDevice> getConnectedDevices() {
if (mService == null) return new ArrayList<BluetoothDevice>(0);
return mService.getDevicesMatchingConnectionStates(
new int[] {BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTING});
}
public String toString() {
return NAME;
}
public int getOrdinal() {
return BluetoothProfile.MAP;
}
public int getNameResource(BluetoothDevice device) {
return R.string.bluetooth_profile_map;
}
public int getSummaryResourceForDevice(BluetoothDevice device) {
int state = getConnectionStatus(device);
switch (state) {
case BluetoothProfile.STATE_DISCONNECTED:
return R.string.bluetooth_map_profile_summary_use_for;
case BluetoothProfile.STATE_CONNECTED:
return R.string.bluetooth_map_profile_summary_connected;
default:
return Utils.getConnectionStateSummary(state);
}
}
public int getDrawableResource(BluetoothClass btClass) {
return R.drawable.ic_bt_cellphone;
}
protected void finalize() {
if (V) Log.d(TAG, "finalize()");
if (mService != null) {
try {
BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.MAP,
mService);
mService = null;
}catch (Throwable t) {
Log.w(TAG, "Error cleaning up MAP proxy", t);
}
}
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2011 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.settingslib.bluetooth;
import com.android.settingslib.R;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
/**
* OppProfile handles Bluetooth OPP.
*/
final class OppProfile implements LocalBluetoothProfile {
static final String NAME = "OPP";
// Order of this profile in device profiles list
private static final int ORDINAL = 2;
public boolean isConnectable() {
return false;
}
public boolean isAutoConnectable() {
return false;
}
public boolean connect(BluetoothDevice device) {
return false;
}
public boolean disconnect(BluetoothDevice device) {
return false;
}
public int getConnectionStatus(BluetoothDevice device) {
return BluetoothProfile.STATE_DISCONNECTED; // Settings app doesn't handle OPP
}
public boolean isPreferred(BluetoothDevice device) {
return false;
}
public int getPreferred(BluetoothDevice device) {
return BluetoothProfile.PRIORITY_OFF; // Settings app doesn't handle OPP
}
public void setPreferred(BluetoothDevice device, boolean preferred) {
}
public boolean isProfileReady() {
return true;
}
public String toString() {
return NAME;
}
public int getOrdinal() {
return ORDINAL;
}
public int getNameResource(BluetoothDevice device) {
return R.string.bluetooth_profile_opp;
}
public int getSummaryResourceForDevice(BluetoothDevice device) {
return 0; // OPP profile not displayed in UI
}
public int getDrawableResource(BluetoothClass btClass) {
return 0; // no icon for OPP
}
}

View File

@@ -0,0 +1,183 @@
/*
* Copyright (C) 2011 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.settingslib.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothPan;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.util.Log;
import com.android.settingslib.R;
import java.util.HashMap;
import java.util.List;
/**
* PanProfile handles Bluetooth PAN profile (NAP and PANU).
*/
final class PanProfile implements LocalBluetoothProfile {
private static final String TAG = "PanProfile";
private static boolean V = true;
private BluetoothPan mService;
private boolean mIsProfileReady;
// Tethering direction for each device
private final HashMap<BluetoothDevice, Integer> mDeviceRoleMap =
new HashMap<BluetoothDevice, Integer>();
static final String NAME = "PAN";
// Order of this profile in device profiles list
private static final int ORDINAL = 4;
// These callbacks run on the main thread.
private final class PanServiceListener
implements BluetoothProfile.ServiceListener {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (V) Log.d(TAG,"Bluetooth service connected");
mService = (BluetoothPan) proxy;
mIsProfileReady=true;
}
public void onServiceDisconnected(int profile) {
if (V) Log.d(TAG,"Bluetooth service disconnected");
mIsProfileReady=false;
}
}
public boolean isProfileReady() {
return mIsProfileReady;
}
PanProfile(Context context) {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
adapter.getProfileProxy(context, new PanServiceListener(),
BluetoothProfile.PAN);
}
public boolean isConnectable() {
return true;
}
public boolean isAutoConnectable() {
return false;
}
public boolean connect(BluetoothDevice device) {
if (mService == null) return false;
List<BluetoothDevice> sinks = mService.getConnectedDevices();
if (sinks != null) {
for (BluetoothDevice sink : sinks) {
mService.disconnect(sink);
}
}
return mService.connect(device);
}
public boolean disconnect(BluetoothDevice device) {
if (mService == null) return false;
return mService.disconnect(device);
}
public int getConnectionStatus(BluetoothDevice device) {
if (mService == null) {
return BluetoothProfile.STATE_DISCONNECTED;
}
return mService.getConnectionState(device);
}
public boolean isPreferred(BluetoothDevice device) {
// return current connection status so profile checkbox is set correctly
return getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED;
}
public int getPreferred(BluetoothDevice device) {
return -1;
}
public void setPreferred(BluetoothDevice device, boolean preferred) {
// ignore: isPreferred is always true for PAN
}
public String toString() {
return NAME;
}
public int getOrdinal() {
return ORDINAL;
}
public int getNameResource(BluetoothDevice device) {
if (isLocalRoleNap(device)) {
return R.string.bluetooth_profile_pan_nap;
} else {
return R.string.bluetooth_profile_pan;
}
}
public int getSummaryResourceForDevice(BluetoothDevice device) {
int state = getConnectionStatus(device);
switch (state) {
case BluetoothProfile.STATE_DISCONNECTED:
return R.string.bluetooth_pan_profile_summary_use_for;
case BluetoothProfile.STATE_CONNECTED:
if (isLocalRoleNap(device)) {
return R.string.bluetooth_pan_nap_profile_summary_connected;
} else {
return R.string.bluetooth_pan_user_profile_summary_connected;
}
default:
return Utils.getConnectionStateSummary(state);
}
}
public int getDrawableResource(BluetoothClass btClass) {
return R.drawable.ic_bt_network_pan;
}
// Tethering direction determines UI strings.
void setLocalRole(BluetoothDevice device, int role) {
mDeviceRoleMap.put(device, role);
}
boolean isLocalRoleNap(BluetoothDevice device) {
if (mDeviceRoleMap.containsKey(device)) {
return mDeviceRoleMap.get(device) == BluetoothPan.LOCAL_NAP_ROLE;
} else {
return false;
}
}
protected void finalize() {
if (V) Log.d(TAG, "finalize()");
if (mService != null) {
try {
BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.PAN, mService);
mService = null;
}catch (Throwable t) {
Log.w(TAG, "Error cleaning up PAN proxy", t);
}
}
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright (C) 2011 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.settingslib.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothPbap;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.content.Context;
import android.os.ParcelUuid;
import android.util.Log;
import com.android.settingslib.R;
import java.util.HashMap;
import java.util.List;
/**
* PBAPServer Profile
*/
public final class PbapServerProfile implements LocalBluetoothProfile {
private static final String TAG = "PbapServerProfile";
private static boolean V = true;
private BluetoothPbap mService;
private boolean mIsProfileReady;
static final String NAME = "PBAP Server";
// Order of this profile in device profiles list
private static final int ORDINAL = 6;
// The UUIDs indicate that remote device might access pbap server
static final ParcelUuid[] PBAB_CLIENT_UUIDS = {
BluetoothUuid.HSP,
BluetoothUuid.Handsfree,
BluetoothUuid.PBAP_PCE
};
// These callbacks run on the main thread.
private final class PbapServiceListener
implements BluetoothPbap.ServiceListener {
public void onServiceConnected(BluetoothPbap proxy) {
if (V) Log.d(TAG,"Bluetooth service connected");
mService = (BluetoothPbap) proxy;
mIsProfileReady=true;
}
public void onServiceDisconnected() {
if (V) Log.d(TAG,"Bluetooth service disconnected");
mIsProfileReady=false;
}
}
public boolean isProfileReady() {
return mIsProfileReady;
}
PbapServerProfile(Context context) {
BluetoothPbap pbap = new BluetoothPbap(context, new PbapServiceListener());
}
public boolean isConnectable() {
return true;
}
public boolean isAutoConnectable() {
return false;
}
public boolean connect(BluetoothDevice device) {
/*Can't connect from server */
return false;
}
public boolean disconnect(BluetoothDevice device) {
if (mService == null) return false;
return mService.disconnect();
}
public int getConnectionStatus(BluetoothDevice device) {
if (mService == null) {
return BluetoothProfile.STATE_DISCONNECTED;
}
if (mService.isConnected(device))
return BluetoothProfile.STATE_CONNECTED;
else
return BluetoothProfile.STATE_DISCONNECTED;
}
public boolean isPreferred(BluetoothDevice device) {
return false;
}
public int getPreferred(BluetoothDevice device) {
return -1;
}
public void setPreferred(BluetoothDevice device, boolean preferred) {
// ignore: isPreferred is always true for PBAP
}
public String toString() {
return NAME;
}
public int getOrdinal() {
return ORDINAL;
}
public int getNameResource(BluetoothDevice device) {
return R.string.bluetooth_profile_pbap;
}
public int getSummaryResourceForDevice(BluetoothDevice device) {
return R.string.bluetooth_profile_pbap_summary;
}
public int getDrawableResource(BluetoothClass btClass) {
return R.drawable.ic_bt_cellphone;
}
protected void finalize() {
if (V) Log.d(TAG, "finalize()");
if (mService != null) {
try {
mService.close();
mService = null;
}catch (Throwable t) {
Log.w(TAG, "Error cleaning up PBAP proxy", t);
}
}
}
}

View File

@@ -0,0 +1,43 @@
package com.android.settingslib.bluetooth;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import com.android.settingslib.R;
public class Utils {
public static final boolean V = false; // verbose logging
public static final boolean D = true; // regular logging
private static ErrorListener sErrorListener;
public static int getConnectionStateSummary(int connectionState) {
switch (connectionState) {
case BluetoothProfile.STATE_CONNECTED:
return R.string.bluetooth_connected;
case BluetoothProfile.STATE_CONNECTING:
return R.string.bluetooth_connecting;
case BluetoothProfile.STATE_DISCONNECTED:
return R.string.bluetooth_disconnected;
case BluetoothProfile.STATE_DISCONNECTING:
return R.string.bluetooth_disconnecting;
default:
return 0;
}
}
static void showError(Context context, String name, int messageResId) {
if (sErrorListener != null) {
sErrorListener.onShowError(context, name, messageResId);
}
}
public static void setErrorListener(ErrorListener listener) {
sErrorListener = listener;
}
public interface ErrorListener {
void onShowError(Context context, String name, int messageResId);
}
}