Merge "Add new callback for MediaRouter to found out about device changes." into jb-dev

This commit is contained in:
Dianne Hackborn
2012-06-15 14:10:42 -07:00
committed by Android (Google) Code Review
7 changed files with 268 additions and 83 deletions

View File

@@ -193,6 +193,7 @@ LOCAL_SRC_FILES += \
location/java/android/location/INetInitiatedListener.aidl \
media/java/android/media/IAudioService.aidl \
media/java/android/media/IAudioFocusDispatcher.aidl \
media/java/android/media/IAudioRoutesObserver.aidl \
media/java/android/media/IMediaScannerListener.aidl \
media/java/android/media/IMediaScannerService.aidl \
media/java/android/media/IRemoteControlClient.aidl \

View File

@@ -0,0 +1,18 @@
/* Copyright 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 android.media;
parcelable AudioRoutesInfo;

View File

@@ -0,0 +1,71 @@
/*
* 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 android.media;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
/**
* Information available from AudioService about the current routes.
* @hide
*/
public class AudioRoutesInfo implements Parcelable {
static final int MAIN_SPEAKER = 0;
static final int MAIN_HEADSET = 1<<0;
static final int MAIN_HEADPHONES = 1<<1;
static final int MAIN_DOCK_SPEAKERS = 1<<2;
static final int MAIN_HDMI = 1<<3;
CharSequence mBluetoothName;
int mMainType = MAIN_SPEAKER;
public AudioRoutesInfo() {
}
public AudioRoutesInfo(AudioRoutesInfo o) {
mBluetoothName = o.mBluetoothName;
mMainType = o.mMainType;
}
AudioRoutesInfo(Parcel src) {
mBluetoothName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(src);
mMainType = src.readInt();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
TextUtils.writeToParcel(mBluetoothName, dest, flags);
dest.writeInt(mMainType);
}
public static final Parcelable.Creator<AudioRoutesInfo> CREATOR
= new Parcelable.Creator<AudioRoutesInfo>() {
public AudioRoutesInfo createFromParcel(Parcel in) {
return new AudioRoutesInfo(in);
}
public AudioRoutesInfo[] newArray(int size) {
return new AudioRoutesInfo[size];
}
};
}

View File

