Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8c1e7f70 authored by Trey Briggs's avatar Trey Briggs Committed by Gerrit Code Review
Browse files

Bluetooth: AVRCP 1.3 features changes (2/2)



Bluetooth module will listen metadata/event change intents
from Music app and post data to Bluez stack.

Merged the following from ICS:
Change-Id: 37151cf5162404e27c985a1b6a874cfd9a51d657
Change-Id: f3d9f182f0eacc3ead491f7d3f571f5ed93f7c20
Change-Id: 432fdab5fbbfb6687b571d1e06d6b51e944ed4d5
Change-Id: b4d51e3bc1b52f6c1ff1d006aae6cff261c739e9
Change-Id: f1c1549254c17be681066544efbbcf7ad1e25913

       modified:   core/java/android/server/BluetoothA2dpService.java
       new file:   core/java/fm/last/api/Station.java
       modified:   core/jni/android_server_BluetoothA2dpService.cpp
       modified:   core/jni/android_server_BluetoothEventLoop.cpp
       new file:   core/res/res/values/avrcp_data.xml
       modified:   core/res/res/values/public.xml

Signed-off-by: default avatarTrey Briggs <treybriggs@gmail.com>
parent d10a6033
Loading
Loading
Loading
Loading
+389 −6
Original line number Diff line number Diff line
@@ -32,20 +32,27 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Message;
import android.os.Bundle;
import android.os.ParcelUuid;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.provider.Settings;
import android.util.Log;

import com.android.internal.R;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;


public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
@@ -69,17 +76,49 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
    private final BluetoothAdapter mAdapter;
    private int   mTargetA2dpState;
    private BluetoothDevice mPlayingA2dpDevice;

    private IntentBroadcastHandler mIntentBroadcastHandler;
    private final WakeLock mWakeLock;

    private static final int MSG_CONNECTION_STATE_CHANGED = 0;

    /* 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;

    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;

    /* AVRCP 1.3 Intents */
    private List<String> metachanged_intents;
    private List<String> playstatechanged_intents;

    /* AVRCP 1.3 special extra keys */
    private List<String> has_special_extra_keys;
    private HashMap<String, String> special_extra_keys;

    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);
