Loading android/app/AndroidManifest.xml +7 −0 Original line number Diff line number Diff line Loading @@ -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" Loading android/app/jni/com_android_bluetooth_avrcp_controller.cpp +67 −38 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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__); Loading Loading @@ -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()); Loading @@ -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, Loading Loading @@ -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__); Loading Loading @@ -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()) { Loading @@ -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, Loading @@ -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 = Loading @@ -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"); Loading @@ -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"); Loading @@ -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"); Loading android/app/res/values/config.xml +3 −0 Original line number Diff line number Diff line Loading @@ -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> Loading android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpBipClient.java 0 → 100644 +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); } } android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java +123 −85 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
android/app/AndroidManifest.xml +7 −0 Original line number Diff line number Diff line Loading @@ -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" Loading
android/app/jni/com_android_bluetooth_avrcp_controller.cpp +67 −38 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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__); Loading Loading @@ -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()); Loading @@ -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, Loading Loading @@ -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__); Loading Loading @@ -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()) { Loading @@ -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, Loading @@ -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 = Loading @@ -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"); Loading @@ -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"); Loading @@ -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"); Loading
android/app/res/values/config.xml +3 −0 Original line number Diff line number Diff line Loading @@ -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> Loading
android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpBipClient.java 0 → 100644 +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); } }
android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java +123 −85 File changed.Preview size limit exceeded, changes collapsed. Show changes