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

Commit 973a2d3c authored by Automerger Merge Worker's avatar Automerger Merge Worker
Browse files

Merge changes from topic "bt-avrcp-controller-cover-art" am: 773a98c2 am:...

Merge changes from topic "bt-avrcp-controller-cover-art" am: 773a98c2 am: 174be8ea am: d2f3103d

Change-Id: I1685a609acb230a33731729c1caaa96ba8377b30
parents 406a0203 d2f3103d
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -345,6 +345,13 @@
                <action android:name="android.bluetooth.IBluetoothAvrcpController" />
            </intent-filter>
        </service>
        <provider android:process="@string/process"
            android:name=".avrcpcontroller.AvrcpCoverArtProvider"
            android:authorities="com.android.bluetooth.avrcpcontroller.AvrcpCoverArtProvider"
            android:enabled="@bool/avrcp_controller_enable_cover_art"
            android:grantUriPermissions="true"
            android:exported="true">
        </provider>
        <service
            android:process="@string/process"
            android:name = ".hid.HidHostService"
+67 −38
Original line number Diff line number Diff line
@@ -49,8 +49,9 @@ static jmethodID method_handleSetAddressedPlayerRsp;
static jmethodID method_handleAddressedPlayerChanged;
static jmethodID method_handleNowPlayingContentChanged;
static jmethodID method_onAvailablePlayerChanged;
static jmethodID method_getRcPsm;

static jclass class_MediaBrowser_MediaItem;
static jclass class_AvrcpItem;
static jclass class_AvrcpPlayer;

static const btrc_ctrl_interface_t* sBluetoothAvrcpInterface = NULL;
@@ -458,7 +459,7 @@ static void btavrcp_get_folder_items_callback(
        sCallbackEnv->NewObjectArray((jint)count, class_AvrcpPlayer, 0));
  } else {
    itemArray.reset(sCallbackEnv->NewObjectArray(
        (jint)count, class_MediaBrowser_MediaItem, 0));
        (jint)count, class_AvrcpItem, 0));
  }
  if (!itemArray.get()) {
    ALOGE("%s itemArray allocation failed.", __func__);
@@ -511,11 +512,11 @@ static void btavrcp_get_folder_items_callback(
        ScopedLocalRef<jobject> mediaObj(
            sCallbackEnv.get(),
            (jobject)sCallbackEnv->CallObjectMethod(
                sCallbacksObj, method_createFromNativeMediaItem, uid,
                (jint)item->media.type, mediaName.get(), attrIdArray.get(),
                attrValArray.get()));
                sCallbacksObj, method_createFromNativeMediaItem, addr.get(),
                uid, (jint)item->media.type, mediaName.get(),
                attrIdArray.get(), attrValArray.get()));
        if (!mediaObj.get()) {
          ALOGE("%s failed to creae MediaItem for type ITEM_MEDIA", __func__);
          ALOGE("%s failed to create AvrcpItem for type ITEM_MEDIA", __func__);
          return;
        }
        sCallbackEnv->SetObjectArrayElement(itemArray.get(), i, mediaObj.get());
