Merge "Move non-ui bt settings code to SettingsLib"
BIN
packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png
Normal file
|
After Width: | Height: | Size: 653 B |
BIN
packages/SettingsLib/res/drawable-hdpi/ic_bt_headphones_a2dp.png
Normal file
|
After Width: | Height: | Size: 631 B |
BIN
packages/SettingsLib/res/drawable-hdpi/ic_bt_headset_hfp.png
Normal file
|
After Width: | Height: | Size: 649 B |
BIN
packages/SettingsLib/res/drawable-hdpi/ic_bt_misc_hid.png
Normal file
|
After Width: | Height: | Size: 342 B |
BIN
packages/SettingsLib/res/drawable-hdpi/ic_bt_network_pan.png
Normal file
|
After Width: | Height: | Size: 528 B |
BIN
packages/SettingsLib/res/drawable-hdpi/ic_bt_pointing_hid.png
Normal file
|
After Width: | Height: | Size: 600 B |
BIN
packages/SettingsLib/res/drawable-hdpi/ic_lockscreen_ime.png
Normal file
|
After Width: | Height: | Size: 285 B |
BIN
packages/SettingsLib/res/drawable-ldrtl-hdpi/ic_bt_cellphone.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
packages/SettingsLib/res/drawable-ldrtl-mdpi/ic_bt_cellphone.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
BIN
packages/SettingsLib/res/drawable-mdpi/ic_bt_cellphone.png
Normal file
|
After Width: | Height: | Size: 450 B |
BIN
packages/SettingsLib/res/drawable-mdpi/ic_bt_headphones_a2dp.png
Normal file
|
After Width: | Height: | Size: 444 B |
BIN
packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png
Normal file
|
After Width: | Height: | Size: 448 B |
BIN
packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png
Normal file
|
After Width: | Height: | Size: 246 B |
BIN
packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png
Normal file
|
After Width: | Height: | Size: 418 B |
BIN
packages/SettingsLib/res/drawable-mdpi/ic_bt_pointing_hid.png
Normal file
|
After Width: | Height: | Size: 370 B |
BIN
packages/SettingsLib/res/drawable-mdpi/ic_lockscreen_ime.png
Normal file
|
After Width: | Height: | Size: 175 B |
BIN
packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png
Normal file
|
After Width: | Height: | Size: 792 B |
|
After Width: | Height: | Size: 764 B |
BIN
packages/SettingsLib/res/drawable-xhdpi/ic_bt_headset_hfp.png
Normal file
|
After Width: | Height: | Size: 771 B |
BIN
packages/SettingsLib/res/drawable-xhdpi/ic_bt_misc_hid.png
Normal file
|
After Width: | Height: | Size: 220 B |
BIN
packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png
Normal file
|
After Width: | Height: | Size: 717 B |
BIN
packages/SettingsLib/res/drawable-xhdpi/ic_bt_pointing_hid.png
Normal file
|
After Width: | Height: | Size: 713 B |
BIN
packages/SettingsLib/res/drawable-xhdpi/ic_lockscreen_ime.png
Normal file
|
After Width: | Height: | Size: 254 B |
BIN
packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
BIN
packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
packages/SettingsLib/res/drawable-xxhdpi/ic_bt_misc_hid.png
Normal file
|
After Width: | Height: | Size: 367 B |
BIN
packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png
Normal file
|
After Width: | Height: | Size: 993 B |
BIN
packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png
Normal file
|
After Width: | Height: | Size: 947 B |
BIN
packages/SettingsLib/res/drawable-xxhdpi/ic_lockscreen_ime.png
Normal file
|
After Width: | Height: | Size: 327 B |
BIN
packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
BIN
packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png
Normal file
|
After Width: | Height: | Size: 305 B |
BIN
packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_pointing_hid.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png
Normal file
|
After Width: | Height: | Size: 412 B |
@@ -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>
|
||||
|
||||
214
packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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!!");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
227
packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
201
packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
89
packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java
Executable 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
|
||||
}
|
||||
}
|
||||
183
packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||