@@ -53,6 +53,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -62,6 +63,7 @@ import android.provider.Settings.System;
import android.speech.RecognizerIntent;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.VolumePanel;
@@ -135,10 +137,11 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
private static final int MSG_RCDISPLAY_UPDATE = 13;
private static final int MSG_SET_ALL_VOLUMES = 14;
private static final int MSG_PERSIST_MASTER_VOLUME_MUTE = 15;
private static final int MSG_REPORT_NEW_ROUTES = 16;
// messages handled under wakelock, can only be queued, i.e. sent with queueMsgUnderWakeLock(),
// and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 16;
private static final int MSG_SET_A2DP_CONNECTION_STATE = 17;
private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 17;
private static final int MSG_SET_A2DP_CONNECTION_STATE = 18;
// flags for MSG_PERSIST_VOLUME indicating if current and/or last audible volume should be
@@ -397,6 +400,11 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
private boolean mBluetoothA2dpEnabled;
private final Object mBluetoothA2dpEnabledLock = new Object();
// Monitoring of audio routes. Protected by mCurAudioRoutes.
final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo();
final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers
= new RemoteCallbackList<IAudioRoutesObserver>();
///////////////////////////////////////////////////////////////////////////
// Construction
///////////////////////////////////////////////////////////////////////////
@@ -3011,6 +3019,26 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
onSetA2dpConnectionState((BluetoothDevice)msg.obj, msg.arg1);
mMediaEventWakeLock.release();
break;
case MSG_REPORT_NEW_ROUTES: {
int N = mRoutesObservers.beginBroadcast();
if (N > 0) {
AudioRoutesInfo routes;
synchronized (mCurAudioRoutes) {
routes = new AudioRoutesInfo(mCurAudioRoutes);
}
while (N > 0) {
N--;
IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(N);
try {
obs.dispatchAudioRoutesChanged(routes);
} catch (RemoteException e) {
}
}
}
mRoutesObservers.finishBroadcast();
break;
}
}
}
}
@@ -3127,6 +3155,13 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
} else {
makeA2dpDeviceUnavailableNow(address);
}
synchronized (mCurAudioRoutes) {
if (mCurAudioRoutes.mBluetoothName != null) {
mCurAudioRoutes.mBluetoothName = null;
sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
SENDMSG_NOOP, 0, 0, null, 0);
}
}
} else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
if (btDevice.isBluetoothDock()) {
// this could be a reconnection after a transient disconnection
@@ -3141,6 +3176,14 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
}
}
makeA2dpDeviceAvailable(address);
synchronized (mCurAudioRoutes) {
String name = btDevice.getAliasName();
if (!TextUtils.equals(mCurAudioRoutes.mBluetoothName, name)) {
mCurAudioRoutes.mBluetoothName = name;
sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
SENDMSG_NOOP, 0, 0, null, 0);
}
}
}
}
}
@@ -3204,20 +3247,43 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
intent.putExtra("name", name);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
int connType = 0;
if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) {
connType = AudioRoutesInfo.MAIN_HEADSET;
intent.setAction(Intent.ACTION_HEADSET_PLUG);
intent.putExtra("microphone", 1);
} else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE) {
connType = AudioRoutesInfo.MAIN_HEADPHONES;
intent.setAction(Intent.ACTION_HEADSET_PLUG);
intent.putExtra("microphone", 0);
} else if (device == AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET) {
connType = AudioRoutesInfo.MAIN_DOCK_SPEAKERS;
intent.setAction(Intent.ACTION_ANALOG_AUDIO_DOCK_PLUG);
} else if (device == AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET) {
connType = AudioRoutesInfo.MAIN_DOCK_SPEAKERS;
intent.setAction(Intent.ACTION_DIGITAL_AUDIO_DOCK_PLUG);
} else if (device == AudioSystem.DEVICE_OUT_AUX_DIGITAL) {
connType = AudioRoutesInfo.MAIN_HDMI;
intent.setAction(Intent.ACTION_HDMI_AUDIO_PLUG);
}
synchronized (mCurAudioRoutes) {
if (connType != 0) {
int newConn = mCurAudioRoutes.mMainType;
if (state != 0) {
newConn |= connType;
} else {
newConn &= ~connType;
}
if (newConn != mCurAudioRoutes.mMainType) {
mCurAudioRoutes.mMainType = newConn;
sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
SENDMSG_NOOP, 0, 0, null, 0);
}
}
}
ActivityManagerNative.broadcastStickyIntent(intent, null);
}
@@ -4795,6 +4861,15 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
return mRingtonePlayer;
}
@Override
public AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
synchronized (mCurAudioRoutes) {
AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes);
mRoutesObservers.register(observer);
return routes;
}
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
@@ -4803,5 +4878,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
dumpFocusStack(pw);
dumpRCStack(pw);
dumpStreamStates(pw);
pw.println("\nAudio routes:");
pw.print(" mMainType=0x"); pw.println(Integer.toHexString(mCurAudioRoutes.mMainType));
pw.print(" mBluetoothName="); pw.println(mCurAudioRoutes.mBluetoothName);
}
}

View File

@@ -0,0 +1,27 @@
/*
* 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 android.media;
import android.media.AudioRoutesInfo;
/**
* AIDL for the AudioService to report changes in available audio routes.
* @hide
*/
oneway interface IAudioRoutesObserver {
void dispatchAudioRoutesChanged(in AudioRoutesInfo newRoutes);
}

View File

