1 /* 2 * Copyright (C) 2012 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 package com.android.bluetooth.a2dp; 18 19 import android.app.PendingIntent; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.graphics.Bitmap; 23 import android.media.AudioManager; 24 import android.media.IRemoteControlDisplay; 25 import android.media.MediaMetadataRetriever; 26 import android.media.RemoteControlClient; 27 import android.os.Bundle; 28 import android.os.Handler; 29 import android.os.HandlerThread; 30 import android.os.Looper; 31 import android.os.Message; 32 import android.os.ParcelUuid; 33 import android.os.PowerManager; 34 import android.os.PowerManager.WakeLock; 35 import android.os.RemoteException; 36 import android.os.ServiceManager; 37 import android.os.SystemClock; 38 import android.util.Log; 39 import com.android.bluetooth.btservice.AdapterService; 40 import com.android.bluetooth.btservice.ProfileService; 41 import com.android.bluetooth.Utils; 42 import com.android.internal.util.IState; 43 import com.android.internal.util.State; 44 import com.android.internal.util.StateMachine; 45 import java.lang.ref.WeakReference; 46 import java.util.ArrayList; 47 import java.util.List; 48 import java.util.Set; 49 50 /** 51 * support Bluetooth AVRCP profile. 52 * support metadata, play status and event notification 53 */ 54 final class Avrcp { 55 private static final boolean DEBUG = false; 56 private static final String TAG = "Avrcp"; 57 58 private Context mContext; 59 private final AudioManager mAudioManager; 60 private AvrcpMessageHandler mHandler; 61 private IRemoteControlDisplayWeak mRemoteControlDisplay; 62 private int mClientGeneration; 63 private Metadata mMetadata; 64 private int mTransportControlFlags; 65 private int mCurrentPlayState; 66 private int mPlayStatusChangedNT; 67 private int mTrackChangedNT; 68 private long mTrackNumber; 69 private long mCurrentPosMs; 70 private long mPlayStartTimeMs; 71 private long mSongLengthMs; 72 private long mPlaybackIntervalMs; 73 private int mPlayPosChangedNT; 74 private long mNextPosMs; 75 private long mPrevPosMs; 76 77 private static final int MESSAGE_GET_PLAY_STATUS = 1; 78 private static final int MESSAGE_GET_ELEM_ATTRS = 2; 79 private static final int MESSAGE_REGISTER_NOTIFICATION = 3; 80 private static final int MESSAGE_PLAY_INTERVAL_TIMEOUT = 4; 81 private static final int MSG_UPDATE_STATE = 100; 82 private static final int MSG_SET_METADATA = 101; 83 private static final int MSG_SET_TRANSPORT_CONTROLS = 102; 84 private static final int MSG_SET_ARTWORK = 103; 85 private static final int MSG_SET_GENERATION_ID = 104; 86 87 static { classInitNative()88 classInitNative(); 89 } 90 Avrcp(Context context)91 private Avrcp(Context context) { 92 mMetadata = new Metadata(); 93 mCurrentPlayState = RemoteControlClient.PLAYSTATE_NONE; // until we get a callback 94 mPlayStatusChangedNT = NOTIFICATION_TYPE_CHANGED; 95 mTrackChangedNT = NOTIFICATION_TYPE_CHANGED; 96 mTrackNumber = -1L; 97 mCurrentPosMs = 0L; 98 mPlayStartTimeMs = -1L; 99 mSongLengthMs = 0L; 100 mPlaybackIntervalMs = 0L; 101 mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED; 102 103 mContext = context; 104 105 initNative(); 106 107 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 108 } 109 start()110 private void start() { 111 HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler"); 112 thread.start(); 113 Looper looper = thread.getLooper(); 114 mHandler = new AvrcpMessageHandler(looper); 115 mRemoteControlDisplay = new IRemoteControlDisplayWeak(mHandler); 116 mAudioManager.registerRemoteControlDisplay(mRemoteControlDisplay); 117 mAudioManager.remoteControlDisplayWantsPlaybackPositionSync( 118 mRemoteControlDisplay, true); 119 } 120 make(Context context)121 static Avrcp make(Context context) { 122 if (DEBUG) Log.v(TAG, "make"); 123 Avrcp ar = new Avrcp(context); 124 ar.start(); 125 return ar; 126 } 127 doQuit()128 public void doQuit() { 129 mHandler.removeCallbacksAndMessages(null); 130 Looper looper = mHandler.getLooper(); 131 if (looper != null) { 132 looper.quit(); 133 } 134 mAudioManager.unregisterRemoteControlDisplay(mRemoteControlDisplay); 135 } 136 cleanup()137 public void cleanup() { 138 cleanupNative(); 139 } 140 141 private static class IRemoteControlDisplayWeak extends IRemoteControlDisplay.Stub { 142 private WeakReference<Handler> mLocalHandler; IRemoteControlDisplayWeak(Handler handler)143 IRemoteControlDisplayWeak(Handler handler) { 144 mLocalHandler = new WeakReference<Handler>(handler); 145 } 146 147 @Override setPlaybackState(int generationId, int state, long stateChangeTimeMs, long currentPosMs, float speed)148 public void setPlaybackState(int generationId, int state, long stateChangeTimeMs, 149 long currentPosMs, float speed) { 150 Handler handler = mLocalHandler.get(); 151 if (handler != null) { 152 handler.obtainMessage(MSG_UPDATE_STATE, generationId, state, 153 new Long(currentPosMs)).sendToTarget(); 154 } 155 } 156 157 @Override setMetadata(int generationId, Bundle metadata)158 public void setMetadata(int generationId, Bundle metadata) { 159 Handler handler = mLocalHandler.get(); 160 if (handler != null) { 161 handler.obtainMessage(MSG_SET_METADATA, generationId, 0, metadata).sendToTarget(); 162 } 163 } 164 165 @Override setTransportControlInfo(int generationId, int flags, int posCapabilities)166 public void setTransportControlInfo(int generationId, int flags, int posCapabilities) { 167 Handler handler = mLocalHandler.get(); 168 if (handler != null) { 169 handler.obtainMessage(MSG_SET_TRANSPORT_CONTROLS, generationId, flags) 170 .sendToTarget(); 171 } 172 } 173 174 @Override setArtwork(int generationId, Bitmap bitmap)175 public void setArtwork(int generationId, Bitmap bitmap) { 176 } 177 178 @Override setAllMetadata(int generationId, Bundle metadata, Bitmap bitmap)179 public void setAllMetadata(int generationId, Bundle metadata, Bitmap bitmap) { 180 Handler handler = mLocalHandler.get(); 181 if (handler != null) { 182 handler.obtainMessage(MSG_SET_METADATA, generationId, 0, metadata).sendToTarget(); 183 handler.obtainMessage(MSG_SET_ARTWORK, generationId, 0, bitmap).sendToTarget(); 184 } 185 } 186 187 @Override setCurrentClientId(int clientGeneration, PendingIntent mediaIntent, boolean clearing)188 public void setCurrentClientId(int clientGeneration, PendingIntent mediaIntent, 189 boolean clearing) throws RemoteException { 190 Handler handler = mLocalHandler.get(); 191 if (handler != null) { 192 handler.obtainMessage(MSG_SET_GENERATION_ID, 193 clientGeneration, (clearing ? 1 : 0), mediaIntent).sendToTarget(); 194 } 195 } 196 } 197 198 /** Handles Avrcp messages. */ 199 private final class AvrcpMessageHandler extends Handler { AvrcpMessageHandler(Looper looper)200 private AvrcpMessageHandler(Looper looper) { 201 super(looper); 202 } 203 204 @Override handleMessage(Message msg)205 public void handleMessage(Message msg) { 206 switch (msg.what) { 207 case MSG_UPDATE_STATE: 208 if (mClientGeneration == msg.arg1) { 209 updatePlayPauseState(msg.arg2, ((Long)msg.obj).longValue()); 210 } 211 break; 212 213 case MSG_SET_METADATA: 214 if (mClientGeneration == msg.arg1) updateMetadata((Bundle) msg.obj); 215 break; 216 217 case MSG_SET_TRANSPORT_CONTROLS: 218 if (mClientGeneration == msg.arg1) updateTransportControls(msg.arg2); 219 break; 220 221 case MSG_SET_ARTWORK: 222 if (mClientGeneration == msg.arg1) { 223 } 224 break; 225 226 case MSG_SET_GENERATION_ID: 227 if (DEBUG) Log.v(TAG, "New genId = " + msg.arg1 + ", clearing = " + msg.arg2); 228 mClientGeneration = msg.arg1; 229 break; 230 231 case MESSAGE_GET_PLAY_STATUS: 232 if (DEBUG) Log.v(TAG, "MESSAGE_GET_PLAY_STATUS"); 233 getPlayStatusRspNative(convertPlayStateToPlayStatus(mCurrentPlayState), 234 (int)mSongLengthMs, (int)getPlayPosition()); 235 break; 236 237 case MESSAGE_GET_ELEM_ATTRS: 238 { 239 String[] textArray; 240 int[] attrIds; 241 byte numAttr = (byte) msg.arg1; 242 ArrayList<Integer> attrList = (ArrayList<Integer>) msg.obj; 243 if (DEBUG) Log.v(TAG, "MESSAGE_GET_ELEM_ATTRS:numAttr=" + numAttr); 244 attrIds = new int[numAttr]; 245 textArray = new String[numAttr]; 246 for (int i = 0; i < numAttr; ++i) { 247 attrIds[i] = attrList.get(i).intValue(); 248 textArray[i] = getAttributeString(attrIds[i]); 249 } 250 getElementAttrRspNative(numAttr, attrIds, textArray); 251 break; 252 } 253 case MESSAGE_REGISTER_NOTIFICATION: 254 if (DEBUG) Log.v(TAG, "MESSAGE_REGISTER_NOTIFICATION:event=" + msg.arg1 + 255 " param=" + msg.arg2); 256 processRegisterNotification(msg.arg1, msg.arg2); 257 break; 258 259 case MESSAGE_PLAY_INTERVAL_TIMEOUT: 260 if (DEBUG) Log.v(TAG, "MESSAGE_PLAY_INTERVAL_TIMEOUT"); 261 mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED; 262 registerNotificationRspPlayPosNative(mPlayPosChangedNT, (int)getPlayPosition()); 263 break; 264 265 } 266 } 267 } 268 updatePlayPauseState(int state, long currentPosMs)269 private void updatePlayPauseState(int state, long currentPosMs) { 270 if (DEBUG) Log.v(TAG, 271 "updatePlayPauseState, old=" + mCurrentPlayState + ", state=" + state); 272 boolean oldPosValid = (mCurrentPosMs != 273 RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN); 274 int oldPlayStatus = convertPlayStateToPlayStatus(mCurrentPlayState); 275 int newPlayStatus = convertPlayStateToPlayStatus(state); 276 277 if ((mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING) && 278 (mCurrentPlayState != state) && oldPosValid) { 279 mCurrentPosMs = getPlayPosition(); 280 } 281 282 mCurrentPlayState = state; 283 if (currentPosMs != RemoteControlClient.PLAYBACK_POSITION_INVALID) { 284 mCurrentPosMs = currentPosMs; 285 } 286 if (state == RemoteControlClient.PLAYSTATE_PLAYING) { 287 mPlayStartTimeMs = SystemClock.elapsedRealtime(); 288 } 289 290 boolean newPosValid = (mCurrentPosMs != 291 RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN); 292 long playPosition = getPlayPosition(); 293 mHandler.removeMessages(MESSAGE_PLAY_INTERVAL_TIMEOUT); 294 /* need send play position changed notification when play status is changed */ 295 if ((mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM) && 296 ((oldPlayStatus != newPlayStatus) || (oldPosValid != newPosValid) || 297 (newPosValid && ((playPosition >= mNextPosMs) || (playPosition <= mPrevPosMs))))) { 298 mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED; 299 registerNotificationRspPlayPosNative(mPlayPosChangedNT, (int)playPosition); 300 } 301 if ((mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM) && newPosValid && 302 (state == RemoteControlClient.PLAYSTATE_PLAYING)) { 303 Message msg = mHandler.obtainMessage(MESSAGE_PLAY_INTERVAL_TIMEOUT); 304 mHandler.sendMessageDelayed(msg, mNextPosMs - playPosition); 305 } 306 307 if ((mPlayStatusChangedNT == NOTIFICATION_TYPE_INTERIM) && (oldPlayStatus != newPlayStatus)) { 308 mPlayStatusChangedNT = NOTIFICATION_TYPE_CHANGED; 309 registerNotificationRspPlayStatusNative(mPlayStatusChangedNT, newPlayStatus); 310 } 311 } 312 updateTransportControls(int transportControlFlags)313 private void updateTransportControls(int transportControlFlags) { 314 mTransportControlFlags = transportControlFlags; 315 } 316 317 class Metadata { 318 private String artist; 319 private String trackTitle; 320 private String albumTitle; 321 Metadata()322 public Metadata() { 323 artist = null; 324 trackTitle = null; 325 albumTitle = null; 326 } 327 toString()328 public String toString() { 329 return "Metadata[artist=" + artist + " trackTitle=" + trackTitle + " albumTitle=" + 330 albumTitle + "]"; 331 } 332 } 333 getMdString(Bundle data, int id)334 private String getMdString(Bundle data, int id) { 335 return data.getString(Integer.toString(id)); 336 } 337 getMdLong(Bundle data, int id)338 private long getMdLong(Bundle data, int id) { 339 return data.getLong(Integer.toString(id)); 340 } 341 updateMetadata(Bundle data)342 private void updateMetadata(Bundle data) { 343 String oldMetadata = mMetadata.toString(); 344 mMetadata.artist = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST); 345 mMetadata.trackTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_TITLE); 346 mMetadata.albumTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUM); 347 if (!oldMetadata.equals(mMetadata.toString())) { 348 mTrackNumber++; 349 if (mTrackChangedNT == NOTIFICATION_TYPE_INTERIM) { 350 mTrackChangedNT = NOTIFICATION_TYPE_CHANGED; 351 sendTrackChangedRsp(); 352 } 353 354 if (mCurrentPosMs != RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) { 355 mCurrentPosMs = 0L; 356 if (mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING) { 357 mPlayStartTimeMs = SystemClock.elapsedRealtime(); 358 } 359 } 360 /* need send play position changed notification when track is changed */ 361 if (mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM) { 362 mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED; 363 registerNotificationRspPlayPosNative(mPlayPosChangedNT, 364 (int)getPlayPosition()); 365 mHandler.removeMessages(MESSAGE_PLAY_INTERVAL_TIMEOUT); 366 } 367 } 368 if (DEBUG) Log.v(TAG, "mMetadata=" + mMetadata.toString()); 369 370 mSongLengthMs = getMdLong(data, MediaMetadataRetriever.METADATA_KEY_DURATION); 371 if (DEBUG) Log.v(TAG, "duration=" + mSongLengthMs); 372 } 373 getPlayStatus()374 private void getPlayStatus() { 375 Message msg = mHandler.obtainMessage(MESSAGE_GET_PLAY_STATUS); 376 mHandler.sendMessage(msg); 377 } 378 getElementAttr(byte numAttr, int[] attrs)379 private void getElementAttr(byte numAttr, int[] attrs) { 380 int i; 381 ArrayList<Integer> attrList = new ArrayList<Integer>(); 382 for (i = 0; i < numAttr; ++i) { 383 attrList.add(attrs[i]); 384 } 385 Message msg = mHandler.obtainMessage(MESSAGE_GET_ELEM_ATTRS, (int)numAttr, 0, attrList); 386 mHandler.sendMessage(msg); 387 } 388 registerNotification(int eventId, int param)389 private void registerNotification(int eventId, int param) { 390 Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_NOTIFICATION, eventId, param); 391 mHandler.sendMessage(msg); 392 } 393 processRegisterNotification(int eventId, int param)394 private void processRegisterNotification(int eventId, int param) { 395 switch (eventId) { 396 case EVT_PLAY_STATUS_CHANGED: 397 mPlayStatusChangedNT = NOTIFICATION_TYPE_INTERIM; 398 registerNotificationRspPlayStatusNative(mPlayStatusChangedNT, 399 convertPlayStateToPlayStatus(mCurrentPlayState)); 400 break; 401 402 case EVT_TRACK_CHANGED: 403 mTrackChangedNT = NOTIFICATION_TYPE_INTERIM; 404 sendTrackChangedRsp(); 405 break; 406 407 case EVT_PLAY_POS_CHANGED: 408 long songPosition = getPlayPosition(); 409 mPlayPosChangedNT = NOTIFICATION_TYPE_INTERIM; 410 mPlaybackIntervalMs = (long)param * 1000L; 411 if (mCurrentPosMs != RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) { 412 mNextPosMs = songPosition + mPlaybackIntervalMs; 413 mPrevPosMs = songPosition - mPlaybackIntervalMs; 414 if (mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING) { 415 Message msg = mHandler.obtainMessage(MESSAGE_PLAY_INTERVAL_TIMEOUT); 416 mHandler.sendMessageDelayed(msg, mPlaybackIntervalMs); 417 } 418 } 419 registerNotificationRspPlayPosNative(mPlayPosChangedNT, (int)songPosition); 420 break; 421 422 } 423 } 424 sendTrackChangedRsp()425 private void sendTrackChangedRsp() { 426 byte[] track = new byte[TRACK_ID_SIZE]; 427 /* track is stored in big endian format */ 428 for (int i = 0; i < TRACK_ID_SIZE; ++i) { 429 track[i] = (byte) (mTrackNumber >> (56 - 8 * i)); 430 } 431 registerNotificationRspTrackChangeNative(mTrackChangedNT, track); 432 } 433 getPlayPosition()434 private long getPlayPosition() { 435 long songPosition = -1L; 436 if (mCurrentPosMs != RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) { 437 if (mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING) { 438 songPosition = SystemClock.elapsedRealtime() - 439 mPlayStartTimeMs + mCurrentPosMs; 440 } else { 441 songPosition = mCurrentPosMs; 442 } 443 } 444 if (DEBUG) Log.v(TAG, "position=" + songPosition); 445 return songPosition; 446 } 447 getAttributeString(int attrId)448 private String getAttributeString(int attrId) { 449 String attrStr = null; 450 switch (attrId) { 451 case MEDIA_ATTR_TITLE: 452 attrStr = mMetadata.trackTitle; 453 break; 454 455 case MEDIA_ATTR_ARTIST: 456 attrStr = mMetadata.artist; 457 break; 458 459 case MEDIA_ATTR_ALBUM: 460 attrStr = mMetadata.albumTitle; 461 break; 462 463 case MEDIA_ATTR_PLAYING_TIME: 464 if (mSongLengthMs != 0L) { 465 attrStr = Long.toString(mSongLengthMs); 466 } 467 break; 468 469 } 470 if (attrStr == null) { 471 attrStr = new String(); 472 } 473 if (DEBUG) Log.v(TAG, "getAttributeString:attrId=" + attrId + " str=" + attrStr); 474 return attrStr; 475 } 476 convertPlayStateToPlayStatus(int playState)477 private int convertPlayStateToPlayStatus(int playState) { 478 int playStatus = PLAYSTATUS_ERROR; 479 switch (playState) { 480 case RemoteControlClient.PLAYSTATE_PLAYING: 481 case RemoteControlClient.PLAYSTATE_BUFFERING: 482 playStatus = PLAYSTATUS_PLAYING; 483 break; 484 485 case RemoteControlClient.PLAYSTATE_STOPPED: 486 case RemoteControlClient.PLAYSTATE_NONE: 487 playStatus = PLAYSTATUS_STOPPED; 488 break; 489 490 case RemoteControlClient.PLAYSTATE_PAUSED: 491 playStatus = PLAYSTATUS_PAUSED; 492 break; 493 494 case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: 495 case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: 496 playStatus = PLAYSTATUS_FWD_SEEK; 497 break; 498 499 case RemoteControlClient.PLAYSTATE_REWINDING: 500 case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: 501 playStatus = PLAYSTATUS_REV_SEEK; 502 break; 503 504 case RemoteControlClient.PLAYSTATE_ERROR: 505 playStatus = PLAYSTATUS_ERROR; 506 break; 507 508 } 509 return playStatus; 510 } 511 512 // Do not modify without updating the HAL bt_rc.h files. 513 514 // match up with btrc_play_status_t enum of bt_rc.h 515 final static int PLAYSTATUS_STOPPED = 0; 516 final static int PLAYSTATUS_PLAYING = 1; 517 final static int PLAYSTATUS_PAUSED = 2; 518 final static int PLAYSTATUS_FWD_SEEK = 3; 519 final static int PLAYSTATUS_REV_SEEK = 4; 520 final static int PLAYSTATUS_ERROR = 255; 521 522 // match up with btrc_media_attr_t enum of bt_rc.h 523 final static int MEDIA_ATTR_TITLE = 1; 524 final static int MEDIA_ATTR_ARTIST = 2; 525 final static int MEDIA_ATTR_ALBUM = 3; 526 final static int MEDIA_ATTR_TRACK_NUM = 4; 527 final static int MEDIA_ATTR_NUM_TRACKS = 5; 528 final static int MEDIA_ATTR_GENRE = 6; 529 final static int MEDIA_ATTR_PLAYING_TIME = 7; 530 531 // match up with btrc_event_id_t enum of bt_rc.h 532 final static int EVT_PLAY_STATUS_CHANGED = 1; 533 final static int EVT_TRACK_CHANGED = 2; 534 final static int EVT_TRACK_REACHED_END = 3; 535 final static int EVT_TRACK_REACHED_START = 4; 536 final static int EVT_PLAY_POS_CHANGED = 5; 537 final static int EVT_BATT_STATUS_CHANGED = 6; 538 final static int EVT_SYSTEM_STATUS_CHANGED = 7; 539 final static int EVT_APP_SETTINGS_CHANGED = 8; 540 541 // match up with btrc_notification_type_t enum of bt_rc.h 542 final static int NOTIFICATION_TYPE_INTERIM = 0; 543 final static int NOTIFICATION_TYPE_CHANGED = 1; 544 545 // match up with BTRC_UID_SIZE of bt_rc.h 546 final static int TRACK_ID_SIZE = 8; 547 classInitNative()548 private native static void classInitNative(); initNative()549 private native void initNative(); cleanupNative()550 private native void cleanupNative(); getPlayStatusRspNative(int playStatus, int songLen, int songPos)551 private native boolean getPlayStatusRspNative(int playStatus, int songLen, int songPos); getElementAttrRspNative(byte numAttr, int[] attrIds, String[] textArray)552 private native boolean getElementAttrRspNative(byte numAttr, int[] attrIds, String[] textArray); registerNotificationRspPlayStatusNative(int type, int playStatus)553 private native boolean registerNotificationRspPlayStatusNative(int type, int playStatus); registerNotificationRspTrackChangeNative(int type, byte[] track)554 private native boolean registerNotificationRspTrackChangeNative(int type, byte[] track); registerNotificationRspPlayPosNative(int type, int playPos)555 private native boolean registerNotificationRspPlayPosNative(int type, int playPos); 556 } 557