M7350v1_en_gpl

This commit is contained in:
T
2024-09-09 08:52:07 +00:00
commit f9cc65cfda
65988 changed files with 26357421 additions and 0 deletions
@@ -0,0 +1,828 @@
/*
* Copyright (C) 2008 The Android Open Source Project
* Copyright (c) 2011, The Linux Foundation. All rights reserved.
*
* 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.
*/
/**
* TODO: Move this to services.jar
* and make the contructor package private again.
* @hide
*/
package android.server;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothA2dp;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Message;
import android.os.ParcelUuid;
import android.provider.Settings;
import android.util.Log;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
private static final String TAG = "BluetoothA2dpService";
private static final boolean DBG = true;
public static final String BLUETOOTH_A2DP_SERVICE = "bluetooth_a2dp";
private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
private static final String BLUETOOTH_ENABLED = "bluetooth_enabled";
private static final String PROPERTY_STATE = "State";
private static final String SINK_STATE_DISCONNECTED = "disconnected";
private static final String SINK_STATE_CONNECTING = "connecting";
private static final String SINK_STATE_CONNECTED = "connected";
private static final String SINK_STATE_PLAYING = "playing";
private static int mSinkCount;
private final Context mContext;
private final IntentFilter mIntentFilter;
private HashMap<BluetoothDevice, Integer> mAudioDevices;
private final AudioManager mAudioManager;
private final BluetoothService mBluetoothService;
private final BluetoothAdapter mAdapter;
private int mTargetA2dpState;
/* AVRCP1.3 Metadata variables */
private String mTrackName = DEFAULT_METADATA_STRING;
private String mArtistName = DEFAULT_METADATA_STRING;
private String mAlbumName = DEFAULT_METADATA_STRING;
private String mMediaNumber = DEFAULT_METADATA_NUMBER;
private String mMediaCount = DEFAULT_METADATA_NUMBER;
private String mDuration = DEFAULT_METADATA_NUMBER;
private int mPlayStatus = (int)Integer.valueOf(DEFAULT_METADATA_NUMBER);
private long mPosition = (long)Long.valueOf(DEFAULT_METADATA_NUMBER);
/* AVRCP1.3 Events */
private final static int EVENT_PLAYSTATUS_CHANGED = 0x1;
private final static int EVENT_TRACK_CHANGED = 0x2;
/*AVRCP 1.3 Music App Intents */
private static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
private static final String META_CHANGED = "com.android.music.metachanged";
private static final String PLAYSTATUS_REQUEST = "com.android.music.playstatusrequest";
private static final String PLAYSTATUS_RESPONSE = "com.android.music.playstatusresponse";
private final static String DEFAULT_METADATA_STRING = "Unknown";
private final static String DEFAULT_METADATA_NUMBER = "0";
/* AVRCP 1.3 PlayStatus */
private final static int STATUS_STOPPED = 0X00;
private final static int STATUS_PLAYING = 0X01;
private final static int STATUS_PAUSED = 0X02;
private final static int STATUS_FWD_SEEK = 0X03;
private final static int STATUS_REV_SEEK = 0X04;
private final static int STATUS_ERROR = 0XFF;
private String mPlayStatusRequestPath = "/";
private final static int MESSAGE_PLAYSTATUS_TIMEOUT = 1;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_PLAYSTATUS_TIMEOUT:
Log.i(TAG, "Timed outM - Sending Playstatus");
sendPlayStatus(mPlayStatusRequestPath);
}
}
};
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
BluetoothDevice device =
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
BluetoothAdapter.ERROR);
switch (state) {
case BluetoothAdapter.STATE_ON:
onBluetoothEnable();
break;
case BluetoothAdapter.STATE_TURNING_OFF:
onBluetoothDisable();
break;
}
} else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
BluetoothDevice.ERROR);
if (device == null) {
Log.e(TAG, "Error! device is null");
return;
}
switch(bondState) {
case BluetoothDevice.BOND_BONDED:
if (getSinkPriority(device) == BluetoothA2dp.PRIORITY_UNDEFINED) {
setSinkPriority(device, BluetoothA2dp.PRIORITY_ON);
}
break;
case BluetoothDevice.BOND_NONE:
setSinkPriority(device, BluetoothA2dp.PRIORITY_UNDEFINED);
break;
}
} else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
synchronized (this) {
if (device == null) {
Log.e(TAG, "Error! device is null");
return;
}
if (mAudioDevices.containsKey(device)) {
int state = mAudioDevices.get(device);
handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
}
}
} else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
if (streamType == AudioManager.STREAM_MUSIC) {
BluetoothDevice sinks[] = getConnectedSinks();
if (sinks.length != 0 && isPhoneDocked(sinks[0])) {
String address = sinks[0].getAddress();
int newVolLevel =
intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
int oldVolLevel =
intent.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0);
String path = mBluetoothService.getObjectPathFromAddress(address);
if (newVolLevel > oldVolLevel) {
avrcpVolumeUpNative(path);
} else if (newVolLevel < oldVolLevel) {
avrcpVolumeDownNative(path);
}
}
}
} else if (action.equals(META_CHANGED)) {
mTrackName = intent.getStringExtra("track");
mArtistName = intent.getStringExtra("artist");
mAlbumName = intent.getStringExtra("album");
if (mTrackName == null)
mTrackName = DEFAULT_METADATA_STRING;
if (mArtistName == null)
mArtistName = DEFAULT_METADATA_STRING;
if (mAlbumName == null)
mAlbumName = DEFAULT_METADATA_STRING;
long extra = intent.getLongExtra("id", 0);
if (extra < 0)
extra = 0;
mMediaNumber = String.valueOf(extra);
extra = intent.getLongExtra("ListSize", 0);;
if (extra < 0)
extra = 0;
mMediaCount = String.valueOf(extra);
extra = intent.getLongExtra("duration", 0);
if (extra < 0)
extra = 0;
mDuration = String.valueOf(extra);
extra = intent.getLongExtra("position", 0);
if (extra < 0)
extra = 0;
mPosition = extra;
if(DBG) {
Log.d(TAG, "Meta data info is trackname: "+ mTrackName+" artist: "+mArtistName);
Log.d(TAG, "mMediaNumber: "+mMediaNumber+" mediaCount "+mMediaCount);
Log.d(TAG, "mPostion "+ mPosition+" album: "+mAlbumName+ "duration "+mDuration);
}
for (String path: getConnectedSinksPaths()) {
sendMetaData(path);
sendEvent(path, EVENT_TRACK_CHANGED, Long.valueOf(mMediaNumber));
}
} else if (action.equals(PLAYSTATE_CHANGED)) {
String currentTrackName = intent.getStringExtra("track");
if ((currentTrackName != null) && (!currentTrackName.equals(mTrackName))) {
mTrackName = currentTrackName;
mArtistName = intent.getStringExtra("artist");
mAlbumName = intent.getStringExtra("album");
if (mTrackName == null)
mTrackName = DEFAULT_METADATA_STRING;
if (mArtistName == null)
mArtistName = DEFAULT_METADATA_STRING;
if (mAlbumName == null)
mAlbumName = DEFAULT_METADATA_STRING;
long extra = intent.getLongExtra("id", 0);
if (extra < 0)
extra = 0;
mMediaNumber = String.valueOf(extra);
extra = intent.getLongExtra("ListSize", 0);;
if (extra < 0)
extra = 0;
mMediaCount = String.valueOf(extra);
extra = intent.getLongExtra("duration", 0);
if (extra < 0)
extra = 0;
mDuration = String.valueOf(extra);
extra = intent.getLongExtra("position", 0);
if (extra < 0)
extra = 0;
mPosition = extra;
for (String path: getConnectedSinksPaths())
sendMetaData(path);
}
boolean playStatus = intent.getBooleanExtra("playing", false);
mPosition = intent.getLongExtra("position", 0);
if (mPosition < 0)
mPosition = 0;
mPlayStatus = convertedPlayStatus(playStatus, mPosition);
if(DBG) Log.d(TAG, "PlayState changed "+ mPlayStatus);
for (String path: getConnectedSinksPaths()) {
sendEvent(path, EVENT_PLAYSTATUS_CHANGED, (long)mPlayStatus);
}
} else if (action.equals(PLAYSTATUS_RESPONSE)) {
if(DBG) Log.d(TAG, "Received PLAYSTATUS_RESPONSE");
long extra = intent.getLongExtra("duration", 0);
if (extra < 0)
extra = 0;
mDuration = String.valueOf(extra);
mPosition = intent.getLongExtra("position", 0);
if (mPosition < 0)
mPosition = 0;
boolean playStatus = intent.getBooleanExtra("playing", false);
mPlayStatus = convertedPlayStatus(playStatus, mPosition);
if(DBG) Log.d(TAG, "Sending Playstatus");
sendPlayStatus(mPlayStatusRequestPath);
}
}
};
private synchronized int convertedPlayStatus(boolean playing, long position) {
if (playing == false && position == 0)
return STATUS_STOPPED;
if (playing == false)
return STATUS_PAUSED;
if (playing == true)
return STATUS_PLAYING;
return STATUS_ERROR;
}
private synchronized void sendMetaData(String path) {
if(DBG) {
Log.d(TAG, "sendMetaData "+ path);
Log.d(TAG, "Meta data info is trackname: "+ mTrackName+" artist: "+mArtistName);
Log.d(TAG, "mMediaNumber: "+mMediaNumber+" mediaCount "+mMediaCount);
Log.d(TAG, "mPostion "+ mPosition+" album: "+mAlbumName+ "duration "+mDuration);
}
sendMetaDataNative(path);
}
private synchronized void sendEvent(String path, int eventId, long data) {
if(DBG) Log.d(TAG, "sendEvent "+path+ " data "+ data);
sendEventNative(path, eventId, data);
}
private synchronized void sendPlayStatus(String path) {
if(DBG) Log.d(TAG, "sendPlayStatus"+ path);
sendPlayStatusNative(path, (int)Integer.valueOf(mDuration), (int)mPosition, mPlayStatus);
}
private void onGetPlayStatusRequest(String path) {
if(DBG) Log.d(TAG, "onGetPlayStatusRequest"+path);
mPlayStatusRequestPath = path;
mContext.sendBroadcast(new Intent(PLAYSTATUS_REQUEST));
mHandler.sendMessageDelayed(
mHandler.obtainMessage(MESSAGE_PLAYSTATUS_TIMEOUT), 130);
}
private boolean isPhoneDocked(BluetoothDevice device) {
// This works only because these broadcast intents are "sticky"
Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
if (i != null) {
int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
BluetoothDevice dockDevice = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (dockDevice != null && device.equals(dockDevice)) {
return true;
}
}
}
return false;
}
public BluetoothA2dpService(Context context, BluetoothService bluetoothService) {
mContext = context;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mBluetoothService = bluetoothService;
if (mBluetoothService == null) {
throw new RuntimeException("Platform does not support Bluetooth");
}
if (!initNative()) {
throw new RuntimeException("Could not init BluetoothA2dpService");
}
mAdapter = BluetoothAdapter.getDefaultAdapter();
mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
mIntentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
mIntentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
mIntentFilter.addAction(PLAYSTATE_CHANGED);
mIntentFilter.addAction(META_CHANGED);
mIntentFilter.addAction(PLAYSTATUS_RESPONSE);
mContext.registerReceiver(mReceiver, mIntentFilter);
mAudioDevices = new HashMap<BluetoothDevice, Integer>();
if (mBluetoothService.isEnabled())
onBluetoothEnable();
mTargetA2dpState = -1;
mBluetoothService.setA2dpService(this);
}
@Override
protected void finalize() throws Throwable {
try {
cleanupNative();
} finally {
super.finalize();
}
}
private int convertBluezSinkStringtoState(String value) {
if (value.equalsIgnoreCase("disconnected"))
return BluetoothA2dp.STATE_DISCONNECTED;
if (value.equalsIgnoreCase("connecting"))
return BluetoothA2dp.STATE_CONNECTING;
if (value.equalsIgnoreCase("connected"))
return BluetoothA2dp.STATE_CONNECTED;
if (value.equalsIgnoreCase("playing"))
return BluetoothA2dp.STATE_PLAYING;
return -1;
}
private boolean isSinkDevice(BluetoothDevice device) {
ParcelUuid[] uuids = mBluetoothService.getRemoteUuids(device.getAddress());
if (uuids != null && BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSink)) {
return true;
}
return false;
}
private synchronized boolean addAudioSink (BluetoothDevice device) {
String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
String propValues[] = (String []) getSinkPropertiesNative(path);
if (propValues == null) {
Log.e(TAG, "Error while getting AudioSink properties for device: " + device);
return false;
}
Integer state = null;
// Properties are name-value pairs
for (int i = 0; i < propValues.length; i+=2) {
if (propValues[i].equals(PROPERTY_STATE)) {
state = new Integer(convertBluezSinkStringtoState(propValues[i+1]));
break;
}
}
mAudioDevices.put(device, state);
handleSinkStateChange(device, BluetoothA2dp.STATE_DISCONNECTED, state);
return true;
}
private synchronized void onBluetoothEnable() {
String devices = mBluetoothService.getProperty("Devices");
mSinkCount = 0;
if (devices != null) {
String [] paths = devices.split(",");
for (String path: paths) {
String address = mBluetoothService.getAddressFromObjectPath(path);
BluetoothDevice device = mAdapter.getRemoteDevice(address);
ParcelUuid[] remoteUuids = mBluetoothService.getRemoteUuids(address);
if (remoteUuids != null)
if (BluetoothUuid.containsAnyUuid(remoteUuids,
new ParcelUuid[] {BluetoothUuid.AudioSink,
BluetoothUuid.AdvAudioDist})) {
addAudioSink(device);
}
}
}
mAudioManager.setParameters(BLUETOOTH_ENABLED+"=true");
mAudioManager.setParameters("A2dpSuspended=false");
}
private synchronized void onBluetoothDisable() {
mAudioManager.setParameters(BLUETOOTH_ENABLED + "=false");
if (!mAudioDevices.isEmpty()) {
BluetoothDevice[] devices = new BluetoothDevice[mAudioDevices.size()];
devices = mAudioDevices.keySet().toArray(devices);
for (BluetoothDevice device : devices) {
int state = getSinkState(device);
switch (state) {
case BluetoothA2dp.STATE_CONNECTING:
case BluetoothA2dp.STATE_CONNECTED:
case BluetoothA2dp.STATE_PLAYING:
disconnectSinkNative(mBluetoothService.getObjectPathFromAddress(
device.getAddress()));
handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
break;
case BluetoothA2dp.STATE_DISCONNECTING:
handleSinkStateChange(device, BluetoothA2dp.STATE_DISCONNECTING,
BluetoothA2dp.STATE_DISCONNECTED);
break;
}
}
mAudioDevices.clear();
}
}
private synchronized boolean isConnectSinkFeasible(BluetoothDevice device) {
if (!mBluetoothService.isEnabled() || !isSinkDevice(device) ||
getSinkPriority(device) == BluetoothA2dp.PRIORITY_OFF) {
return false;
}
if (mAudioDevices.get(device) == null && !addAudioSink(device)) {
return false;
}
String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
if (path == null) {
return false;
}
return true;
}
public synchronized boolean connectSink(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
if (DBG) log("connectSink(" + device + ")");
if (!isConnectSinkFeasible(device)) return false;
return mBluetoothService.connectSink(device.getAddress());
}
public synchronized boolean connectSinkInternal(BluetoothDevice device) {
if (!mBluetoothService.isEnabled()) return false;
int state = mAudioDevices.get(device);
// ignore if there are any active sinks
if (lookupSinksMatchingStates(new int[] {
BluetoothA2dp.STATE_CONNECTING,
BluetoothA2dp.STATE_CONNECTED,
BluetoothA2dp.STATE_PLAYING,
BluetoothA2dp.STATE_DISCONNECTING}).size() != 0) {
return false;
}
switch (state) {
case BluetoothA2dp.STATE_CONNECTED:
case BluetoothA2dp.STATE_PLAYING:
case BluetoothA2dp.STATE_DISCONNECTING:
return false;
case BluetoothA2dp.STATE_CONNECTING:
return true;
}
String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
// State is DISCONNECTED and we are connecting.
if (getSinkPriority(device) < BluetoothA2dp.PRIORITY_AUTO_CONNECT) {
setSinkPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT);
}
handleSinkStateChange(device, state, BluetoothA2dp.STATE_CONNECTING);
if (!connectSinkNative(path)) {
// Restore previous state
handleSinkStateChange(device, mAudioDevices.get(device), state);
return false;
}
return true;
}
private synchronized boolean isDisconnectSinkFeasible(BluetoothDevice device) {
String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
if (path == null) {
return false;
}
int state = getSinkState(device);
switch (state) {
case BluetoothA2dp.STATE_DISCONNECTED:
return false;
case BluetoothA2dp.STATE_DISCONNECTING:
return true;
}
return true;
}
public synchronized boolean disconnectSink(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
if (DBG) log("disconnectSink(" + device + ")");
if (!isDisconnectSinkFeasible(device)) return false;
return mBluetoothService.disconnectSink(device.getAddress());
}
public synchronized boolean disconnectSinkInternal(BluetoothDevice device) {
int state = getSinkState(device);
String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
switch (state) {
case BluetoothA2dp.STATE_DISCONNECTED:
case BluetoothA2dp.STATE_DISCONNECTING:
return false;
}
// State is CONNECTING or CONNECTED or PLAYING
handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTING);
if (!disconnectSinkNative(path)) {
// Restore previous state
handleSinkStateChange(device, mAudioDevices.get(device), state);
return false;
}
return true;
}
public synchronized boolean suspendSink(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
if (DBG) log("suspendSink(" + device + "), mTargetA2dpState: "+mTargetA2dpState);
if (device == null || mAudioDevices == null) {
return false;
}
String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
Integer state = mAudioDevices.get(device);
if (path == null || state == null) {
return false;
}
mTargetA2dpState = BluetoothA2dp.STATE_CONNECTED;
return checkSinkSuspendState(state.intValue());
}
public synchronized boolean resumeSink(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
if (DBG) log("resumeSink(" + device + "), mTargetA2dpState: "+mTargetA2dpState);
if (device == null || mAudioDevices == null) {
return false;
}
String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
Integer state = mAudioDevices.get(device);
if (path == null || state == null) {
return false;
}
mTargetA2dpState = BluetoothA2dp.STATE_PLAYING;
return checkSinkSuspendState(state.intValue());
}
public synchronized BluetoothDevice[] getConnectedSinks() {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Set<BluetoothDevice> sinks = lookupSinksMatchingStates(
new int[] {BluetoothA2dp.STATE_CONNECTED, BluetoothA2dp.STATE_PLAYING});
return sinks.toArray(new BluetoothDevice[sinks.size()]);
}
public synchronized String[] getConnectedSinksPaths() {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
BluetoothDevice[] btDevices = getConnectedSinks();
String[] paths = new String[btDevices.length];
int index = 0;
for(BluetoothDevice device:btDevices) {
paths[index++] = mBluetoothService.getObjectPathFromAddress(device.getAddress());
}
return paths;
}
public synchronized BluetoothDevice[] getNonDisconnectedSinks() {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Set<BluetoothDevice> sinks = lookupSinksMatchingStates(
new int[] {BluetoothA2dp.STATE_CONNECTED,
BluetoothA2dp.STATE_PLAYING,
BluetoothA2dp.STATE_CONNECTING,
BluetoothA2dp.STATE_DISCONNECTING});
return sinks.toArray(new BluetoothDevice[sinks.size()]);
}
public synchronized boolean isPlayingSink() {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Set<BluetoothDevice> sinks = lookupSinksMatchingStates(
new int[] {BluetoothA2dp.STATE_PLAYING});
return sinks.size() > 0;
}
public synchronized int getSinkState(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Integer state = mAudioDevices.get(device);
if (state == null)
return BluetoothA2dp.STATE_DISCONNECTED;
return state;
}
public synchronized int getSinkPriority(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()),
BluetoothA2dp.PRIORITY_UNDEFINED);
}
public synchronized boolean setSinkPriority(BluetoothDevice device, int priority) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
if (!BluetoothAdapter.checkBluetoothAddress(device.getAddress())) {
return false;
}
return Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()), priority);
}
public synchronized boolean allowIncomingConnect(BluetoothDevice device, boolean value) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
String address = device.getAddress();
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
return false;
}
Integer data = mBluetoothService.getAuthorizationAgentRequestData(address);
if (data == null) {
Log.w(TAG, "allowIncomingConnect(" + device + ") called but no native data available");
return false;
}
log("allowIncomingConnect: A2DP: " + device + ":" + value);
return mBluetoothService.setAuthorizationNative(address, value, data.intValue());
}
private synchronized void onSinkPropertyChanged(String path, String []propValues) {
if (!mBluetoothService.isEnabled()) {
return;
}
String name = propValues[0];
String address = mBluetoothService.getAddressFromObjectPath(path);
if (address == null) {
Log.e(TAG, "onSinkPropertyChanged: Address of the remote device in null");
return;
}
BluetoothDevice device = mAdapter.getRemoteDevice(address);
if (name.equals(PROPERTY_STATE)) {
int state = convertBluezSinkStringtoState(propValues[1]);
if (mAudioDevices.get(device) == null) {
// This is for an incoming connection for a device not known to us.
// We have authorized it and bluez state has changed.
addAudioSink(device);
} else {
int prevState = mAudioDevices.get(device);
handleSinkStateChange(device, prevState, state);
}
}
}
private void handleSinkStateChange(BluetoothDevice device, int prevState, int state) {
if (state != prevState) {
if (state == BluetoothA2dp.STATE_DISCONNECTED ||
state == BluetoothA2dp.STATE_DISCONNECTING) {
mSinkCount--;
} else if (state == BluetoothA2dp.STATE_CONNECTED) {
mSinkCount ++;
}
mAudioDevices.put(device, state);
checkSinkSuspendState(state);
mTargetA2dpState = -1;
if (getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF &&
state == BluetoothA2dp.STATE_CONNECTED) {
// We have connected or attempting to connect.
// Bump priority
setSinkPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT);
// We will only have 1 device with AUTO_CONNECT priority
// To be backward compatible set everyone else to have PRIORITY_ON
adjustOtherSinkPriorities(device);
}
Intent intent = new Intent(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.putExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, prevState);
intent.putExtra(BluetoothA2dp.EXTRA_SINK_STATE, state);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
if (DBG) log("A2DP state : device: " + device + " State:" + prevState + "->" + state);
}
if (prevState == BluetoothA2dp.STATE_CONNECTING &&
state == BluetoothA2dp.STATE_CONNECTED) {
for (String path: getConnectedSinksPaths()) {
sendMetaData(path);
sendEvent(path, EVENT_PLAYSTATUS_CHANGED, (long)mPlayStatus);
}
}
}
private void adjustOtherSinkPriorities(BluetoothDevice connectedDevice) {
for (BluetoothDevice device : mAdapter.getBondedDevices()) {
if (getSinkPriority(device) >= BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
!device.equals(connectedDevice)) {
setSinkPriority(device, BluetoothA2dp.PRIORITY_ON);
}
}
}
private synchronized Set<BluetoothDevice> lookupSinksMatchingStates(int[] states) {
Set<BluetoothDevice> sinks = new HashSet<BluetoothDevice>();
if (mAudioDevices.isEmpty()) {
return sinks;
}
for (BluetoothDevice device: mAudioDevices.keySet()) {
int sinkState = getSinkState(device);
for (int state : states) {
if (state == sinkState) {
sinks.add(device);
break;
}
}
}
return sinks;
}
private boolean checkSinkSuspendState(int state) {
boolean result = true;
if (state != mTargetA2dpState) {
if (state == BluetoothA2dp.STATE_PLAYING &&
mTargetA2dpState == BluetoothA2dp.STATE_CONNECTED) {
mAudioManager.setParameters("A2dpSuspended=true");
} else if (state == BluetoothA2dp.STATE_CONNECTED &&
mTargetA2dpState == BluetoothA2dp.STATE_PLAYING) {
mAudioManager.setParameters("A2dpSuspended=false");
} else {
result = false;
}
}
return result;
}
private void onConnectSinkResult(String deviceObjectPath, boolean result) {
// If the call was a success, ignore we will update the state
// when we a Sink Property Change
if (!result) {
if (deviceObjectPath != null) {
String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
if (address == null) return;
BluetoothDevice device = mAdapter.getRemoteDevice(address);
int state = getSinkState(device);
handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
}
}
}
@Override
protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mAudioDevices.isEmpty()) return;
pw.println("Cached audio devices:");
for (BluetoothDevice device : mAudioDevices.keySet()) {
int state = mAudioDevices.get(device);
pw.println(device + " " + BluetoothA2dp.stateToString(state));
}
}
private static void log(String msg) {
Log.d(TAG, msg);
}
private native boolean initNative();
private native void cleanupNative();
private synchronized native boolean connectSinkNative(String path);
private synchronized native boolean disconnectSinkNative(String path);
private synchronized native boolean suspendSinkNative(String path);
private synchronized native boolean resumeSinkNative(String path);
private synchronized native Object []getSinkPropertiesNative(String path);
private synchronized native boolean avrcpVolumeUpNative(String path);
private synchronized native boolean avrcpVolumeDownNative(String path);
private synchronized native boolean sendMetaDataNative(String path);
private synchronized native boolean sendEventNative(String path, int eventId, long data);
private synchronized native boolean sendPlayStatusNative(String path, int duration,
int position, int playStatus);
}
@@ -0,0 +1,814 @@
/*
* Copyright (C) 2008 The Android Open Source Project
* Copyright (c) 2010, 2011 The Linux Foundation. All rights reserved.
*
* 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 android.server;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothUuid;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import android.os.ParcelUuid;
import android.util.Log;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import java.util.HashMap;
import java.util.Set;
/**
* TODO: Move this to
* java/services/com/android/server/BluetoothEventLoop.java
* and make the constructor package private again.
*
* @hide
*/
class BluetoothEventLoop {
private static final String TAG = "BluetoothEventLoop";
private static final boolean DBG = true;
private int mNativeData;
private Thread mThread;
private boolean mStarted;
private boolean mInterrupted;
private final HashMap<String, Integer> mPasskeyAgentRequestData;
private final HashMap<String, Integer> mAuthorizationRequestData;
private final HashMap<String, Integer> mAuthorizationAgentRequestData;
private final BluetoothService mBluetoothService;
private final BluetoothAdapter mAdapter;
private final Context mContext;
private static final int EVENT_RESTART_BLUETOOTH = 1;
private static final int EVENT_PAIRING_CONSENT_DELAYED_ACCEPT = 2;
private static final int EVENT_AGENT_CANCEL = 3;
private static final int EVENT_PAIRING_TIMEOUT = 4;
private static final int CREATE_DEVICE_ALREADY_EXISTS = 1;
private static final int CREATE_DEVICE_SUCCESS = 0;
private static final int CREATE_DEVICE_FAILED = -1;
// The 1.2/2.0 incoming pairing request would timeout in 30 seconds at LMP since LMP
// Response time out is 30 seconds. Setting the INCOMING_PAIRING_TIMEOUT to 26 seconds
// to make sure that the pairing clean up happens from the HOST side.
private static final int INCOMING_PAIRING_TIMEOUT = 26000;
private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
String address = null;
switch (msg.what) {
case EVENT_RESTART_BLUETOOTH:
mBluetoothService.restart();
break;
case EVENT_PAIRING_CONSENT_DELAYED_ACCEPT:
address = (String)msg.obj;
if (address != null) {
mBluetoothService.setPairingConfirmation(address, true);
}
break;
case EVENT_AGENT_CANCEL:
// Set the Bond State to BOND_NONE.
// We always have only 1 device in BONDING state.
String[] devices = mBluetoothService.listInState(BluetoothDevice.BOND_BONDING);
if (devices.length == 0) {
break;
} else if (devices.length > 1) {
Log.e(TAG, " There is more than one device in the Bonding State");
break;
}
address = devices[0];
mBluetoothService.setBondState(address,
BluetoothDevice.BOND_NONE,
BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED);
break;
case EVENT_PAIRING_TIMEOUT:
address = (String) msg.obj;
Log.d(TAG, "Cancelling bond process");
mBluetoothService.cancelBondProcess(address);
}
}
};
static { classInitNative(); }
private static native void classInitNative();
/* package */ BluetoothEventLoop(Context context, BluetoothAdapter adapter,
BluetoothService bluetoothService) {
mBluetoothService = bluetoothService;
mContext = context;
mPasskeyAgentRequestData = new HashMap();
mAuthorizationRequestData = new HashMap();
mAuthorizationAgentRequestData = new HashMap<String, Integer>();
mAdapter = adapter;
initializeNativeDataNative();
}
protected void finalize() throws Throwable {
try {
cleanupNativeDataNative();
} finally {
super.finalize();
}
}
/* package */ HashMap<String, Integer> getPasskeyAgentRequestData() {
return mPasskeyAgentRequestData;
}
/* package */ HashMap<String, Integer> getAuthorizationRequestData() {
return mAuthorizationRequestData;
}
/* package */ HashMap<String, Integer> getAuthorizationAgentRequestData() {
return mAuthorizationAgentRequestData;
}
/* package */ void start() {
if (!isEventLoopRunningNative()) {
if (DBG) log("Starting Event Loop thread");
startEventLoopNative();
}
}
public void stop() {
if (isEventLoopRunningNative()) {
if (DBG) log("Stopping Event Loop thread");
stopEventLoopNative();
}
}
public boolean isEventLoopRunning() {
return isEventLoopRunningNative();
}
private void addDevice(String address, String[] properties) {
mBluetoothService.addRemoteDeviceProperties(address, properties);
String rssi = mBluetoothService.getRemoteDeviceProperty(address, "RSSI");
String classValue = mBluetoothService.getRemoteDeviceProperty(address, "Class");
String devType = mBluetoothService.getRemoteDeviceProperty(address, "Type");
String name = mBluetoothService.getRemoteDeviceProperty(address, "Name");
short rssiValue;
// For incoming connections, we don't get the RSSI value. Use a default of MIN_VALUE.
// If we accept the pairing, we will automatically show it at the top of the list.
if (rssi != null) {
rssiValue = (short)Integer.valueOf(rssi).intValue();
} else {
rssiValue = Short.MIN_VALUE;
}
if (classValue != null) {
Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
intent.putExtra(BluetoothDevice.EXTRA_CLASS,
new BluetoothClass(Integer.valueOf(classValue)));
intent.putExtra(BluetoothDevice.EXTRA_RSSI, rssiValue);
intent.putExtra(BluetoothDevice.EXTRA_NAME, name);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
} else if (devType != null) {
if (DBG) log("Device type: " + devType);
if ("LE".equals(devType)) {
Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
intent.putExtra(BluetoothDevice.EXTRA_RSSI, rssiValue);
intent.putExtra(BluetoothDevice.EXTRA_NAME, name);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
} else {
log ("ClassValue: " + classValue + " for remote device: " + address + " is null");
}
}
private void onDeviceFound(String address, String[] properties) {
if (properties == null) {
Log.e(TAG, "ERROR: Remote device properties are null");
return;
}
if (!mBluetoothService.isEnabled()) {
Log.e(TAG, "Bluetooth is not on");
return;
}
addDevice(address, properties);
}
private void onDeviceDisappeared(String address) {
Intent intent = new Intent(BluetoothDevice.ACTION_DISAPPEARED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
private void onDeviceDisconnectRequested(String deviceObjectPath) {
String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
if (address == null) {
Log.e(TAG, "onDeviceDisconnectRequested: Address of the remote device in null");
return;
}
Intent intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
private void onCreatePairedDeviceResult(String address, int result) {
address = address.toUpperCase();
mBluetoothService.onCreatePairedDeviceResult(address, result);
}
private void onDeviceCreated(String deviceObjectPath) {
String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
if (!mBluetoothService.isRemoteDeviceInCache(address)) {
// Incoming connection, we haven't seen this device, add to cache.
String[] properties = mBluetoothService.getRemoteDeviceProperties(address);
if (properties != null) {
addDevice(address, properties);
}
}
return;
}
private void onDeviceRemoved(String deviceObjectPath) {
String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
if (address != null) {
mBluetoothService.setBondState(address.toUpperCase(), BluetoothDevice.BOND_NONE,
BluetoothDevice.UNBOND_REASON_REMOVED);
mBluetoothService.setRemoteDeviceProperty(address, "UUIDs", null);
mBluetoothService.setRemoteDeviceProperty(address, "Services", null);
mBluetoothService.setRemoteDeviceProperty(address, "Trusted", "false");
mBluetoothService.clearRemoteDeviceGattServices(address);
}
}
/*package*/ void onPropertyChanged(String[] propValues) {
if (mBluetoothService.isAdapterPropertiesEmpty()) {
// We have got a property change before
// we filled up our cache.
mBluetoothService.getAllProperties();
}
String name = propValues[0];
if (name.equals("Name")) {
mBluetoothService.setProperty(name, propValues[1]);
Intent intent = new Intent(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
intent.putExtra(BluetoothAdapter.EXTRA_LOCAL_NAME, propValues[1]);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
} else if (name.equals("Pairable") || name.equals("Discoverable")) {
String pairable = name.equals("Pairable") ? propValues[1] :
mBluetoothService.getPropertyInternal("Pairable");
String discoverable = name.equals("Discoverable") ? propValues[1] :
mBluetoothService.getPropertyInternal("Discoverable");
// This shouldn't happen, unless Adapter Properties are null.
if (pairable == null || discoverable == null)
return;
mBluetoothService.setProperty(name, propValues[1]);
int mode = BluetoothService.bluezStringToScanMode(
pairable.equals("true"),
discoverable.equals("true"));
if (mode >= 0) {
Intent intent = new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
intent.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE, mode);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
} else if (name.equals("Discovering")) {
Intent intent;
mBluetoothService.setProperty(name, propValues[1]);
if (propValues[1].equals("true")) {
mBluetoothService.setIsDiscovering(true);
intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
} else {
// Stop the discovery.
mBluetoothService.cancelDiscovery();
mBluetoothService.setIsDiscovering(false);
intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
}
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
} else if (name.equals("Devices")) {
String value = null;
int len = Integer.valueOf(propValues[1]);
if (len > 0) {
StringBuilder str = new StringBuilder();
for (int i = 2; i < propValues.length; i++) {
str.append(propValues[i]);
str.append(",");
}
value = str.toString();
}
mBluetoothService.setProperty(name, value);
} else if (name.equals("Powered")) {
// bluetoothd has restarted, re-read all our properties.
// Note: bluez only sends this property change when it restarts.
if (propValues[1].equals("true"))
onRestartRequired();
}
}
private void onDevicePropertyChanged(String deviceObjectPath, String[] propValues) {
if (!mBluetoothService.isEnabled()) {
Log.e(TAG, "Bluetooth is not enabled");
return;
}
String name = propValues[0];
String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
if (address == null) {
Log.e(TAG, "onDevicePropertyChanged: Address of the remote device in null");
return;
}
if (DBG) {
log("Device property changed:" + address + ", property: " + name);
}
BluetoothDevice device = mAdapter.getRemoteDevice(address);
if (name.equals("Name")) {
mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
Intent intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.putExtra(BluetoothDevice.EXTRA_NAME, propValues[1]);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
} else if (name.equals("Class")) {
mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
Intent intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.putExtra(BluetoothDevice.EXTRA_CLASS,
new BluetoothClass(Integer.valueOf(propValues[1])));
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
} else if (name.equals("Connected")) {
Log.d(TAG, "Device property Connected: " + propValues[1]);
mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
Intent intent = null;
if (propValues[1].equals("true")) {
intent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
// Set the link timeout to 8000 slots (5 sec timeout)
// for bluetooth docks.
if (mBluetoothService.isBluetoothDock(address)) {
mBluetoothService.setLinkTimeout(address, 8000);
}
} else {
// Check and clean-up if bonding is in progress
if (mBluetoothService.getBondState(address) ==
BluetoothDevice.BOND_BONDING) {
mBluetoothService.setBondState(address,
BluetoothDevice.BOND_NONE);
}
intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
}
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
} else if (name.equals("UUIDs")) {
String uuid = null;
int len = Integer.valueOf(propValues[1]);
if (len > 0) {
StringBuilder str = new StringBuilder();
for (int i = 2; i < propValues.length; i++) {
str.append(propValues[i]);
str.append(",");
}
uuid = str.toString();
}
mBluetoothService.setRemoteDeviceProperty(address, name, uuid);
// UUIDs have changed, query remote service channel and update cache.
mBluetoothService.updateDeviceServiceChannelCache(address);
mBluetoothService.sendUuidIntent(address);
} else if (name.equals("Services")) {
String services = null;
int len = Integer.valueOf(propValues[1]);
if (len > 0) {
StringBuilder str = new StringBuilder();
for (int i = 2; i < propValues.length; i++) {
str.append(propValues[i]);
str.append(",");
}
services = str.toString();
}
mBluetoothService.setRemoteDeviceProperty(address, name, services);
mBluetoothService.sendGattIntent(address, BluetoothDevice.GATT_RESULT_SUCCESS);
} else if (name.equals("Paired")) {
if (propValues[1].equals("true")) {
// If locally initiated pairing, we will
// not go to BOND_BONDED state until we have received a
// successful return value in onCreatePairedDeviceResult
// There can be one outgoing and one incoming pairing at the same time
if (!address.equals(mBluetoothService.getPendingOutgoingBonding())) {
mBluetoothService.setBondState(address, BluetoothDevice.BOND_BONDED);
}
} else {
mBluetoothService.setBondState(address, BluetoothDevice.BOND_NONE);
mBluetoothService.setRemoteDeviceProperty(address, "Trusted", "false");
}
} else if (name.equals("Trusted")) {
if (DBG)
log("set trust state succeded, value is " + propValues[1]);
mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
}
}
private String checkAuthorizationRequestAndGetAddress(String objectPath, int nativeData) {
String address = mBluetoothService.getAddressFromObjectPath(objectPath);
if (address == null) {
Log.e(TAG, "Unable to get device address in checkAuthorizationRequestAndGetAddress, " +
"returning null");
return null;
}
address = address.toUpperCase();
mAuthorizationRequestData.put(address, new Integer(nativeData));
if (mBluetoothService.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF) {
// shutdown path
mBluetoothService.sapAuthorize(address, false);
return null;
}
return address;
}
private String checkPairingRequestAndGetAddress(String objectPath, int nativeData) {
String address = mBluetoothService.getAddressFromObjectPath(objectPath);
if (address == null) {
Log.e(TAG, "Unable to get device address in checkPairingRequestAndGetAddress, " +
"returning null");
return null;
}
address = address.toUpperCase();
mPasskeyAgentRequestData.put(address, new Integer(nativeData));
if (mBluetoothService.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF) {
// shutdown path
mBluetoothService.cancelPairingUserInput(address);
return null;
}
// Set state to BONDING. For incoming connections it will be set here.
// For outgoing connections, it gets set when we call createBond.
// Also set it only when the state is not already Bonded, we can sometimes
// get an authorization request from the remote end if it doesn't have the link key
// while we still have it.
if (mBluetoothService.getBondState(address) != BluetoothDevice.BOND_BONDED)
mBluetoothService.setBondState(address, BluetoothDevice.BOND_BONDING);
return address;
}
private void onRequestPairingConsent(String objectPath, int nativeData) {
String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
if (address == null) return;
/* The link key will not be stored if the incoming request has MITM
* protection switched on. Unfortunately, some devices have MITM
* switched on even though their capabilities are NoInputNoOutput,
* so we may get this request many times. Also if we respond immediately,
* the other end is unable to handle it. Delay sending the message.
*/
if (mBluetoothService.getBondState(address) == BluetoothDevice.BOND_BONDED) {
Message message = mHandler.obtainMessage(EVENT_PAIRING_CONSENT_DELAYED_ACCEPT);
message.obj = address;
mHandler.sendMessageDelayed(message, 1500);
return;
}
Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
BluetoothDevice.PAIRING_VARIANT_CONSENT);
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
return;
}
private void onRequestPasskeyConfirmation(String objectPath, int passkey, int nativeData) {
String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
if (address == null) return;
Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
intent.putExtra(BluetoothDevice.EXTRA_PASSKEY, passkey);
intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION);
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
return;
}
private void onRequestPasskey(String objectPath, int nativeData) {
String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
if (address == null) return;
Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
BluetoothDevice.PAIRING_VARIANT_PASSKEY);
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
return;
}
private void onSapAuthorize(String objectPath, String uuid, int nativeData) {
Log.i(TAG, "onSapAuthorize" + objectPath + uuid);
String address = checkAuthorizationRequestAndGetAddress(objectPath, nativeData);
if (address == null) {
Log.e(TAG, "address is null");
return;
}
/*Get the Trust state of the device*/
boolean trusted = mBluetoothService.getTrustState(address);
if (trusted) {
/*Say as authorized to lower layers without popping up to
user*/
mBluetoothService.sapAuthorize(address, true);
} else {
Intent intent = new Intent(BluetoothService.SAP_AUTHORIZE_INTENT);
intent.putExtra("name", mBluetoothService.getRemoteName(address));
intent.putExtra("address", address);
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
}
}
private void onSapStateChanged(String objectPath, String state, int nativeData) {
Log.i(TAG, "onSapStateChanged" + objectPath + state);
String address = mBluetoothService.getAddressFromObjectPath(objectPath);
if (address == null) {
Log.e(TAG, "Unable to get device address , " +
"returning null");
return;
}
address = address.toUpperCase();
int sapState;
if(state.equals("Connected")) {
sapState = 1;
} else {
sapState = 0;
}
Intent intent = new Intent(BluetoothService.SAP_STATECHANGE_INTENT);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
intent.putExtra("state", sapState);
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
}
private void onRequestPinCode(String objectPath, int nativeData, boolean secure) {
String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
if (address == null) return;
Log.i(TAG, "Secure pairing is "+ secure);
String pendingOutgoingAddress =
mBluetoothService.getPendingOutgoingBonding();
if (address.equals(pendingOutgoingAddress)) {
// we initiated the bonding
// Check if its a dock
if (mBluetoothService.isBluetoothDock(address)) {
String pin = mBluetoothService.getDockPin();
mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes(pin));
return;
}
BluetoothClass btClass = new BluetoothClass(mBluetoothService.getRemoteClass(address));
// try 0000 once if the device looks dumb
switch (btClass.getDeviceClass()) {
case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES:
case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO:
case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
if (mBluetoothService.attemptAutoPair(address)) return;
}
}
Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.PAIRING_VARIANT_PIN);
intent.putExtra(BluetoothDevice.EXTRA_SECURE_PAIRING, secure);
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
if (mHandler.hasMessages(EVENT_PAIRING_TIMEOUT)) {
Log.d(TAG, "Ther is EVENT_PAIRING_TIMEOUT message");
mHandler.removeMessages(EVENT_PAIRING_TIMEOUT);
}
if (!(address.equals(pendingOutgoingAddress))) {
Message msg = mHandler.obtainMessage(EVENT_PAIRING_TIMEOUT);
msg.obj = address;
mHandler.sendMessageDelayed(msg, INCOMING_PAIRING_TIMEOUT);
Log.d(TAG, "Queuing INCOMING_PAIRING_TIMEOUT msg");
}
return;
}
private void onDisplayPasskey(String objectPath, int passkey, int nativeData) {
String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
if (address == null) return;
Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
intent.putExtra(BluetoothDevice.EXTRA_PASSKEY, passkey);
intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY);
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
}
private void onRequestOobData(String objectPath , int nativeData) {
String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
if (address == null) return;
Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT);
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
}
private void onAgentAuthorize(String objectPath, String deviceUuid, int nativeData) {
String address = mBluetoothService.getAddressFromObjectPath(objectPath);
if (address == null) {
Log.e(TAG, "Unable to get device address in onAuthAgentAuthorize");
return;
}
boolean authorized = false;
ParcelUuid uuid = ParcelUuid.fromString(deviceUuid);
BluetoothA2dp a2dp = new BluetoothA2dp(mContext);
BluetoothDevice device = mAdapter.getRemoteDevice(address);
mAuthorizationAgentRequestData.put(address, new Integer(nativeData));
// Bluez sends the UUID of the local service being accessed, _not_ the
// remote service
if (mBluetoothService.isEnabled() &&
(BluetoothUuid.isAudioSource(uuid) || BluetoothUuid.isAvrcpTarget(uuid)
|| BluetoothUuid.isAdvAudioDist(uuid)) &&
!isOtherSinkInNonDisconnectingState(address)) {
authorized = a2dp.getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF;
if (authorized) {
Log.i(TAG, "First check pass for incoming A2DP / AVRCP connection from " + address);
// Some headsets try to connect AVCTP before AVDTP - against the recommendation
// If AVCTP connection fails, we get stuck in IncomingA2DP state in the state
// machine. We don't handle AVCTP signals currently. We only send
// intents for AVDTP state changes. We need to handle both of them in
// some cases. For now, just don't move to incoming state in this case.
if (!BluetoothUuid.isAvrcpTarget(uuid)) {
mBluetoothService.notifyIncomingA2dpConnection(address);
} else {
// The below change will make sure A2DP connection is
// established. If the remote device sends AVRCP connection
// first DUT will accept the AVRCP connection and initiates
// A2DP connection from handset.
a2dp.allowIncomingConnect(device, authorized);
Log.d(TAG,"AVRCP target connection");
mBluetoothService.notifyConnectA2dp(address);
return;
}
} else {
Log.i(TAG, "Rejecting incoming A2DP / AVRCP connection from " + address);
}
} else {
Log.i(TAG, "Rejecting incoming " + deviceUuid + " connection from " + address);
}
log("onAgentAuthorize(" + objectPath + ", " + deviceUuid + ") = " + authorized);
if (!authorized) a2dp.allowIncomingConnect(device, authorized);
}
private boolean onAgentOutOfBandDataAvailable(String objectPath) {
if (!mBluetoothService.isEnabled()) return false;
String address = mBluetoothService.getAddressFromObjectPath(objectPath);
if (address == null) return false;
if (mBluetoothService.getDeviceOutOfBandData(
mAdapter.getRemoteDevice(address)) != null) {
return true;
}
return false;
}
private boolean isOtherSinkInNonDisconnectingState(String address) {
BluetoothA2dp a2dp = new BluetoothA2dp(mContext);
Set<BluetoothDevice> devices = a2dp.getNonDisconnectedSinks();
if (devices.size() == 0) return false;
for(BluetoothDevice dev: devices) {
if (!dev.getAddress().equals(address)) return true;
}
return false;
}
private void onAgentCancel() {
Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_CANCEL);
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_AGENT_CANCEL),
1500);
return;
}
private void onDiscoverServicesResult(String deviceObjectPath, boolean result) {
if (!mBluetoothService.isEnabled()) {
log("Bluetooth is not on");
return;
}
String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
// We don't parse the xml here, instead just query Bluez for the properties.
if (address == null) {
Log.e(TAG, "Unexpected error! address is null");
return;
}
if (result) {
mBluetoothService.updateRemoteDevicePropertiesCache(address);
}
mBluetoothService.sendUuidIntent(address);
mBluetoothService.makeServiceChannelCallbacks(address);
}
private void onCreateDeviceResult(String address, int result) {
if (DBG) log("Result of onCreateDeviceResult:" + result);
switch (result) {
case CREATE_DEVICE_ALREADY_EXISTS:
String path = mBluetoothService.getObjectPathFromAddress(address);
if (path != null) {
mBluetoothService.discoverServicesNative(path, "");
break;
}
Log.w(TAG, "Device exists, but we dont have the bluez path, failing");
// fall-through
case CREATE_DEVICE_FAILED:
mBluetoothService.sendUuidIntent(address);
mBluetoothService.sendGattIntent(address, BluetoothDevice.GATT_RESULT_FAIL);
mBluetoothService.makeServiceChannelCallbacks(address);
break;
case CREATE_DEVICE_SUCCESS:
// nothing to do, UUID intent's will be sent via property changed
}
}
private void onRestartRequired() {
if (mBluetoothService.isEnabled()) {
Log.e(TAG, "*** A serious error occurred (did bluetoothd crash?) - " +
"restarting Bluetooth ***");
mHandler.sendEmptyMessage(EVENT_RESTART_BLUETOOTH);
}
}
private void onDiscoverCharacteristicsResult(String serviceObjectPath, boolean result) {
Log.d(TAG, "onDiscoverCharacteristicsResult: " + result);
if (result) {
mBluetoothService.updateGattServicePropertiesCache(serviceObjectPath);
}
mBluetoothService.makeDiscoverCharacteristicsCallback(serviceObjectPath, result);
}
private void onSetCharacteristicPropertyResult(String path, String property, boolean result) {
Log.d(TAG, "onSetCharPropResult path " + path + " property = " + property);
Log.d(TAG, "Result = " + result);
mBluetoothService.makeSetCharacteristicPropertyCallback(path, property, result);
}
private void onWatcherValueChanged(String characteristicPath, String value) {
// TODO: Send this to upper layer
mBluetoothService.makeWatcherValueChangedCallback(characteristicPath, value);
}
private void onUpdateCharacteristicValueResult(String charObjectPath, boolean result) {
mBluetoothService.makeUpdateCharacteristicValueCallback(charObjectPath, result);
}
private static void log(String msg) {
Log.d(TAG, msg);
}
private native void initializeNativeDataNative();
private native void startEventLoopNative();
private native void stopEventLoopNative();
private native boolean isEventLoopRunningNative();
private native void cleanupNativeDataNative();
}
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
<body>
{@hide}
</body>
@@ -0,0 +1,143 @@
/*
* Copyright (C) 2007 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 android.server.search;
import com.android.internal.content.PackageMonitor;
import android.app.ISearchManager;
import android.app.SearchManager;
import android.app.SearchableInfo;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Process;
import android.util.Log;
import java.util.List;
/**
* The search manager service handles the search UI, and maintains a registry of searchable
* activities.
*/
public class SearchManagerService extends ISearchManager.Stub {
// general debugging support
private static final String TAG = "SearchManagerService";
// Context that the service is running in.
private final Context mContext;
// This field is initialized lazily in getSearchables(), and then never modified.
private Searchables mSearchables;
/**
* Initializes the Search Manager service in the provided system context.
* Only one instance of this object should be created!
*
* @param context to use for accessing DB, window manager, etc.
*/
public SearchManagerService(Context context) {
mContext = context;
mContext.registerReceiver(new BootCompletedReceiver(),
new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
}
private synchronized Searchables getSearchables() {
if (mSearchables == null) {
Log.i(TAG, "Building list of searchable activities");
new MyPackageMonitor().register(mContext, true);
mSearchables = new Searchables(mContext);
mSearchables.buildSearchableList();
}
return mSearchables;
}
/**
* Creates the initial searchables list after boot.
*/
private final class BootCompletedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
new Thread() {
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mContext.unregisterReceiver(BootCompletedReceiver.this);
getSearchables();
}
}.start();
}
}
/**
* Refreshes the "searchables" list when packages are added/removed.
*/
class MyPackageMonitor extends PackageMonitor {
@Override
public void onSomePackagesChanged() {
// Update list of searchable activities
getSearchables().buildSearchableList();
// Inform all listeners that the list of searchables has been updated.
Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
mContext.sendBroadcast(intent);
}
}
//
// Searchable activities API
//
/**
* Returns the SearchableInfo for a given activity.
*
* @param launchActivity The activity from which we're launching this search.
* @return Returns a SearchableInfo record describing the parameters of the search,
* or null if no searchable metadata was available.
*/
public SearchableInfo getSearchableInfo(final ComponentName launchActivity) {
if (launchActivity == null) {
Log.e(TAG, "getSearchableInfo(), activity == null");
return null;
}
return getSearchables().getSearchableInfo(launchActivity);
}
/**
* Returns a list of the searchable activities that can be included in global search.
*/
public List<SearchableInfo> getSearchablesInGlobalSearch() {
return getSearchables().getSearchablesInGlobalSearchList();
}
/**
* Gets the name of the global search activity.
*/
public ComponentName getGlobalSearchActivity() {
return getSearchables().getGlobalSearchActivity();
}
/**
* Gets the name of the web search activity.
*/
public ComponentName getWebSearchActivity() {
return getSearchables().getWebSearchActivity();
}
}
@@ -0,0 +1,322 @@
/*
* Copyright (C) 2009 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 android.server.search;
import android.Manifest;
import android.app.SearchManager;
import android.app.SearchableInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* This class maintains the information about all searchable activities.
*/
public class Searchables {
private static final String LOG_TAG = "Searchables";
// static strings used for XML lookups, etc.
// TODO how should these be documented for the developer, in a more structured way than
// the current long wordy javadoc in SearchManager.java ?
private static final String MD_LABEL_DEFAULT_SEARCHABLE = "android.app.default_searchable";
private static final String MD_SEARCHABLE_SYSTEM_SEARCH = "*";
private Context mContext;
private HashMap<ComponentName, SearchableInfo> mSearchablesMap = null;
private ArrayList<SearchableInfo> mSearchablesList = null;
private ArrayList<SearchableInfo> mSearchablesInGlobalSearchList = null;
private ComponentName mGlobalSearchActivity = null;
private ComponentName mWebSearchActivity = null;
public static String GOOGLE_SEARCH_COMPONENT_NAME =
"com.android.googlesearch/.GoogleSearch";
public static String ENHANCED_GOOGLE_SEARCH_COMPONENT_NAME =
"com.google.android.providers.enhancedgooglesearch/.Launcher";
/**
*
* @param context Context to use for looking up activities etc.
*/
public Searchables (Context context) {
mContext = context;
}
/**
* Look up, or construct, based on the activity.
*
* The activities fall into three cases, based on meta-data found in
* the manifest entry:
* <ol>
* <li>The activity itself implements search. This is indicated by the
* presence of a "android.app.searchable" meta-data attribute.
* The value is a reference to an XML file containing search information.</li>
* <li>A related activity implements search. This is indicated by the
* presence of a "android.app.default_searchable" meta-data attribute.
* The value is a string naming the activity implementing search. In this
* case the factory will "redirect" and return the searchable data.</li>
* <li>No searchability data is provided. We return null here and other
* code will insert the "default" (e.g. contacts) search.
*
* TODO: cache the result in the map, and check the map first.
* TODO: it might make sense to implement the searchable reference as
* an application meta-data entry. This way we don't have to pepper each
* and every activity.
* TODO: can we skip the constructor step if it's a non-searchable?
* TODO: does it make sense to plug the default into a slot here for
* automatic return? Probably not, but it's one way to do it.
*
* @param activity The name of the current activity, or null if the
* activity does not define any explicit searchable metadata.
*/
public SearchableInfo getSearchableInfo(ComponentName activity) {
// Step 1. Is the result already hashed? (case 1)
SearchableInfo result;
synchronized (this) {
result = mSearchablesMap.get(activity);
if (result != null) return result;
}
// Step 2. See if the current activity references a searchable.
// Note: Conceptually, this could be a while(true) loop, but there's
// no point in implementing reference chaining here and risking a loop.
// References must point directly to searchable activities.
ActivityInfo ai = null;
try {
ai = mContext.getPackageManager().
getActivityInfo(activity, PackageManager.GET_META_DATA );
String refActivityName = null;
// First look for activity-specific reference
Bundle md = ai.metaData;
if (md != null) {
refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
}
// If not found, try for app-wide reference
if (refActivityName == null) {
md = ai.applicationInfo.metaData;
if (md != null) {
refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
}
}
// Irrespective of source, if a reference was found, follow it.
if (refActivityName != null)
{
// This value is deprecated, return null
if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) {
return null;
}
String pkg = activity.getPackageName();
ComponentName referredActivity;
if (refActivityName.charAt(0) == '.') {
referredActivity = new ComponentName(pkg, pkg + refActivityName);
} else {
referredActivity = new ComponentName(pkg, refActivityName);
}
// Now try the referred activity, and if found, cache
// it against the original name so we can skip the check
synchronized (this) {
result = mSearchablesMap.get(referredActivity);
if (result != null) {
mSearchablesMap.put(activity, result);
return result;
}
}
}
} catch (PackageManager.NameNotFoundException e) {
// case 3: no metadata
}
// Step 3. None found. Return null.
return null;
}
/**
* Builds an entire list (suitable for display) of
* activities that are searchable, by iterating the entire set of
* ACTION_SEARCH & ACTION_WEB_SEARCH intents.
*
* Also clears the hash of all activities -> searches which will
* refill as the user clicks "search".
*
* This should only be done at startup and again if we know that the
* list has changed.
*
* TODO: every activity that provides a ACTION_SEARCH intent should
* also provide searchability meta-data. There are a bunch of checks here
* that, if data is not found, silently skip to the next activity. This
* won't help a developer trying to figure out why their activity isn't
* showing up in the list, but an exception here is too rough. I would
* like to find a better notification mechanism.
*
* TODO: sort the list somehow? UI choice.
*/
public void buildSearchableList() {
// These will become the new values at the end of the method
HashMap<ComponentName, SearchableInfo> newSearchablesMap
= new HashMap<ComponentName, SearchableInfo>();
ArrayList<SearchableInfo> newSearchablesList
= new ArrayList<SearchableInfo>();
ArrayList<SearchableInfo> newSearchablesInGlobalSearchList
= new ArrayList<SearchableInfo>();
final PackageManager pm = mContext.getPackageManager();
// Use intent resolver to generate list of ACTION_SEARCH & ACTION_WEB_SEARCH receivers.
List<ResolveInfo> searchList;
final Intent intent = new Intent(Intent.ACTION_SEARCH);
searchList = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
List<ResolveInfo> webSearchInfoList;
final Intent webSearchIntent = new Intent(Intent.ACTION_WEB_SEARCH);
webSearchInfoList = pm.queryIntentActivities(webSearchIntent, PackageManager.GET_META_DATA);
// analyze each one, generate a Searchables record, and record
if (searchList != null || webSearchInfoList != null) {
int search_count = (searchList == null ? 0 : searchList.size());
int web_search_count = (webSearchInfoList == null ? 0 : webSearchInfoList.size());
int count = search_count + web_search_count;
for (int ii = 0; ii < count; ii++) {
// for each component, try to find metadata
ResolveInfo info = (ii < search_count)
? searchList.get(ii)
: webSearchInfoList.get(ii - search_count);
ActivityInfo ai = info.activityInfo;
// Check first to avoid duplicate entries.
if (newSearchablesMap.get(new ComponentName(ai.packageName, ai.name)) == null) {
SearchableInfo searchable = SearchableInfo.getActivityMetaData(mContext, ai);
if (searchable != null) {
newSearchablesList.add(searchable);
newSearchablesMap.put(searchable.getSearchActivity(), searchable);
if (searchable.shouldIncludeInGlobalSearch()) {
newSearchablesInGlobalSearchList.add(searchable);
}
}
}
}
}
// Find the global search activity
ComponentName newGlobalSearchActivity = findGlobalSearchActivity();
// Find the web search activity
ComponentName newWebSearchActivity = findWebSearchActivity(newGlobalSearchActivity);
// Store a consistent set of new values
synchronized (this) {
mSearchablesMap = newSearchablesMap;
mSearchablesList = newSearchablesList;
mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList;
mGlobalSearchActivity = newGlobalSearchActivity;
mWebSearchActivity = newWebSearchActivity;
}
}
/**
* Finds the global search activity.
*
* This is currently implemented by returning the first activity that handles
* the GLOBAL_SEARCH intent and has the GLOBAL_SEARCH permission. If we allow
* more than one global search activity to be installed, this code must be changed.
*/
private ComponentName findGlobalSearchActivity() {
Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> activities =
pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
int count = activities == null ? 0 : activities.size();
for (int i = 0; i < count; i++) {
ActivityInfo ai = activities.get(i).activityInfo;
if (pm.checkPermission(Manifest.permission.GLOBAL_SEARCH,
ai.packageName) == PackageManager.PERMISSION_GRANTED) {
return new ComponentName(ai.packageName, ai.name);
} else {
Log.w(LOG_TAG, "Package " + ai.packageName + " wants to handle GLOBAL_SEARCH, "
+ "but does not have the GLOBAL_SEARCH permission.");
}
}
Log.w(LOG_TAG, "No global search activity found");
return null;
}
/**
* Finds the web search activity.
*
* Only looks in the package of the global search activity.
*/
private ComponentName findWebSearchActivity(ComponentName globalSearchActivity) {
if (globalSearchActivity == null) {
return null;
}
Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
intent.setPackage(globalSearchActivity.getPackageName());
PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> activities =
pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
int count = activities == null ? 0 : activities.size();
for (int i = 0; i < count; i++) {
ActivityInfo ai = activities.get(i).activityInfo;
// TODO: do some sanity checks here?
return new ComponentName(ai.packageName, ai.name);
}
Log.w(LOG_TAG, "No web search activity found");
return null;
}
/**
* Returns the list of searchable activities.
*/
public synchronized ArrayList<SearchableInfo> getSearchablesList() {
ArrayList<SearchableInfo> result = new ArrayList<SearchableInfo>(mSearchablesList);
return result;
}
/**
* Returns a list of the searchable activities that can be included in global search.
*/
public synchronized ArrayList<SearchableInfo> getSearchablesInGlobalSearchList() {
return new ArrayList<SearchableInfo>(mSearchablesInGlobalSearchList);
}
/**
* Gets the name of the global search activity.
*/
public synchronized ComponentName getGlobalSearchActivity() {
return mGlobalSearchActivity;
}
/**
* Gets the name of the web search activity.
*/
public synchronized ComponentName getWebSearchActivity() {
return mWebSearchActivity;
}
}
@@ -0,0 +1,5 @@
<body>
{@hide}
</body>