diff options
author | Zhihai Xu <zhihaixu@google.com> | 2013-03-21 16:47:25 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2013-03-21 16:47:25 +0000 |
commit | de06cdccc4629cdfb12c16de226b3297377912e4 (patch) | |
tree | 16e285b1d00fc31aa8d7b01064acc0c1c4634c7a /src | |
parent | 5d5746124a5fd14a13ca7fd085aae6ec600d0ca7 (diff) | |
parent | c1c259c0ace7195240f1443c805995bfe8692a72 (diff) | |
download | android_packages_apps_Bluetooth-de06cdccc4629cdfb12c16de226b3297377912e4.tar.gz android_packages_apps_Bluetooth-de06cdccc4629cdfb12c16de226b3297377912e4.tar.bz2 android_packages_apps_Bluetooth-de06cdccc4629cdfb12c16de226b3297377912e4.zip |
Merge "framework support for new Bluetooth profiles AVRCP 1.3:Metadata and play status" into jb-mr2-dev
Diffstat (limited to 'src')
-rwxr-xr-x | src/com/android/bluetooth/a2dp/A2dpService.java | 7 | ||||
-rwxr-xr-x | src/com/android/bluetooth/a2dp/Avrcp.java | 444 |
2 files changed, 451 insertions, 0 deletions
diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java index 0e7515a8f..2ff648775 100755 --- a/src/com/android/bluetooth/a2dp/A2dpService.java +++ b/src/com/android/bluetooth/a2dp/A2dpService.java @@ -39,6 +39,7 @@ public class A2dpService extends ProfileService { private static final String TAG="A2dpService"; private A2dpStateMachine mStateMachine; + private Avrcp mAvrcp; private static A2dpService sAd2dpService; protected String getName() { @@ -51,12 +52,14 @@ public class A2dpService extends ProfileService { protected boolean start() { mStateMachine = A2dpStateMachine.make(this, this); + mAvrcp = Avrcp.make(this); setA2dpService(this); return true; } protected boolean stop() { mStateMachine.doQuit(); + mAvrcp.doQuit(); return true; } @@ -64,6 +67,10 @@ public class A2dpService extends ProfileService { if (mStateMachine!= null) { mStateMachine.cleanup(); } + if (mAvrcp != null) { + mAvrcp.cleanup(); + mAvrcp = null; + } clearA2dpService(); return true; } diff --git a/src/com/android/bluetooth/a2dp/Avrcp.java b/src/com/android/bluetooth/a2dp/Avrcp.java new file mode 100755 index 000000000..d657d57c0 --- /dev/null +++ b/src/com/android/bluetooth/a2dp/Avrcp.java @@ -0,0 +1,444 @@ +/* + * 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.bluetooth.a2dp; + +import android.app.PendingIntent; +import android.content.Context; +import android.graphics.Bitmap; +import android.media.AudioManager; +import android.media.IRemoteControlDisplay; +import android.media.MediaMetadataRetriever; +import android.media.RemoteControlClient; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelUuid; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.content.Intent; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ParcelUuid; +import android.util.Log; +import com.android.bluetooth.Utils; +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.btservice.ProfileService; +import com.android.internal.util.IState; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * support Bluetooth AVRCP profile. + * support metadata, play status and event notification + */ +final class Avrcp { + private static final boolean DEBUG = false; + private static final String TAG = "Avrcp"; + + private Context mContext; + private final AudioManager mAudioManager; + private AvrcpMessageHandler mHandler; + private IRemoteControlDisplayWeak mRemoteControlDisplay; + private int mClientGeneration; + private Metadata mMetadata; + private int mTransportControlFlags; + private int mCurrentPlayState; + private int mPlayStatusChangedNT; + private int mTrackChangedNT; + private long mTrackNumber; + private static final int MESSAGE_GET_PLAY_STATUS = 1; + private static final int MESSAGE_GET_ELEM_ATTRS = 2; + private static final int MESSAGE_REGISTER_NOTIFICATION = 3; + private static final int MSG_UPDATE_STATE = 100; + private static final int MSG_SET_METADATA = 101; + private static final int MSG_SET_TRANSPORT_CONTROLS = 102; + private static final int MSG_SET_ARTWORK = 103; + private static final int MSG_SET_GENERATION_ID = 104; + + static { + classInitNative(); + } + + private Avrcp(Context context) { + mMetadata = new Metadata(); + mCurrentPlayState = RemoteControlClient.PLAYSTATE_NONE; // until we get a callback + mPlayStatusChangedNT = NOTIFICATION_TYPE_CHANGED; + mTrackChangedNT = NOTIFICATION_TYPE_CHANGED; + mTrackNumber = 0L; + + mContext = context; + + initNative(); + + mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + } + + private void start() { + HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler"); + thread.start(); + Looper looper = thread.getLooper(); + mHandler = new AvrcpMessageHandler(looper); + mRemoteControlDisplay = new IRemoteControlDisplayWeak(mHandler); + mAudioManager.registerRemoteControlDisplay(mRemoteControlDisplay); + } + + static Avrcp make(Context context) { + if (DEBUG) Log.v(TAG, "make"); + Avrcp ar = new Avrcp(context); + ar.start(); + return ar; + } + + public void doQuit() { + mHandler.removeCallbacksAndMessages(null); + Looper looper = mHandler.getLooper(); + if (looper != null) { + looper.quit(); + } + mAudioManager.unregisterRemoteControlDisplay(mRemoteControlDisplay); + } + + public void cleanup() { + cleanupNative(); + } + + private static class IRemoteControlDisplayWeak extends IRemoteControlDisplay.Stub { + private WeakReference<Handler> mLocalHandler; + IRemoteControlDisplayWeak(Handler handler) { + mLocalHandler = new WeakReference<Handler>(handler); + } + + @Override + public void setPlaybackState(int generationId, int state, long stateChangeTimeMs) { + Handler handler = mLocalHandler.get(); + if (handler != null) { + handler.obtainMessage(MSG_UPDATE_STATE, generationId, state).sendToTarget(); + } + } + + @Override + public void setMetadata(int generationId, Bundle metadata) { + Handler handler = mLocalHandler.get(); + if (handler != null) { + handler.obtainMessage(MSG_SET_METADATA, generationId, 0, metadata).sendToTarget(); + } + } + + @Override + public void setTransportControlFlags(int generationId, int flags) { + Handler handler = mLocalHandler.get(); + if (handler != null) { + handler.obtainMessage(MSG_SET_TRANSPORT_CONTROLS, generationId, flags) + .sendToTarget(); + } + } + + @Override + public void setArtwork(int generationId, Bitmap bitmap) { + } + + @Override + public void setAllMetadata(int generationId, Bundle metadata, Bitmap bitmap) { + Handler handler = mLocalHandler.get(); + if (handler != null) { + handler.obtainMessage(MSG_SET_METADATA, generationId, 0, metadata).sendToTarget(); + handler.obtainMessage(MSG_SET_ARTWORK, generationId, 0, bitmap).sendToTarget(); + } + } + + @Override + public void setCurrentClientId(int clientGeneration, PendingIntent mediaIntent, + boolean clearing) throws RemoteException { + Handler handler = mLocalHandler.get(); + if (handler != null) { + handler.obtainMessage(MSG_SET_GENERATION_ID, + clientGeneration, (clearing ? 1 : 0), mediaIntent).sendToTarget(); + } + } + } + + /** Handles Avrcp messages. */ + private final class AvrcpMessageHandler extends Handler { + private AvrcpMessageHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE_STATE: + if (mClientGeneration == msg.arg1) updatePlayPauseState(msg.arg2); + break; + + case MSG_SET_METADATA: + if (mClientGeneration == msg.arg1) updateMetadata((Bundle) msg.obj); + break; + + case MSG_SET_TRANSPORT_CONTROLS: + if (mClientGeneration == msg.arg1) updateTransportControls(msg.arg2); + break; + + case MSG_SET_ARTWORK: + if (mClientGeneration == msg.arg1) { + } + break; + + case MSG_SET_GENERATION_ID: + if (DEBUG) Log.v(TAG, "New genId = " + msg.arg1 + ", clearing = " + msg.arg2); + mClientGeneration = msg.arg1; + break; + + case MESSAGE_GET_PLAY_STATUS: + if (DEBUG) Log.v(TAG, "MESSAGE_GET_PLAY_STATUS"); + getPlayStatusRspNative(convertPlayStateToPlayStatus(mCurrentPlayState), -1, -1); + break; + + case MESSAGE_GET_ELEM_ATTRS: + { + String[] textArray; + int[] attrIds; + byte numAttr = (byte) msg.arg1; + ArrayList<Integer> attrList = (ArrayList<Integer>) msg.obj; + if (DEBUG) Log.v(TAG, "MESSAGE_GET_ELEM_ATTRS:numAttr=" + numAttr); + attrIds = new int[numAttr]; + textArray = new String[numAttr]; + for (int i = 0; i < numAttr; ++i) { + attrIds[i] = attrList.get(i).intValue(); + textArray[i] = getAttributeString(attrIds[i]); + } + getElementAttrRspNative(numAttr, attrIds, textArray); + break; + } + case MESSAGE_REGISTER_NOTIFICATION: + if (DEBUG) Log.v(TAG, "MESSAGE_REGISTER_NOTIFICATION:event=" + msg.arg1 + + " param=" + msg.arg2); + processRegisterNotification(msg.arg1, msg.arg2); + break; + + } + } + } + + private void updatePlayPauseState(int state) { + if (DEBUG) Log.v(TAG, + "updatePlayPauseState(), old=" + mCurrentPlayState + ", state=" + state); + if (state == mCurrentPlayState) { + return; + } + + int oldPlayStatus = convertPlayStateToPlayStatus(mCurrentPlayState); + int newPlayStatus = convertPlayStateToPlayStatus(state); + mCurrentPlayState = state; + if ((mPlayStatusChangedNT == NOTIFICATION_TYPE_INTERIM) && (oldPlayStatus != newPlayStatus)) { + mPlayStatusChangedNT = NOTIFICATION_TYPE_CHANGED; + registerNotificationRspPlayStatusNative(mPlayStatusChangedNT, newPlayStatus); + } + } + + private void updateTransportControls(int transportControlFlags) { + mTransportControlFlags = transportControlFlags; + } + + class Metadata { + private String artist; + private String trackTitle; + private String albumTitle; + + public Metadata() { + artist = null; + trackTitle = null; + albumTitle = null; + } + + public String toString() { + return "Metadata[artist=" + artist + " trackTitle=" + trackTitle + " albumTitle=" + + albumTitle + "]"; + } + } + + private String getMdString(Bundle data, int id) { + return data.getString(Integer.toString(id)); + } + + private void updateMetadata(Bundle data) { + String oldMetadata = mMetadata.toString(); + mMetadata.artist = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST); + mMetadata.trackTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_TITLE); + mMetadata.albumTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUM); + if (!oldMetadata.equals(mMetadata.toString())) { + mTrackNumber++; + if (mTrackChangedNT == NOTIFICATION_TYPE_INTERIM) { + mTrackChangedNT = NOTIFICATION_TYPE_CHANGED; + sendTrackChangedRsp(); + } + } + if (DEBUG) Log.v(TAG, "mMetadata=" + mMetadata.toString()); + } + + private void getPlayStatus() { + Message msg = mHandler.obtainMessage(MESSAGE_GET_PLAY_STATUS); + mHandler.sendMessage(msg); + } + + private void getElementAttr(byte numAttr, int[] attrs) { + int i; + ArrayList<Integer> attrList = new ArrayList<Integer>(); + for (i = 0; i < numAttr; ++i) { + attrList.add(attrs[i]); + } + Message msg = mHandler.obtainMessage(MESSAGE_GET_ELEM_ATTRS, (int)numAttr, 0, attrList); + mHandler.sendMessage(msg); + } + + private void registerNotification(int eventId, int param) { + Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_NOTIFICATION, eventId, param); + mHandler.sendMessage(msg); + } + + private void processRegisterNotification(int eventId, int param) { + switch (eventId) { + case EVT_PLAY_STATUS_CHANGED: + mPlayStatusChangedNT = NOTIFICATION_TYPE_INTERIM; + registerNotificationRspPlayStatusNative(mPlayStatusChangedNT, + convertPlayStateToPlayStatus(mCurrentPlayState)); + break; + + case EVT_TRACK_CHANGED: + mTrackChangedNT = NOTIFICATION_TYPE_INTERIM; + sendTrackChangedRsp(); + break; + + } + } + + private void sendTrackChangedRsp() { + byte[] track = new byte[TRACK_ID_SIZE]; + for (int i = 0; i < TRACK_ID_SIZE; ++i) { + track[i] = (byte) (mTrackNumber >> (8 * i)); + } + registerNotificationRspTrackChangeNative(mTrackChangedNT, track); + } + + private String getAttributeString(int attrId) { + String attrStr = null; + switch (attrId) { + case MEDIA_ATTR_TITLE: + attrStr = mMetadata.trackTitle; + break; + + case MEDIA_ATTR_ARTIST: + attrStr = mMetadata.artist; + break; + + case MEDIA_ATTR_ALBUM: + attrStr = mMetadata.albumTitle; + break; + + } + if (attrStr == null) { + attrStr = new String(); + } + if (DEBUG) Log.v(TAG, "getAttributeString:attrId=" + attrId + " str=" + attrStr); + return attrStr; + } + + private int convertPlayStateToPlayStatus(int playState) { + int playStatus = PLAYSTATUS_ERROR; + switch (playState) { + case RemoteControlClient.PLAYSTATE_PLAYING: + case RemoteControlClient.PLAYSTATE_BUFFERING: + playStatus = PLAYSTATUS_PLAYING; + break; + + case RemoteControlClient.PLAYSTATE_STOPPED: + playStatus = PLAYSTATUS_STOPPED; + break; + + case RemoteControlClient.PLAYSTATE_PAUSED: + playStatus = PLAYSTATUS_PAUSED; + break; + + case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: + case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: + playStatus = PLAYSTATUS_FWD_SEEK; + break; + + case RemoteControlClient.PLAYSTATE_REWINDING: + case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: + playStatus = PLAYSTATUS_REV_SEEK; + break; + + case RemoteControlClient.PLAYSTATE_ERROR: + case RemoteControlClient.PLAYSTATE_NONE: + playStatus = PLAYSTATUS_ERROR; + break; + + } + return playStatus; + } + + // Do not modify without updating the HAL bt_rc.h files. + + // match up with btrc_play_status_t enum of bt_rc.h + final static int PLAYSTATUS_STOPPED = 0; + final static int PLAYSTATUS_PLAYING = 1; + final static int PLAYSTATUS_PAUSED = 2; + final static int PLAYSTATUS_FWD_SEEK = 3; + final static int PLAYSTATUS_REV_SEEK = 4; + final static int PLAYSTATUS_ERROR = 255; + + // match up with btrc_media_attr_t enum of bt_rc.h + final static int MEDIA_ATTR_TITLE = 1; + final static int MEDIA_ATTR_ARTIST = 2; + final static int MEDIA_ATTR_ALBUM = 3; + final static int MEDIA_ATTR_TRACK_NUM = 4; + final static int MEDIA_ATTR_NUM_TRACKS = 5; + final static int MEDIA_ATTR_GENRE = 6; + final static int MEDIA_ATTR_PLAYING_TIME = 7; + + // match up with btrc_event_id_t enum of bt_rc.h + final static int EVT_PLAY_STATUS_CHANGED = 1; + final static int EVT_TRACK_CHANGED = 2; + final static int EVT_TRACK_REACHED_END = 3; + final static int EVT_TRACK_REACHED_START = 4; + final static int EVT_PLAY_POS_CHANGED = 5; + final static int EVT_BATT_STATUS_CHANGED = 6; + final static int EVT_SYSTEM_STATUS_CHANGED = 7; + final static int EVT_APP_SETTINGS_CHANGED = 8; + + // match up with btrc_notification_type_t enum of bt_rc.h + final static int NOTIFICATION_TYPE_INTERIM = 0; + final static int NOTIFICATION_TYPE_CHANGED = 1; + + // match up with BTRC_UID_SIZE of bt_rc.h + final static int TRACK_ID_SIZE = 8; + + private native static void classInitNative(); + private native void initNative(); + private native void cleanupNative(); + private native boolean getPlayStatusRspNative(int playStatus, int songLen, int songPos); + private native boolean getElementAttrRspNative(byte numAttr, int[] attrIds, String[] textArray); + private native boolean registerNotificationRspPlayStatusNative(int type, int playStatus); + private native boolean registerNotificationRspTrackChangeNative(int type, byte[] track); +} |