Avrcp.java revision 0e949676c5097e1f259caa2e549ecedf0c09269d
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.MediaMetadataRetriever; 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 Metadata mMetadata; 79 private int mTransportControlFlags; 80 private PlaybackState mCurrentPlayState; 81 private int mPlayStatusChangedNT; 82 private int mTrackChangedNT; 83 private long mTrackNumber; 84 private long mCurrentPosMs; 85 private long mPlayStartTimeMs; 86 private long mSongLengthMs; 87 private long mPlaybackIntervalMs; 88 private int mPlayPosChangedNT; 89 private long mNextPosMs; 90 private long mPrevPosMs; 91 private long mSkipStartTime; 92 private int mFeatures; 93 private int mRemoteVolume; 94 private int mLastRemoteVolume; 95 private int mInitialRemoteVolume; 96 97 /* Local volume in audio index 0-15 */ 98 private int mLocalVolume; 99 private int mLastLocalVolume; 100 private int mAbsVolThreshold; 101 102 private String mAddress; 103 private HashMap<Integer, Integer> mVolumeMapping; 104 105 private int mLastDirection; 106 private final int mVolumeStep; 107 private final int mAudioStreamMax; 108 private boolean mVolCmdAdjustInProgress; 109 private boolean mVolCmdSetInProgress; 110 private int mAbsVolRetryTimes; 111 private int mSkipAmount; 112 113 /* BTRC features */ 114 public static final int BTRC_FEAT_METADATA = 0x01; 115 public static final int BTRC_FEAT_ABSOLUTE_VOLUME = 0x02; 116 public static final int BTRC_FEAT_BROWSE = 0x04; 117 118 /* AVRC response codes, from avrc_defs */ 119 private static final int AVRC_RSP_NOT_IMPL = 8; 120 private static final int AVRC_RSP_ACCEPT = 9; 121 private static final int AVRC_RSP_REJ = 10; 122 private static final int AVRC_RSP_IN_TRANS = 11; 123 private static final int AVRC_RSP_IMPL_STBL = 12; 124 private static final int AVRC_RSP_CHANGED = 13; 125 private static final int AVRC_RSP_INTERIM = 15; 126 127 private static final int MESSAGE_GET_RC_FEATURES = 1; 128 private static final int MESSAGE_GET_PLAY_STATUS = 2; 129 private static final int MESSAGE_GET_ELEM_ATTRS = 3; 130 private static final int MESSAGE_REGISTER_NOTIFICATION = 4; 131 private static final int MESSAGE_PLAY_INTERVAL_TIMEOUT = 5; 132 private static final int MESSAGE_VOLUME_CHANGED = 6; 133 private static final int MESSAGE_ADJUST_VOLUME = 7; 134 private static final int MESSAGE_SET_ABSOLUTE_VOLUME = 8; 135 private static final int MESSAGE_ABS_VOL_TIMEOUT = 9; 136 private static final int MESSAGE_FAST_FORWARD = 10; 137 private static final int MESSAGE_REWIND = 11; 138 private static final int MESSAGE_CHANGE_PLAY_POS = 12; 139 private static final int MESSAGE_SET_A2DP_AUDIO_STATE = 13; 140 141 private static final int BUTTON_TIMEOUT_TIME = 2000; 142 private static final int BASE_SKIP_AMOUNT = 2000; 143 private static final int KEY_STATE_PRESS = 1; 144 private static final int KEY_STATE_RELEASE = 0; 145 private static final int SKIP_PERIOD = 400; 146 private static final int SKIP_DOUBLE_INTERVAL = 3000; 147 private static final long MAX_MULTIPLIER_VALUE = 128L; 148 private static final int CMD_TIMEOUT_DELAY = 2000; 149 private static final int MAX_ERROR_RETRY_TIMES = 3; 150 private static final int AVRCP_MAX_VOL = 127; 151 private static final int AVRCP_BASE_VOLUME_STEP = 1; 152 153 static { 154 classInitNative(); 155 } 156 157 private Avrcp(Context context) { 158 mMetadata = new Metadata(); 159 mCurrentPlayState = new PlaybackState.Builder().setState(PlaybackState.STATE_NONE, -1L, 0.0f).build(); 160 mPlayStatusChangedNT = NOTIFICATION_TYPE_CHANGED; 161 mTrackChangedNT = NOTIFICATION_TYPE_CHANGED; 162 mTrackNumber = -1L; 163 mCurrentPosMs = PlaybackState.PLAYBACK_POSITION_UNKNOWN; 164 mPlayStartTimeMs = -1L; 165 mSongLengthMs = 0L; 166 mPlaybackIntervalMs = 0L; 167 mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED; 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 updatePlayPauseState(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 + " from " + 266 controller.getPackageName()); 267 if (mMediaController != null) { 268 mMediaController.unregisterCallback(mMediaControllerCb); 269 } 270 mMediaController = controller; 271 if (mMediaController == null) { 272 updateMetadata(null); 273 updatePlayPauseState(null); 274 return; 275 } 276 mMediaController.registerCallback(mMediaControllerCb, mHandler); 277 updateMetadata(mMediaController.getMetadata()); 278 updatePlayPauseState(mMediaController.getPlaybackState()); 279 } 280 281 /** Handles Avrcp messages. */ 282 private final class AvrcpMessageHandler extends Handler { 283 private AvrcpMessageHandler(Looper looper) { 284 super(looper); 285 } 286 287 @Override 288 public void handleMessage(Message msg) { 289 switch (msg.what) { 290 case MESSAGE_GET_RC_FEATURES: 291 String address = (String) msg.obj; 292 if (DEBUG) Log.v(TAG, "MESSAGE_GET_RC_FEATURES: address="+address+ 293 ", features="+msg.arg1); 294 mFeatures = msg.arg1; 295 mFeatures = modifyRcFeatureFromBlacklist(mFeatures, address); 296 mAudioManager.avrcpSupportsAbsoluteVolume(address, isAbsoluteVolumeSupported()); 297 mLastLocalVolume = -1; 298 mRemoteVolume = -1; 299 mLocalVolume = -1; 300 mInitialRemoteVolume = -1; 301 mAddress = address; 302 if (mVolumeMapping != null) 303 mVolumeMapping.clear(); 304 break; 305 306 case MESSAGE_GET_PLAY_STATUS: 307 if (DEBUG) Log.v(TAG, "MESSAGE_GET_PLAY_STATUS"); 308 getPlayStatusRspNative(convertPlayStateToPlayStatus(mCurrentPlayState), 309 (int)mSongLengthMs, (int)getPlayPosition()); 310 break; 311 312 case MESSAGE_GET_ELEM_ATTRS: 313 String[] textArray; 314 int[] attrIds; 315 byte numAttr = (byte) msg.arg1; 316 ArrayList<Integer> attrList = (ArrayList<Integer>) msg.obj; 317 Log.v(TAG, "MESSAGE_GET_ELEM_ATTRS:numAttr=" + numAttr); 318 attrIds = new int[numAttr]; 319 textArray = new String[numAttr]; 320 for (int i = 0; i < numAttr; ++i) { 321 attrIds[i] = attrList.get(i).intValue(); 322 textArray[i] = getAttributeString(attrIds[i]); 323 } 324 getElementAttrRspNative(numAttr, attrIds, textArray); 325 break; 326 327 case MESSAGE_REGISTER_NOTIFICATION: 328 if (DEBUG) Log.v(TAG, "MESSAGE_REGISTER_NOTIFICATION:event=" + msg.arg1 + 329 " param=" + msg.arg2); 330 processRegisterNotification(msg.arg1, msg.arg2); 331 break; 332 333 case MESSAGE_PLAY_INTERVAL_TIMEOUT: 334 if (DEBUG) Log.v(TAG, "MESSAGE_PLAY_INTERVAL_TIMEOUT"); 335 mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED; 336 registerNotificationRspPlayPosNative(mPlayPosChangedNT, (int)getPlayPosition()); 337 break; 338 339 case MESSAGE_VOLUME_CHANGED: 340 if (!isAbsoluteVolumeSupported()) { 341 if (DEBUG) Log.v(TAG, "ignore MESSAGE_VOLUME_CHANGED"); 342 break; 343 } 344 345 if (DEBUG) Log.v(TAG, "MESSAGE_VOLUME_CHANGED: volume=" + ((byte)msg.arg1 & 0x7f) 346 + " ctype=" + msg.arg2); 347 348 349 boolean volAdj = false; 350 if (msg.arg2 == AVRC_RSP_ACCEPT || msg.arg2 == AVRC_RSP_REJ) { 351 if (mVolCmdAdjustInProgress == false && mVolCmdSetInProgress == false) { 352 Log.e(TAG, "Unsolicited response, ignored"); 353 break; 354 } 355 removeMessages(MESSAGE_ABS_VOL_TIMEOUT); 356 357 volAdj = mVolCmdAdjustInProgress; 358 mVolCmdAdjustInProgress = false; 359 mVolCmdSetInProgress = false; 360 mAbsVolRetryTimes = 0; 361 } 362 363 byte absVol = (byte)((byte)msg.arg1 & 0x7f); // discard MSB as it is RFD 364 // convert remote volume to local volume 365 int volIndex = convertToAudioStreamVolume(absVol); 366 if (mInitialRemoteVolume == -1) { 367 mInitialRemoteVolume = absVol; 368 if (mAbsVolThreshold > 0 && mAbsVolThreshold < mAudioStreamMax && volIndex > mAbsVolThreshold) { 369 if (DEBUG) Log.v(TAG, "remote inital volume too high " + volIndex + ">" + mAbsVolThreshold); 370 Message msg1 = mHandler.obtainMessage(MESSAGE_SET_ABSOLUTE_VOLUME, mAbsVolThreshold , 0); 371 mHandler.sendMessage(msg1); 372 mRemoteVolume = absVol; 373 mLocalVolume = volIndex; 374 break; 375 } 376 } 377 378 if (mLocalVolume != volIndex && (msg.arg2 == AVRC_RSP_ACCEPT || 379 msg.arg2 == AVRC_RSP_CHANGED || 380 msg.arg2 == AVRC_RSP_INTERIM)) { 381 /* If the volume has successfully changed */ 382 mLocalVolume = volIndex; 383 if (mLastLocalVolume != -1 && msg.arg2 == AVRC_RSP_ACCEPT) { 384 if (mLastLocalVolume != volIndex) { 385 /* remote volume changed more than requested due to 386 * local and remote has different volume steps */ 387 if (DEBUG) Log.d(TAG, "Remote returned volume does not match desired volume " 388 + mLastLocalVolume + " vs " 389 + volIndex); 390 mLastLocalVolume = mLocalVolume; 391 } 392 } 393 // remember the remote volume value, as it's the one supported by remote 394 if (volAdj) { 395 synchronized (mVolumeMapping) { 396 mVolumeMapping.put(volIndex, (int)absVol); 397 if (DEBUG) Log.v(TAG, "remember volume mapping " +volIndex+ "-"+absVol); 398 } 399 } 400 401 notifyVolumeChanged(mLocalVolume); 402 mRemoteVolume = absVol; 403 long pecentVolChanged = ((long)absVol * 100) / 0x7f; 404 Log.e(TAG, "percent volume changed: " + pecentVolChanged + "%"); 405 } else if (msg.arg2 == AVRC_RSP_REJ) { 406 Log.e(TAG, "setAbsoluteVolume call rejected"); 407 } else if (volAdj && mLastRemoteVolume > 0 && mLastRemoteVolume < AVRCP_MAX_VOL && 408 mLocalVolume == volIndex && 409 (msg.arg2 == AVRC_RSP_ACCEPT )) { 410 /* oops, the volume is still same, remote does not like the value 411 * retry a volume one step up/down */ 412 if (DEBUG) Log.d(TAG, "Remote device didn't tune volume, let's try one more step."); 413 int retry_volume = Math.min(AVRCP_MAX_VOL, 414 Math.max(0, mLastRemoteVolume + mLastDirection)); 415 if (setVolumeNative(retry_volume)) { 416 mLastRemoteVolume = retry_volume; 417 sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT), 418 CMD_TIMEOUT_DELAY); 419 mVolCmdAdjustInProgress = true; 420 } 421 } 422 break; 423 424 case MESSAGE_ADJUST_VOLUME: 425 if (!isAbsoluteVolumeSupported()) { 426 if (DEBUG) Log.v(TAG, "ignore MESSAGE_ADJUST_VOLUME"); 427 break; 428 } 429 430 if (DEBUG) Log.d(TAG, "MESSAGE_ADJUST_VOLUME: direction=" + msg.arg1); 431 432 if (mVolCmdAdjustInProgress || mVolCmdSetInProgress) { 433 if (DEBUG) Log.w(TAG, "There is already a volume command in progress."); 434 break; 435 } 436 437 // Remote device didn't set initial volume. Let's black list it 438 if (mInitialRemoteVolume == -1) { 439 Log.d(TAG, "remote " + mAddress + " never tell us initial volume, black list it."); 440 blackListCurrentDevice(); 441 break; 442 } 443 444 // Wait on verification on volume from device, before changing the volume. 445 if (mRemoteVolume != -1 && (msg.arg1 == -1 || msg.arg1 == 1)) { 446 int setVol = -1; 447 int targetVolIndex = -1; 448 if (mLocalVolume == 0 && msg.arg1 == -1) { 449 if (DEBUG) Log.w(TAG, "No need to Vol down from 0."); 450 break; 451 } 452 if (mLocalVolume == mAudioStreamMax && msg.arg1 == 1) { 453 if (DEBUG) Log.w(TAG, "No need to Vol up from max."); 454 break; 455 } 456 457 targetVolIndex = mLocalVolume + msg.arg1; 458 if (DEBUG) Log.d(TAG, "Adjusting volume to " + targetVolIndex); 459 460 Integer i; 461 synchronized (mVolumeMapping) { 462 i = mVolumeMapping.get(targetVolIndex); 463 } 464 465 if (i != null) { 466 /* if we already know this volume mapping, use it */ 467 setVol = i.byteValue(); 468 if (setVol == mRemoteVolume) { 469 if (DEBUG) Log.d(TAG, "got same volume from mapping for " + targetVolIndex + ", ignore."); 470 setVol = -1; 471 } 472 if (DEBUG) Log.d(TAG, "set volume from mapping " + targetVolIndex + "-" + setVol); 473 } 474 475 if (setVol == -1) { 476 /* otherwise use phone steps */ 477 setVol = Math.min(AVRCP_MAX_VOL, 478 convertToAvrcpVolume(Math.max(0, targetVolIndex))); 479 if (DEBUG) Log.d(TAG, "set volume from local volume "+ targetVolIndex+"-"+ setVol); 480 } 481 482 if (setVolumeNative(setVol)) { 483 sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT), 484 CMD_TIMEOUT_DELAY); 485 mVolCmdAdjustInProgress = true; 486 mLastDirection = msg.arg1; 487 mLastRemoteVolume = setVol; 488 mLastLocalVolume = targetVolIndex; 489 } else { 490 if (DEBUG) Log.d(TAG, "setVolumeNative failed"); 491 } 492 } else { 493 Log.e(TAG, "Unknown direction in MESSAGE_ADJUST_VOLUME"); 494 } 495 break; 496 497 case MESSAGE_SET_ABSOLUTE_VOLUME: 498 if (!isAbsoluteVolumeSupported()) { 499 if (DEBUG) Log.v(TAG, "ignore MESSAGE_SET_ABSOLUTE_VOLUME"); 500 break; 501 } 502 503 if (DEBUG) Log.v(TAG, "MESSAGE_SET_ABSOLUTE_VOLUME"); 504 505 if (mVolCmdSetInProgress || mVolCmdAdjustInProgress) { 506 if (DEBUG) Log.w(TAG, "There is already a volume command in progress."); 507 break; 508 } 509 510 // Remote device didn't set initial volume. Let's black list it 511 if (mInitialRemoteVolume == -1) { 512 if (DEBUG) Log.d(TAG, "remote " + mAddress + " never tell us initial volume, black list it."); 513 blackListCurrentDevice(); 514 break; 515 } 516 517 int avrcpVolume = convertToAvrcpVolume(msg.arg1); 518 avrcpVolume = Math.min(AVRCP_MAX_VOL, Math.max(0, avrcpVolume)); 519 if (DEBUG) Log.d(TAG, "Setting volume to " + msg.arg1+"-"+avrcpVolume); 520 if (setVolumeNative(avrcpVolume)) { 521 sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT), CMD_TIMEOUT_DELAY); 522 mVolCmdSetInProgress = true; 523 mLastRemoteVolume = avrcpVolume; 524 mLastLocalVolume = msg.arg1; 525 } else { 526 if (DEBUG) Log.d(TAG, "setVolumeNative failed"); 527 } 528 break; 529 530 case MESSAGE_ABS_VOL_TIMEOUT: 531 if (DEBUG) Log.v(TAG, "MESSAGE_ABS_VOL_TIMEOUT: Volume change cmd timed out."); 532 mVolCmdAdjustInProgress = false; 533 mVolCmdSetInProgress = false; 534 if (mAbsVolRetryTimes >= MAX_ERROR_RETRY_TIMES) { 535 mAbsVolRetryTimes = 0; 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 updatePlayPauseState(builder.build()); 633 } 634 } 635 636 private void updatePlayPauseState(PlaybackState state) { 637 if (DEBUG) Log.v(TAG, 638 "updatePlayPauseState: old=" + mCurrentPlayState + ", state=" + state); 639 if (state == null) { 640 state = new PlaybackState.Builder().setState(PlaybackState.STATE_NONE, 641 PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f).build(); 642 } 643 boolean oldPosValid = (mCurrentPosMs != PlaybackState.PLAYBACK_POSITION_UNKNOWN); 644 int oldPlayStatus = convertPlayStateToPlayStatus(mCurrentPlayState); 645 int newPlayStatus = convertPlayStateToPlayStatus(state); 646 647 if ((mCurrentPlayState.getState() == PlaybackState.STATE_PLAYING) && 648 (mCurrentPlayState != state) && oldPosValid) { 649 mCurrentPosMs = getPlayPosition(); 650 } 651 652 if (state.getState() == PlaybackState.STATE_NONE || 653 state.getState() == PlaybackState.STATE_ERROR) { 654 mCurrentPosMs = PlaybackState.PLAYBACK_POSITION_UNKNOWN; 655 } else { 656 mCurrentPosMs = state.getPosition(); 657 } 658 659 if ((state.getState() == PlaybackState.STATE_PLAYING) && 660 (mCurrentPlayState.getState() != PlaybackState.STATE_PLAYING)) { 661 mPlayStartTimeMs = SystemClock.elapsedRealtime(); 662 } 663 664 mCurrentPlayState = state; 665 666 boolean newPosValid = mCurrentPosMs != PlaybackState.PLAYBACK_POSITION_UNKNOWN; 667 long playPosition = getPlayPosition(); 668 669 mHandler.removeMessages(MESSAGE_PLAY_INTERVAL_TIMEOUT); 670 /* need send play position changed notification when play status is changed */ 671 if ((mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM) && 672 ((oldPlayStatus != newPlayStatus) || (oldPosValid != newPosValid) || 673 (newPosValid && ((playPosition >= mNextPosMs) || (playPosition <= mPrevPosMs))))) { 674 mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED; 675 registerNotificationRspPlayPosNative(mPlayPosChangedNT, (int)playPosition); 676 } 677 if ((mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM) && newPosValid && 678 (state.getState() == PlaybackState.STATE_PLAYING)) { 679 Message msg = mHandler.obtainMessage(MESSAGE_PLAY_INTERVAL_TIMEOUT); 680 mHandler.sendMessageDelayed(msg, mNextPosMs - playPosition); 681 } 682 683 if ((mPlayStatusChangedNT == NOTIFICATION_TYPE_INTERIM) && (oldPlayStatus != newPlayStatus)) { 684 mPlayStatusChangedNT = NOTIFICATION_TYPE_CHANGED; 685 registerNotificationRspPlayStatusNative(mPlayStatusChangedNT, newPlayStatus); 686 } 687 } 688 689 private void updateTransportControls(int transportControlFlags) { 690 mTransportControlFlags = transportControlFlags; 691 } 692 693 class Metadata { 694 private String artist; 695 private String trackTitle; 696 private String albumTitle; 697 698 public Metadata() { 699 artist = null; 700 trackTitle = null; 701 albumTitle = null; 702 } 703 704 public String toString() { 705 return "Metadata[artist=" + artist + " trackTitle=" + trackTitle + " albumTitle=" + 706 albumTitle + "]"; 707 } 708 } 709 710 private void updateMetadata(MediaMetadata data) { 711 String oldMetadata = mMetadata.toString(); 712 if (data == null) { 713 mMetadata = new Metadata(); 714 mSongLengthMs = 0L; 715 } else { 716 mMetadata.artist = data.getString(MediaMetadata.METADATA_KEY_ARTIST); 717 mMetadata.trackTitle = data.getString(MediaMetadata.METADATA_KEY_TITLE); 718 mMetadata.albumTitle = data.getString(MediaMetadata.METADATA_KEY_ALBUM); 719 mSongLengthMs = data.getLong(MediaMetadata.METADATA_KEY_DURATION); 720 } 721 if (!oldMetadata.equals(mMetadata.toString())) { 722 Log.v(TAG, "Metadata Changed to " + mMetadata.toString()); 723 mTrackNumber++; 724 if (mTrackChangedNT == NOTIFICATION_TYPE_INTERIM) { 725 mTrackChangedNT = NOTIFICATION_TYPE_CHANGED; 726 sendTrackChangedRsp(); 727 } 728 729 if (mCurrentPosMs != PlaybackState.PLAYBACK_POSITION_UNKNOWN && 730 isPlayingState(mCurrentPlayState)) { 731 mPlayStartTimeMs = SystemClock.elapsedRealtime(); 732 } 733 /* need send play position changed notification when track is changed */ 734 if (mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM) { 735 mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED; 736 registerNotificationRspPlayPosNative(mPlayPosChangedNT, 737 (int)getPlayPosition()); 738 mHandler.removeMessages(MESSAGE_PLAY_INTERVAL_TIMEOUT); 739 } 740 } else { 741 Log.v(TAG, "Updated " + mMetadata.toString() + " but no change!"); 742 } 743 744 } 745 746 private void getRcFeatures(byte[] address, int features) { 747 Message msg = mHandler.obtainMessage(MESSAGE_GET_RC_FEATURES, features, 0, 748 Utils.getAddressStringFromByte(address)); 749 mHandler.sendMessage(msg); 750 } 751 752 private void getPlayStatus() { 753 Message msg = mHandler.obtainMessage(MESSAGE_GET_PLAY_STATUS); 754 mHandler.sendMessage(msg); 755 } 756 757 private void getElementAttr(byte numAttr, int[] attrs) { 758 int i; 759 ArrayList<Integer> attrList = new ArrayList<Integer>(); 760 for (i = 0; i < numAttr; ++i) { 761 attrList.add(attrs[i]); 762 } 763 Message msg = mHandler.obtainMessage(MESSAGE_GET_ELEM_ATTRS, numAttr, 0, attrList); 764 mHandler.sendMessage(msg); 765 } 766 767 private void registerNotification(int eventId, int param) { 768 Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_NOTIFICATION, eventId, param); 769 mHandler.sendMessage(msg); 770 } 771 772 private void processRegisterNotification(int eventId, int param) { 773 switch (eventId) { 774 case EVT_PLAY_STATUS_CHANGED: 775 mPlayStatusChangedNT = NOTIFICATION_TYPE_INTERIM; 776 registerNotificationRspPlayStatusNative(mPlayStatusChangedNT, 777 convertPlayStateToPlayStatus(mCurrentPlayState)); 778 break; 779 780 case EVT_TRACK_CHANGED: 781 Log.v(TAG, "Track changed notification enabled"); 782 mTrackChangedNT = NOTIFICATION_TYPE_INTERIM; 783 sendTrackChangedRsp(); 784 break; 785 786 case EVT_PLAY_POS_CHANGED: 787 long songPosition = getPlayPosition(); 788 mPlayPosChangedNT = NOTIFICATION_TYPE_INTERIM; 789 mPlaybackIntervalMs = (long)param * 1000L; 790 if (mCurrentPosMs != PlaybackState.PLAYBACK_POSITION_UNKNOWN) { 791 mNextPosMs = songPosition + mPlaybackIntervalMs; 792 mPrevPosMs = songPosition - mPlaybackIntervalMs; 793 if (isPlayingState(mCurrentPlayState)) { 794 Message msg = mHandler.obtainMessage(MESSAGE_PLAY_INTERVAL_TIMEOUT); 795 mHandler.sendMessageDelayed(msg, mPlaybackIntervalMs); 796 } 797 } 798 registerNotificationRspPlayPosNative(mPlayPosChangedNT, (int)songPosition); 799 break; 800 801 } 802 } 803 804 private void handlePassthroughCmd(int id, int keyState) { 805 switch (id) { 806 case BluetoothAvrcp.PASSTHROUGH_ID_REWIND: 807 rewind(keyState); 808 break; 809 case BluetoothAvrcp.PASSTHROUGH_ID_FAST_FOR: 810 fastForward(keyState); 811 break; 812 } 813 } 814 815 private void fastForward(int keyState) { 816 Message msg = mHandler.obtainMessage(MESSAGE_FAST_FORWARD, keyState, 0); 817 mHandler.sendMessage(msg); 818 } 819 820 private void rewind(int keyState) { 821 Message msg = mHandler.obtainMessage(MESSAGE_REWIND, keyState, 0); 822 mHandler.sendMessage(msg); 823 } 824 825 private void changePositionBy(long amount) { 826 long currentPosMs = getPlayPosition(); 827 if (currentPosMs == -1L) return; 828 long newPosMs = Math.max(0L, currentPosMs + amount); 829 mMediaController.getTransportControls().seekTo(newPosMs); 830 } 831 832 private int getSkipMultiplier() { 833 long currentTime = SystemClock.elapsedRealtime(); 834 long multi = (long) Math.pow(2, (currentTime - mSkipStartTime)/SKIP_DOUBLE_INTERVAL); 835 return (int) Math.min(MAX_MULTIPLIER_VALUE, multi); 836 } 837 838 private void sendTrackChangedRsp() { 839 byte[] track = new byte[TRACK_ID_SIZE]; 840 841 /* If no track is currently selected, then return 842 0xFFFFFFFFFFFFFFFF in the interim response */ 843 long trackNumberRsp = -1L; 844 845 if (mCurrentPlayState.getState() != PlaybackState.STATE_NONE && 846 mCurrentPlayState.getState() != PlaybackState.STATE_ERROR) { 847 trackNumberRsp = mTrackNumber; 848 } 849 850 /* track is stored in big endian format */ 851 for (int i = 0; i < TRACK_ID_SIZE; ++i) { 852 track[i] = (byte) (trackNumberRsp >> (56 - 8 * i)); 853 } 854 registerNotificationRspTrackChangeNative(mTrackChangedNT, track); 855 } 856 857 private long getPlayPosition() { 858 long songPosition = -1L; 859 if (mCurrentPosMs != PlaybackState.PLAYBACK_POSITION_UNKNOWN) { 860 if (mCurrentPlayState.getState() == PlaybackState.STATE_PLAYING) { 861 songPosition = SystemClock.elapsedRealtime() - 862 mPlayStartTimeMs + mCurrentPosMs; 863 } else { 864 songPosition = mCurrentPosMs; 865 } 866 } 867 if (DEBUG) Log.v(TAG, "position=" + songPosition); 868 return songPosition; 869 } 870 871 private String getAttributeString(int attrId) { 872 String attrStr = null; 873 switch (attrId) { 874 case MEDIA_ATTR_TITLE: 875 attrStr = mMetadata.trackTitle; 876 break; 877 878 case MEDIA_ATTR_ARTIST: 879 attrStr = mMetadata.artist; 880 break; 881 882 case MEDIA_ATTR_ALBUM: 883 attrStr = mMetadata.albumTitle; 884 break; 885 886 case MEDIA_ATTR_PLAYING_TIME: 887 if (mSongLengthMs != 0L) { 888 attrStr = Long.toString(mSongLengthMs); 889 } 890 break; 891 892 } 893 if (attrStr == null) { 894 attrStr = new String(); 895 } 896 Log.v(TAG, "getAttributeString:attrId=" + attrId + " str=" + attrStr); 897 return attrStr; 898 } 899 900 private int convertPlayStateToPlayStatus(PlaybackState state) { 901 int playStatus = PLAYSTATUS_ERROR; 902 switch (state.getState()) { 903 case PlaybackState.STATE_PLAYING: 904 case PlaybackState.STATE_BUFFERING: 905 playStatus = PLAYSTATUS_PLAYING; 906 break; 907 908 case PlaybackState.STATE_STOPPED: 909 case PlaybackState.STATE_NONE: 910 playStatus = PLAYSTATUS_STOPPED; 911 break; 912 913 case PlaybackState.STATE_PAUSED: 914 playStatus = PLAYSTATUS_PAUSED; 915 break; 916 917 case PlaybackState.STATE_FAST_FORWARDING: 918 case PlaybackState.STATE_SKIPPING_TO_NEXT: 919 case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM: 920 playStatus = PLAYSTATUS_FWD_SEEK; 921 break; 922 923 case PlaybackState.STATE_REWINDING: 924 case PlaybackState.STATE_SKIPPING_TO_PREVIOUS: 925 playStatus = PLAYSTATUS_REV_SEEK; 926 break; 927 928 case PlaybackState.STATE_ERROR: 929 playStatus = PLAYSTATUS_ERROR; 930 break; 931 932 } 933 return playStatus; 934 } 935 936 private boolean isPlayingState(PlaybackState state) { 937 return (state.getState() == PlaybackState.STATE_PLAYING) || 938 (state.getState() == PlaybackState.STATE_BUFFERING); 939 } 940 941 /** 942 * This is called from AudioService. It will return whether this device supports abs volume. 943 * NOT USED AT THE MOMENT. 944 */ 945 public boolean isAbsoluteVolumeSupported() { 946 return ((mFeatures & BTRC_FEAT_ABSOLUTE_VOLUME) != 0); 947 } 948 949 /** 950 * We get this call from AudioService. This will send a message to our handler object, 951 * requesting our handler to call setVolumeNative() 952 */ 953 public void adjustVolume(int direction) { 954 Message msg = mHandler.obtainMessage(MESSAGE_ADJUST_VOLUME, direction, 0); 955 mHandler.sendMessage(msg); 956 } 957 958 public void setAbsoluteVolume(int volume) { 959 if (volume == mLocalVolume) { 960 if (DEBUG) Log.v(TAG, "setAbsoluteVolume is setting same index, ignore "+volume); 961 return; 962 } 963 964 mHandler.removeMessages(MESSAGE_ADJUST_VOLUME); 965 Message msg = mHandler.obtainMessage(MESSAGE_SET_ABSOLUTE_VOLUME, volume, 0); 966 mHandler.sendMessage(msg); 967 } 968 969 /* Called in the native layer as a btrc_callback to return the volume set on the carkit in the 970 * case when the volume is change locally on the carkit. This notification is not called when 971 * the volume is changed from the phone. 972 * 973 * This method will send a message to our handler to change the local stored volume and notify 974 * AudioService to update the UI 975 */ 976 private void volumeChangeCallback(int volume, int ctype) { 977 Message msg = mHandler.obtainMessage(MESSAGE_VOLUME_CHANGED, volume, ctype); 978 mHandler.sendMessage(msg); 979 } 980 981 private void notifyVolumeChanged(int volume) { 982 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 983 AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME); 984 } 985 986 private int convertToAudioStreamVolume(int volume) { 987 // Rescale volume to match AudioSystem's volume 988 return (int) Math.floor((double) volume*mAudioStreamMax/AVRCP_MAX_VOL); 989 } 990 991 private int convertToAvrcpVolume(int volume) { 992 return (int) Math.ceil((double) volume*AVRCP_MAX_VOL/mAudioStreamMax); 993 } 994 995 private void blackListCurrentDevice() { 996 mFeatures &= ~BTRC_FEAT_ABSOLUTE_VOLUME; 997 mAudioManager.avrcpSupportsAbsoluteVolume(mAddress, isAbsoluteVolumeSupported()); 998 999 SharedPreferences pref = mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST, 1000 Context.MODE_PRIVATE); 1001 SharedPreferences.Editor editor = pref.edit(); 1002 editor.putBoolean(mAddress, true); 1003 editor.commit(); 1004 } 1005 1006 private int modifyRcFeatureFromBlacklist(int feature, String address) { 1007 SharedPreferences pref = mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST, 1008 Context.MODE_PRIVATE); 1009 if (!pref.contains(address)) { 1010 return feature; 1011 } 1012 if (pref.getBoolean(address, false)) { 1013 feature &= ~BTRC_FEAT_ABSOLUTE_VOLUME; 1014 } 1015 return feature; 1016 } 1017 1018 public void resetBlackList(String address) { 1019 SharedPreferences pref = mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST, 1020 Context.MODE_PRIVATE); 1021 SharedPreferences.Editor editor = pref.edit(); 1022 editor.remove(address); 1023 editor.commit(); 1024 } 1025 1026 /** 1027 * This is called from A2dpStateMachine to set A2dp audio state. 1028 */ 1029 public void setA2dpAudioState(int state) { 1030 Message msg = mHandler.obtainMessage(MESSAGE_SET_A2DP_AUDIO_STATE, state, 0); 1031 mHandler.sendMessage(msg); 1032 } 1033 1034 public void dump(StringBuilder sb) { 1035 sb.append("AVRCP:\n"); 1036 ProfileService.println(sb, "mMetadata: " + mMetadata); 1037 ProfileService.println(sb, "mTransportControlFlags: " + mTransportControlFlags); 1038 ProfileService.println(sb, "mCurrentPlayState: " + mCurrentPlayState); 1039 ProfileService.println(sb, "mPlayStatusChangedNT: " + mPlayStatusChangedNT); 1040 ProfileService.println(sb, "mTrackChangedNT: " + mTrackChangedNT); 1041 ProfileService.println(sb, "mTrackNumber: " + mTrackNumber); 1042 ProfileService.println(sb, "mCurrentPosMs: " + mCurrentPosMs); 1043 ProfileService.println(sb, "mPlayStartTimeMs: " + mPlayStartTimeMs); 1044 ProfileService.println(sb, "mSongLengthMs: " + mSongLengthMs); 1045 ProfileService.println(sb, "mPlaybackIntervalMs: " + mPlaybackIntervalMs); 1046 ProfileService.println(sb, "mPlayPosChangedNT: " + mPlayPosChangedNT); 1047 ProfileService.println(sb, "mNextPosMs: " + mNextPosMs); 1048 ProfileService.println(sb, "mPrevPosMs: " + mPrevPosMs); 1049 ProfileService.println(sb, "mSkipStartTime: " + mSkipStartTime); 1050 ProfileService.println(sb, "mFeatures: " + mFeatures); 1051 ProfileService.println(sb, "mRemoteVolume: " + mRemoteVolume); 1052 ProfileService.println(sb, "mLastRemoteVolume: " + mLastRemoteVolume); 1053 ProfileService.println(sb, "mLastDirection: " + mLastDirection); 1054 ProfileService.println(sb, "mVolumeStep: " + mVolumeStep); 1055 ProfileService.println(sb, "mAudioStreamMax: " + mAudioStreamMax); 1056 ProfileService.println(sb, "mVolCmdAdjustInProgress: " + mVolCmdAdjustInProgress); 1057 ProfileService.println(sb, "mVolCmdSetInProgress: " + mVolCmdSetInProgress); 1058 ProfileService.println(sb, "mAbsVolRetryTimes: " + mAbsVolRetryTimes); 1059 ProfileService.println(sb, "mSkipAmount: " + mSkipAmount); 1060 ProfileService.println(sb, "mVolumeMapping: " + mVolumeMapping.toString()); 1061 } 1062 1063 // Do not modify without updating the HAL bt_rc.h files. 1064 1065 // match up with btrc_play_status_t enum of bt_rc.h 1066 final static int PLAYSTATUS_STOPPED = 0; 1067 final static int PLAYSTATUS_PLAYING = 1; 1068 final static int PLAYSTATUS_PAUSED = 2; 1069 final static int PLAYSTATUS_FWD_SEEK = 3; 1070 final static int PLAYSTATUS_REV_SEEK = 4; 1071 final static int PLAYSTATUS_ERROR = 255; 1072 1073 // match up with btrc_media_attr_t enum of bt_rc.h 1074 final static int MEDIA_ATTR_TITLE = 1; 1075 final static int MEDIA_ATTR_ARTIST = 2; 1076 final static int MEDIA_ATTR_ALBUM = 3; 1077 final static int MEDIA_ATTR_TRACK_NUM = 4; 1078 final static int MEDIA_ATTR_NUM_TRACKS = 5; 1079 final static int MEDIA_ATTR_GENRE = 6; 1080 final static int MEDIA_ATTR_PLAYING_TIME = 7; 1081 1082 // match up with btrc_event_id_t enum of bt_rc.h 1083 final static int EVT_PLAY_STATUS_CHANGED = 1; 1084 final static int EVT_TRACK_CHANGED = 2; 1085 final static int EVT_TRACK_REACHED_END = 3; 1086 final static int EVT_TRACK_REACHED_START = 4; 1087 final static int EVT_PLAY_POS_CHANGED = 5; 1088 final static int EVT_BATT_STATUS_CHANGED = 6; 1089 final static int EVT_SYSTEM_STATUS_CHANGED = 7; 1090 final static int EVT_APP_SETTINGS_CHANGED = 8; 1091 1092 // match up with btrc_notification_type_t enum of bt_rc.h 1093 final static int NOTIFICATION_TYPE_INTERIM = 0; 1094 final static int NOTIFICATION_TYPE_CHANGED = 1; 1095 1096 // match up with BTRC_UID_SIZE of bt_rc.h 1097 final static int TRACK_ID_SIZE = 8; 1098 1099 private native static void classInitNative(); 1100 private native void initNative(); 1101 private native void cleanupNative(); 1102 private native boolean getPlayStatusRspNative(int playStatus, int songLen, int songPos); 1103 private native boolean getElementAttrRspNative(byte numAttr, int[] attrIds, String[] textArray); 1104 private native boolean registerNotificationRspPlayStatusNative(int type, int playStatus); 1105 private native boolean registerNotificationRspTrackChangeNative(int type, byte[] track); 1106 private native boolean registerNotificationRspPlayPosNative(int type, int playPos); 1107 private native boolean setVolumeNative(int volume); 1108 private native boolean sendPassThroughCommandNative(int keyCode, int keyState); 1109 1110} 1111