@@ -19,7 +19,9 @@ package android.media;
import android.app.PendingIntent;
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.media.AudioRoutesInfo;
import android.media.IAudioFocusDispatcher;
import android.media.IAudioRoutesObserver;
import android.media.IRemoteControlClient;
import android.media.IRemoteControlDisplay;
import android.media.IRingtonePlayer;
@@ -137,4 +139,6 @@ interface IAudioService {
void setWiredDeviceConnectionState(int device, int state, String name);
int setBluetoothA2dpDeviceConnectionState(in BluetoothDevice device, int state);
AudioRoutesInfo startWatchingRoutes(in IAudioRoutesObserver observer);
}

View File

@@ -16,14 +16,14 @@
package android.media;
import android.bluetooth.BluetoothA2dp;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.Log;
import java.util.ArrayList;
@@ -46,7 +46,7 @@ public class MediaRouter {
static class Static {
final Resources mResources;
final AudioManager mAudioManager;
final IAudioService mAudioService;
final Handler mHandler;
final ArrayList<CallbackInfo> mCallbacks = new ArrayList<CallbackInfo>();
@@ -54,38 +54,89 @@ public class MediaRouter {
final ArrayList<RouteCategory> mCategories = new ArrayList<RouteCategory>();
final RouteCategory mSystemCategory;
final HeadphoneChangedBroadcastReceiver mHeadphoneChangedReceiver;
final AudioRoutesInfo mCurRoutesInfo = new AudioRoutesInfo();
RouteInfo mDefaultAudio;
RouteInfo mBluetoothA2dpRoute;
RouteInfo mSelectedRoute;
final IAudioRoutesObserver.Stub mRoutesObserver = new IAudioRoutesObserver.Stub() {
public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
mHandler.post(new Runnable() {
@Override public void run() {
updateRoutes(newRoutes);
}
});
}
};
Static(Context appContext) {
mResources = Resources.getSystem();
mHandler = new Handler(appContext.getMainLooper());
mAudioManager = (AudioManager)appContext.getSystemService(Context.AUDIO_SERVICE);
IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
mAudioService = IAudioService.Stub.asInterface(b);
// XXX this doesn't deal with locale changes!
mSystemCategory = new RouteCategory(mResources.getText(
com.android.internal.R.string.default_audio_route_category_name),
ROUTE_TYPE_LIVE_AUDIO, false);
final IntentFilter speakerFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
speakerFilter.addAction(Intent.ACTION_ANALOG_AUDIO_DOCK_PLUG);
speakerFilter.addAction(Intent.ACTION_DIGITAL_AUDIO_DOCK_PLUG);
speakerFilter.addAction(Intent.ACTION_HDMI_AUDIO_PLUG);
mHeadphoneChangedReceiver = new HeadphoneChangedBroadcastReceiver();
appContext.registerReceiver(mHeadphoneChangedReceiver, speakerFilter);
}
// Called after sStatic is initialized
void initDefaultRoutes() {
void startMonitoringRoutes() {
mDefaultAudio = new RouteInfo(mSystemCategory);
mDefaultAudio.mNameResId = com.android.internal.R.string.default_audio_route_name;
mDefaultAudio.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
addRoute(mDefaultAudio);
AudioRoutesInfo newRoutes = null;
try {
newRoutes = mAudioService.startWatchingRoutes(mRoutesObserver);
} catch (RemoteException e) {
}
if (newRoutes != null) {
updateRoutes(newRoutes);
}
}
void updateRoutes(AudioRoutesInfo newRoutes) {
if (newRoutes.mMainType != mCurRoutesInfo.mMainType) {
mCurRoutesInfo.mMainType = newRoutes.mMainType;
int name;
if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADPHONES) != 0
|| (newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADSET) != 0) {
name = com.android.internal.R.string.default_audio_route_name_headphones;
} else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
} else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HDMI) != 0) {
name = com.android.internal.R.string.default_audio_route_name_hdmi;
} else {
name = com.android.internal.R.string.default_audio_route_name;
}
sStatic.mDefaultAudio.mNameResId = name;
dispatchRouteChanged(sStatic.mDefaultAudio);
}
if (!TextUtils.equals(newRoutes.mBluetoothName, mCurRoutesInfo.mBluetoothName)) {
mCurRoutesInfo.mBluetoothName = newRoutes.mBluetoothName;
if (mCurRoutesInfo.mBluetoothName != null) {
if (sStatic.mBluetoothA2dpRoute == null) {
final RouteInfo info = new RouteInfo(sStatic.mSystemCategory);
info.mName = mCurRoutesInfo.mBluetoothName;
info.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
sStatic.mBluetoothA2dpRoute = info;
addRoute(sStatic.mBluetoothA2dpRoute);
} else {
sStatic.mBluetoothA2dpRoute.mName = mCurRoutesInfo.mBluetoothName;
dispatchRouteChanged(sStatic.mBluetoothA2dpRoute);
}
} else if (sStatic.mBluetoothA2dpRoute != null) {
removeRoute(sStatic.mBluetoothA2dpRoute);
sStatic.mBluetoothA2dpRoute = null;
}
}
}
}
@@ -132,7 +183,7 @@ public class MediaRouter {
synchronized (Static.class) {
if (sStatic == null) {
sStatic = new Static(context.getApplicationContext());
sStatic.initDefaultRoutes();
sStatic.startMonitoringRoutes();
}
}
}
@@ -154,19 +205,6 @@ public class MediaRouter {
return sStatic.mSelectedRoute;
}
static void onHeadphonesPlugged(boolean headphonesPresent, String headphonesName) {
if (headphonesPresent) {
sStatic.mDefaultAudio.mName = headphonesName;
sStatic.mDefaultAudio.mNameResId = 0;
} else {
sStatic.mDefaultAudio.mName = null;
sStatic.mDefaultAudio.mNameResId =
com.android.internal.R.string.default_audio_route_name;
}
dispatchRouteChanged(sStatic.mDefaultAudio);
}
/**
* Add a callback to listen to events about specific kinds of media routes.
* If the specified callback is already registered, its registration will be updated for any
@@ -528,18 +566,6 @@ public class MediaRouter {
}
}
static void onA2dpDeviceConnected() {
final RouteInfo info = new RouteInfo(sStatic.mSystemCategory);
info.mNameResId = com.android.internal.R.string.bluetooth_a2dp_audio_route_name;
sStatic.mBluetoothA2dpRoute = info;
addRoute(sStatic.mBluetoothA2dpRoute);
}
static void onA2dpDeviceDisconnected() {
removeRoute(sStatic.mBluetoothA2dpRoute);
sStatic.mBluetoothA2dpRoute = null;
}
/**
* Information about a media route.
*/
@@ -1158,44 +1184,4 @@ public class MediaRouter {
}
}
class BtChangedBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
final int state = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, -1);
if (state == BluetoothA2dp.STATE_CONNECTED) {
onA2dpDeviceConnected();
} else if (state == BluetoothA2dp.STATE_DISCONNECTING ||
state == BluetoothA2dp.STATE_DISCONNECTED) {
onA2dpDeviceDisconnected();
}
}
}
}
static class HeadphoneChangedBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (Intent.ACTION_HEADSET_PLUG.equals(action)) {
final boolean plugged = intent.getIntExtra("state", 0) != 0;
final String name = sStatic.mResources.getString(
com.android.internal.R.string.default_audio_route_name_headphones);
onHeadphonesPlugged(plugged, name);
} else if (Intent.ACTION_ANALOG_AUDIO_DOCK_PLUG.equals(action) ||
Intent.ACTION_DIGITAL_AUDIO_DOCK_PLUG.equals(action)) {
final boolean plugged = intent.getIntExtra("state", 0) != 0;
final String name = sStatic.mResources.getString(
com.android.internal.R.string.default_audio_route_name_dock_speakers);
onHeadphonesPlugged(plugged, name);
} else if (Intent.ACTION_HDMI_AUDIO_PLUG.equals(action)) {
final boolean plugged = intent.getIntExtra("state", 0) != 0;
final String name = sStatic.mResources.getString(
com.android.internal.R.string.default_audio_route_name_hdmi);
onHeadphonesPlugged(plugged, name);
}
}
}
}