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