@@ -93,6 +132,8 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
                }
            } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
                synchronized (this) {
                    BluetoothDevice device =
                    intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    if (mAudioDevices.containsKey(device)) {
                        int state = mAudioDevices.get(device);
                        handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
@@ -117,10 +158,273 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
                        }
                    }
                }
            } else if (metachanged_intents.contains(action)) {
                try {
                    if(DBG) {
                        Log.d(TAG, "action: " + action);

                        Bundle extras = intent.getExtras();

                        if (extras != null) {
                            Set<String> ks = extras.keySet();
                            Iterator<String> iterator = ks.iterator();
                            while (iterator.hasNext()) {
                                String key = iterator.next();
                                Object value = extras.get(key);
                                if (value != null)
                                    Log.d(TAG, key + ": " + value.toString());
                            }
                        }
                    }

                    // check if there are special extra keys that we will use
                    if (has_special_extra_keys.contains(action)) {
                        if (special_extra_keys.containsKey(action + "_track")) {
                            mTrackName = intent.getStringExtra(special_extra_keys.get(action + "_track"));
                        }
                        else {
                            mTrackName = intent.getStringExtra("track");
                        }

                        if (special_extra_keys.containsKey(action + "_artist")) {
                            mArtistName = intent.getStringExtra(special_extra_keys.get(action + "_artist"));
                        }
                        else {
                            mArtistName = intent.getStringExtra("artist");
                        }

                        if (special_extra_keys.containsKey(action + "_album")) {
                            mAlbumName = intent.getStringExtra(special_extra_keys.get(action + "_album"));
                        }
                        else {
                            mAlbumName = intent.getStringExtra("album");
                        }

                        long extra;
                        if (special_extra_keys.containsKey(action + "_id")){
                            extra = intent.getLongExtra(special_extra_keys.get(action + "_id"), 0);
                        }
                        else {
                            extra = intent.getLongExtra("id", 0);
                        }
                        if (extra < 0)
                            extra = 0;
                        mMediaNumber = String.valueOf(extra);
                    }
                    else {
                        mTrackName = intent.getStringExtra("track");
                        mArtistName = intent.getStringExtra("artist");
                        mAlbumName = intent.getStringExtra("album");
                        long extra = intent.getLongExtra("id", 0);
                        if (extra < 0)
                            extra = 0;
                        mMediaNumber = String.valueOf(extra);
                    }

                    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("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 changed " + mPlayStatus);
                        Log.d(TAG, "player: " + action);
                        Log.d(TAG, "trackname: "+ mTrackName + " artist: " + mArtistName);
                        Log.d(TAG, "album: "+ mAlbumName);
                        Log.d(TAG, "medianumber: " + mMediaNumber + " mediacount " + mMediaCount);
                        Log.d(TAG, "postion "+ mPosition + " duration "+ mDuration);
                    }
                    for (String path: getConnectedSinksPaths()) {
                        sendMetaData(path);
                        sendEvent(path, EVENT_TRACK_CHANGED, Long.valueOf(mMediaNumber));
                    }
                }
                catch (Exception e) {
                    Log.e(TAG, "Error getting metadata from intent", e);
                }
            } else if (playstatechanged_intents.contains(action)) {
                try {
                    if(DBG) {
                        Log.d(TAG, "action: " + action);

                        Bundle extras = intent.getExtras();

                        if (extras != null) {
                            Set<String> ks = extras.keySet();
                            Iterator<String> iterator = ks.iterator();
                            while (iterator.hasNext()) {
                                String key = iterator.next();
                                Object value = extras.get(key);
                                if (value != null)
                                    Log.d(TAG, key + ": " + value.toString());
                            }
                        }
                    }

                    String currentTrackName;
                    // check if there are special extra keys that we will use
                    if (has_special_extra_keys.contains(action)) {
                        if (special_extra_keys.containsKey(action + "_track")) {
                            currentTrackName = intent.getStringExtra(special_extra_keys.get(action + "_track"));
                        }
                        else {
                            currentTrackName = intent.getStringExtra("track");
                        }
                        if (currentTrackName == null)
                            currentTrackName = DEFAULT_METADATA_STRING;
                    }
                    else {
                        currentTrackName = intent.getStringExtra("track");
                        if (currentTrackName == null)
                            currentTrackName = DEFAULT_METADATA_STRING;
                    }
                    if ((!currentTrackName.equals(DEFAULT_METADATA_STRING)) && (!currentTrackName.equals(mTrackName))) {
                        mTrackName = currentTrackName;
                        // check if there are special extra keys that we will use
                        if (has_special_extra_keys.contains(action)) {
                            if (special_extra_keys.containsKey(action + "_artist")) {
                                mArtistName = intent.getStringExtra(special_extra_keys.get(action + "_artist"));
                            }
                            else {
                                mArtistName = intent.getStringExtra("artist");
                            }

                            if (special_extra_keys.containsKey(action + "_album")) {
                                mAlbumName = intent.getStringExtra(special_extra_keys.get(action + "_album"));
                            }
                            else {
                                mAlbumName = intent.getStringExtra("album");
                            }

                            long extra;
                            if (special_extra_keys.containsKey(action + "_id")) {
                                extra = intent.getLongExtra(special_extra_keys.get(action + "_id"), 0);
                            }
                            else {
                                extra = intent.getLongExtra("id", 0);
                            }
                            if (extra < 0)
                                extra = 0;
                            mMediaNumber = String.valueOf(extra);
                        }
                        else {
                            mArtistName = intent.getStringExtra("artist");
                            mAlbumName = intent.getStringExtra("album");
                            long extra = intent.getLongExtra("id", 0);
                            if (extra < 0)
                                extra = 0;
                            mMediaNumber = String.valueOf(extra);
                        }

                        if (mArtistName == null)
                            mArtistName = DEFAULT_METADATA_STRING;
                        if (mAlbumName == null)
                            mAlbumName = DEFAULT_METADATA_STRING;

                        long 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 playStatusPlaying = intent.getBooleanExtra("playing", false);
                    boolean playStatusPlaystate = intent.getBooleanExtra("playstate", false);
                    boolean playStatusState;

                    int state = intent.getIntExtra("state", 2);

                    if ((state == 0) || (state == 1))
                        playStatusState = true;
                    else
                        playStatusState = false;

                    boolean playStatus = playStatusPlaying || playStatusPlaystate || playStatusState;

                    mPosition = intent.getLongExtra("position", 0);
                    if (mPosition < 0)
                        mPosition = 0;
                    mPlayStatus = convertedPlayStatus(playStatus, mPosition);
                    if(DBG) {
                        Log.d(TAG, "PlayState changed " + mPlayStatus);
                        Log.d(TAG, "player: " + action);
                        Log.d(TAG, "trackname: "+ mTrackName + " artist: " + mArtistName);
                        Log.d(TAG, "album: "+ mAlbumName);
                        Log.d(TAG, "medianumber: " + mMediaNumber + " mediacount " + mMediaCount);
                        Log.d(TAG, "postion "+ mPosition + " duration "+ mDuration);
                    }

                    for (String path: getConnectedSinksPaths()) {
                        sendEvent(path, EVENT_PLAYSTATUS_CHANGED, (long)mPlayStatus);
                    }
                }
                catch (Exception e) {
                    Log.e(TAG, "Error getting playstate from intent", e);
                }
            }
        }
    };

    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);
        }
        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() {
        if(DBG)
            Log.d(TAG, "onGetPlayStatusRequest");
        for (String path: getConnectedSinksPaths()) {
            sendPlayStatus(path);
        }
    }

    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));
