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