@@ -536,11 +537,11 @@ static void btavrcp_get_folder_items_callback(
        ScopedLocalRef<jobject> folderObj(
            sCallbackEnv.get(),
            (jobject)sCallbackEnv->CallObjectMethod(
                sCallbacksObj, method_createFromNativeFolderItem, uid,
                (jint)item->folder.type, folderName.get(),
                sCallbacksObj, method_createFromNativeFolderItem, addr.get(),
                uid, (jint)item->folder.type, folderName.get(),
                (jint)item->folder.playable));
        if (!folderObj.get()) {
          ALOGE("%s failed to create MediaItem for type ITEM_FOLDER", __func__);
          ALOGE("%s failed to create AvrcpItem for type ITEM_FOLDER", __func__);
          return;
        }
        sCallbackEnv->SetObjectArrayElement(itemArray.get(), i,
@@ -576,8 +577,8 @@ static void btavrcp_get_folder_items_callback(
        ScopedLocalRef<jobject> playerObj(
            sCallbackEnv.get(),
            (jobject)sCallbackEnv->CallObjectMethod(
                sCallbacksObj, method_createFromNativePlayerItem, id,
                playerName.get(), featureBitArray.get(), playStatus,
                sCallbacksObj, method_createFromNativePlayerItem, addr.get(),
                id, playerName.get(), featureBitArray.get(), playStatus,
                playerType));
        if (!playerObj.get()) {
          ALOGE("%s failed to create AvrcpPlayer from ITEM_PLAYER", __func__);
@@ -723,15 +724,14 @@ static void btavrcp_now_playing_content_changed_callback(
static void btavrcp_available_player_changed_callback (
    const RawAddress& bd_addr) {
  ALOGI("%s", __func__);

  std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);

  CallbackEnv sCallbackEnv(__func__);
  if (!sCallbacksObj) {
      ALOGE("%s: sCallbacksObj is null", __func__);
      return;
  }
  if (!sCallbackEnv.valid()) return;

  ScopedLocalRef<jbyteArray> addr(
      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
  if (!addr.get()) {
@@ -745,6 +745,30 @@ static void btavrcp_available_player_changed_callback (
      sCallbacksObj, method_onAvailablePlayerChanged, addr.get());
}

static void btavrcp_get_rcpsm_callback(const RawAddress& bd_addr,
                                       uint16_t psm) {
  ALOGE("%s -> psm received of %d", __func__, psm);
  std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
  CallbackEnv sCallbackEnv(__func__);
  if (!sCallbacksObj) {
    ALOGE("%s: sCallbacksObj is null", __func__);
    return;
  }
  if (!sCallbackEnv.valid()) return;

  ScopedLocalRef<jbyteArray> addr(
      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
  if (!addr.get()) {
    ALOGE("%s: Failed to allocate a new byte array", __func__);
    return;
  }

  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
                                   (jbyte*)&bd_addr.address);
  sCallbackEnv->CallVoidMethod(sCallbacksObj, method_getRcPsm, addr.get(),
                               (jint)psm);
}

static btrc_ctrl_callbacks_t sBluetoothAvrcpCallbacks = {
    sizeof(sBluetoothAvrcpCallbacks),
    btavrcp_passthrough_response_callback,
@@ -765,7 +789,8 @@ static btrc_ctrl_callbacks_t sBluetoothAvrcpCallbacks = {
    btavrcp_set_addressed_player_callback,
    btavrcp_addressed_player_changed_callback,
    btavrcp_now_playing_content_changed_callback,
    btavrcp_available_player_changed_callback};
    btavrcp_available_player_changed_callback,
    btavrcp_get_rcpsm_callback};

static void classInitNative(JNIEnv* env, jclass clazz) {
  method_handlePassthroughRsp =
@@ -779,6 +804,8 @@ static void classInitNative(JNIEnv* env, jclass clazz) {

  method_getRcFeatures = env->GetMethodID(clazz, "getRcFeatures", "([BI)V");

  method_getRcPsm = env->GetMethodID(clazz, "getRcPsm", "([BI)V");

  method_setplayerappsettingrsp =
      env->GetMethodID(clazz, "setPlayerAppSettingRsp", "([BB)V");

@@ -805,21 +832,23 @@ static void classInitNative(JNIEnv* env, jclass clazz) {

  method_handleGetFolderItemsRsp =
      env->GetMethodID(clazz, "handleGetFolderItemsRsp",
                       "([BI[Landroid/media/browse/MediaBrowser$MediaItem;)V");
                       "([BI[Lcom/android/bluetooth/avrcpcontroller/"
                       "AvrcpItem;)V");
  method_handleGetPlayerItemsRsp = env->GetMethodID(
      clazz, "handleGetPlayerItemsRsp",
      "([B[Lcom/android/bluetooth/avrcpcontroller/AvrcpPlayer;)V");

  method_createFromNativeMediaItem =
      env->GetMethodID(clazz, "createFromNativeMediaItem",
                       "(JILjava/lang/String;[I[Ljava/lang/String;)Landroid/"
                       "media/browse/MediaBrowser$MediaItem;");
                       "([BJILjava/lang/String;[I[Ljava/lang/String;)Lcom/"
                       "android/bluetooth/avrcpcontroller/AvrcpItem;");
  method_createFromNativeFolderItem = env->GetMethodID(
      clazz, "createFromNativeFolderItem",
      "(JILjava/lang/String;I)Landroid/media/browse/MediaBrowser$MediaItem;");
      "([BJILjava/lang/String;I)Lcom/android/bluetooth/avrcpcontroller/"
      "AvrcpItem;");
  method_createFromNativePlayerItem =
      env->GetMethodID(clazz, "createFromNativePlayerItem",
                       "(ILjava/lang/String;[BII)Lcom/android/bluetooth/"
                       "([BILjava/lang/String;[BII)Lcom/android/bluetooth/"
                       "avrcpcontroller/AvrcpPlayer;");
  method_handleChangeFolderRsp =
      env->GetMethodID(clazz, "handleChangeFolderRsp", "([BI)V");
@@ -840,9 +869,9 @@ static void classInitNative(JNIEnv* env, jclass clazz) {
static void initNative(JNIEnv* env, jobject object) {
  std::unique_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);

  jclass tmpMediaItem =
      env->FindClass("android/media/browse/MediaBrowser$MediaItem");
  class_MediaBrowser_MediaItem = (jclass)env->NewGlobalRef(tmpMediaItem);
  jclass tmpAvrcpItem =
      env->FindClass("com/android/bluetooth/avrcpcontroller/AvrcpItem");
  class_AvrcpItem = (jclass)env->NewGlobalRef(tmpAvrcpItem);

  jclass tmpBtPlayer =
      env->FindClass("com/android/bluetooth/avrcpcontroller/AvrcpPlayer");
+3 −0
Original line number Diff line number Diff line
@@ -72,6 +72,9 @@
    <!-- If true, device requests audio focus and start avrcp updates on source start or play -->
    <bool name="a2dp_sink_automatically_request_audio_focus">false</bool>

    <!-- For enabling the AVRCP Controller Cover Artwork feature -->
    <bool name="avrcp_controller_enable_cover_art">false</bool>

    <!-- For enabling the hfp client connection service -->
    <bool name="hfp_client_connection_service_enabled">false</bool>

+403 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.avrcpcontroller;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothSocket;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import com.android.bluetooth.BluetoothObexTransport;

import java.io.IOException;
import java.lang.ref.WeakReference;

import javax.obex.ClientSession;
import javax.obex.HeaderSet;
import javax.obex.ResponseCodes;

/**
 * A client to a remote device's BIP Image Pull Server, as defined by a PSM passed in at
 * construction time.
 *
 * Once the client connection is established you can use this client to get image properties and
 * download images. The connection to the server is held open to service multiple requests.
 *
 * Client is good for one connection lifecycle. Please call shutdown() to clean up safely. Once a
 * disconnection has occurred, please create a new client.
 */
public class AvrcpBipClient {
    private static final String TAG = "AvrcpBipClient";
    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);

    // AVRCP Controller BIP Image Initiator/Cover Art UUID - AVRCP 1.6 Section 5.14.2.1
    private static final byte[] BLUETOOTH_UUID_AVRCP_COVER_ART = new byte[] {
        (byte) 0x71,
        (byte) 0x63,
        (byte) 0xDD,
        (byte) 0x54,
        (byte) 0x4A,
        (byte) 0x7E,
        (byte) 0x11,
        (byte) 0xE2,
        (byte) 0xB4,
        (byte) 0x7C,
        (byte) 0x00,
        (byte) 0x50,
        (byte) 0xC2,
        (byte) 0x49,
        (byte) 0x00,
        (byte) 0x48
    };

    private static final int CONNECT = 0;
    private static final int DISCONNECT = 1;
    private static final int REQUEST = 2;

    private final Handler mHandler;
    private final HandlerThread mThread;

    private final BluetoothDevice mDevice;
    private final int mPsm;
    private int mState = BluetoothProfile.STATE_DISCONNECTED;

    private BluetoothSocket mSocket;
    private BluetoothObexTransport mTransport;
    private ClientSession mSession;

    private final Callback mCallback;

    /**
     * Callback object used to be notified of when a request has been completed.
     */
    interface Callback {

        /**
         * Notify of a connection state change in the client
         *
         * @param oldState The old state of the client
         * @param newState The new state of the client
         */
        void onConnectionStateChanged(int oldState, int newState);

        /**
         * Notify of a get image properties completing
         *
         * @param status A status code to indicate a success or error
         * @param properties The BipImageProperties object returned if successful, null otherwise
         */
        void onGetImagePropertiesComplete(int status, String imageHandle,
                BipImageProperties properties);

        /**
         * Notify of a get image operation completing
         *
         * @param status A status code of the request. success or error
         * @param image The BipImage object returned if successful, null otherwise
         */
        void onGetImageComplete(int status, String imageHandle, BipImage image);
    }

    /**
     * Creates a BIP image pull client and connects to a remote device's BIP image push server.
     */
    public AvrcpBipClient(BluetoothDevice remoteDevice, int psm, Callback callback) {
        if (remoteDevice == null) {
            throw new NullPointerException("Remote device is null");
        }
        if (callback == null) {
            throw new NullPointerException("Callback is null");
        }

        mDevice = remoteDevice;
        mPsm = psm;
        mCallback = callback;

        mThread = new HandlerThread("AvrcpBipClient");
        mThread.start();

        Looper looper = mThread.getLooper();

        mHandler = new AvrcpBipClientHandler(looper, this);
        mHandler.obtainMessage(CONNECT).sendToTarget();
    }

    /**
     * Safely disconnects the client from the server
     */
    public void shutdown() {
        debug("Shutdown client");
        try {
            mHandler.obtainMessage(DISCONNECT).sendToTarget();
        } catch (IllegalStateException e) {
            // Means we haven't been started or we're already stopped. Doing this makes this call
            // always safe no matter the state.
            return;
        }
        mThread.quitSafely();
    }

    /**
     * Determines if this client is connected to the server
     *
     * @return True if connected, False otherwise
     */
    public synchronized int getState() {
        return mState;
    }

    /**
     * Determines if this client is connected to the server
     *
     * @return True if connected, False otherwise
     */
    public boolean isConnected() {
        return getState() == BluetoothProfile.STATE_CONNECTED;
    }

    /**
     * Retrieve the image properties associated with the given imageHandle
     */
    public boolean getImageProperties(String imageHandle) {
        RequestGetImageProperties request =  new RequestGetImageProperties(imageHandle);
        boolean status = mHandler.sendMessage(mHandler.obtainMessage(REQUEST, request));
        if (!status) {
            error("Adding messages failed, connection state: " + isConnected());
            return false;
        }
        return true;
    }

    /**
     * Download the image object associated with the given imageHandle
     */
    public boolean getImage(String imageHandle, BipImageDescriptor descriptor) {
        RequestGetImage request =  new RequestGetImage(imageHandle, descriptor);
        boolean status = mHandler.sendMessage(mHandler.obtainMessage(REQUEST, request));
        if (!status) {
            error("Adding messages failed, connection state: " + isConnected());
            return false;
        }
        return true;
    }

    /**
     * Update our client's connection state and notify of the new status
     */
    private void setConnectionState(int state) {
        int oldState = -1;
        synchronized (this) {
            oldState = mState;
            mState = state;
        }
        if (oldState != state)  {
            mCallback.onConnectionStateChanged(oldState, mState);
        }
    }

    /**
     * Connects to the remote device's BIP Image Pull server
     */
    private synchronized void connect() {
        debug("Connect using psm: " + mPsm);
        if (isConnected()) {
            warn("Already connected");
            return;
        }

        try {
            setConnectionState(BluetoothProfile.STATE_CONNECTING);

            mSocket = mDevice.createL2capSocket(mPsm);
            mSocket.connect();

            mTransport = new BluetoothObexTransport(mSocket);
            mSession = new ClientSession(mTransport);

            HeaderSet headerSet = new HeaderSet();
            headerSet.setHeader(HeaderSet.TARGET, BLUETOOTH_UUID_AVRCP_COVER_ART);

            headerSet = mSession.connect(headerSet);
            int responseCode = headerSet.getResponseCode();
            if (responseCode == ResponseCodes.OBEX_HTTP_OK) {
                setConnectionState(BluetoothProfile.STATE_CONNECTED);
            } else {
                error("Error connecting, code: " + responseCode);
                disconnect();
            }
            debug("Connection established");

        } catch (IOException e) {
            error("Exception while connecting to AVRCP BIP server", e);
            disconnect();
        }
    }

    /**
     * Permanently disconnects this client from the remote device's BIP server and notifies of the
     * new connection status.
     *
     */
    private synchronized void disconnect() {
        if (mSession != null) {
            setConnectionState(BluetoothProfile.STATE_DISCONNECTING);

            try {
                mSession.disconnect(null);
            } catch (IOException e) {
                error("Exception while disconnecting from AVRCP BIP server: " + e.toString());
            }

            try {
                mSession.close();
            } catch (IOException e) {
                error("Exception while closing AVRCP BIP session: " + e.toString());
            }

            mSession = null;
        }
        setConnectionState(BluetoothProfile.STATE_DISCONNECTED);
    }

    private void executeRequest(BipRequest request) {
        if (!isConnected()) {
            error("Cannot execute request " + request.toString()
                    + ", we're not connected");
            notifyCaller(request);
            return;
        }

        try {
            request.execute(mSession);
            notifyCaller(request);
            debug("Completed request - " + request.toString());
        } catch (IOException e) {
            error("Request failed: " + request.toString());
            notifyCaller(request);
            disconnect();
        }
    }

    private void notifyCaller(BipRequest request) {
        int type = request.getType();
        int responseCode = request.getResponseCode();
        String imageHandle = null;

        debug("Notifying caller of request complete - " + request.toString());
        switch (type) {
            case BipRequest.TYPE_GET_IMAGE_PROPERTIES:
                imageHandle = ((RequestGetImageProperties) request).getImageHandle();
                BipImageProperties properties =
                        ((RequestGetImageProperties) request).getImageProperties();
                mCallback.onGetImagePropertiesComplete(responseCode, imageHandle, properties);
                break;
            case BipRequest.TYPE_GET_IMAGE:
                imageHandle = ((RequestGetImage) request).getImageHandle();
                BipImage image = ((RequestGetImage) request).getImage();
                mCallback.onGetImageComplete(responseCode, imageHandle, image); // TODO: add handle
                break;
        }
    }

    /**
     * Handles this AVRCP BIP Image Pull Client's requests
     */
    private static class AvrcpBipClientHandler extends Handler {
        WeakReference<AvrcpBipClient> mInst;

        AvrcpBipClientHandler(Looper looper, AvrcpBipClient inst) {
            super(looper);
            mInst = new WeakReference<>(inst);
        }

        @Override
        public void handleMessage(Message msg) {
            AvrcpBipClient inst = mInst.get();
            switch (msg.what) {
                case CONNECT:
                    if (!inst.isConnected()) {
                        inst.connect();
                    }
                    break;

                case DISCONNECT:
                    if (inst.isConnected()) {
                        inst.disconnect();
                    }
                    break;

                case REQUEST:
                    if (inst.isConnected()) {
                        inst.executeRequest((BipRequest) msg.obj);
                    }
                    break;
            }
        }
    }

    private String getStateName() {
        int state = getState();
        switch (state) {
            case BluetoothProfile.STATE_DISCONNECTED:
                return "Disconnected";
            case BluetoothProfile.STATE_CONNECTING:
                return "Connecting";
            case BluetoothProfile.STATE_CONNECTED:
                return "Connected";
            case BluetoothProfile.STATE_DISCONNECTING:
                return "Disconnecting";
        }
        return "Unknown";
    }

    @Override
    public String toString() {
        return "<AvrcpBipClient" + " device=" + mDevice.getAddress() + " psm=" + mPsm
                + " state=" + getStateName() + ">";
    }

    /**
     * Print to debug if debug is enabled for this class
     */
    private void debug(String msg) {
        if (DBG) {
            Log.d(TAG, "[" + mDevice.getAddress() + "] " + msg);
        }
    }

    /**
     * Print to warn
     */
    private void warn(String msg) {
        Log.w(TAG, "[" + mDevice.getAddress() + "] " + msg);
    }

    /**
     * Print to error
     */
    private void error(String msg) {
        Log.e(TAG, "[" + mDevice.getAddress() + "] " + msg);
    }

    private void error(String msg, Throwable e) {
        Log.e(TAG, "[" + mDevice.getAddress() + "] " + msg, e);
    }
}
+123 −85

File changed.

Preview size limit exceeded, changes collapsed.

Loading