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