@@ -161,6 +465,59 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
        mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
        mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
        mIntentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION);

        Resources res = mContext.getResources();
        try {
            /* AVRCP 1.3 Intents */
            metachanged_intents = Arrays.asList(res.getStringArray(R.array.avrcp_meta_changed_intents));
            playstatechanged_intents = Arrays.asList(res.getStringArray(R.array.avrcp_playstate_changed_intents));

            for (String intent: metachanged_intents) {
                mIntentFilter.addAction(intent);
            }

            for (String intent: playstatechanged_intents) {
                mIntentFilter.addAction(intent);
            }
        }
        catch (Exception e) {
            Log.e(TAG, "Error getting AVRCP 1.3 intents from the resource file.");
        }

        try {
            /* AVRCP 1.3 special extra keys */
            has_special_extra_keys = Arrays.asList(res.getStringArray(R.array.avrcp_special_extra_keys));

            special_extra_keys = new HashMap<String, String>();

            String key_name;
            int resID;

            List<String> overridable_extra_keys = Arrays.asList(res.getStringArray(R.array.avrcp_overridable_extra_keys));

            for (String intent: has_special_extra_keys) {
                if(DBG) {
                    Log.d(TAG, "has_special_extra_keys: " + intent);
                }
                for (String key: overridable_extra_keys) {
                    key_name = intent + "_" + key;
                    if(DBG) {
                        Log.d(TAG, "key_name: " + key_name);
                    }
                    resID = res.getIdentifier(key_name, "string", mContext.getPackageName());
                    if (resID != 0) {
                        special_extra_keys.put(key_name, res.getString(resID));
                        if(DBG) {
                            Log.d(TAG, key_name + ": " + special_extra_keys.get(key_name));
                        }
                    }
                }
            }
        }
        catch (Exception e) {
            Log.e(TAG, "Error getting AVRCP 1.3 special extra keys from the resource file.");
        }

        mContext.registerReceiver(mReceiver, mIntentFilter);

        mAudioDevices = new HashMap<BluetoothDevice, Integer>();
