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

Commit c5c65b3d authored by Steve Kondik's avatar Steve Kondik Committed by Gerrit Code Review
Browse files

Merge "frameworks/base: bluetooth: AVRCP 1.3 feature changes" into gingerbread

parents 3433a688 0f53325d
Loading
Loading
Loading
Loading
+198 −0
Original line number Original line Diff line number Diff line
@@ -72,6 +72,53 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
    private final BluetoothAdapter mAdapter;
    private final BluetoothAdapter mAdapter;
    private int   mTargetA2dpState;
    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 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:
                    for (String path: getConnectedSinksPaths()) {
                        Log.i(TAG, "Timed outM - Sending Playstatus");
                        sendPlayStatus(path);
                    }
            }
        }
    };


    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        @Override
        public void onReceive(Context context, Intent intent) {
        public void onReceive(Context context, Intent intent) {
@@ -127,10 +174,136 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
                        }
                        }
                    }
                    }
                }
                }
            } 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);
                for (String path: getConnectedSinksPaths()) {
                    if(DBG) Log.d(TAG, "Sending Playstatus");
                    sendPlayStatus(path);
                }
            }
            }
        }
        }
    };
    };


    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() {
        if(DBG) Log.d(TAG, "onGetPlayStatusRequest");
        mContext.sendBroadcast(new Intent(PLAYSTATUS_REQUEST));
        mHandler.sendMessageDelayed(
            mHandler.obtainMessage(MESSAGE_PLAYSTATUS_TIMEOUT), 130);
    }


    private boolean isPhoneDocked(BluetoothDevice device) {
    private boolean isPhoneDocked(BluetoothDevice device) {
        // This works only because these broadcast intents are "sticky"
        // This works only because these broadcast intents are "sticky"
@@ -168,6 +341,9 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
        mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
        mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
        mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
        mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
        mIntentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
        mIntentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
        mIntentFilter.addAction(PLAYSTATE_CHANGED);
        mIntentFilter.addAction(META_CHANGED);
        mIntentFilter.addAction(PLAYSTATUS_RESPONSE);
        mContext.registerReceiver(mReceiver, mIntentFilter);
        mContext.registerReceiver(mReceiver, mIntentFilter);


        mAudioDevices = new HashMap<BluetoothDevice, Integer>();
        mAudioDevices = new HashMap<BluetoothDevice, Integer>();
@@ -422,6 +598,17 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
        return sinks.toArray(new BluetoothDevice[sinks.size()]);
        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() {
    public synchronized BluetoothDevice[] getNonDisconnectedSinks() {
        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
        Set<BluetoothDevice> sinks = lookupSinksMatchingStates(
        Set<BluetoothDevice> sinks = lookupSinksMatchingStates(
@@ -531,6 +718,13 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {


            if (DBG) log("A2DP state : device: " + device + " State:" + prevState + "->" + state);
            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) {
    private void adjustOtherSinkPriorities(BluetoothDevice connectedDevice) {
@@ -613,4 +807,8 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
    private synchronized native Object []getSinkPropertiesNative(String path);
    private synchronized native Object []getSinkPropertiesNative(String path);
    private synchronized native boolean avrcpVolumeUpNative(String path);
    private synchronized native boolean avrcpVolumeUpNative(String path);
    private synchronized native boolean avrcpVolumeDownNative(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);
}
}
+122 −1
Original line number Original line Diff line number Diff line
@@ -39,6 +39,13 @@ namespace android {
#ifdef HAVE_BLUETOOTH
#ifdef HAVE_BLUETOOTH
static jmethodID method_onSinkPropertyChanged;
static jmethodID method_onSinkPropertyChanged;
static jmethodID method_onConnectSinkResult;
static jmethodID method_onConnectSinkResult;
static jmethodID method_onGetPlayStatusRequest;
static jfieldID field_mTrackName;
static jfieldID field_mArtistName;
static jfieldID field_mAlbumName;
static jfieldID field_mMediaNumber;
static jfieldID field_mMediaCount;
static jfieldID field_mDuration;


typedef struct {
typedef struct {
    JavaVM *vm;
    JavaVM *vm;
@@ -49,6 +56,7 @@ typedef struct {


static native_data_t *nat = NULL;  // global native data
static native_data_t *nat = NULL;  // global native data
static void onConnectSinkResult(DBusMessage *msg, void *user, void *n);
static void onConnectSinkResult(DBusMessage *msg, void *user, void *n);
static void onStatusReply(DBusMessage *msg, void *user, void *n);


static Properties sink_properties[] = {
static Properties sink_properties[] = {
        {"State", DBUS_TYPE_STRING},
        {"State", DBUS_TYPE_STRING},
@@ -216,6 +224,92 @@ static jboolean avrcpVolumeUpNative(JNIEnv *env, jobject object,
    return JNI_FALSE;
    return JNI_FALSE;
}
}


static jboolean sendMetaDataNative(JNIEnv *env, jobject obj,
                                     jstring path) {
#ifdef HAVE_BLUETOOTH
    LOGV(__FUNCTION__);
    if (nat) {
        jstring title, artist, album, media_number, total_media_count, playing_time;
        const char *c_title, *c_artist, *c_album, *c_media_number;
        const char *c_total_media_count, *c_playing_time;
        const char *c_path = env->GetStringUTFChars(path, NULL);
        title = (jstring) env->GetObjectField(obj, field_mTrackName);
        artist = (jstring) env->GetObjectField(obj, field_mArtistName);
        album = (jstring) env->GetObjectField(obj, field_mAlbumName);
        media_number = (jstring) env->GetObjectField(obj, field_mMediaNumber);
        total_media_count = (jstring) env->GetObjectField(obj, field_mMediaCount);
        playing_time = (jstring) env->GetObjectField(obj, field_mDuration);

        c_title = env->GetStringUTFChars(title, NULL);
        c_artist = env->GetStringUTFChars(artist, NULL);
        c_album = env->GetStringUTFChars(album, NULL);
        c_media_number = env->GetStringUTFChars(media_number, NULL);
        c_total_media_count = env->GetStringUTFChars(total_media_count, NULL);
        c_playing_time = env->GetStringUTFChars(playing_time, NULL);

        bool ret = dbus_func_args_async(env, nat->conn, -1, onStatusReply, NULL, nat,
                           c_path, "org.bluez.Control", "UpdateMetaData",
                           DBUS_TYPE_STRING, &c_title,
                           DBUS_TYPE_STRING, &c_artist,
                           DBUS_TYPE_STRING, &c_album,
                           DBUS_TYPE_STRING, &c_media_number,
                           DBUS_TYPE_STRING, &c_total_media_count,
                           DBUS_TYPE_STRING, &c_playing_time,
                           DBUS_TYPE_INVALID);

        env->ReleaseStringUTFChars(path, c_path);
        env->ReleaseStringUTFChars(title, c_title);
        env->ReleaseStringUTFChars(artist, c_artist);
        env->ReleaseStringUTFChars(album, c_album);
        env->ReleaseStringUTFChars(media_number, c_media_number);
        env->ReleaseStringUTFChars(total_media_count, c_total_media_count);
        env->ReleaseStringUTFChars(playing_time, c_playing_time);

        return ret ? JNI_TRUE : JNI_FALSE;
    }
#endif
    return JNI_FALSE;
}


static jboolean sendPlayStatusNative(JNIEnv *env, jobject object, jstring path,
                                        jint duration, jint position, jint play_status) {
#ifdef HAVE_BLUETOOTH
    LOGV(__FUNCTION__);
    if (nat) {
        const char *c_path = env->GetStringUTFChars(path, NULL);
        bool ret = dbus_func_args_async(env, nat->conn, -1, onStatusReply, NULL, nat,
                           c_path, "org.bluez.Control", "UpdatePlayStatus",
                           DBUS_TYPE_UINT32, &duration,
                           DBUS_TYPE_UINT32, &position,
                           DBUS_TYPE_UINT32, &play_status,
                           DBUS_TYPE_INVALID);
        env->ReleaseStringUTFChars(path, c_path);
        return ret ? JNI_TRUE : JNI_FALSE;
    }
#endif
    return JNI_FALSE;
}

static jboolean sendEventNative(JNIEnv *env, jobject object,
                                     jstring path, jint event_id, jlong data) {
#ifdef HAVE_BLUETOOTH
    LOGV(__FUNCTION__);
    if (nat) {
        const char *c_path = env->GetStringUTFChars(path, NULL);

        bool ret = dbus_func_args_async(env, nat->conn, -1, onStatusReply, NULL, nat,
                           c_path, "org.bluez.Control", "UpdateNotification",
                           DBUS_TYPE_UINT16, &event_id,
                           DBUS_TYPE_UINT64, &data,
                           DBUS_TYPE_INVALID);
        env->ReleaseStringUTFChars(path, c_path);
        return ret ? JNI_TRUE : JNI_FALSE;
    }
#endif
    return JNI_FALSE;
}

static jboolean avrcpVolumeDownNative(JNIEnv *env, jobject object,
static jboolean avrcpVolumeDownNative(JNIEnv *env, jobject object,
                                     jstring path) {
                                     jstring path) {
#ifdef HAVE_BLUETOOTH
#ifdef HAVE_BLUETOOTH
@@ -264,6 +358,11 @@ DBusHandlerResult a2dp_event_filter(DBusMessage *msg, JNIEnv *env) {
        env->DeleteLocalRef(path);
        env->DeleteLocalRef(path);
        result = DBUS_HANDLER_RESULT_HANDLED;
        result = DBUS_HANDLER_RESULT_HANDLED;
        return result;
        return result;
    } else if (dbus_message_is_signal(msg, "org.bluez.Control",
                                      "GetPlayStatus")) {
        env->CallVoidMethod(nat->me, method_onGetPlayStatusRequest);
        result = DBUS_HANDLER_RESULT_HANDLED;
        return result;
    }else {
    }else {
        LOGV("... ignored");
        LOGV("... ignored");
    }
    }
@@ -304,6 +403,17 @@ void onConnectSinkResult(DBusMessage *msg, void *user, void *n) {
    free(user);
    free(user);
}
}


void onStatusReply(DBusMessage *msg, void *user, void *n) {
    LOGV(__FUNCTION__);

    native_data_t *nat = (native_data_t *)n;
    DBusError err;
    dbus_error_init(&err);
    if (dbus_set_error_from_message(&err, msg)) {
        LOG_AND_FREE_DBUS_ERROR(&err);
    }
}



#endif
#endif


@@ -321,6 +431,9 @@ static JNINativeMethod sMethods[] = {
                                    (void *)getSinkPropertiesNative},
                                    (void *)getSinkPropertiesNative},
    {"avrcpVolumeUpNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeUpNative},
    {"avrcpVolumeUpNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeUpNative},
    {"avrcpVolumeDownNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeDownNative},
    {"avrcpVolumeDownNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeDownNative},
    {"sendMetaDataNative", "(Ljava/lang/String;)Z", (void*)sendMetaDataNative},
    {"sendEventNative", "(Ljava/lang/String;IJ)Z", (void*)sendEventNative},
    {"sendPlayStatusNative", "(Ljava/lang/String;III)Z", (void*)sendPlayStatusNative},
};
};


int register_android_server_BluetoothA2dpService(JNIEnv *env) {
int register_android_server_BluetoothA2dpService(JNIEnv *env) {
@@ -335,6 +448,14 @@ int register_android_server_BluetoothA2dpService(JNIEnv *env) {
                                          "(Ljava/lang/String;[Ljava/lang/String;)V");
                                          "(Ljava/lang/String;[Ljava/lang/String;)V");
    method_onConnectSinkResult = env->GetMethodID(clazz, "onConnectSinkResult",
    method_onConnectSinkResult = env->GetMethodID(clazz, "onConnectSinkResult",
                                                         "(Ljava/lang/String;Z)V");
                                                         "(Ljava/lang/String;Z)V");
    method_onGetPlayStatusRequest = env->GetMethodID(clazz, "onGetPlayStatusRequest",
                                          "()V");
    field_mTrackName = env->GetFieldID(clazz, "mTrackName", "Ljava/lang/String;");
    field_mArtistName = env->GetFieldID(clazz, "mArtistName", "Ljava/lang/String;");
    field_mAlbumName = env->GetFieldID(clazz, "mAlbumName", "Ljava/lang/String;");
    field_mMediaNumber = env->GetFieldID(clazz, "mMediaNumber", "Ljava/lang/String;");
    field_mMediaCount = env->GetFieldID(clazz, "mMediaCount", "Ljava/lang/String;");
    field_mDuration = env->GetFieldID(clazz, "mDuration", "Ljava/lang/String;");
#endif
#endif


    return AndroidRuntime::registerNativeMethods(env,
    return AndroidRuntime::registerNativeMethods(env,
+13 −0
Original line number Original line Diff line number Diff line
@@ -238,6 +238,13 @@ static jboolean setUpEventLoop(native_data_t *nat) {
            LOG_AND_FREE_DBUS_ERROR(&err);
            LOG_AND_FREE_DBUS_ERROR(&err);
            return JNI_FALSE;
            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;
        }


        const char *agent_path = "/android/bluetooth/agent";
        const char *agent_path = "/android/bluetooth/agent";
        const char *capabilities = "DisplayYesNo";
        const char *capabilities = "DisplayYesNo";
@@ -395,6 +402,12 @@ static void tearDownEventLoop(native_data_t *nat) {
        if (dbus_error_is_set(&err)) {
        if (dbus_error_is_set(&err)) {
            LOG_AND_FREE_DBUS_ERROR(&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,
        dbus_bus_remove_match(nat->conn,
                "type='signal',interface='org.bluez.Device'",
                "type='signal',interface='org.bluez.Device'",
                &err);
                &err);