1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define LOG_TAG "AvrcpTargetJni"
18
19 #include <base/bind.h>
20 #include <base/callback.h>
21 #include <map>
22 #include <mutex>
23 #include <shared_mutex>
24 #include <vector>
25
26 #include "avrcp.h"
27 #include "com_android_bluetooth.h"
28 #include "utils/Log.h"
29
30 using namespace bluetooth::avrcp;
31
32 namespace android {
33
34 // Static Variables
35 static MediaCallbacks* mServiceCallbacks;
36 static ServiceInterface* sServiceInterface;
37 static jobject mJavaInterface;
38 static std::shared_timed_mutex interface_mutex;
39 static std::shared_timed_mutex callbacks_mutex;
40
41 // Forward Declarations
42 static void sendMediaKeyEvent(int, KeyState);
43 static std::string getCurrentMediaId();
44 static SongInfo getSongInfo();
45 static PlayStatus getCurrentPlayStatus();
46 static std::vector<SongInfo> getNowPlayingList();
47 static uint16_t getCurrentPlayerId();
48 static std::vector<MediaPlayerInfo> getMediaPlayerList();
49 using SetBrowsedPlayerCb = MediaInterface::SetBrowsedPlayerCallback;
50 static void setBrowsedPlayer(uint16_t player_id, SetBrowsedPlayerCb);
51 using GetFolderItemsCb = MediaInterface::FolderItemsCallback;
52 static void getFolderItems(uint16_t player_id, std::string media_id,
53 GetFolderItemsCb cb);
54 static void playItem(uint16_t player_id, bool now_playing,
55 std::string media_id);
56 static void setActiveDevice(const RawAddress& address);
57
58 static void volumeDeviceConnected(const RawAddress& address);
59 static void volumeDeviceConnected(
60 const RawAddress& address,
61 ::bluetooth::avrcp::VolumeInterface::VolumeChangedCb cb);
62 static void volumeDeviceDisconnected(const RawAddress& address);
63 static void setVolume(int8_t volume);
64
65 // Local Variables
66 // TODO (apanicke): Use a map here to store the callback in order to
67 // support multi-browsing
68 SetBrowsedPlayerCb set_browsed_player_cb;
69 using map_entry = std::pair<std::string, GetFolderItemsCb>;
70 std::map<std::string, GetFolderItemsCb> get_folder_items_cb_map;
71 std::map<RawAddress, ::bluetooth::avrcp::VolumeInterface::VolumeChangedCb>
72 volumeCallbackMap;
73
74 // TODO (apanicke): In the future, this interface should guarantee that
75 // all calls happen on the JNI Thread. Right now this is very difficult
76 // as it is hard to get a handle on the JNI thread from here.
77 class AvrcpMediaInterfaceImpl : public MediaInterface {
78 public:
SendKeyEvent(uint8_t key,KeyState state)79 void SendKeyEvent(uint8_t key, KeyState state) {
80 sendMediaKeyEvent(key, state);
81 }
82
GetSongInfo(SongInfoCallback cb)83 void GetSongInfo(SongInfoCallback cb) override {
84 auto info = getSongInfo();
85 cb.Run(info);
86 }
87
GetPlayStatus(PlayStatusCallback cb)88 void GetPlayStatus(PlayStatusCallback cb) override {
89 auto status = getCurrentPlayStatus();
90 cb.Run(status);
91 }
92
GetNowPlayingList(NowPlayingCallback cb)93 void GetNowPlayingList(NowPlayingCallback cb) override {
94 auto curr_song_id = getCurrentMediaId();
95 auto now_playing_list = getNowPlayingList();
96 cb.Run(curr_song_id, std::move(now_playing_list));
97 }
98
GetMediaPlayerList(MediaListCallback cb)99 void GetMediaPlayerList(MediaListCallback cb) override {
100 uint16_t current_player = getCurrentPlayerId();
101 auto player_list = getMediaPlayerList();
102 cb.Run(current_player, std::move(player_list));
103 }
104
GetFolderItems(uint16_t player_id,std::string media_id,FolderItemsCallback folder_cb)105 void GetFolderItems(uint16_t player_id, std::string media_id,
106 FolderItemsCallback folder_cb) override {
107 getFolderItems(player_id, media_id, folder_cb);
108 }
109
SetBrowsedPlayer(uint16_t player_id,SetBrowsedPlayerCallback browse_cb)110 void SetBrowsedPlayer(uint16_t player_id,
111 SetBrowsedPlayerCallback browse_cb) override {
112 setBrowsedPlayer(player_id, browse_cb);
113 }
114
RegisterUpdateCallback(MediaCallbacks * callback)115 void RegisterUpdateCallback(MediaCallbacks* callback) override {
116 // TODO (apanicke): Allow multiple registrations in the future
117 mServiceCallbacks = callback;
118 }
119
UnregisterUpdateCallback(MediaCallbacks * callback)120 void UnregisterUpdateCallback(MediaCallbacks* callback) override {
121 mServiceCallbacks = nullptr;
122 }
123
PlayItem(uint16_t player_id,bool now_playing,std::string media_id)124 void PlayItem(uint16_t player_id, bool now_playing,
125 std::string media_id) override {
126 playItem(player_id, now_playing, media_id);
127 }
128
SetActiveDevice(const RawAddress & address)129 void SetActiveDevice(const RawAddress& address) override {
130 setActiveDevice(address);
131 }
132 };
133 static AvrcpMediaInterfaceImpl mAvrcpInterface;
134
135 class VolumeInterfaceImpl : public VolumeInterface {
136 public:
DeviceConnected(const RawAddress & bdaddr)137 void DeviceConnected(const RawAddress& bdaddr) override {
138 volumeDeviceConnected(bdaddr);
139 }
140
DeviceConnected(const RawAddress & bdaddr,VolumeChangedCb cb)141 void DeviceConnected(const RawAddress& bdaddr, VolumeChangedCb cb) override {
142 volumeDeviceConnected(bdaddr, cb);
143 }
144
DeviceDisconnected(const RawAddress & bdaddr)145 void DeviceDisconnected(const RawAddress& bdaddr) override {
146 volumeDeviceDisconnected(bdaddr);
147 }
148
SetVolume(int8_t volume)149 void SetVolume(int8_t volume) override { setVolume(volume); }
150 };
151 static VolumeInterfaceImpl mVolumeInterface;
152
153 static jmethodID method_getCurrentSongInfo;
154 static jmethodID method_getPlaybackStatus;
155 static jmethodID method_sendMediaKeyEvent;
156
157 static jmethodID method_getCurrentMediaId;
158 static jmethodID method_getNowPlayingList;
159
160 static jmethodID method_setBrowsedPlayer;
161 static jmethodID method_getCurrentPlayerId;
162 static jmethodID method_getMediaPlayerList;
163 static jmethodID method_getFolderItemsRequest;
164 static jmethodID method_playItem;
165
166 static jmethodID method_setActiveDevice;
167
168 static jmethodID method_volumeDeviceConnected;
169 static jmethodID method_volumeDeviceDisconnected;
170
171 static jmethodID method_setVolume;
172
classInitNative(JNIEnv * env,jclass clazz)173 static void classInitNative(JNIEnv* env, jclass clazz) {
174 method_getCurrentSongInfo = env->GetMethodID(
175 clazz, "getCurrentSongInfo", "()Lcom/android/bluetooth/avrcp/Metadata;");
176
177 method_getPlaybackStatus = env->GetMethodID(
178 clazz, "getPlayStatus", "()Lcom/android/bluetooth/avrcp/PlayStatus;");
179
180 method_sendMediaKeyEvent =
181 env->GetMethodID(clazz, "sendMediaKeyEvent", "(IZ)V");
182
183 method_getCurrentMediaId =
184 env->GetMethodID(clazz, "getCurrentMediaId", "()Ljava/lang/String;");
185
186 method_getNowPlayingList =
187 env->GetMethodID(clazz, "getNowPlayingList", "()Ljava/util/List;");
188
189 method_getCurrentPlayerId =
190 env->GetMethodID(clazz, "getCurrentPlayerId", "()I");
191
192 method_getMediaPlayerList =
193 env->GetMethodID(clazz, "getMediaPlayerList", "()Ljava/util/List;");
194
195 method_setBrowsedPlayer = env->GetMethodID(clazz, "setBrowsedPlayer", "(I)V");
196
197 method_getFolderItemsRequest = env->GetMethodID(
198 clazz, "getFolderItemsRequest", "(ILjava/lang/String;)V");
199
200 method_playItem =
201 env->GetMethodID(clazz, "playItem", "(IZLjava/lang/String;)V");
202
203 method_setActiveDevice =
204 env->GetMethodID(clazz, "setActiveDevice", "(Ljava/lang/String;)V");
205
206 // Volume Management functions
207 method_volumeDeviceConnected =
208 env->GetMethodID(clazz, "deviceConnected", "(Ljava/lang/String;Z)V");
209
210 method_volumeDeviceDisconnected =
211 env->GetMethodID(clazz, "deviceDisconnected", "(Ljava/lang/String;)V");
212
213 method_setVolume = env->GetMethodID(clazz, "setVolume", "(I)V");
214
215 ALOGI("%s: AvrcpTargetJni initialized!", __func__);
216 }
217
initNative(JNIEnv * env,jobject object)218 static void initNative(JNIEnv* env, jobject object) {
219 ALOGD("%s", __func__);
220 std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
221 std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
222 mJavaInterface = env->NewGlobalRef(object);
223
224 sServiceInterface = getBluetoothInterface()->get_avrcp_service();
225 sServiceInterface->Init(&mAvrcpInterface, &mVolumeInterface);
226 }
227
sendMediaUpdateNative(JNIEnv * env,jobject object,jboolean metadata,jboolean state,jboolean queue)228 static void sendMediaUpdateNative(JNIEnv* env, jobject object,
229 jboolean metadata, jboolean state,
230 jboolean queue) {
231 ALOGD("%s", __func__);
232 std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
233 if (mServiceCallbacks == nullptr) {
234 ALOGW("%s: Service not loaded.", __func__);
235 return;
236 }
237
238 mServiceCallbacks->SendMediaUpdate(metadata == JNI_TRUE, state == JNI_TRUE,
239 queue == JNI_TRUE);
240 }
241
sendFolderUpdateNative(JNIEnv * env,jobject object,jboolean available_players,jboolean addressed_player,jboolean uids)242 static void sendFolderUpdateNative(JNIEnv* env, jobject object,
243 jboolean available_players,
244 jboolean addressed_player, jboolean uids) {
245 ALOGD("%s", __func__);
246 std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
247 if (mServiceCallbacks == nullptr) {
248 ALOGW("%s: Service not loaded.", __func__);
249 return;
250 }
251
252 mServiceCallbacks->SendFolderUpdate(available_players == JNI_TRUE,
253 addressed_player == JNI_TRUE,
254 uids == JNI_TRUE);
255 }
256
cleanupNative(JNIEnv * env,jobject object)257 static void cleanupNative(JNIEnv* env, jobject object) {
258 std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
259 std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
260
261 get_folder_items_cb_map.clear();
262 volumeCallbackMap.clear();
263
264 sServiceInterface->Cleanup();
265 env->DeleteGlobalRef(mJavaInterface);
266 mJavaInterface = nullptr;
267 mServiceCallbacks = nullptr;
268 sServiceInterface = nullptr;
269 }
270
connectDeviceNative(JNIEnv * env,jobject object,jstring address)271 jboolean connectDeviceNative(JNIEnv* env, jobject object, jstring address) {
272 ALOGD("%s", __func__);
273 std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
274 if (mServiceCallbacks == nullptr) {
275 ALOGW("%s: Service not loaded.", __func__);
276 return JNI_FALSE;
277 }
278
279 const char* tmp_addr = env->GetStringUTFChars(address, 0);
280 RawAddress bdaddr;
281 bool success = RawAddress::FromString(tmp_addr, bdaddr);
282 env->ReleaseStringUTFChars(address, tmp_addr);
283
284 if (!success) return JNI_FALSE;
285
286 return sServiceInterface->ConnectDevice(bdaddr) == true ? JNI_TRUE
287 : JNI_FALSE;
288 }
289
disconnectDeviceNative(JNIEnv * env,jobject object,jstring address)290 jboolean disconnectDeviceNative(JNIEnv* env, jobject object, jstring address) {
291 ALOGD("%s", __func__);
292 std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
293 if (mServiceCallbacks == nullptr) {
294 ALOGW("%s: Service not loaded.", __func__);
295 return JNI_FALSE;
296 }
297
298 const char* tmp_addr = env->GetStringUTFChars(address, 0);
299 RawAddress bdaddr;
300 bool success = RawAddress::FromString(tmp_addr, bdaddr);
301 env->ReleaseStringUTFChars(address, tmp_addr);
302
303 if (!success) return JNI_FALSE;
304
305 return sServiceInterface->DisconnectDevice(bdaddr) == true ? JNI_TRUE
306 : JNI_FALSE;
307 }
308
sendMediaKeyEvent(int key,KeyState state)309 static void sendMediaKeyEvent(int key, KeyState state) {
310 ALOGD("%s", __func__);
311 std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
312 CallbackEnv sCallbackEnv(__func__);
313 if (!sCallbackEnv.valid() || !mJavaInterface) return;
314 sCallbackEnv->CallVoidMethod(
315 mJavaInterface, method_sendMediaKeyEvent, key,
316 state == KeyState::PUSHED ? JNI_TRUE : JNI_FALSE);
317 }
318
getSongInfoFromJavaObj(JNIEnv * env,jobject metadata)319 static SongInfo getSongInfoFromJavaObj(JNIEnv* env, jobject metadata) {
320 SongInfo info;
321
322 if (metadata == nullptr) return info;
323
324 jclass class_metadata = env->GetObjectClass(metadata);
325 jfieldID field_mediaId =
326 env->GetFieldID(class_metadata, "mediaId", "Ljava/lang/String;");
327 jfieldID field_title =
328 env->GetFieldID(class_metadata, "title", "Ljava/lang/String;");
329 jfieldID field_artist =
330 env->GetFieldID(class_metadata, "artist", "Ljava/lang/String;");
331 jfieldID field_album =
332 env->GetFieldID(class_metadata, "album", "Ljava/lang/String;");
333 jfieldID field_trackNum =
334 env->GetFieldID(class_metadata, "trackNum", "Ljava/lang/String;");
335 jfieldID field_numTracks =
336 env->GetFieldID(class_metadata, "numTracks", "Ljava/lang/String;");
337 jfieldID field_genre =
338 env->GetFieldID(class_metadata, "genre", "Ljava/lang/String;");
339 jfieldID field_playingTime =
340 env->GetFieldID(class_metadata, "duration", "Ljava/lang/String;");
341
342 jstring jstr = (jstring)env->GetObjectField(metadata, field_mediaId);
343 if (jstr != nullptr) {
344 const char* value = env->GetStringUTFChars(jstr, nullptr);
345 info.media_id = std::string(value);
346 env->ReleaseStringUTFChars(jstr, value);
347 env->DeleteLocalRef(jstr);
348 }
349
350 jstr = (jstring)env->GetObjectField(metadata, field_title);
351 if (jstr != nullptr) {
352 const char* value = env->GetStringUTFChars(jstr, nullptr);
353 info.attributes.insert(
354 AttributeEntry(Attribute::TITLE, std::string(value)));
355 env->ReleaseStringUTFChars(jstr, value);
356 env->DeleteLocalRef(jstr);
357 }
358
359 jstr = (jstring)env->GetObjectField(metadata, field_artist);
360 if (jstr != nullptr) {
361 const char* value = env->GetStringUTFChars(jstr, nullptr);
362 info.attributes.insert(
363 AttributeEntry(Attribute::ARTIST_NAME, std::string(value)));
364 env->ReleaseStringUTFChars(jstr, value);
365 env->DeleteLocalRef(jstr);
366 }
367
368 jstr = (jstring)env->GetObjectField(metadata, field_album);
369 if (jstr != nullptr) {
370 const char* value = env->GetStringUTFChars(jstr, nullptr);
371 info.attributes.insert(
372 AttributeEntry(Attribute::ALBUM_NAME, std::string(value)));
373 env->ReleaseStringUTFChars(jstr, value);
374 env->DeleteLocalRef(jstr);
375 }
376
377 jstr = (jstring)env->GetObjectField(metadata, field_trackNum);
378 if (jstr != nullptr) {
379 const char* value = env->GetStringUTFChars(jstr, nullptr);
380 info.attributes.insert(
381 AttributeEntry(Attribute::TRACK_NUMBER, std::string(value)));
382 env->ReleaseStringUTFChars(jstr, value);
383 env->DeleteLocalRef(jstr);
384 }
385
386 jstr = (jstring)env->GetObjectField(metadata, field_numTracks);
387 if (jstr != nullptr) {
388 const char* value = env->GetStringUTFChars(jstr, nullptr);
389 info.attributes.insert(
390 AttributeEntry(Attribute::TOTAL_NUMBER_OF_TRACKS, std::string(value)));
391 env->ReleaseStringUTFChars(jstr, value);
392 env->DeleteLocalRef(jstr);
393 }
394
395 jstr = (jstring)env->GetObjectField(metadata, field_genre);
396 if (jstr != nullptr) {
397 const char* value = env->GetStringUTFChars(jstr, nullptr);
398 info.attributes.insert(
399 AttributeEntry(Attribute::GENRE, std::string(value)));
400 env->ReleaseStringUTFChars(jstr, value);
401 env->DeleteLocalRef(jstr);
402 }
403
404 jstr = (jstring)env->GetObjectField(metadata, field_playingTime);
405 if (jstr != nullptr) {
406 const char* value = env->GetStringUTFChars(jstr, nullptr);
407 info.attributes.insert(
408 AttributeEntry(Attribute::PLAYING_TIME, std::string(value)));
409 env->ReleaseStringUTFChars(jstr, value);
410 env->DeleteLocalRef(jstr);
411 }
412
413 return info;
414 }
415
getFolderInfoFromJavaObj(JNIEnv * env,jobject folder)416 static FolderInfo getFolderInfoFromJavaObj(JNIEnv* env, jobject folder) {
417 FolderInfo info;
418
419 jclass class_folder = env->GetObjectClass(folder);
420 jfieldID field_mediaId =
421 env->GetFieldID(class_folder, "mediaId", "Ljava/lang/String;");
422 jfieldID field_isPlayable = env->GetFieldID(class_folder, "isPlayable", "Z");
423 jfieldID field_name =
424 env->GetFieldID(class_folder, "title", "Ljava/lang/String;");
425
426 jstring jstr = (jstring)env->GetObjectField(folder, field_mediaId);
427 if (jstr != nullptr) {
428 const char* value = env->GetStringUTFChars(jstr, nullptr);
429 info.media_id = std::string(value);
430 env->ReleaseStringUTFChars(jstr, value);
431 }
432
433 info.is_playable = env->GetBooleanField(folder, field_isPlayable) == JNI_TRUE;
434
435 jstr = (jstring)env->GetObjectField(folder, field_name);
436 if (jstr != nullptr) {
437 const char* value = env->GetStringUTFChars(jstr, nullptr);
438 info.name = std::string(value);
439 env->ReleaseStringUTFChars(jstr, value);
440 }
441
442 return info;
443 }
444
getSongInfo()445 static SongInfo getSongInfo() {
446 ALOGD("%s", __func__);
447 std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
448 CallbackEnv sCallbackEnv(__func__);
449 if (!sCallbackEnv.valid() || !mJavaInterface) return SongInfo();
450
451 jobject metadata =
452 sCallbackEnv->CallObjectMethod(mJavaInterface, method_getCurrentSongInfo);
453 return getSongInfoFromJavaObj(sCallbackEnv.get(), metadata);
454 }
455
getCurrentPlayStatus()456 static PlayStatus getCurrentPlayStatus() {
457 ALOGD("%s", __func__);
458 std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
459 CallbackEnv sCallbackEnv(__func__);
460 if (!sCallbackEnv.valid() || !mJavaInterface) return PlayStatus();
461
462 PlayStatus status;
463 jobject playStatus =
464 sCallbackEnv->CallObjectMethod(mJavaInterface, method_getPlaybackStatus);
465
466 if (playStatus == nullptr) {
467 ALOGE("%s: Got a null play status", __func__);
468 return status;
469 }
470
471 jclass class_playStatus = sCallbackEnv->GetObjectClass(playStatus);
472 jfieldID field_position =
473 sCallbackEnv->GetFieldID(class_playStatus, "position", "J");
474 jfieldID field_duration =
475 sCallbackEnv->GetFieldID(class_playStatus, "duration", "J");
476 jfieldID field_state =
477 sCallbackEnv->GetFieldID(class_playStatus, "state", "B");
478
479 status.position = sCallbackEnv->GetLongField(playStatus, field_position);
480 status.duration = sCallbackEnv->GetLongField(playStatus, field_duration);
481 status.state = (PlayState)sCallbackEnv->GetByteField(playStatus, field_state);
482
483 return status;
484 }
485
getCurrentMediaId()486 static std::string getCurrentMediaId() {
487 ALOGD("%s", __func__);
488 std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
489 CallbackEnv sCallbackEnv(__func__);
490 if (!sCallbackEnv.valid() || !mJavaInterface) return "";
491
492 jstring media_id = (jstring)sCallbackEnv->CallObjectMethod(
493 mJavaInterface, method_getCurrentMediaId);
494 if (media_id == nullptr) {
495 ALOGE("%s: Got a null media ID", __func__);
496 return "";
497 }
498
499 const char* value = sCallbackEnv->GetStringUTFChars(media_id, nullptr);
500 std::string ret(value);
501 sCallbackEnv->ReleaseStringUTFChars(media_id, value);
502 return ret;
503 }
504
getNowPlayingList()505 static std::vector<SongInfo> getNowPlayingList() {
506 ALOGD("%s", __func__);
507 std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
508 CallbackEnv sCallbackEnv(__func__);
509 if (!sCallbackEnv.valid() || !mJavaInterface) return std::vector<SongInfo>();
510
511 jobject song_list =
512 sCallbackEnv->CallObjectMethod(mJavaInterface, method_getNowPlayingList);
513 if (song_list == nullptr) {
514 ALOGE("%s: Got a null now playing list", __func__);
515 return std::vector<SongInfo>();
516 }
517
518 jclass class_list = sCallbackEnv->GetObjectClass(song_list);
519 jmethodID method_get =
520 sCallbackEnv->GetMethodID(class_list, "get", "(I)Ljava/lang/Object;");
521 jmethodID method_size = sCallbackEnv->GetMethodID(class_list, "size", "()I");
522
523 auto size = sCallbackEnv->CallIntMethod(song_list, method_size);
524 if (size == 0) return std::vector<SongInfo>();
525 std::vector<SongInfo> ret;
526 for (int i = 0; i < size; i++) {
527 jobject song = sCallbackEnv->CallObjectMethod(song_list, method_get, i);
528 ret.push_back(getSongInfoFromJavaObj(sCallbackEnv.get(), song));
529 sCallbackEnv->DeleteLocalRef(song);
530 }
531
532 return ret;
533 }
534
getCurrentPlayerId()535 static uint16_t getCurrentPlayerId() {
536 ALOGD("%s", __func__);
537 std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
538 CallbackEnv sCallbackEnv(__func__);
539 if (!sCallbackEnv.valid() || !mJavaInterface) return 0u;
540
541 jint id =
542 sCallbackEnv->CallIntMethod(mJavaInterface, method_getCurrentPlayerId);
543
544 return (static_cast<int>(id) & 0xFFFF);
545 }
546
getMediaPlayerList()547 static std::vector<MediaPlayerInfo> getMediaPlayerList() {
548 ALOGD("%s", __func__);
549 std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
550 CallbackEnv sCallbackEnv(__func__);
551 if (!sCallbackEnv.valid() || !mJavaInterface)
552 return std::vector<MediaPlayerInfo>();
553
554 jobject player_list = (jobject)sCallbackEnv->CallObjectMethod(
555 mJavaInterface, method_getMediaPlayerList);
556
557 if (player_list == nullptr) {
558 ALOGE("%s: Got a null media player list", __func__);
559 return std::vector<MediaPlayerInfo>();
560 }
561
562 jclass class_list = sCallbackEnv->GetObjectClass(player_list);
563 jmethodID method_get =
564 sCallbackEnv->GetMethodID(class_list, "get", "(I)Ljava/lang/Object;");
565 jmethodID method_size = sCallbackEnv->GetMethodID(class_list, "size", "()I");
566
567 jint list_size = sCallbackEnv->CallIntMethod(player_list, method_size);
568 if (list_size == 0) {
569 return std::vector<MediaPlayerInfo>();
570 }
571
572 jclass class_playerInfo = sCallbackEnv->GetObjectClass(
573 sCallbackEnv->CallObjectMethod(player_list, method_get, 0));
574 jfieldID field_playerId =
575 sCallbackEnv->GetFieldID(class_playerInfo, "id", "I");
576 jfieldID field_name =
577 sCallbackEnv->GetFieldID(class_playerInfo, "name", "Ljava/lang/String;");
578 jfieldID field_browsable =
579 sCallbackEnv->GetFieldID(class_playerInfo, "browsable", "Z");
580
581 std::vector<MediaPlayerInfo> ret_list;
582 for (jsize i = 0; i < list_size; i++) {
583 jobject player = sCallbackEnv->CallObjectMethod(player_list, method_get, i);
584
585 MediaPlayerInfo temp;
586 temp.id = sCallbackEnv->GetIntField(player, field_playerId);
587
588 jstring jstr = (jstring)sCallbackEnv->GetObjectField(player, field_name);
589 if (jstr != nullptr) {
590 const char* value = sCallbackEnv->GetStringUTFChars(jstr, nullptr);
591 temp.name = std::string(value);
592 sCallbackEnv->ReleaseStringUTFChars(jstr, value);
593 sCallbackEnv->DeleteLocalRef(jstr);
594 }
595
596 temp.browsing_supported =
597 sCallbackEnv->GetBooleanField(player, field_browsable) == JNI_TRUE
598 ? true
599 : false;
600
601 ret_list.push_back(std::move(temp));
602 sCallbackEnv->DeleteLocalRef(player);
603 }
604
605 return ret_list;
606 }
607
setBrowsedPlayer(uint16_t player_id,SetBrowsedPlayerCb cb)608 static void setBrowsedPlayer(uint16_t player_id, SetBrowsedPlayerCb cb) {
609 ALOGD("%s", __func__);
610 std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
611 CallbackEnv sCallbackEnv(__func__);
612 if (!sCallbackEnv.valid() || !mJavaInterface) return;
613
614 set_browsed_player_cb = cb;
615 sCallbackEnv->CallVoidMethod(mJavaInterface, method_setBrowsedPlayer,
616 player_id);
617 }
618
setBrowsedPlayerResponseNative(JNIEnv * env,jobject object,jint player_id,jboolean success,jstring root_id,jint num_items)619 static void setBrowsedPlayerResponseNative(JNIEnv* env, jobject object,
620 jint player_id, jboolean success,
621 jstring root_id, jint num_items) {
622 ALOGD("%s", __func__);
623
624 std::string root;
625 if (root_id != nullptr) {
626 const char* value = env->GetStringUTFChars(root_id, nullptr);
627 root = std::string(value);
628 env->ReleaseStringUTFChars(root_id, value);
629 }
630
631 set_browsed_player_cb.Run(success == JNI_TRUE, root, num_items);
632 }
633
getFolderItemsResponseNative(JNIEnv * env,jobject object,jstring parent_id,jobject list)634 static void getFolderItemsResponseNative(JNIEnv* env, jobject object,
635 jstring parent_id, jobject list) {
636 ALOGD("%s", __func__);
637
638 std::string id;
639 if (parent_id != nullptr) {
640 const char* value = env->GetStringUTFChars(parent_id, nullptr);
641 id = std::string(value);
642 env->ReleaseStringUTFChars(parent_id, value);
643 }
644
645 // TODO (apanicke): Right now browsing will fail on a second device if two
646 // devices browse the same folder. Use a MultiMap to fix this behavior so
647 // that both callbacks can be handled with one lookup if a request comes
648 // for a folder that is already trying to be looked at.
649 if (get_folder_items_cb_map.find(id) == get_folder_items_cb_map.end()) {
650 ALOGE("Could not find response callback for the request of \"%s\"",
651 id.c_str());
652 return;
653 }
654
655 auto callback = get_folder_items_cb_map.find(id)->second;
656 get_folder_items_cb_map.erase(id);
657
658 if (list == nullptr) {
659 ALOGE("%s: Got a null get folder items response list", __func__);
660 callback.Run(std::vector<ListItem>());
661 return;
662 }
663
664 jclass class_list = env->GetObjectClass(list);
665 jmethodID method_get =
666 env->GetMethodID(class_list, "get", "(I)Ljava/lang/Object;");
667 jmethodID method_size = env->GetMethodID(class_list, "size", "()I");
668
669 jint list_size = env->CallIntMethod(list, method_size);
670 if (list_size == 0) {
671 callback.Run(std::vector<ListItem>());
672 return;
673 }
674
675 jclass class_listItem =
676 env->GetObjectClass(env->CallObjectMethod(list, method_get, 0));
677 jfieldID field_isFolder = env->GetFieldID(class_listItem, "isFolder", "Z");
678 jfieldID field_folder = env->GetFieldID(
679 class_listItem, "folder", "Lcom/android/bluetooth/avrcp/Folder;");
680 jfieldID field_song = env->GetFieldID(
681 class_listItem, "song", "Lcom/android/bluetooth/avrcp/Metadata;");
682
683 std::vector<ListItem> ret_list;
684 for (jsize i = 0; i < list_size; i++) {
685 jobject item = env->CallObjectMethod(list, method_get, i);
686
687 bool is_folder = env->GetBooleanField(item, field_isFolder) == JNI_TRUE;
688
689 if (is_folder) {
690 ListItem temp = {ListItem::FOLDER,
691 getFolderInfoFromJavaObj(
692 env, env->GetObjectField(item, field_folder)),
693 SongInfo()};
694
695 ret_list.push_back(temp);
696 } else {
697 ListItem temp = {
698 ListItem::SONG, FolderInfo(),
699 getSongInfoFromJavaObj(env, env->GetObjectField(item, field_song))};
700
701 ret_list.push_back(temp);
702 }
703 env->DeleteLocalRef(item);
704 }
705
706 callback.Run(std::move(ret_list));
707 }
708
getFolderItems(uint16_t player_id,std::string media_id,GetFolderItemsCb cb)709 static void getFolderItems(uint16_t player_id, std::string media_id,
710 GetFolderItemsCb cb) {
711 ALOGD("%s", __func__);
712 std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
713 CallbackEnv sCallbackEnv(__func__);
714 if (!sCallbackEnv.valid() || !mJavaInterface) return;
715
716 // TODO (apanicke): Fix a potential media_id collision if two media players
717 // use the same media_id scheme or two devices browse the same content.
718 get_folder_items_cb_map.insert(map_entry(media_id, cb));
719
720 jstring j_media_id = sCallbackEnv->NewStringUTF(media_id.c_str());
721 sCallbackEnv->CallVoidMethod(mJavaInterface, method_getFolderItemsRequest,
722 player_id, j_media_id);
723 }
724
playItem(uint16_t player_id,bool now_playing,std::string media_id)725 static void playItem(uint16_t player_id, bool now_playing,
726 std::string media_id) {
727 ALOGD("%s", __func__);
728 std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
729 CallbackEnv sCallbackEnv(__func__);
730 if (!sCallbackEnv.valid() || !mJavaInterface) return;
731
732 jstring j_media_id = sCallbackEnv->NewStringUTF(media_id.c_str());
733 sCallbackEnv->CallVoidMethod(mJavaInterface, method_playItem, player_id,
734 now_playing ? JNI_TRUE : JNI_FALSE, j_media_id);
735 }
736
setActiveDevice(const RawAddress & address)737 static void setActiveDevice(const RawAddress& address) {
738 ALOGD("%s", __func__);
739 std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
740 CallbackEnv sCallbackEnv(__func__);
741 if (!sCallbackEnv.valid() || !mJavaInterface) return;
742
743 jstring j_bdaddr = sCallbackEnv->NewStringUTF(address.ToString().c_str());
744 sCallbackEnv->CallVoidMethod(mJavaInterface, method_setActiveDevice,
745 j_bdaddr);
746 }
747
volumeDeviceConnected(const RawAddress & address)748 static void volumeDeviceConnected(const RawAddress& address) {
749 ALOGD("%s", __func__);
750 std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
751 CallbackEnv sCallbackEnv(__func__);
752 if (!sCallbackEnv.valid() || !mJavaInterface) return;
753
754 jstring j_bdaddr = sCallbackEnv->NewStringUTF(address.ToString().c_str());
755 sCallbackEnv->CallVoidMethod(mJavaInterface, method_volumeDeviceConnected,
756 j_bdaddr, JNI_FALSE);
757 }
758
volumeDeviceConnected(const RawAddress & address,::bluetooth::avrcp::VolumeInterface::VolumeChangedCb cb)759 static void volumeDeviceConnected(
760 const RawAddress& address,
761 ::bluetooth::avrcp::VolumeInterface::VolumeChangedCb cb) {
762 ALOGD("%s", __func__);
763 std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
764 CallbackEnv sCallbackEnv(__func__);
765 if (!sCallbackEnv.valid() || !mJavaInterface) return;
766
767 volumeCallbackMap.emplace(address, cb);
768
769 jstring j_bdaddr = sCallbackEnv->NewStringUTF(address.ToString().c_str());
770 sCallbackEnv->CallVoidMethod(mJavaInterface, method_volumeDeviceConnected,
771 j_bdaddr, JNI_TRUE);
772 }
773
volumeDeviceDisconnected(const RawAddress & address)774 static void volumeDeviceDisconnected(const RawAddress& address) {
775 ALOGD("%s", __func__);
776 std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
777 CallbackEnv sCallbackEnv(__func__);
778 if (!sCallbackEnv.valid() || !mJavaInterface) return;
779
780 volumeCallbackMap.erase(address);
781
782 jstring j_bdaddr = sCallbackEnv->NewStringUTF(address.ToString().c_str());
783 sCallbackEnv->CallVoidMethod(mJavaInterface, method_volumeDeviceDisconnected,
784 j_bdaddr);
785 }
786
sendVolumeChangedNative(JNIEnv * env,jobject object,jstring address,jint volume)787 static void sendVolumeChangedNative(JNIEnv* env, jobject object,
788 jstring address, jint volume) {
789 const char* tmp_addr = env->GetStringUTFChars(address, 0);
790 RawAddress bdaddr;
791 bool success = RawAddress::FromString(tmp_addr, bdaddr);
792 env->ReleaseStringUTFChars(address, tmp_addr);
793
794 if (!success) return;
795
796 ALOGD("%s", __func__);
797 if (volumeCallbackMap.find(bdaddr) != volumeCallbackMap.end()) {
798 volumeCallbackMap.find(bdaddr)->second.Run(volume & 0x7F);
799 }
800 }
801
setVolume(int8_t volume)802 static void setVolume(int8_t volume) {
803 ALOGD("%s", __func__);
804 std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
805 CallbackEnv sCallbackEnv(__func__);
806 if (!sCallbackEnv.valid() || !mJavaInterface) return;
807
808 sCallbackEnv->CallVoidMethod(mJavaInterface, method_setVolume, volume);
809 }
810
811 static JNINativeMethod sMethods[] = {
812 {"classInitNative", "()V", (void*)classInitNative},
813 {"initNative", "()V", (void*)initNative},
814 {"sendMediaUpdateNative", "(ZZZ)V", (void*)sendMediaUpdateNative},
815 {"sendFolderUpdateNative", "(ZZZ)V", (void*)sendFolderUpdateNative},
816 {"setBrowsedPlayerResponseNative", "(IZLjava/lang/String;I)V",
817 (void*)setBrowsedPlayerResponseNative},
818 {"getFolderItemsResponseNative", "(Ljava/lang/String;Ljava/util/List;)V",
819 (void*)getFolderItemsResponseNative},
820 {"cleanupNative", "()V", (void*)cleanupNative},
821 {"connectDeviceNative", "(Ljava/lang/String;)Z",
822 (void*)connectDeviceNative},
823 {"disconnectDeviceNative", "(Ljava/lang/String;)Z",
824 (void*)disconnectDeviceNative},
825 {"sendVolumeChangedNative", "(Ljava/lang/String;I)V",
826 (void*)sendVolumeChangedNative},
827 };
828
register_com_android_bluetooth_avrcp_target(JNIEnv * env)829 int register_com_android_bluetooth_avrcp_target(JNIEnv* env) {
830 return jniRegisterNativeMethods(
831 env, "com/android/bluetooth/avrcp/AvrcpNativeInterface", sMethods,
832 NELEM(sMethods));
833 }
834
835 } // namespace android
836