@@ -213,6 +570,10 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
            for (String path: paths) {
                String address = mBluetoothService.getAddressFromObjectPath(path);
                BluetoothDevice device = mAdapter.getRemoteDevice(address);
                if (DBG) {
                    log("RemoteName: " + mBluetoothService.getRemoteName(address));
                    log("RemoteAlias: " + mBluetoothService.getRemoteAlias(address));
                }
                ParcelUuid[] remoteUuids = mBluetoothService.getRemoteUuids(address);
                if (remoteUuids != null)
                    if (BluetoothUuid.containsAnyUuid(remoteUuids,
@@ -410,6 +771,16 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
        return state;
    }

    public synchronized List<String> getConnectedSinksPaths() {
        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
        List<BluetoothDevice> btDevices = getConnectedDevices();
        ArrayList<String> paths = new ArrayList<String>();
        for(BluetoothDevice device:btDevices) {
            paths.add(mBluetoothService.getObjectPathFromAddress(device.getAddress()));
        }
        return paths;
    }

    public synchronized List<BluetoothDevice> getConnectedDevices() {
        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
        List<BluetoothDevice> sinks = getDevicesMatchingConnectionStates(
@@ -537,6 +908,13 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
                                                            device),
                                                       delay);
        }
        if (prevState == BluetoothA2dp.STATE_CONNECTING &&
             state == BluetoothA2dp.STATE_CONNECTED) {
            for (String path: getConnectedSinksPaths()) {
                sendMetaData(path);
                sendEvent(path, EVENT_PLAYSTATUS_CHANGED, (long)mPlayStatus);
            }
        }
    }

    private void handleSinkPlayingStateChange(BluetoothDevice device, int state, int prevState) {
@@ -625,6 +1003,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
        }
    }


    @Override
    protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
@@ -650,4 +1029,8 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
    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);
}
+58 −0
Original line number Diff line number Diff line
/***************************************************************************
 *   Copyright 2005-2009 Last.fm Ltd.                                      *
 *   Portions contributed by Casey Link, Lukasz Wisniewski,                *
 *   Mike Jennings, and Michael Novak Jr.                                  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
 ***************************************************************************/
package fm.last.api;

import java.io.Serializable;

/**
 * @author jennings Date: Oct 23, 2008
 */
public class Station implements Serializable {

	private static final long serialVersionUID = 1499806102972292532L;
	private String name;
	private String type;
	private String url;
	private String supportsDiscovery;

	public Station(String name, String type, String url, String supportsDiscovery) {
		this.name = name;
		this.type = type;
		this.url = url;
		this.supportsDiscovery = supportsDiscovery;
	}

	public String getName() {
		return name;
	}

	public String getType() {
		return type;
	}

	public String getUrl() {
		return url;
	}

	public String getSupportsDiscovery() {
		return supportsDiscovery;
	}
}
+122 −1

File changed.

Preview size limit exceeded, changes collapsed.

+13 −0
Original line number Diff line number Diff line
@@ -287,6 +287,13 @@ static jboolean setUpEventLoop(native_data_t *nat) {
            LOG_AND_FREE_DBUS_ERROR(&err);
            return JNI_FALSE;
        }
        dbus_bus_add_match(nat->conn,
                "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".Control'",
                &err);
        if (dbus_error_is_set(&err)) {
            LOG_AND_FREE_DBUS_ERROR(&err);
            return JNI_FALSE;
        }

        dbus_bus_add_match(nat->conn,
                "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".HealthDevice'",
@@ -453,6 +460,12 @@ static void tearDownEventLoop(native_data_t *nat) {
        if (dbus_error_is_set(&err)) {
            LOG_AND_FREE_DBUS_ERROR(&err);
        }
        dbus_bus_remove_match(nat->conn,
                "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".Control'",
                &err);
        if (dbus_error_is_set(&err)) {
            LOG_AND_FREE_DBUS_ERROR(&err);
        }
        dbus_bus_remove_match(nat->conn,
                "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".Device'",
                &err);
+112 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading