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 17package com.android.bluetooth.avrcp; 18 19import java.util.Timer; 20import java.util.TimerTask; 21 22import android.bluetooth.BluetoothA2dp; 23import android.bluetooth.BluetoothAvrcp; 24import android.content.Context; 25import android.content.Intent; 26import android.content.res.Resources; 27import android.content.SharedPreferences; 28import android.graphics.Bitmap; 29import android.media.AudioManager; 30import android.media.MediaDescription; 31import android.media.MediaMetadata; 32import android.media.session.MediaController; 33import android.media.session.MediaSessionManager; 34import android.media.session.PlaybackState; 35import android.os.Bundle; 36import android.os.Handler; 37import android.os.HandlerThread; 38import android.os.Looper; 39import android.os.Message; 40import android.os.ParcelUuid; 41import android.os.PowerManager; 42import android.os.PowerManager.WakeLock; 43import android.os.RemoteException; 44import android.os.ServiceManager; 45import android.os.SystemClock; 46import android.util.Log; 47import android.view.KeyEvent; 48 49import com.android.bluetooth.R; 50import com.android.bluetooth.btservice.AdapterService; 51import com.android.bluetooth.btservice.ProfileService; 52import com.android.bluetooth.Utils; 53import com.android.internal.util.IState; 54import com.android.internal.util.State; 55import com.android.internal.util.StateMachine; 56 57import java.lang.ref.WeakReference; 58import java.util.ArrayList; 59import java.util.HashMap; 60import java.util.List; 61import java.util.Set; 62/** 63 * support Bluetooth AVRCP profile. 64 * support metadata, play status and event notification 65 */ 66public final class Avrcp { 67 private static final boolean DEBUG = false; 68 private static final String TAG = "Avrcp"; 69 private static final String ABSOLUTE_VOLUME_BLACKLIST = "absolute_volume_blacklist"; 70 71 private Context mContext; 72 private final AudioManager mAudioManager; 73 private AvrcpMessageHandler mHandler; 74 private MediaSessionManager mMediaSessionManager; 75 private MediaSessionChangeListener mSessionChangeListener; 76 private MediaController mMediaController; 77 private MediaControllerListener mMediaControllerCb; 78 private MediaAttributes mMediaAttributes; 79 private int mTransportControlFlags; 80 private PlaybackState mCurrentPlayState; 81 private long mLastStateUpdate; 82 private int mPlayStatusChangedNT; 83 private int mTrackChangedNT; 84 private int mPlayPosChangedNT; 85 private long mTrackNumber; 86 private long mSongLengthMs; 87 private long mPlaybackIntervalMs; 88 private long mNextPosMs; 89 private long mPrevPosMs; 90 private long mSkipStartTime; 91 private int mFeatures; 92 private int mRemoteVolume; 93 private int mLastRemoteVolume; 94 private int mInitialRemoteVolume; 95 96 /* Local volume in audio index 0-15 */ 97 private int mLocalVolume; 98 private int mLastLocalVolume; 99 private int mAbsVolThreshold; 100 101 private String mAddress; 102 private HashMap<Integer, Integer> mVolumeMapping; 103 104 private int mLastDirection; 105 private final int mVolumeStep; 106 private final int mAudioStreamMax; 107 private boolean mVolCmdAdjustInProgress; 108 private boolean mVolCmdSetInProgress; 109 private int mAbsVolRetryTimes; 110 private int mSkipAmount; 111 112 /* BTRC features */ 113 public static final int BTRC_FEAT_METADATA = 0x01; 114 public static final int BTRC_FEAT_ABSOLUTE_VOLUME = 0x02; 115 public static final int BTRC_FEAT_BROWSE = 0x04; 116 117 /* AVRC response codes, from avrc_defs */ 118 private static final int AVRC_RSP_NOT_IMPL = 8; 119 private static final int AVRC_RSP_ACCEPT = 9; 120 private static final int AVRC_RSP_REJ = 10; 121 private static final int AVRC_RSP_IN_TRANS = 11; 122 private static final int AVRC_RSP_IMPL_STBL = 12; 123 private static final int AVRC_RSP_CHANGED = 13; 124 private static final int AVRC_RSP_INTERIM = 15; 125 126 private static final int MESSAGE_GET_RC_FEATURES = 1; 127 private static final int MESSAGE_GET_PLAY_STATUS = 2; 128 private static final int MESSAGE_GET_ELEM_ATTRS = 3; 129 private static final int MESSAGE_REGISTER_NOTIFICATION = 4; 130 private static final int MESSAGE_PLAY_INTERVAL_TIMEOUT = 5; 131 private static final int MESSAGE_VOLUME_CHANGED = 6; 132 private static final int MESSAGE_ADJUST_VOLUME = 7; 133 private static final int MESSAGE_SET_ABSOLUTE_VOLUME = 8; 134 private static final int MESSAGE_ABS_VOL_TIMEOUT = 9; 135 private static final int MESSAGE_FAST_FORWARD = 10; 136 private static final int MESSAGE_REWIND = 11; 137 private static final int MESSAGE_CHANGE_PLAY_POS = 12; 138 private static final int MESSAGE_SET_A2DP_AUDIO_STATE = 13; 139 140 private static final int BUTTON_TIMEOUT_TIME = 2000; 141 private static final int BASE_SKIP_AMOUNT = 2000; 142 private static final int KEY_STATE_PRESS = 1; 143 private static final int KEY_STATE_RELEASE = 0; 144 private static final int SKIP_PERIOD = 400; 145 private static final int SKIP_DOUBLE_INTERVAL = 3000; 146 private static final long MAX_MULTIPLIER_VALUE = 128L; 147 private static final int CMD_TIMEOUT_DELAY = 2000; 148 private static final int MAX_ERROR_RETRY_TIMES = 6; 149 private static final int AVRCP_MAX_VOL = 127; 150 private static final int AVRCP_BASE_VOLUME_STEP = 1; 151 152 static { 153 classInitNative(); 154 } 155 156 private Avrcp(Context context) { 157 mMediaAttributes = new MediaAttributes(null); 158 mCurrentPlayState = new PlaybackState.Builder().setState(PlaybackState.STATE_NONE, -1L, 0.0f).build(); 159 mPlayStatusChangedNT = NOTIFICATION_TYPE_CHANGED; 160 mTrackChangedNT = NOTIFICATION_TYPE_CHANGED; 161 mTrackNumber = -1L; 162 mLastStateUpdate = -1L; 163 mSongLengthMs = 0L; 164 mPlaybackIntervalMs = 0L; 165 mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED; 166 mNextPosMs = -1; 167 mPrevPosMs = -1; 168 mFeatures = 0; 169 mRemoteVolume = -1; 170 mInitialRemoteVolume = -1; 171 mLastRemoteVolume = -1; 172 mLastDirection = 0; 173 mVolCmdAdjustInProgress = false; 174 mVolCmdSetInProgress = false; 175 mAbsVolRetryTimes = 0; 176 mLocalVolume = -1; 177 mLastLocalVolume = -1; 178 mAbsVolThreshold = 0; 179 mVolumeMapping = new HashMap<Integer, Integer>(); 180 181 mContext = context; 182 183 initNative(); 184 185 mMediaSessionManager = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE); 186 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 187 mAudioStreamMax = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 188 mVolumeStep = Math.max(AVRCP_BASE_VOLUME_STEP, AVRCP_MAX_VOL/mAudioStreamMax); 189 Resources resources = context.getResources(); 190 if (resources != null) { 191 mAbsVolThreshold = resources.getInteger(R.integer.a2dp_absolute_volume_initial_threshold); 192 } 193 } 194 195 private void start() { 196 HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler"); 197 thread.start(); 198 Looper looper = thread.getLooper(); 199 mHandler = new AvrcpMessageHandler(looper); 200 201 mSessionChangeListener = new MediaSessionChangeListener(); 202 mMediaSessionManager.addOnActiveSessionsChangedListener(mSessionChangeListener, null, mHandler); 203 List<MediaController> sessions = mMediaSessionManager.getActiveSessions(null); 204 mMediaControllerCb = new MediaControllerListener(); 205 if (sessions.size() > 0) { 206 updateCurrentMediaController(sessions.get(0)); 207 } 208 } 209 210 public static Avrcp make(Context context) { 211 if (DEBUG) Log.v(TAG, "make"); 212 Avrcp ar = new Avrcp(context); 213 ar.start(); 214 return ar; 215 } 216 217 public void doQuit() { 218 mHandler.removeCallbacksAndMessages(null); 219 Looper looper = mHandler.getLooper(); 220 if (looper != null) { 221 looper.quit(); 222 } 223 mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionChangeListener); 224 } 225 226 public void cleanup() { 227 cleanupNative(); 228 if (mVolumeMapping != null) 229 mVolumeMapping.clear(); 230 } 231 232 private class MediaControllerListener extends MediaController.Callback { 233 @Override 234 public void onMetadataChanged(MediaMetadata metadata) { 235 Log.v(TAG, "MediaController metadata changed"); 236 updateMetadata(metadata); 237 } 238 239 @Override 240 public void onPlaybackStateChanged(PlaybackState state) { 241 Log.v(TAG, "MediaController playback changed: " + state.toString()); 242 updatePlaybackState(state); 243 } 244 245 @Override 246 public void onSessionDestroyed() { 247 Log.v(TAG, "MediaController session destroyed"); 248 } 249 } 250 251 private class MediaSessionChangeListener implements MediaSessionManager.OnActiveSessionsChangedListener { 252 public MediaSessionChangeListener() { 253 } 254 255 @Override 256 public void onActiveSessionsChanged(List<MediaController> controllers) { 257 Log.v(TAG, "Active sessions changed, " + controllers.size() + " sessions"); 258 if (controllers.size() > 0) { 259 updateCurrentMediaController(controllers.get(0)); 260 } 261 } 262 } 263 264 private void updateCurrentMediaController(MediaController controller) { 265 Log.v(TAG, "Updating media controller to " + controller); 266 if (mMediaController != null) { 267 mMediaController.unregisterCallback(mMediaControllerCb); 268 } 269 mMediaController = controller; 270 if (mMediaController == null) { 271 updateMetadata(null); 272 return; 273 } 274 mMediaController.registerCallback(mMediaControllerCb, mHandler); 275 updateMetadata(mMediaController.getMetadata()); 276 } 277 278 /** Handles Avrcp messages. */ 279 private final class AvrcpMessageHandler extends Handler { 280 private AvrcpMessageHandler(Looper looper) { 281 super(looper); 282 } 283 284 @Override 285 public void handleMessage(Message msg) { 286 switch (msg.what) { 287 case MESSAGE_GET_RC_FEATURES: 288 String address = (String) msg.obj; 289 if (DEBUG) Log.v(TAG, "MESSAGE_GET_RC_FEATURES: address="+address+ 290 ", features="+msg.arg1); 291 mFeatures = msg.arg1; 292 mFeatures = modifyRcFeatureFromBlacklist(mFeatures, address); 293 mAudioManager.avrcpSupportsAbsoluteVolume(address, isAbsoluteVolumeSupported()); 294 mLastLocalVolume = -1; 295 mRemoteVolume = -1; 296 mLocalVolume = -1; 297 mInitialRemoteVolume = -1; 298 mAddress = address; 299 if (mVolumeMapping != null) 300 mVolumeMapping.clear(); 301 break; 302 303 case MESSAGE_GET_PLAY_STATUS: 304 if (DEBUG) Log.v(TAG, "MESSAGE_GET_PLAY_STATUS"); 305 getPlayStatusRspNative(convertPlayStateToPlayStatus(mCurrentPlayState), 306 (int)mSongLengthMs, (int)getPlayPosition()); 307 break; 308 309 case MESSAGE_GET_ELEM_ATTRS: 310 String[] textArray; 311 int[] attrIds; 312 byte numAttr = (byte) msg.arg1; 313 ArrayList<Integer> attrList = (ArrayList<Integer>) msg.obj; 314 Log.v(TAG, "MESSAGE_GET_ELEM_ATTRS:numAttr=" + numAttr); 315 attrIds = new int[numAttr]; 316 textArray = new String[numAttr]; 317 for (int i = 0; i < numAttr; ++i) { 318 attrIds[i] = attrList.get(i).intValue(); 319 textArray[i] = mMediaAttributes.getString(attrIds[i]); 320 Log.v(TAG, "getAttributeString:attrId=" + attrIds[i] + 321 " str=" + textArray[i]); 322 } 323 getElementAttrRspNative(numAttr, attrIds, textArray); 324 break; 325 326 case MESSAGE_REGISTER_NOTIFICATION: 327 if (DEBUG) Log.v(TAG, "MESSAGE_REGISTER_NOTIFICATION:event=" + msg.arg1 + 328 " param=" + msg.arg2); 329 processRegisterNotification(msg.arg1, msg.arg2); 330 break; 331 332 case MESSAGE_PLAY_INTERVAL_TIMEOUT: 333 if (DEBUG) Log.v(TAG, "MESSAGE_PLAY_INTERVAL_TIMEOUT"); 334 sendPlayPosNotificationRsp(false); 335 break; 336 337 case MESSAGE_VOLUME_CHANGED: 338 if (!isAbsoluteVolumeSupported()) { 339 if (DEBUG) Log.v(TAG, "ignore MESSAGE_VOLUME_CHANGED"); 340 break; 341 } 342 343 if (DEBUG) Log.v(TAG, "MESSAGE_VOLUME_CHANGED: volume=" + ((byte)msg.arg1 & 0x7f) 344 + " ctype=" + msg.arg2); 345 346 347 boolean volAdj = false; 348 if (msg.arg2 == AVRC_RSP_ACCEPT || msg.arg2 == AVRC_RSP_REJ) { 349 if (mVolCmdAdjustInProgress == false && mVolCmdSetInProgress == false) { 350 Log.e(TAG, "Unsolicited response, ignored"); 351 break; 352 } 353 removeMessages(MESSAGE_ABS_VOL_TIMEOUT); 354 355 volAdj = mVolCmdAdjustInProgress; 356 mVolCmdAdjustInProgress = false; 357 mVolCmdSetInProgress = false; 358 mAbsVolRetryTimes = 0; 359 } 360 361 byte absVol = (byte)((byte)msg.arg1 & 0x7f); // discard MSB as it is RFD 362 // convert remote volume to local volume 363 int volIndex = convertToAudioStreamVolume(absVol); 364 if (mInitialRemoteVolume == -1) { 365 mInitialRemoteVolume = absVol; 366 if (mAbsVolThreshold > 0 && mAbsVolThreshold < mAudioStreamMax && volIndex > mAbsVolThreshold) { 367 if (DEBUG) Log.v(TAG, "remote inital volume too high " + volIndex + ">" + mAbsVolThreshold); 368 Message msg1 = mHandler.obtainMessage(MESSAGE_SET_ABSOLUTE_VOLUME, mAbsVolThreshold , 0); 369 mHandler.sendMessage(msg1); 370 mRemoteVolume = absVol; 371 mLocalVolume = volIndex; 372 break; 373 } 374 } 375 376 if (mLocalVolume != volIndex && (msg.arg2 == AVRC_RSP_ACCEPT || 377 msg.arg2 == AVRC_RSP_CHANGED || 378 msg.arg2 == AVRC_RSP_INTERIM)) { 379 /* If the volume has successfully changed */ 380 mLocalVolume = volIndex; 381 if (mLastLocalVolume != -1 && msg.arg2 == AVRC_RSP_ACCEPT) { 382 if (mLastLocalVolume != volIndex) { 383 /* remote volume changed more than requested due to 384 * local and remote has different volume steps */ 385 if (DEBUG) Log.d(TAG, "Remote returned volume does not match desired volume " 386 + mLastLocalVolume + " vs " 387 + volIndex); 388 mLastLocalVolume = mLocalVolume; 389 } 390 } 391 // remember the remote volume value, as it's the one supported by remote 392 if (volAdj) { 393 synchronized (mVolumeMapping) { 394 mVolumeMapping.put(volIndex, (int)absVol); 395 if (DEBUG) Log.v(TAG, "remember volume mapping " +volIndex+ "-"+absVol); 396 } 397 } 398 399 notifyVolumeChanged(mLocalVolume); 400 mRemoteVolume = absVol; 401 long pecentVolChanged = ((long)absVol * 100) / 0x7f; 402 Log.e(TAG, "percent volume changed: " + pecentVolChanged + "%"); 403 } else if (msg.arg2 == AVRC_RSP_REJ) { 404 Log.e(TAG, "setAbsoluteVolume call rejected"); 405 } else if (volAdj && mLastRemoteVolume > 0 && mLastRemoteVolume < AVRCP_MAX_VOL && 406 mLocalVolume == volIndex && 407 (msg.arg2 == AVRC_RSP_ACCEPT )) { 408 /* oops, the volume is still same, remote does not like the value 409 * retry a volume one step up/down */ 410 if (DEBUG) Log.d(TAG, "Remote device didn't tune volume, let's try one more step."); 411 int retry_volume = Math.min(AVRCP_MAX_VOL, 412 Math.max(0, mLastRemoteVolume + mLastDirection)); 413 if (setVolumeNative(retry_volume)) { 414 mLastRemoteVolume = retry_volume; 415 sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT), 416 CMD_TIMEOUT_DELAY); 417 mVolCmdAdjustInProgress = true; 418 } 419 } 420 break; 421 422 case MESSAGE_ADJUST_VOLUME: 423 if (!isAbsoluteVolumeSupported()) { 424 if (DEBUG) Log.v(TAG, "ignore MESSAGE_ADJUST_VOLUME"); 425 break; 426 } 427 428 if (DEBUG) Log.d(TAG, "MESSAGE_ADJUST_VOLUME: direction=" + msg.arg1); 429 430 if (mVolCmdAdjustInProgress || mVolCmdSetInProgress) { 431 if (DEBUG) Log.w(TAG, "There is already a volume command in progress."); 432 break; 433 } 434 435 // Remote device didn't set initial volume. Let's black list it 436 if (mInitialRemoteVolume == -1) { 437 Log.d(TAG, "remote " + mAddress + " never tell us initial volume, black list it."); 438 blackListCurrentDevice(); 439 break; 440 } 441 442 // Wait on verification on volume from device, before changing the volume. 443 if (mRemoteVolume != -1 && (msg.arg1 == -1 || msg.arg1 == 1)) { 444 int setVol = -1; 445 int targetVolIndex = -1; 446 if (mLocalVolume == 0 && msg.arg1 == -1) { 447 if (DEBUG) Log.w(TAG, "No need to Vol down from 0."); 448 break; 449 } 450 if (mLocalVolume == mAudioStreamMax && msg.arg1 == 1) { 451 if (DEBUG) Log.w(TAG, "No need to Vol up from max."); 452 break; 453 } 454 455 targetVolIndex = mLocalVolume + msg.arg1; 456 if (DEBUG) Log.d(TAG, "Adjusting volume to " + targetVolIndex); 457 458 Integer i; 459 synchronized (mVolumeMapping) { 460 i = mVolumeMapping.get(targetVolIndex); 461 } 462 463 if (i != null) { 464 /* if we already know this volume mapping, use it */ 465 setVol = i.byteValue(); 466 if (setVol == mRemoteVolume) { 467 if (DEBUG) Log.d(TAG, "got same volume from mapping for " + targetVolIndex + ", ignore."); 468 setVol = -1; 469 } 470 if (DEBUG) Log.d(TAG, "set volume from mapping " + targetVolIndex + "-" + setVol); 471 } 472 473 if (setVol == -1) { 474 /* otherwise use phone steps */ 475 setVol = Math.min(AVRCP_MAX_VOL, 476 convertToAvrcpVolume(Math.max(0, targetVolIndex))); 477 if (DEBUG) Log.d(TAG, "set volume from local volume "+ targetVolIndex+"-"+ setVol); 478 } 479 480 if (setVolumeNative(setVol)) { 481 sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT), 482 CMD_TIMEOUT_DELAY); 483 mVolCmdAdjustInProgress = true; 484 mLastDirection = msg.arg1; 485 mLastRemoteVolume = setVol; 486 mLastLocalVolume = targetVolIndex; 487 } else { 488 if (DEBUG) Log.d(TAG, "setVolumeNative failed"); 489 } 490 } else { 491 Log.e(TAG, "Unknown direction in MESSAGE_ADJUST_VOLUME"); 492 } 493 break; 494 495 case MESSAGE_SET_ABSOLUTE_VOLUME: 496 if (!isAbsoluteVolumeSupported()) { 497 if (DEBUG) Log.v(TAG, "ignore MESSAGE_SET_ABSOLUTE_VOLUME"); 498 break; 499 } 500 501 if (DEBUG) Log.v(TAG, "MESSAGE_SET_ABSOLUTE_VOLUME"); 502 503 if (mVolCmdSetInProgress || mVolCmdAdjustInProgress) { 504 if (DEBUG) Log.w(TAG, "There is already a volume command in progress."); 505 break; 506 } 507 508 // Remote device didn't set initial volume. Let's black list it 509 if (mInitialRemoteVolume == -1) { 510 if (DEBUG) Log.d(TAG, "remote " + mAddress + " never tell us initial volume, black list it."); 511 blackListCurrentDevice(); 512 break; 513 } 514 515 int avrcpVolume = convertToAvrcpVolume(msg.arg1); 516 avrcpVolume = Math.min(AVRCP_MAX_VOL, Math.max(0, avrcpVolume)); 517 if (DEBUG) Log.d(TAG, "Setting volume to " + msg.arg1 + "-" + avrcpVolume); 518 if (setVolumeNative(avrcpVolume)) { 519 sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT), CMD_TIMEOUT_DELAY); 520 mVolCmdSetInProgress = true; 521 mLastRemoteVolume = avrcpVolume; 522 mLastLocalVolume = msg.arg1; 523 } else { 524 if (DEBUG) Log.d(TAG, "setVolumeNative failed"); 525 } 526 break; 527 528 case MESSAGE_ABS_VOL_TIMEOUT: 529 if (DEBUG) Log.v(TAG, "MESSAGE_ABS_VOL_TIMEOUT: Volume change cmd timed out."); 530 mVolCmdAdjustInProgress = false; 531 mVolCmdSetInProgress = false; 532 if (mAbsVolRetryTimes >= MAX_ERROR_RETRY_TIMES) { 533 mAbsVolRetryTimes = 0; 534 /* too many volume change failures, black list the device */ 535 blackListCurrentDevice(); 536 } else { 537 mAbsVolRetryTimes += 1; 538 if (setVolumeNative(mLastRemoteVolume)) { 539 sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT), 540 CMD_TIMEOUT_DELAY); 541 mVolCmdSetInProgress = true; 542 } 543 } 544 break; 545 546 case MESSAGE_FAST_FORWARD: 547 case MESSAGE_REWIND: 548 if (msg.what == MESSAGE_FAST_FORWARD) { 549 if ((mCurrentPlayState.getActions() & 550 PlaybackState.ACTION_FAST_FORWARD) != 0) { 551 int keyState = msg.arg1 == KEY_STATE_PRESS ? 552 KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP; 553 KeyEvent keyEvent = 554 new KeyEvent(keyState, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD); 555 mMediaController.dispatchMediaButtonEvent(keyEvent); 556 break; 557 } 558 } else if ((mCurrentPlayState.getActions() & 559 PlaybackState.ACTION_REWIND) != 0) { 560 int keyState = msg.arg1 == KEY_STATE_PRESS ? 561 KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP; 562 KeyEvent keyEvent = 563 new KeyEvent(keyState, KeyEvent.KEYCODE_MEDIA_REWIND); 564 mMediaController.dispatchMediaButtonEvent(keyEvent); 565 break; 566 } 567 568 int skipAmount; 569 if (msg.what == MESSAGE_FAST_FORWARD) { 570 if (DEBUG) Log.v(TAG, "MESSAGE_FAST_FORWARD"); 571 removeMessages(MESSAGE_FAST_FORWARD); 572 skipAmount = BASE_SKIP_AMOUNT; 573 } else { 574 if (DEBUG) Log.v(TAG, "MESSAGE_REWIND"); 575 removeMessages(MESSAGE_REWIND); 576 skipAmount = -BASE_SKIP_AMOUNT; 577 } 578 579 if (hasMessages(MESSAGE_CHANGE_PLAY_POS) && 580 (skipAmount != mSkipAmount)) { 581 Log.w(TAG, "missing release button event:" + mSkipAmount); 582 } 583 584 if ((!hasMessages(MESSAGE_CHANGE_PLAY_POS)) || 585 (skipAmount != mSkipAmount)) { 586 mSkipStartTime = SystemClock.elapsedRealtime(); 587 } 588 589 removeMessages(MESSAGE_CHANGE_PLAY_POS); 590 if (msg.arg1 == KEY_STATE_PRESS) { 591 mSkipAmount = skipAmount; 592 changePositionBy(mSkipAmount * getSkipMultiplier()); 593 Message posMsg = obtainMessage(MESSAGE_CHANGE_PLAY_POS); 594 posMsg.arg1 = 1; 595 sendMessageDelayed(posMsg, SKIP_PERIOD); 596 } 597 598 break; 599 600 case MESSAGE_CHANGE_PLAY_POS: 601 if (DEBUG) Log.v(TAG, "MESSAGE_CHANGE_PLAY_POS:" + msg.arg1); 602 changePositionBy(mSkipAmount * getSkipMultiplier()); 603 if (msg.arg1 * SKIP_PERIOD < BUTTON_TIMEOUT_TIME) { 604 Message posMsg = obtainMessage(MESSAGE_CHANGE_PLAY_POS); 605 posMsg.arg1 = msg.arg1 + 1; 606 sendMessageDelayed(posMsg, SKIP_PERIOD); 607 } 608 break; 609 610 case MESSAGE_SET_A2DP_AUDIO_STATE: 611 if (DEBUG) Log.v(TAG, "MESSAGE_SET_A2DP_AUDIO_STATE:" + msg.arg1); 612 updateA2dpAudioState(msg.arg1); 613 break; 614 } 615 } 616 } 617 618 private void updateA2dpAudioState(int state) { 619 boolean isPlaying = (state == BluetoothA2dp.STATE_PLAYING); 620 if (isPlaying != isPlayingState(mCurrentPlayState)) { 621 /* if a2dp is streaming, check to make sure music is active */ 622 if (isPlaying && !mAudioManager.isMusicActive()) 623 return; 624 PlaybackState.Builder builder = new PlaybackState.Builder(); 625 if (isPlaying) { 626 builder.setState(PlaybackState.STATE_PLAYING, 627 PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1.0f); 628 } else { 629 builder.setState(PlaybackState.STATE_PAUSED, 630 PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f); 631 } 632 updatePlaybackState(builder.build()); 633 } 634 } 635 636 private void updatePlaybackState(PlaybackState state) { 637 if (DEBUG) Log.v(TAG, 638 "updatePlaybackState: old=" + mCurrentPlayState + ", new=" + state); 639 if (state == null) { 640 state = new PlaybackState.Builder().setState(PlaybackState.STATE_NONE, 641 PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f).build(); 642 } 643 644 int newPlayStatus = convertPlayStateToPlayStatus(state); 645 646 mCurrentPlayState = state; 647 mLastStateUpdate = SystemClock.elapsedRealtime(); 648 649 sendPlayPosNotificationRsp(false); 650 651 if (mPlayStatusChangedNT == NOTIFICATION_TYPE_INTERIM) { 652 mPlayStatusChangedNT = NOTIFICATION_TYPE_CHANGED; 653 registerNotificationRspPlayStatusNative(mPlayStatusChangedNT, newPlayStatus); 654 } 655 } 656 657 private void updateTransportControls(int transportControlFlags) { 658 mTransportControlFlags = transportControlFlags; 659 } 660 661 class MediaAttributes { 662 private boolean exists; 663 private String title; 664 private String artistName; 665 private String albumName; 666 private String mediaNumber; 667 private String mediaTotalNumber; 668 private String genre; 669 private String playingTimeMs; 670 671 private static final int ATTR_TITLE = 1; 672 private static final int ATTR_ARTIST_NAME = 2; 673 private static final int ATTR_ALBUM_NAME = 3; 674 private static final int ATTR_MEDIA_NUMBER = 4; 675 private static final int ATTR_MEDIA_TOTAL_NUMBER = 5; 676 private static final int ATTR_GENRE = 6; 677 private static final int ATTR_PLAYING_TIME_MS = 7; 678 679 680 public MediaAttributes(MediaMetadata data) { 681 exists = data != null; 682 if (!exists) 683 return; 684 685 artistName = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_ARTIST)); 686 albumName = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_ALBUM)); 687 mediaNumber = longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER)); 688 mediaTotalNumber = longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS)); 689 genre = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_GENRE)); 690 playingTimeMs = longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_DURATION)); 691 692 // Try harder for the title. 693 title = data.getString(MediaMetadata.METADATA_KEY_TITLE); 694 695 if (title == null) { 696 MediaDescription desc = data.getDescription(); 697 if (desc != null) { 698 CharSequence val = desc.getDescription(); 699 if (val != null) 700 title = val.toString(); 701 } 702 } 703 704 if (title == null) 705 title = new String(); 706 } 707 708 public boolean equals(MediaAttributes other) { 709 if (other == null) 710 return false; 711 712 if (exists != other.exists) 713 return false; 714 715 if (exists == false) 716 return true; 717 718 return (title.equals(other.title)) && 719 (artistName.equals(other.artistName)) && 720 (albumName.equals(other.albumName)) && 721 (mediaNumber.equals(other.mediaNumber)) && 722 (mediaTotalNumber.equals(other.mediaTotalNumber)) && 723 (genre.equals(other.genre)) && 724 (playingTimeMs.equals(other.playingTimeMs)); 725 } 726 727 public String getString(int attrId) { 728 if (!exists) 729 return new String(); 730 731 switch (attrId) { 732 case ATTR_TITLE: 733 return title; 734 case ATTR_ARTIST_NAME: 735 return artistName; 736 case ATTR_ALBUM_NAME: 737 return albumName; 738 case ATTR_MEDIA_NUMBER: 739 return mediaNumber; 740 case ATTR_MEDIA_TOTAL_NUMBER: 741 return mediaTotalNumber; 742 case ATTR_GENRE: 743 return genre; 744 case ATTR_PLAYING_TIME_MS: 745 return playingTimeMs; 746 default: 747 return new String(); 748 } 749 } 750 751 private String stringOrBlank(String s) { 752 return s == null ? new String() : s; 753 } 754 755 private String longStringOrBlank(Long s) { 756 return s == null ? new String() : s.toString(); 757 } 758 759 public String toString() { 760 if (!exists) 761 return "[MediaAttributes: none]"; 762 763 return "[MediaAttributes: " + title + " - " + albumName + " by " 764 + artistName + " (" + mediaNumber + "/" + mediaTotalNumber + ") " 765 + genre + "]"; 766 } 767 } 768 769 private void updateMetadata(MediaMetadata data) { 770 MediaAttributes oldAttributes = mMediaAttributes; 771 mMediaAttributes = new MediaAttributes(data); 772 if (data == null) { 773 mSongLengthMs = 0L; 774 } else { 775 mSongLengthMs = data.getLong(MediaMetadata.METADATA_KEY_DURATION); 776 } 777 if (!oldAttributes.equals(mMediaAttributes)) { 778 Log.v(TAG, "MediaAttributes Changed to " + mMediaAttributes.toString()); 779 mTrackNumber++; 780 781 // Update the play state, which sends play state and play position 782 // notifications if needed. 783 if (mMediaController != null) { 784 updatePlaybackState(mMediaController.getPlaybackState()); 785 } else { 786 updatePlaybackState(null); 787 } 788 789 if (mTrackChangedNT == NOTIFICATION_TYPE_INTERIM) { 790 mTrackChangedNT = NOTIFICATION_TYPE_CHANGED; 791 sendTrackChangedRsp(); 792 } 793 } else { 794 Log.v(TAG, "Updated " + mMediaAttributes.toString() + " but no change!"); 795 } 796 797 } 798 799 private void getRcFeatures(byte[] address, int features) { 800 Message msg = mHandler.obtainMessage(MESSAGE_GET_RC_FEATURES, features, 0, 801 Utils.getAddressStringFromByte(address)); 802 mHandler.sendMessage(msg); 803 } 804 805 private void getPlayStatus() { 806 Message msg = mHandler.obtainMessage(MESSAGE_GET_PLAY_STATUS); 807 mHandler.sendMessage(msg); 808 } 809 810 private void getElementAttr(byte numAttr, int[] attrs) { 811 int i; 812 ArrayList<Integer> attrList = new ArrayList<Integer>(); 813 for (i = 0; i < numAttr; ++i) { 814 attrList.add(attrs[i]); 815 } 816 Message msg = mHandler.obtainMessage(MESSAGE_GET_ELEM_ATTRS, numAttr, 0, attrList); 817 mHandler.sendMessage(msg); 818 } 819 820 private void registerNotification(int eventId, int param) { 821 Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_NOTIFICATION, eventId, param); 822 mHandler.sendMessage(msg); 823 } 824 825 private void processRegisterNotification(int eventId, int param) { 826 switch (eventId) { 827 case EVT_PLAY_STATUS_CHANGED: 828 mPlayStatusChangedNT = NOTIFICATION_TYPE_INTERIM; 829 registerNotificationRspPlayStatusNative(mPlayStatusChangedNT, 830 convertPlayStateToPlayStatus(mCurrentPlayState)); 831 break; 832 833 case EVT_TRACK_CHANGED: 834 mTrackChangedNT = NOTIFICATION_TYPE_INTERIM; 835 sendTrackChangedRsp(); 836 break; 837 838 case EVT_PLAY_POS_CHANGED: 839 mPlayPosChangedNT = NOTIFICATION_TYPE_INTERIM; 840 sendPlayPosNotificationRsp(true); 841 mPlaybackIntervalMs = (long)param * 1000L; 842 break; 843 844 } 845 } 846 847 private void handlePassthroughCmd(int id, int keyState) { 848 switch (id) { 849 case BluetoothAvrcp.PASSTHROUGH_ID_REWIND: 850 rewind(keyState); 851 break; 852 case BluetoothAvrcp.PASSTHROUGH_ID_FAST_FOR: 853 fastForward(keyState); 854 break; 855 } 856 } 857 858 private void fastForward(int keyState) { 859 Message msg = mHandler.obtainMessage(MESSAGE_FAST_FORWARD, keyState, 0); 860 mHandler.sendMessage(msg); 861 } 862 863 private void rewind(int keyState) { 864 Message msg = mHandler.obtainMessage(MESSAGE_REWIND, keyState, 0); 865 mHandler.sendMessage(msg); 866 } 867 868 private void changePositionBy(long amount) { 869 long currentPosMs = getPlayPosition(); 870 if (currentPosMs == -1L) return; 871 long newPosMs = Math.max(0L, currentPosMs + amount); 872 mMediaController.getTransportControls().seekTo(newPosMs); 873 } 874 875 private int getSkipMultiplier() { 876 long currentTime = SystemClock.elapsedRealtime(); 877 long multi = (long) Math.pow(2, (currentTime - mSkipStartTime)/SKIP_DOUBLE_INTERVAL); 878 return (int) Math.min(MAX_MULTIPLIER_VALUE, multi); 879 } 880 881 private void sendTrackChangedRsp() { 882 byte[] track = new byte[TRACK_ID_SIZE]; 883 884 /* If no track is currently selected, then return 885 0xFFFFFFFFFFFFFFFF in the interim response */ 886 long trackNumberRsp = -1L; 887 888 if (isPlayingState(mCurrentPlayState)) { 889 trackNumberRsp = mTrackNumber; 890 } 891 892 /* track is stored in big endian format */ 893 for (int i = 0; i < TRACK_ID_SIZE; ++i) { 894 track[i] = (byte) (trackNumberRsp >> (56 - 8 * i)); 895 } 896 registerNotificationRspTrackChangeNative(mTrackChangedNT, track); 897 } 898 899 private long getPlayPosition() { 900 if (mCurrentPlayState == null) 901 return -1L; 902 903 if (mCurrentPlayState.getPosition() == PlaybackState.PLAYBACK_POSITION_UNKNOWN) 904 return -1L; 905 906 if (isPlayingState(mCurrentPlayState)) { 907 return SystemClock.elapsedRealtime() - mLastStateUpdate + mCurrentPlayState.getPosition(); 908 } 909 910 return mCurrentPlayState.getPosition(); 911 } 912 913 private int convertPlayStateToPlayStatus(PlaybackState state) { 914 int playStatus = PLAYSTATUS_ERROR; 915 switch (state.getState()) { 916 case PlaybackState.STATE_PLAYING: 917 case PlaybackState.STATE_BUFFERING: 918 playStatus = PLAYSTATUS_PLAYING; 919 break; 920 921 case PlaybackState.STATE_STOPPED: 922 case PlaybackState.STATE_NONE: 923 playStatus = PLAYSTATUS_STOPPED; 924 break; 925 926 case PlaybackState.STATE_PAUSED: 927 playStatus = PLAYSTATUS_PAUSED; 928 break; 929 930 case PlaybackState.STATE_FAST_FORWARDING: 931 case PlaybackState.STATE_SKIPPING_TO_NEXT: 932 case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM: 933 playStatus = PLAYSTATUS_FWD_SEEK; 934 break; 935 936 case PlaybackState.STATE_REWINDING: 937 case PlaybackState.STATE_SKIPPING_TO_PREVIOUS: 938 playStatus = PLAYSTATUS_REV_SEEK; 939 break; 940 941 case PlaybackState.STATE_ERROR: 942 playStatus = PLAYSTATUS_ERROR; 943 break; 944 945 } 946 return playStatus; 947 } 948 949 private boolean isPlayingState(PlaybackState state) { 950 return (state.getState() == PlaybackState.STATE_PLAYING) || 951 (state.getState() == PlaybackState.STATE_BUFFERING); 952 } 953 954 /** 955 * Sends a play position notification, or schedules one to be 956 * sent later at an appropriate time. If |requested| is true, 957 * does both because this was called in reponse to a request from the 958 * TG. 959 */ 960 private void sendPlayPosNotificationRsp(boolean requested) { 961 long playPositionMs = getPlayPosition(); 962 963 // mNextPosMs is set to -1 when the previous position was invalid 964 // so this will be true if the new position is valid & old was invalid. 965 // mPlayPositionMs is set to -1 when the new position is invalid, 966 // and the old mPrevPosMs is >= 0 so this is true when the new is invalid 967 // and the old was valid. 968 if (requested || ((mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM) && 969 ((playPositionMs >= mNextPosMs) || (playPositionMs <= mPrevPosMs)))) { 970 if (!requested) mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED; 971 registerNotificationRspPlayPosNative(mPlayStatusChangedNT, (int)playPositionMs); 972 if (playPositionMs != PlaybackState.PLAYBACK_POSITION_UNKNOWN) { 973 mNextPosMs = playPositionMs + mPlaybackIntervalMs; 974 mPrevPosMs = playPositionMs - mPlaybackIntervalMs; 975 } else { 976 mNextPosMs = -1; 977 mPrevPosMs = -1; 978 } 979 } 980 981 mHandler.removeMessages(MESSAGE_PLAY_INTERVAL_TIMEOUT); 982 if (mPlayStatusChangedNT == NOTIFICATION_TYPE_INTERIM) { 983 Message msg = mHandler.obtainMessage(MESSAGE_PLAY_INTERVAL_TIMEOUT); 984 long delay = mPlaybackIntervalMs; 985 if (mNextPosMs != -1) { 986 delay = mNextPosMs - (playPositionMs > 0 ? playPositionMs : 0); 987 } 988 mHandler.sendMessageDelayed(msg, delay); 989 } 990 } 991 992 /** 993 * This is called from AudioService. It will return whether this device supports abs volume. 994 * NOT USED AT THE MOMENT. 995 */ 996 public boolean isAbsoluteVolumeSupported() { 997 return ((mFeatures & BTRC_FEAT_ABSOLUTE_VOLUME) != 0); 998 } 999 1000 /** 1001 * We get this call from AudioService. This will send a message to our handler object, 1002 * requesting our handler to call setVolumeNative() 1003 */ 1004 public void adjustVolume(int direction) { 1005 Message msg = mHandler.obtainMessage(MESSAGE_ADJUST_VOLUME, direction, 0); 1006 mHandler.sendMessage(msg); 1007 } 1008 1009 public void setAbsoluteVolume(int volume) { 1010 if (volume == mLocalVolume) { 1011 if (DEBUG) Log.v(TAG, "setAbsoluteVolume is setting same index, ignore "+volume); 1012 return; 1013 } 1014 1015 mHandler.removeMessages(MESSAGE_ADJUST_VOLUME); 1016 Message msg = mHandler.obtainMessage(MESSAGE_SET_ABSOLUTE_VOLUME, volume, 0); 1017 mHandler.sendMessage(msg); 1018 } 1019 1020 /* Called in the native layer as a btrc_callback to return the volume set on the carkit in the 1021 * case when the volume is change locally on the carkit. This notification is not called when 1022 * the volume is changed from the phone. 1023 * 1024 * This method will send a message to our handler to change the local stored volume and notify 1025 * AudioService to update the UI 1026 */ 1027 private void volumeChangeCallback(int volume, int ctype) { 1028 Message msg = mHandler.obtainMessage(MESSAGE_VOLUME_CHANGED, volume, ctype); 1029 mHandler.sendMessage(msg); 1030 } 1031 1032 private void notifyVolumeChanged(int volume) { 1033 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 1034 AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME); 1035 } 1036 1037 private int convertToAudioStreamVolume(int volume) { 1038 // Rescale volume to match AudioSystem's volume 1039 return (int) Math.floor((double) volume*mAudioStreamMax/AVRCP_MAX_VOL); 1040 } 1041 1042 private int convertToAvrcpVolume(int volume) { 1043 return (int) Math.ceil((double) volume*AVRCP_MAX_VOL/mAudioStreamMax); 1044 } 1045 1046 private void blackListCurrentDevice() { 1047 mFeatures &= ~BTRC_FEAT_ABSOLUTE_VOLUME; 1048 mAudioManager.avrcpSupportsAbsoluteVolume(mAddress, isAbsoluteVolumeSupported()); 1049 1050 SharedPreferences pref = mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST, 1051 Context.MODE_PRIVATE); 1052 SharedPreferences.Editor editor = pref.edit(); 1053 editor.putBoolean(mAddress, true); 1054 editor.commit(); 1055 } 1056 1057 private int modifyRcFeatureFromBlacklist(int feature, String address) { 1058 SharedPreferences pref = mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST, 1059 Context.MODE_PRIVATE); 1060 if (!pref.contains(address)) { 1061 return feature; 1062 } 1063 if (pref.getBoolean(address, false)) { 1064 feature &= ~BTRC_FEAT_ABSOLUTE_VOLUME; 1065 } 1066 return feature; 1067 } 1068 1069 public void resetBlackList(String address) { 1070 SharedPreferences pref = mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST, 1071 Context.MODE_PRIVATE); 1072 SharedPreferences.Editor editor = pref.edit(); 1073 editor.remove(address); 1074 editor.commit(); 1075 } 1076 1077 /** 1078 * This is called from A2dpStateMachine to set A2dp audio state. 1079 */ 1080 public void setA2dpAudioState(int state) { 1081 Message msg = mHandler.obtainMessage(MESSAGE_SET_A2DP_AUDIO_STATE, state, 0); 1082 mHandler.sendMessage(msg); 1083 } 1084 1085 public void dump(StringBuilder sb) { 1086 sb.append("AVRCP:\n"); 1087 ProfileService.println(sb, "mMediaAttributes: " + mMediaAttributes); 1088 ProfileService.println(sb, "mTransportControlFlags: " + mTransportControlFlags); 1089 ProfileService.println(sb, "mCurrentPlayState: " + mCurrentPlayState); 1090 ProfileService.println(sb, "mLastStateUpdate: " + mLastStateUpdate); 1091 ProfileService.println(sb, "mPlayStatusChangedNT: " + mPlayStatusChangedNT); 1092 ProfileService.println(sb, "mTrackChangedNT: " + mTrackChangedNT); 1093 ProfileService.println(sb, "mTrackNumber: " + mTrackNumber); 1094 ProfileService.println(sb, "mSongLengthMs: " + mSongLengthMs); 1095 ProfileService.println(sb, "mPlaybackIntervalMs: " + mPlaybackIntervalMs); 1096 ProfileService.println(sb, "mPlayPosChangedNT: " + mPlayPosChangedNT); 1097 ProfileService.println(sb, "mNextPosMs: " + mNextPosMs); 1098 ProfileService.println(sb, "mPrevPosMs: " + mPrevPosMs); 1099 ProfileService.println(sb, "mSkipStartTime: " + mSkipStartTime); 1100 ProfileService.println(sb, "mFeatures: " + mFeatures); 1101 ProfileService.println(sb, "mRemoteVolume: " + mRemoteVolume); 1102 ProfileService.println(sb, "mLastRemoteVolume: " + mLastRemoteVolume); 1103 ProfileService.println(sb, "mLastDirection: " + mLastDirection); 1104 ProfileService.println(sb, "mVolumeStep: " + mVolumeStep); 1105 ProfileService.println(sb, "mAudioStreamMax: " + mAudioStreamMax); 1106 ProfileService.println(sb, "mVolCmdAdjustInProgress: " + mVolCmdAdjustInProgress); 1107 ProfileService.println(sb, "mVolCmdSetInProgress: " + mVolCmdSetInProgress); 1108 ProfileService.println(sb, "mAbsVolRetryTimes: " + mAbsVolRetryTimes); 1109 ProfileService.println(sb, "mSkipAmount: " + mSkipAmount); 1110 ProfileService.println(sb, "mVolumeMapping: " + mVolumeMapping.toString()); 1111 if (mMediaController != null) 1112 ProfileService.println(sb, "mMediaSession pkg: " + mMediaController.getPackageName()); 1113 } 1114 1115 // Do not modify without updating the HAL bt_rc.h files. 1116 1117 // match up with btrc_play_status_t enum of bt_rc.h 1118 final static int PLAYSTATUS_STOPPED = 0; 1119 final static int PLAYSTATUS_PLAYING = 1; 1120 final static int PLAYSTATUS_PAUSED = 2; 1121 final static int PLAYSTATUS_FWD_SEEK = 3; 1122 final static int PLAYSTATUS_REV_SEEK = 4; 1123 final static int PLAYSTATUS_ERROR = 255; 1124 1125 // match up with btrc_media_attr_t enum of bt_rc.h 1126 final static int MEDIA_ATTR_TITLE = 1; 1127 final static int MEDIA_ATTR_ARTIST = 2; 1128 final static int MEDIA_ATTR_ALBUM = 3; 1129 final static int MEDIA_ATTR_TRACK_NUM = 4; 1130 final static int MEDIA_ATTR_NUM_TRACKS = 5; 1131 final static int MEDIA_ATTR_GENRE = 6; 1132 final static int MEDIA_ATTR_PLAYING_TIME = 7; 1133 1134 // match up with btrc_event_id_t enum of bt_rc.h 1135 final static int EVT_PLAY_STATUS_CHANGED = 1; 1136 final static int EVT_TRACK_CHANGED = 2; 1137 final static int EVT_TRACK_REACHED_END = 3; 1138 final static int EVT_TRACK_REACHED_START = 4; 1139 final static int EVT_PLAY_POS_CHANGED = 5; 1140 final static int EVT_BATT_STATUS_CHANGED = 6; 1141 final static int EVT_SYSTEM_STATUS_CHANGED = 7; 1142 final static int EVT_APP_SETTINGS_CHANGED = 8; 1143 1144 // match up with btrc_notification_type_t enum of bt_rc.h 1145 final static int NOTIFICATION_TYPE_INTERIM = 0; 1146 final static int NOTIFICATION_TYPE_CHANGED = 1; 1147 1148 // match up with BTRC_UID_SIZE of bt_rc.h 1149 final static int TRACK_ID_SIZE = 8; 1150 1151 private native static void classInitNative(); 1152 private native void initNative(); 1153 private native void cleanupNative(); 1154 private native boolean getPlayStatusRspNative(int playStatus, int songLen, int songPos); 1155 private native boolean getElementAttrRspNative(byte numAttr, int[] attrIds, String[] textArray); 1156 private native boolean registerNotificationRspPlayStatusNative(int type, int playStatus); 1157 private native boolean registerNotificationRspTrackChangeNative(int type, byte[] track); 1158 private native boolean registerNotificationRspPlayPosNative(int type, int playPos); 1159 private native boolean setVolumeNative(int volume); 1160 private native boolean sendPassThroughCommandNative(int keyCode, int keyState); 1161 1162} 1163