/* * Copyright (C) 2013-2014, The Linux Foundation. All rights reserved. * Not a Contribution. * * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bluetooth.avrcp; import java.util.Timer; import java.util.TimerTask; import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAvrcp; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Bitmap; import android.media.AudioManager; import android.media.IRemoteControlDisplay; import android.media.MediaMetadataRetriever; import android.media.RemoteControlClient; import android.media.RemoteController; import android.media.RemoteController.MetadataEditor; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.ParcelUuid; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.util.Log; import android.view.KeyEvent; import android.content.BroadcastReceiver; import com.android.bluetooth.a2dp.A2dpService; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.Utils; import com.android.internal.util.IState; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.Iterator; import android.provider.MediaStore; import android.content.ContentResolver; import android.database.Cursor; import android.database.sqlite.SQLiteException; import android.net.Uri; import android.app.Notification; import android.app.NotificationManager; /** * support Bluetooth AVRCP profile. * support metadata, play status and event notification */ public final class Avrcp { private static final boolean DEBUG = true; private static final String TAG = "Avrcp"; private Context mContext; private final AudioManager mAudioManager; private A2dpService mA2dpService; private AvrcpMessageHandler mHandler; private RemoteController mRemoteController; private RemoteControllerWeak mRemoteControllerCb; private AvrcpRemoteControllerWeak mAvrcpRemoteControllerCb; private Metadata mMetadata; private int mTransportControlFlags; private int mCurrentPlayerState; private int mPlayStatusChangedNT; private int mTrackChangedNT; private long mCurrentPosMs; private long mPlayStartTimeMs; private long mTrackNumber; private long mSongLengthMs; private long mPlaybackIntervalMs; private int mPlayPosChangedNT; private long mSkipStartTime; private final int mVolumeStep; private final int mAudioStreamMax; private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; private int mSkipAmount; private final BluetoothAdapter mAdapter; private static Uri mMediaUriStatic; private static long currentTrackPos; private static boolean updatePlayTime; private static boolean updateValues; private int mAddressedPlayerId; /* BTRC features */ public static final int BTRC_FEAT_METADATA = 0x01; public static final int BTRC_FEAT_ABSOLUTE_VOLUME = 0x02; public static final int BTRC_FEAT_BROWSE = 0x04; public static final int BTRC_FEAT_AVRC_UI_UPDATE = 0x08; /* AVRC response codes, from avrc_defs */ private static final int AVRC_RSP_NOT_IMPL = 8; private static final int AVRC_RSP_ACCEPT = 9; private static final int AVRC_RSP_REJ = 10; private static final int AVRC_RSP_IN_TRANS = 11; private static final int AVRC_RSP_IMPL_STBL = 12; private static final int AVRC_RSP_CHANGED = 13; private static final int AVRC_RSP_INTERIM = 15; private static final int MESSAGE_GET_RC_FEATURES = 1; private static final int MESSAGE_GET_PLAY_STATUS = 2; private static final int MESSAGE_GET_ELEM_ATTRS = 3; private static final int MESSAGE_REGISTER_NOTIFICATION = 4; private static final int MESSAGE_PLAY_INTERVAL_TIMEOUT = 5; private static final int MESSAGE_VOLUME_CHANGED = 6; private static final int MESSAGE_ADJUST_VOLUME = 7; private static final int MESSAGE_SET_ABSOLUTE_VOLUME = 8; private static final int MESSAGE_ABS_VOL_TIMEOUT = 9; private static final int MESSAGE_FAST_FORWARD = 10; private static final int MESSAGE_REWIND = 11; private static final int MESSAGE_CHANGE_PLAY_POS = 12; private static final int MESSAGE_SET_A2DP_AUDIO_STATE = 13; private static final int MESSAGE_SET_ADDR_PLAYER_REQ_TIMEOUT = 14; private static final int AVRCP_BR_RSP_TIMEOUT = 2000; private static final int MESSAGE_SEND_PASS_THROUGH_CMD = 2001; private static final int MESSAGE_SET_ADDR_PLAYER = 2002; private static final int MESSAGE_GET_FOLDER_ITEMS = 2003; private static final int MESSAGE_SET_BROWSED_PLAYER = 2004; private static final int MESSAGE_CHANGE_PATH = 2005; private static final int MESSAGE_PLAY_ITEM = 2006; private static final int MESSAGE_GET_ITEM_ATTRS = 2007; private CachedRequest mCachedRequest = null; private static final int MSG_UPDATE_STATE = 100; private static final int MSG_SET_METADATA = 101; private static final int MSG_SET_TRANSPORT_CONTROLS = 102; private static final int MSG_SET_GENERATION_ID = 104; private static final int MSG_UPDATE_AVAILABLE_PLAYERS = 201; private static final int MSG_UPDATE_ADDRESSED_PLAYER = 202; private static final int MSG_UPDATE_RCC_CHANGE = 203; private static final int MSG_UPDATE_BROWSED_PLAYER_FOLDER = 204; private static final int MSG_UPDATE_NOW_PLAYING_CONTENT_CHANGED = 205; private static final int MSG_PLAY_ITEM_RESPONSE = 206; private static final int MSG_NOW_PLAYING_ENTRIES_RECEIVED = 207; private MediaPlayerInfo mediaPlayerInfo1; private static final int BUTTON_TIMEOUT_TIME = 2000; private static final int BASE_SKIP_AMOUNT = 2000; private static final int KEY_STATE_PRESS = 1; private static final int KEY_STATE_RELEASE = 0; private static final int SKIP_PERIOD = 400; private static final int SKIP_DOUBLE_INTERVAL = 3000; private static final long MAX_MULTIPLIER_VALUE = 128L; private static final int CMD_TIMEOUT_DELAY = 2000; private static final int MAX_ERROR_RETRY_TIMES = 3; private static final int AVRCP_MAX_VOL = 127; private static final int AVRCP_BASE_VOLUME_STEP = 1; private final static int MESSAGE_PLAYERSETTINGS_TIMEOUT = 602; private static final int AVRCP_CONNECTED = 1; public static final int KEY_STATE_PRESSED = 0; public static final int KEY_STATE_RELEASED = 1; private final static int TYPE_MEDIA_PLAYER_ITEM = 0x01; private final static int TYPE_FOLDER_ITEM = 0x02; private final static int TYPE_MEDIA_ELEMENT_ITEM = 0x03; private final static int FOLDER_UP = 0x00; private final static int FOLDER_DOWN = 0x01; private static final String PATH_INVALID = "invalid"; private static final String PATH_ROOT = "root"; private static final String PATH_TITLES = "titles"; private static final String PATH_ALBUMS = "albums"; private static final String PATH_ARTISTS = "artists"; private static final String PATH_PLAYLISTS = "playlists"; private final static long UID_TITLES = 0x01; private final static long UID_ALBUM = 0x02; private final static long UID_ARTIST = 0x03; private final static long UID_PLAYLIST = 0x04; private final static int NUM_ROOT_ELEMENTS = 0x04; private static final int INTERNAL_ERROR = 0x03; private static final int OPERATION_SUCCESSFUL = 0x04; private static final int INVALID_DIRECTION = 0x07; private static final int NOT_A_DIRECTORY = 0x08; private static final int DOES_NOT_EXIST = 0x09; private static final int INVALID_SCOPE = 0x0a; private static final int RANGE_OUT_OF_BOUNDS = 0x0b; private static final int UID_A_DIRECTORY = 0x0c; private static final int MEDIA_IN_USE = 0x0d; private static final int INVALID_PLAYER_ID = 0x11; private static final int PLAYER_NOT_BROWSABLE = 0x12; private static final int PLAYER_NOT_ADDRESSED = 0x13; private static final int FOLDER_TYPE_MIXED = 0x00; private static final int FOLDER_TYPE_TITLES = 0x01; private static final int FOLDER_TYPE_ALBUMS = 0x02; private static final int FOLDER_TYPE_ARTISTS = 0x03; private static final int FOLDER_TYPE_GENRES = 0x04; private static final int FOLDER_TYPE_PLAYLISTS = 0x05; private static final int MEDIA_TYPE_AUDIO = 0X00; private static final int MEDIA_TYPE_VIDEO = 0X01; private static final int MAX_BROWSE_ITEM_TO_SEND = 0x03; private static final int MAX_ATTRIB_COUNT = 0x07; private final static int ALBUMS_ITEM_INDEX = 0; private final static int ARTISTS_ITEM_INDEX = 1; private final static int PLAYLISTS_ITEM_INDEX = 2; private final static int TITLES_ITEM_INDEX = 3; //Intents for PlayerApplication Settings private static final String PLAYERSETTINGS_REQUEST = "org.codeaurora.music.playersettingsrequest"; private static final String PLAYERSETTINGS_RESPONSE = "org.codeaurora.music.playersettingsresponse"; // Max number of Avrcp connections at any time private int maxAvrcpConnections = 1; BluetoothDevice mBrowserDevice = null; private static final int INVALID_DEVICE_INDEX = 0xFF; // codes for reset of of notifications private static final int PLAY_POSITION_CHANGE_NOTIFICATION = 101; private static final int PLAY_STATUS_CHANGE_NOTIFICATION = 102; private static final int TRACK_CHANGE_NOTIFICATION = 103; private static final int NOW_PALYING_CONTENT_CHANGED_NOTIFICATION = 104; private static final int INVALID_ADDRESSED_PLAYER_ID = -1; // Device dependent registered Notification & Variables private class DeviceDependentFeature { private BluetoothDevice mCurrentDevice; private int mCurrentPlayState; private int mPlayStatusChangedNT; private int mPlayerStatusChangeNT; private int mTrackChangedNT; private long mNextPosMs; private long mPrevPosMs; private long mPlaybackIntervalMs; private int mPlayPosChangedNT; private int mFeatures; private int mAbsoluteVolume; private int mLastSetVolume; private int mLastDirection; private boolean mVolCmdInProgress; private int mAbsVolRetryTimes; private int keyPressState; private int mAddressedPlayerChangedNT; private int mAvailablePlayersChangedNT; private int mNowPlayingContentChangedNT; private String mRequestedAddressedPlayerPackageName; private String mCurrentPath; private String mCurrentPathUid; private Uri mMediaUri; private boolean isMusicAppResponsePending; private boolean isBrowsingSupported; private boolean isAbsoluteVolumeSupportingDevice; public DeviceDependentFeature() { mCurrentDevice = null; mCurrentPlayState = RemoteControlClient.PLAYSTATE_NONE; mPlayStatusChangedNT = NOTIFICATION_TYPE_CHANGED; mPlayerStatusChangeNT = NOTIFICATION_TYPE_CHANGED; mTrackChangedNT = NOTIFICATION_TYPE_CHANGED; mPlaybackIntervalMs = 0L; mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED; mFeatures = 0; mAbsoluteVolume = -1; mLastSetVolume = -1; mLastDirection = 0; mVolCmdInProgress = false; mAbsVolRetryTimes = 0; mSkipAmount = 0; keyPressState = KEY_STATE_RELEASE; //Key release state mAddressedPlayerChangedNT = NOTIFICATION_TYPE_CHANGED; mAvailablePlayersChangedNT = NOTIFICATION_TYPE_CHANGED; mNowPlayingContentChangedNT = NOTIFICATION_TYPE_CHANGED; mRequestedAddressedPlayerPackageName = null; mCurrentPath = PATH_INVALID; mCurrentPathUid = null; mMediaUri = Uri.EMPTY; isMusicAppResponsePending = false; isBrowsingSupported = false; isAbsoluteVolumeSupportingDevice = false; } }; private class PlayerSettings { public byte attr; public byte [] attrIds; public String path; }; private PlayerSettings mPlayerSettings = new PlayerSettings(); private class localPlayerSettings { public byte eq_value = 0x01; public byte repeat_value = 0x01; public byte shuffle_value = 0x01; public byte scan_value = 0x01; }; private localPlayerSettings settingValues = new localPlayerSettings(); private static final String COMMAND = "command"; private static final String CMDGET = "get"; private static final String CMDSET = "set"; private static final String EXTRA_GET_COMMAND = "commandExtra"; private static final String EXTRA_GET_RESPONSE = "Response"; private static final int GET_ATTRIBUTE_IDS = 0; private static final int GET_VALUE_IDS = 1; private static final int GET_ATTRIBUTE_TEXT = 2; private static final int GET_VALUE_TEXT = 3; private static final int GET_ATTRIBUTE_VALUES = 4; private static final int NOTIFY_ATTRIBUTE_VALUES = 5; private static final int SET_ATTRIBUTE_VALUES = 6; private static final int GET_INVALID = 0xff; private static final String EXTRA_ATTRIBUTE_ID = "Attribute"; private static final String EXTRA_VALUE_STRING_ARRAY = "ValueStrings"; private static final String EXTRA_ATTRIB_VALUE_PAIRS = "AttribValuePairs"; private static final String EXTRA_ATTRIBUTE_STRING_ARRAY = "AttributeStrings"; private static final String EXTRA_VALUE_ID_ARRAY = "Values"; private static final String EXTRA_ATTIBUTE_ID_ARRAY = "Attributes"; public static final int VALUE_SHUFFLEMODE_OFF = 1; public static final int VALUE_SHUFFLEMODE_ALL = 2; public static final int VALUE_REPEATMODE_OFF = 1; public static final int VALUE_REPEATMODE_SINGLE = 2; public static final int VALUE_REPEATMODE_ALL = 3; public static final int VALUE_INVALID = 0; public static final int ATTRIBUTE_NOTSUPPORTED = -1; public static final int ATTRIBUTE_EQUALIZER = 1; public static final int ATTRIBUTE_REPEATMODE = 2; public static final int ATTRIBUTE_SHUFFLEMODE = 3; public static final int ATTRIBUTE_SCANMODE = 4; public static final int NUMPLAYER_ATTRIBUTE = 2; private byte [] def_attrib = new byte [] {ATTRIBUTE_REPEATMODE, ATTRIBUTE_SHUFFLEMODE}; private byte [] value_repmode = new byte [] { VALUE_REPEATMODE_OFF, VALUE_REPEATMODE_SINGLE, VALUE_REPEATMODE_ALL }; private byte [] value_shufmode = new byte [] { VALUE_SHUFFLEMODE_OFF, VALUE_SHUFFLEMODE_ALL }; private byte [] value_default = new byte [] {0}; private final String UPDATE_ATTRIBUTES = "UpdateSupportedAttributes"; private final String UPDATE_VALUES = "UpdateSupportedValues"; private final String UPDATE_ATTRIB_VALUE = "UpdateCurrentValues"; private final String UPDATE_ATTRIB_TEXT = "UpdateAttributesText"; private final String UPDATE_VALUE_TEXT = "UpdateValuesText"; private ArrayList mPendingCmds; private ArrayList mPendingSetAttributes; DeviceDependentFeature[] deviceFeatures; static { classInitNative(); } private Avrcp(Context context, A2dpService svc, int maxConnections ) { if (DEBUG) Log.v(TAG, "Avrcp"); mAdapter = BluetoothAdapter.getDefaultAdapter(); mMetadata = new Metadata(); mTrackNumber = -1L; mCurrentPosMs = -1L; mPlayStartTimeMs = -1L; mSongLengthMs = 0L; mA2dpService = svc; maxAvrcpConnections = maxConnections; deviceFeatures = new DeviceDependentFeature[maxAvrcpConnections]; mAddressedPlayerId = INVALID_ADDRESSED_PLAYER_ID; mCurrentPlayerState = RemoteControlClient.PLAYSTATE_NONE; for(int i = 0; i < maxAvrcpConnections; i++) { deviceFeatures[i] = new DeviceDependentFeature(); } mContext = context; initNative(maxConnections); mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mAudioStreamMax = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); mVolumeStep = Math.max(AVRCP_BASE_VOLUME_STEP, AVRCP_MAX_VOL/mAudioStreamMax); } private void start() { if (DEBUG) Log.v(TAG, "start"); HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler"); thread.start(); Looper looper = thread.getLooper(); mHandler = new AvrcpMessageHandler(looper); mPendingCmds = new ArrayList(); mPendingSetAttributes = new ArrayList(); // clear path for all devices for (int i = 0; i < maxAvrcpConnections; i++) { deviceFeatures[i].mCurrentPath = PATH_INVALID; deviceFeatures[i].mCurrentPathUid = null; deviceFeatures[i].mMediaUri = Uri.EMPTY; } IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(AudioManager.RCC_CHANGED_ACTION); intentFilter.addAction(PLAYERSETTINGS_RESPONSE); try { mContext.registerReceiver(mIntentReceiver, intentFilter); }catch (Exception e) { Log.e(TAG,"Unable to register Avrcp receiver", e); } registerMediaPlayers(); mRemoteControllerCb = new RemoteControllerWeak(mHandler); mAvrcpRemoteControllerCb = new AvrcpRemoteControllerWeak(mHandler); mRemoteController = new RemoteController(mContext, mRemoteControllerCb, null, mAvrcpRemoteControllerCb); mAudioManager.registerRemoteController(mRemoteController); mRemoteController.setSynchronizationMode(RemoteController.POSITION_SYNCHRONIZATION_CHECK); } //Listen to intents from MediaPlayer and Audio Manager and update data structures private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(AudioManager.RCC_CHANGED_ACTION)) { Log.v(TAG, "received RCC_CHANGED_ACTION"); int isRCCFocussed = 0; int isRCCAvailable = 0; String callingPackageName = intent.getStringExtra(AudioManager.EXTRA_CALLING_PACKAGE_NAME); boolean isFocussed = intent.getBooleanExtra(AudioManager.EXTRA_FOCUS_CHANGED_VALUE, false); boolean isAvailable = intent.getBooleanExtra(AudioManager.EXTRA_AVAILABLITY_CHANGED_VALUE, false); if (isFocussed) isRCCFocussed = 1; if (isAvailable) isRCCAvailable = 1; Log.v(TAG, "focus: " + isFocussed + " , availability: " + isAvailable); if (mHandler != null) { mHandler.obtainMessage(MSG_UPDATE_RCC_CHANGE, isRCCFocussed, isRCCAvailable, callingPackageName).sendToTarget(); } } else if (action.equals(PLAYERSETTINGS_RESPONSE)) { int getResponse = intent.getIntExtra(EXTRA_GET_RESPONSE, GET_INVALID); byte [] data; String [] text; boolean isSetAttrValRsp = false; BluetoothDevice device = null; synchronized (mPendingCmds) { Integer val = new Integer(getResponse); if (mPendingCmds.contains(val)) { if (getResponse == SET_ATTRIBUTE_VALUES) { isSetAttrValRsp = true; if (DEBUG) Log.v(TAG,"Response received for SET_ATTRIBUTE_VALUES"); } mHandler.removeMessages(MESSAGE_PLAYERSETTINGS_TIMEOUT); mPendingCmds.remove(val); } } for (int i = 0; i < maxAvrcpConnections; i++) { if (deviceFeatures[i].isMusicAppResponsePending == true) { device = deviceFeatures[i].mCurrentDevice; deviceFeatures[i].isMusicAppResponsePending = false; break; } } if (DEBUG) Log.v(TAG,"getResponse" + getResponse); switch (getResponse) { case GET_ATTRIBUTE_IDS: if (device == null) { Log.e(TAG,"ERROR!!! device is null"); return; } data = intent.getByteArrayExtra(EXTRA_ATTIBUTE_ID_ARRAY); byte numAttr = (byte) data.length; if (DEBUG) Log.v(TAG,"GET_ATTRIBUTE_IDS"); getListPlayerappAttrRspNative(numAttr , data ,getByteAddress(device)); break; case GET_VALUE_IDS: if (device == null) { Log.e(TAG,"ERROR!!! device is null"); return; } data = intent.getByteArrayExtra(EXTRA_VALUE_ID_ARRAY); numAttr = (byte) data.length; if (DEBUG) Log.v(TAG,"GET_VALUE_IDS" + numAttr); getPlayerAppValueRspNative(numAttr, data, getByteAddress(device)); break; case GET_ATTRIBUTE_VALUES: if (device == null) { Log.e(TAG,"ERROR!!! device is null"); return; } data = intent.getByteArrayExtra(EXTRA_ATTRIB_VALUE_PAIRS); updateLocalPlayerSettings(data); numAttr = (byte) data.length; if (DEBUG) Log.v(TAG,"GET_ATTRIBUTE_VALUES" + numAttr); SendCurrentPlayerValueRspNative(numAttr , data, getByteAddress(device)); break; case SET_ATTRIBUTE_VALUES: data = intent.getByteArrayExtra(EXTRA_ATTRIB_VALUE_PAIRS); updateLocalPlayerSettings(data); if (isSetAttrValRsp) { isSetAttrValRsp = false; for (int i = 0; i < maxAvrcpConnections; i++) { if (deviceFeatures[i].mCurrentDevice != null) { Log.v(TAG,"Respond to SET_ATTRIBUTE_VALUES request"); if (checkPlayerAttributeResponse(data)) { SendSetPlayerAppRspNative(OPERATION_SUCCESSFUL, getByteAddress(deviceFeatures[i].mCurrentDevice)); } else { SendSetPlayerAppRspNative(INTERNAL_ERROR, getByteAddress(deviceFeatures[i].mCurrentDevice)); } } } mPendingSetAttributes.clear(); } for (int i = 0; i < maxAvrcpConnections; i++) { if (deviceFeatures[i].mPlayerStatusChangeNT == NOTIFICATION_TYPE_INTERIM) { Log.v(TAG,"device has registered for"+ "mPlayerStatusChangeNT"); deviceFeatures[i].mPlayerStatusChangeNT = NOTIFICATION_TYPE_CHANGED; sendPlayerAppChangedRsp(deviceFeatures[i].mPlayerStatusChangeNT, deviceFeatures[i].mCurrentDevice); } else { Log.v(TAG,"Drop Set Attr Val update from media player"); } } break; case GET_ATTRIBUTE_TEXT: text = intent.getStringArrayExtra(EXTRA_ATTRIBUTE_STRING_ARRAY); if (device == null) { Log.e(TAG,"ERROR!!! device is null"); return; } sendSettingsTextRspNative(mPlayerSettings.attrIds.length , mPlayerSettings.attrIds ,text.length, text, getByteAddress(device)); if (DEBUG) Log.v(TAG,"mPlayerSettings.attrIds" + mPlayerSettings.attrIds.length); break; case GET_VALUE_TEXT: text = intent.getStringArrayExtra(EXTRA_VALUE_STRING_ARRAY); if (device == null) { Log.e(TAG,"ERROR!!! device is null"); return; } sendValueTextRspNative(mPlayerSettings.attrIds.length , mPlayerSettings.attrIds, text.length, text, getByteAddress(device)); break; } } } }; /* This method is used for create entries of existing media players on RCD start * Later when media players become avaialable corresponding entries * are marked accordingly and similarly when media players changes focus * the corresponding fields are modified */ private void registerMediaPlayers () { if (DEBUG) Log.v(TAG, "registerMediaPlayers"); int[] featureMasks = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; byte[] playerName1 = {0x4d, 0x75, 0x73, 0x69, 0x63}/*Music*/; featureMasks[FEATURE_MASK_PLAY_OFFSET] = featureMasks[FEATURE_MASK_PLAY_OFFSET] | FEATURE_MASK_PLAY_MASK; featureMasks[FEATURE_MASK_PAUSE_OFFSET] = featureMasks[FEATURE_MASK_PAUSE_OFFSET] | FEATURE_MASK_PAUSE_MASK; featureMasks[FEATURE_MASK_STOP_OFFSET] = featureMasks[FEATURE_MASK_STOP_OFFSET] | FEATURE_MASK_STOP_MASK; featureMasks[FEATURE_MASK_PAGE_UP_OFFSET] = featureMasks[FEATURE_MASK_PAGE_UP_OFFSET] | FEATURE_MASK_PAGE_UP_MASK; featureMasks[FEATURE_MASK_PAGE_DOWN_OFFSET] = featureMasks[FEATURE_MASK_PAGE_DOWN_OFFSET] | FEATURE_MASK_PAGE_DOWN_MASK; featureMasks[FEATURE_MASK_REWIND_OFFSET] = featureMasks[FEATURE_MASK_REWIND_OFFSET] | FEATURE_MASK_REWIND_MASK; featureMasks[FEATURE_MASK_FAST_FWD_OFFSET] = featureMasks[FEATURE_MASK_FAST_FWD_OFFSET] | FEATURE_MASK_FAST_FWD_MASK; featureMasks[FEATURE_MASK_VENDOR_OFFSET] = featureMasks[FEATURE_MASK_VENDOR_OFFSET] | FEATURE_MASK_VENDOR_MASK; featureMasks[FEATURE_MASK_ADV_CTRL_OFFSET] = featureMasks[FEATURE_MASK_ADV_CTRL_OFFSET] | FEATURE_MASK_ADV_CTRL_MASK; featureMasks[FEATURE_MASK_BROWSE_OFFSET] = featureMasks[FEATURE_MASK_BROWSE_OFFSET] | FEATURE_MASK_BROWSE_MASK; featureMasks[FEATURE_MASK_NOW_PLAY_OFFSET] = featureMasks[FEATURE_MASK_NOW_PLAY_OFFSET] | FEATURE_MASK_NOW_PLAY_MASK; featureMasks[FEATURE_MASK_BR_WH_ADDR_OFFSET] = featureMasks[FEATURE_MASK_BR_WH_ADDR_OFFSET] | FEATURE_MASK_BR_WH_ADDR_MASK; mediaPlayerInfo1 = new MediaPlayerInfo ((short)0x0001, MAJOR_TYPE_AUDIO, SUB_TYPE_NONE, (byte)RemoteControlClient.PLAYSTATE_PAUSED, CHAR_SET_UTF8, (short)0x05, playerName1, "com.android.music", true, featureMasks); mMediaPlayers.add(mediaPlayerInfo1); } public static Avrcp make(Context context, A2dpService svc, int maxConnections) { if (DEBUG) Log.v(TAG, "make"); Avrcp ar = new Avrcp(context, svc, maxConnections); ar.start(); return ar; } public void doQuit() { if (DEBUG) Log.v(TAG, "doQuit"); mHandler.removeCallbacksAndMessages(null); Looper looper = mHandler.getLooper(); if (looper != null) { looper.quit(); } mAudioManager.unregisterRemoteController(mRemoteController); clearDeviceDependentFeature(); for (int i = 0; i < maxAvrcpConnections; i++) { cleanupDeviceFeaturesIndex(i); } try { mContext.unregisterReceiver(mIntentReceiver); }catch (Exception e) { Log.e(TAG,"Unable to unregister Avrcp receiver", e); } mMediaPlayers.clear(); if (mHandler.hasMessages(MESSAGE_SET_ADDR_PLAYER_REQ_TIMEOUT)) { mHandler.removeMessages(MESSAGE_SET_ADDR_PLAYER_REQ_TIMEOUT); if (DEBUG) Log.v(TAG, "Addressed player message cleanup as part of doQuit"); } } public void clearDeviceDependentFeature() { for (int i = 0; i < maxAvrcpConnections; i++) { deviceFeatures[i].keyPressState = KEY_STATE_RELEASE; //Key release state deviceFeatures[i].mCurrentPath = PATH_INVALID; deviceFeatures[i].mMediaUri = Uri.EMPTY; deviceFeatures[i].mCurrentPathUid = null; deviceFeatures[i].mRequestedAddressedPlayerPackageName = null; } } public void cleanup() { if (DEBUG) Log.v(TAG, "cleanup"); cleanupNative(); } private static class RemoteControllerWeak implements RemoteController.OnClientUpdateListener { private final WeakReference mLocalHandler; public RemoteControllerWeak(Handler handler) { mLocalHandler = new WeakReference(handler); } @Override public void onClientChange(boolean clearing) { Handler handler = mLocalHandler.get(); if (handler != null) { handler.obtainMessage(MSG_SET_GENERATION_ID, 0, (clearing ? 1 : 0), null).sendToTarget(); } } @Override public void onClientPlaybackStateUpdate(int state) { // Should never be called with the existing code, but just in case Handler handler = mLocalHandler.get(); if (handler != null) { handler.obtainMessage(MSG_UPDATE_STATE, 0, state, new Long(RemoteControlClient.PLAYBACK_POSITION_INVALID)).sendToTarget(); } } @Override public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs, long currentPosMs, float speed) { Handler handler = mLocalHandler.get(); if (handler != null) { handler.obtainMessage(MSG_UPDATE_STATE, 0, state, new Long(currentPosMs)).sendToTarget(); } } @Override public void onClientTransportControlUpdate(int transportControlFlags) { Handler handler = mLocalHandler.get(); if (handler != null) { handler.obtainMessage(MSG_SET_TRANSPORT_CONTROLS, 0, transportControlFlags) .sendToTarget(); } } @Override public void onClientMetadataUpdate(MetadataEditor metadataEditor) { Handler handler = mLocalHandler.get(); if (handler != null) { handler.obtainMessage(MSG_SET_METADATA, 0, 0, metadataEditor).sendToTarget(); } } } private static class AvrcpRemoteControllerWeak implements RemoteController.OnClientAvrcpUpdateListener { private final WeakReference mLocalHandler; public AvrcpRemoteControllerWeak(Handler handler) { mLocalHandler = new WeakReference(handler); } @Override public void onClientFolderInfoBrowsedPlayer(String stringUri) { Log.v(TAG, "onClientFolderInfoBrowsedPlayer: stringUri: " + stringUri); Handler handler = mLocalHandler.get(); if (stringUri != null) { String[] ExternalPath = stringUri.split("/"); if (ExternalPath.length < 4) { Log.d(TAG, "Wrong entries."); handler.obtainMessage(MSG_UPDATE_BROWSED_PLAYER_FOLDER, 0, INTERNAL_ERROR, null).sendToTarget(); return; } Uri uri = Uri.parse(stringUri); Log.v(TAG, "URI received: " + uri); String[] SplitPath = new String[ExternalPath.length - 3]; for (int count = 2; count < (ExternalPath.length - 1); count++) { SplitPath[count - 2] = ExternalPath[count]; Log.d(TAG, "SplitPath[" + (count - 2) + "] = " + SplitPath[count - 2]); } Log.v(TAG, "folderDepth: " + SplitPath.length); for (int count = 0; count < SplitPath.length; count++) { Log.v(TAG, "folderName: " + SplitPath[count]); } mMediaUriStatic = uri; if (handler != null) { // Don't send the complete path to CK as few gets confused by that // Send only the name of the root folder handler.obtainMessage(MSG_UPDATE_BROWSED_PLAYER_FOLDER, NUM_ROOT_ELEMENTS, OPERATION_SUCCESSFUL, SplitPath).sendToTarget(); } } else { handler.obtainMessage(MSG_UPDATE_BROWSED_PLAYER_FOLDER, 0, INTERNAL_ERROR, null).sendToTarget(); } } @Override public void onClientUpdateNowPlayingEntries(long[] playList) { Log.v(TAG, "onClientUpdateNowPlayingEntries"); Handler handler = mLocalHandler.get(); if (handler != null) { handler.obtainMessage(MSG_NOW_PLAYING_ENTRIES_RECEIVED, 0, 0, playList).sendToTarget(); } } @Override public void onClientNowPlayingContentChange() { Log.v(TAG, "onClientNowPlayingContentChange"); Handler handler = mLocalHandler.get(); if (handler != null) { handler.obtainMessage(MSG_UPDATE_NOW_PLAYING_CONTENT_CHANGED).sendToTarget(); } } @Override public void onClientPlayItemResponse(boolean success) { Log.v(TAG, "onClientPlayItemResponse"); Handler handler = mLocalHandler.get(); if (handler != null) { handler.obtainMessage(MSG_PLAY_ITEM_RESPONSE, 0, 0, new Boolean(success)) .sendToTarget(); } } } /* Handles Avrcp messages. */ private final class AvrcpMessageHandler extends Handler { private AvrcpMessageHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { int deviceIndex = INVALID_DEVICE_INDEX; switch (msg.what) { case MESSAGE_PLAYERSETTINGS_TIMEOUT: Log.e(TAG, "**MESSAGE_PLAYSTATUS_TIMEOUT: Addr: " + (String)msg.obj + " Msg: " + msg.arg1); synchronized (mPendingCmds) { Integer val = new Integer(msg.arg1); if (!mPendingCmds.contains(val)) { break; } mPendingCmds.remove(val); } switch (msg.arg1) { case GET_ATTRIBUTE_IDS: getListPlayerappAttrRspNative((byte)def_attrib.length , def_attrib, getByteAddress( mAdapter.getRemoteDevice((String) msg.obj))); break; case GET_VALUE_IDS: switch (mPlayerSettings.attr) { case ATTRIBUTE_REPEATMODE: getPlayerAppValueRspNative((byte)value_repmode.length, value_repmode, getByteAddress(mAdapter.getRemoteDevice( (String) msg.obj))); break; case ATTRIBUTE_SHUFFLEMODE: getPlayerAppValueRspNative((byte)value_shufmode.length, value_shufmode, getByteAddress(mAdapter.getRemoteDevice( (String) msg.obj))); break; default: getPlayerAppValueRspNative((byte)value_default.length, value_default, getByteAddress(mAdapter.getRemoteDevice( (String) msg.obj))); break; } break; case GET_ATTRIBUTE_VALUES: int j = 0; byte [] retVal = new byte [mPlayerSettings.attrIds.length*2]; for (int i = 0; i < mPlayerSettings.attrIds.length; i++) { retVal[j++] = mPlayerSettings.attrIds[i]; if (mPlayerSettings.attrIds[i] == ATTRIBUTE_REPEATMODE) { retVal[j++] = settingValues.repeat_value; } else if (mPlayerSettings.attrIds[i] == ATTRIBUTE_SHUFFLEMODE) { retVal[j++] = settingValues.shuffle_value; } else { retVal[j++] = 0x0; } } SendCurrentPlayerValueRspNative((byte)retVal.length , retVal, getByteAddress(mAdapter.getRemoteDevice( (String) msg.obj))); break; case SET_ATTRIBUTE_VALUES : SendSetPlayerAppRspNative(INTERNAL_ERROR, getByteAddress( mAdapter.getRemoteDevice((String) msg.obj))); break; case GET_ATTRIBUTE_TEXT: String [] attribText = new String [mPlayerSettings.attrIds.length]; for (int i = 0; i < mPlayerSettings.attrIds.length; i++) { attribText[i] = ""; } sendSettingsTextRspNative(mPlayerSettings.attrIds.length , mPlayerSettings.attrIds, attribText.length, attribText, getByteAddress(mAdapter.getRemoteDevice( (String) msg.obj))); break; case GET_VALUE_TEXT: String [] valueText = new String [mPlayerSettings.attrIds.length]; for (int i = 0; i < mPlayerSettings.attrIds.length; i++) { valueText[i] = ""; } sendValueTextRspNative(mPlayerSettings.attrIds.length , mPlayerSettings.attrIds, valueText.length, valueText,getByteAddress(mAdapter.getRemoteDevice( (String) msg.obj))); break; default : Log.e(TAG,"in default case"); break; } break; case MSG_UPDATE_STATE: /* since we get this from music app we need to update * current playing start time */ Log.i(TAG,"State change for music app"); updatePlayPauseState(msg.arg2, ((Long) msg.obj).longValue(), null); break; case MSG_SET_METADATA: updateMetadata((MetadataEditor) msg.obj); break; case MSG_UPDATE_AVAILABLE_PLAYERS: updateAvailableMediaPlayers(); break; case MSG_UPDATE_ADDRESSED_PLAYER: updateAddressedMediaPlayer(msg.arg1); break; case MSG_UPDATE_BROWSED_PLAYER_FOLDER: Log.v(TAG, "MSG_UPDATE_BROWSED_PLAYER_FOLDER"); updateBrowsedPlayerFolder(msg.arg1, msg.arg2, (String [])msg.obj); break; case MSG_UPDATE_NOW_PLAYING_CONTENT_CHANGED: Log.v(TAG, "MSG_UPDATE_NOW_PLAYING_CONTENT_CHANGED"); updateNowPlayingContentChanged(); break; case MSG_PLAY_ITEM_RESPONSE: Log.v(TAG, "MSG_PLAY_ITEM_RESPONSE"); boolean success = ((Boolean)msg.obj).booleanValue(); Log.v(TAG, "success: " + success); updatePlayItemResponse(success); break; case MSG_NOW_PLAYING_ENTRIES_RECEIVED: Log.v(TAG, "MSG_NOW_PLAYING_ENTRIES_RECEIVED"); updateNowPlayingEntriesReceived((long [])msg.obj); break; case MSG_SET_TRANSPORT_CONTROLS: updateTransportControls(msg.arg2); break; case MSG_SET_GENERATION_ID: if (DEBUG) Log.v(TAG, "New genId = " + msg.arg1 + ", clearing = " + msg.arg2); break; case MESSAGE_GET_RC_FEATURES: { String address = (String) msg.obj; if (DEBUG) Log.v(TAG, "MESSAGE_GET_RC_FEATURES: address="+address+ ", features="+msg.arg1); BluetoothDevice device = mAdapter.getRemoteDevice(address); deviceIndex = getIndexForDevice(device); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.v(TAG,"device entry not present, bailing out"); return; } deviceFeatures[deviceIndex].mFeatures = msg.arg1; deviceFeatures[deviceIndex].isAbsoluteVolumeSupportingDevice = ((deviceFeatures[deviceIndex].mFeatures & BTRC_FEAT_ABSOLUTE_VOLUME) != 0); mAudioManager.avrcpSupportsAbsoluteVolume(device.getAddress(), isAbsoluteVolumeSupported()); Log.v(TAG," update audio manager for abs vol state = " + isAbsoluteVolumeSupported()); if ((deviceFeatures[deviceIndex].mFeatures & BTRC_FEAT_AVRC_UI_UPDATE) != 0) { int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth; Notification notification = new Notification.Builder(mContext) .setContentTitle("Bluetooth Media Browsing") .setContentText("Peer supports advanced feature") .setSubText("Re-pair from peer to enable it") .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth) .setDefaults(Notification.DEFAULT_ALL) .build(); NotificationManager manager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); manager.notify(NOTIFICATION_ID, notification); Log.v(TAG," update notification manager on remote repair request"); } break; } case MESSAGE_GET_PLAY_STATUS: { BluetoothDevice device; int playState, position; if (DEBUG) Log.v(TAG, "MESSAGE_GET_PLAY_STATUS"); Log.v(TAG, "Event for device address " + (String)msg.obj); device = mAdapter.getRemoteDevice((String) msg.obj); deviceIndex = getIndexForDevice(device); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.e(TAG,"Invalid device index for play status"); break; } playState = convertPlayStateToPlayStatus(deviceFeatures[deviceIndex].mCurrentPlayState); position = (int)getPlayPosition(device); Log.v(TAG, "Play Status for : " + device.getName() + " state: " + playState + " position: " + position); if (position == -1) { Log.v(TAG, "Force play postion to 0, for getPlayStatus Rsp"); position = 0; } getPlayStatusRspNative(playState, (int)mSongLengthMs, position, getByteAddress(device)); break; } case MESSAGE_GET_ELEM_ATTRS: { String[] textArray; int[] attrIds; byte numAttr = (byte) msg.arg1; ItemAttr itemAttr = (ItemAttr)msg.obj; Log.v(TAG, "event for device address " + itemAttr.mAddress); ArrayList attrList = itemAttr.mAttrList; if (DEBUG) Log.v(TAG, "MESSAGE_GET_ELEM_ATTRS:numAttr=" + numAttr); attrIds = new int[numAttr]; textArray = new String[numAttr]; for (int i = 0; i < numAttr; ++i) { attrIds[i] = attrList.get(i).intValue(); textArray[i] = getAttributeString(attrIds[i]); } getElementAttrRspNative(numAttr ,attrIds ,textArray , getByteAddress(mAdapter.getRemoteDevice(itemAttr.mAddress))); break; } case MESSAGE_REGISTER_NOTIFICATION: if (DEBUG) Log.v(TAG, "MESSAGE_REGISTER_NOTIFICATION:event=" + msg.arg1 + " param=" + msg.arg2); processRegisterNotification(msg.arg1, msg.arg2, (String) msg.obj); break; case MESSAGE_PLAY_INTERVAL_TIMEOUT: if (DEBUG) Log.v(TAG, "MESSAGE_PLAY_INTERVAL_TIMEOUT"); Log.v(TAG, "event for device address " + (BluetoothDevice)msg.obj); deviceIndex = getIndexForDevice((BluetoothDevice) msg.obj); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.e(TAG,"invalid index for device"); break; } deviceFeatures[deviceIndex].mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED; Log.v(TAG, "event for device address " + (BluetoothDevice) msg.obj); registerNotificationRspPlayPosNative(deviceFeatures[deviceIndex].mPlayPosChangedNT, (int)getPlayPosition((BluetoothDevice) msg.obj) , getByteAddress((BluetoothDevice) msg.obj)); break; case MESSAGE_SET_ADDR_PLAYER_REQ_TIMEOUT: if (DEBUG) Log.v(TAG, "setAddressedPlayer fails, Times out"); deviceIndex = getIndexForDevice(mAdapter.getRemoteDevice((String) msg.obj)); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.e(TAG,"invalid device index"); break; } Log.v(TAG, "event for device address " + (String)msg.obj); setAdressedPlayerRspNative((byte)PLAYER_NOT_ADDRESSED, getByteAddress(mAdapter.getRemoteDevice((String) msg.obj))); deviceFeatures[deviceIndex].mRequestedAddressedPlayerPackageName = null; break; case MESSAGE_VOLUME_CHANGED: if (DEBUG) Log.v(TAG, "MESSAGE_VOLUME_CHANGED: volume=" + ((byte)msg.arg1 & 0x7f) + " ctype=" + msg.arg2); Log.v(TAG, "event for device address " + (String)msg.obj); deviceIndex = getIndexForDevice(mAdapter.getRemoteDevice((String) msg.obj)); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.e(TAG,"invalid index for device"); break; } if (msg.arg2 == AVRC_RSP_ACCEPT || msg.arg2 == AVRC_RSP_REJ) { if (deviceFeatures[deviceIndex].mVolCmdInProgress == false) { Log.e(TAG, "Unsolicited response, ignored"); break; } removeMessages(MESSAGE_ABS_VOL_TIMEOUT); deviceFeatures[deviceIndex].mVolCmdInProgress = false; deviceFeatures[deviceIndex].mAbsVolRetryTimes = 0; } if (deviceFeatures[deviceIndex].mAbsoluteVolume != msg.arg1 && (msg.arg2 == AVRC_RSP_ACCEPT || msg.arg2 == AVRC_RSP_CHANGED || msg.arg2 == AVRC_RSP_INTERIM)) { byte absVol = (byte)((byte)msg.arg1 & 0x7f); // discard MSB as it is RFD deviceFeatures[deviceIndex].mAbsoluteVolume = absVol; long pecentVolChanged = ((long)absVol * 100) / 0x7f; Log.v(TAG, "Absolute Volume change received as: " + absVol); Log.v(TAG, "Percent volume changed: " + pecentVolChanged + "%"); if (isAbsoluteVolumeSupported() && deviceFeatures[deviceIndex].mAbsoluteVolume != -1) { Log.v(TAG," update audio manager for absolute volume = " + deviceFeatures[deviceIndex].mAbsoluteVolume); notifyVolumeChanged(deviceFeatures[deviceIndex].mAbsoluteVolume, deviceFeatures[deviceIndex].mCurrentDevice); } } else if (msg.arg2 == AVRC_RSP_REJ) { if (DEBUG) Log.v(TAG, "setAbsoluteVolume call rejected"); } break; case MESSAGE_ADJUST_VOLUME: { List playingDevice = mA2dpService.getA2dpPlayingDevice(); if (DEBUG) Log.d(TAG, "MESSAGE_ADJUST_VOLUME: direction=" + msg.arg1); for (int i = 0; i < playingDevice.size(); i++) { Log.v(TAG, "event for device address " + playingDevice.get(i).getAddress()); deviceIndex = getIndexForDevice(playingDevice.get(i)); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.e(TAG,"Unkown playing device"); sendAdjustVolume(msg.arg1); continue; } if (deviceFeatures[deviceIndex].mVolCmdInProgress) { if (DEBUG) Log.w(TAG, "already a volume command in progress" + "for this device."); continue; } // Wait on verification on volume from device, before changing the volume. if (deviceFeatures[deviceIndex].mAbsoluteVolume != -1 && (msg.arg1 == -1 || msg.arg1 == 1)) { int setVol = Math.min(AVRCP_MAX_VOL, Math.max(0, deviceFeatures[deviceIndex].mAbsoluteVolume + msg.arg1*mVolumeStep)); boolean isSetVol = setVolumeNative(setVol , getByteAddress(playingDevice.get(i))); if (isSetVol) { sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT, 0, 0, deviceFeatures[deviceIndex].mCurrentDevice), CMD_TIMEOUT_DELAY); deviceFeatures[deviceIndex].mVolCmdInProgress = true; deviceFeatures[deviceIndex].mLastDirection = msg.arg1; deviceFeatures[deviceIndex].mLastSetVolume = setVol; } } else { Log.e(TAG, "Unknown direction in MESSAGE_ADJUST_VOLUME"); } } break; } case MESSAGE_SET_ABSOLUTE_VOLUME: { if (DEBUG) Log.v(TAG, "MESSAGE_SET_ABSOLUTE_VOLUME"); List playingDevice = mA2dpService.getA2dpPlayingDevice(); if (playingDevice.size() == 0) { Log.e(TAG,"Volume cmd without a2dp playing"); } for (int i = 0; i < playingDevice.size(); i++) { deviceIndex = getIndexForDevice(playingDevice.get(i)); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.e(TAG,"Unkown playing device"); sendSetAbsoluteVolume(msg.arg1); continue; } Log.v(TAG, "event for device address " + playingDevice.get(i).getAddress()); if (deviceFeatures[deviceIndex].mVolCmdInProgress) { if (DEBUG) Log.w(TAG, "There is already a volume command in progress."); continue; } Log.v(TAG, "event for device address " + (String)msg.obj); boolean isSetVol = setVolumeNative(msg.arg1 , getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); if (isSetVol) { sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT, 0, 0, deviceFeatures[deviceIndex].mCurrentDevice), CMD_TIMEOUT_DELAY); deviceFeatures[deviceIndex].mVolCmdInProgress = true; deviceFeatures[deviceIndex].mLastSetVolume = msg.arg1; } } break; } case MESSAGE_ABS_VOL_TIMEOUT: if (DEBUG) Log.v(TAG, "MESSAGE_ABS_VOL_TIMEOUT: Volume change cmd timed out."); deviceIndex = getIndexForDevice((BluetoothDevice) msg.obj); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.e(TAG,"invalid device index for abs vol timeout"); for (int i = 0; i < maxAvrcpConnections; i++) { if (deviceFeatures[i].mVolCmdInProgress == true) deviceFeatures[i].mVolCmdInProgress = false; } break; } deviceFeatures[deviceIndex].mVolCmdInProgress = false; Log.v(TAG, "event for device address " + (BluetoothDevice)msg.obj); if (deviceFeatures[deviceIndex].mAbsVolRetryTimes >= MAX_ERROR_RETRY_TIMES) { deviceFeatures[deviceIndex].mAbsVolRetryTimes = 0; } else { deviceFeatures[deviceIndex].mAbsVolRetryTimes += 1; boolean isSetVol = setVolumeNative(deviceFeatures[deviceIndex].mLastSetVolume , getByteAddress((BluetoothDevice) msg.obj)); if (isSetVol) { sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT, 0, 0, msg.obj), CMD_TIMEOUT_DELAY); deviceFeatures[deviceIndex].mVolCmdInProgress = true; } } break; case MESSAGE_FAST_FORWARD: case MESSAGE_REWIND: if(msg.what == MESSAGE_FAST_FORWARD) { if((mTransportControlFlags & RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD) != 0) { int keyState = msg.arg1 == KEY_STATE_PRESS ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP; KeyEvent keyEvent = new KeyEvent(keyState, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD); mRemoteController.sendMediaKeyEvent(keyEvent); break; } } else if((mTransportControlFlags & RemoteControlClient.FLAG_KEY_MEDIA_REWIND) != 0) { int keyState = msg.arg1 == KEY_STATE_PRESS ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP; KeyEvent keyEvent = new KeyEvent(keyState, KeyEvent.KEYCODE_MEDIA_REWIND); mRemoteController.sendMediaKeyEvent(keyEvent); break; } int skipAmount; if (msg.what == MESSAGE_FAST_FORWARD) { if (DEBUG) Log.v(TAG, "MESSAGE_FAST_FORWARD"); removeMessages(MESSAGE_FAST_FORWARD); skipAmount = BASE_SKIP_AMOUNT; } else { if (DEBUG) Log.v(TAG, "MESSAGE_REWIND"); removeMessages(MESSAGE_REWIND); skipAmount = -BASE_SKIP_AMOUNT; } if (hasMessages(MESSAGE_CHANGE_PLAY_POS) && (skipAmount != mSkipAmount)) { Log.w(TAG, "missing release button event:" + mSkipAmount); } if ((!hasMessages(MESSAGE_CHANGE_PLAY_POS)) || (skipAmount != mSkipAmount)) { mSkipStartTime = SystemClock.elapsedRealtime(); } removeMessages(MESSAGE_CHANGE_PLAY_POS); if (msg.arg1 == KEY_STATE_PRESS) { mSkipAmount = skipAmount; changePositionBy(mSkipAmount * getSkipMultiplier(), (String)msg.obj); Message posMsg = obtainMessage(MESSAGE_CHANGE_PLAY_POS, 0, 0, msg.obj); posMsg.arg1 = 1; sendMessageDelayed(posMsg, SKIP_PERIOD); } break; case MESSAGE_CHANGE_PLAY_POS: if (DEBUG) Log.v(TAG, "MESSAGE_CHANGE_PLAY_POS:" + msg.arg1); changePositionBy(mSkipAmount * getSkipMultiplier(), (String)msg.obj); if (msg.arg1 * SKIP_PERIOD < BUTTON_TIMEOUT_TIME) { Message posMsg = obtainMessage(MESSAGE_CHANGE_PLAY_POS, 0, 0, msg.obj); posMsg.arg1 = msg.arg1 + 1; sendMessageDelayed(posMsg, SKIP_PERIOD); } break; case MESSAGE_SET_A2DP_AUDIO_STATE: if (DEBUG) Log.v(TAG, "MESSAGE_SET_A2DP_AUDIO_STATE:" + msg.arg1); BluetoothDevice playStateChangeDevice = (BluetoothDevice)msg.obj; Log.v(TAG, "event for device address " + playStateChangeDevice.getAddress()); deviceIndex = getIndexForDevice(playStateChangeDevice); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.e(TAG,"in valid index for device"); break; } updateA2dpAudioState(msg.arg1, (BluetoothDevice)msg.obj); break; case MSG_UPDATE_RCC_CHANGE: Log.v(TAG, "MSG_UPDATE_RCC_CHANGE"); String callingPackageName = (String)msg.obj; int isFocussed = msg.arg1; int isAvailable = msg.arg2; processRCCStateChange(callingPackageName, isFocussed, isAvailable); break; case MESSAGE_SET_ADDR_PLAYER: processSetAddressedPlayer(msg.arg1, (String) msg.obj); break; case MESSAGE_SET_BROWSED_PLAYER: processSetBrowsedPlayer(msg.arg1, (String) msg.obj); break; case MESSAGE_CHANGE_PATH: ItemAttr itemAttr = (ItemAttr)msg.obj; processChangePath(msg.arg1, itemAttr.mUid, itemAttr.mAddress); break; case MESSAGE_PLAY_ITEM: itemAttr = (ItemAttr)msg.obj; processPlayItem(msg.arg1, itemAttr.mUid, itemAttr.mAddress); break; case MESSAGE_GET_ITEM_ATTRS: int[] attrIds; itemAttr = (ItemAttr)msg.obj; attrIds = new int[msg.arg1]; for (int i = 0; i < msg.arg1; ++i) { attrIds[i] = itemAttr.mAttrList.get(i).intValue(); } processGetItemAttr((byte)msg.arg2, itemAttr.mUid, (byte)msg.arg1, attrIds, itemAttr.mAddress); break; case MESSAGE_GET_FOLDER_ITEMS: FolderListEntries folderListEntries = (FolderListEntries)msg.obj; attrIds = new int[folderListEntries.mNumAttr]; for (int i = 0; i < folderListEntries.mNumAttr; ++i) { attrIds[i] = folderListEntries.mAttrList.get(i).intValue(); } processGetFolderItems(folderListEntries.mScope, folderListEntries.mStart, folderListEntries.mEnd, folderListEntries.mAttrCnt, folderListEntries.mNumAttr, attrIds, folderListEntries.mAddress); break; } } } private void sendAdjustVolume(int val) { Log.v(TAG, "in sendAdjustVolume" + " " + val); for (int i = 0; i < maxAvrcpConnections; i++) { if (deviceFeatures[i].mCurrentDevice != null && ((deviceFeatures[i].mFeatures & BTRC_FEAT_ABSOLUTE_VOLUME) != 0)) { if (deviceFeatures[i].mAbsoluteVolume != -1 && (val == -1 || val == 1)) { int setVol = Math.min(AVRCP_MAX_VOL, Math.max(0, deviceFeatures[i].mAbsoluteVolume + val*mVolumeStep)); boolean isSetVol = setVolumeNative(setVol , getByteAddress((deviceFeatures[i].mCurrentDevice))); if (isSetVol) { mHandler.sendMessageDelayed(mHandler.obtainMessage(MESSAGE_ABS_VOL_TIMEOUT, 0, 0, deviceFeatures[i].mCurrentDevice), CMD_TIMEOUT_DELAY); deviceFeatures[i].mVolCmdInProgress = true; deviceFeatures[i].mLastDirection = val; deviceFeatures[i].mLastSetVolume = setVol; } } else { Log.e(TAG, "Unknown direction in MESSAGE_ADJUST_VOLUME"); } } } } private void sendSetAbsoluteVolume(int val) { Log.v(TAG, "in sendSetAbsoluteVolume " + " " + val); for (int i = 0; i < maxAvrcpConnections; i++) { if (deviceFeatures[i].mCurrentDevice != null && ((deviceFeatures[i].mFeatures & BTRC_FEAT_ABSOLUTE_VOLUME) != 0)) { Log.v(TAG, "in sending for device " + deviceFeatures[i].mCurrentDevice); boolean isSetVol = setVolumeNative(val , getByteAddress((deviceFeatures[i].mCurrentDevice))); if (isSetVol) { mHandler.sendMessageDelayed(mHandler.obtainMessage(MESSAGE_ABS_VOL_TIMEOUT, 0, 0, deviceFeatures[i].mCurrentDevice), CMD_TIMEOUT_DELAY); deviceFeatures[i].mVolCmdInProgress = true; deviceFeatures[i].mLastSetVolume = val; } } } } private void updateA2dpAudioState(int state, BluetoothDevice device) { boolean isPlaying = (state == BluetoothA2dp.STATE_PLAYING); Log.v(TAG,"updateA2dpAudioState"); if ((isPlaying) && !mAudioManager.isMusicActive()) { /* Play state to be updated only for music streaming, not touchtone */ Log.v(TAG,"updateA2dpAudioState: Stream state not active "); return; } for (int i = 0; i < maxAvrcpConnections; i++) { if ((isPlaying != isPlayingState(deviceFeatures[i].mCurrentPlayState)) && (device.equals(deviceFeatures[i].mCurrentDevice))) { updatePlayPauseState(isPlaying ? RemoteControlClient.PLAYSTATE_PLAYING : RemoteControlClient.PLAYSTATE_PAUSED, RemoteControlClient.PLAYBACK_POSITION_INVALID, device); break; } } } private void updateResetNotificationForDevice(BluetoothDevice device, int index) { Log.i(TAG,"in updateResetNotificationForDevice " + device + " index " + index); if (deviceFeatures[index].mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM) { if (DEBUG) Log.v(TAG, "send Play Position reject to stack"); deviceFeatures[index].mPlayPosChangedNT = NOTIFICATION_TYPE_REJECT; registerNotificationRspPlayPosNative(deviceFeatures[index].mPlayPosChangedNT, -1 ,getByteAddress(device)); mHandler.removeMessages(MESSAGE_PLAY_INTERVAL_TIMEOUT); } else { Log.v(TAG,"index " + index + " status is"+ deviceFeatures[index].mPlayPosChangedNT); } } private void updatePlayPauseState(int state, long currentPosMs, BluetoothDevice device) { Log.v(TAG,"updatePlayPauseState, state: " + state + " device: " + device); for (int i = 0; i < maxAvrcpConnections; i++) { Log.v(TAG,"Device: " + ((deviceFeatures[i].mCurrentDevice == null) ? "no name: " : deviceFeatures[i].mCurrentDevice.getName() + " : old state: " + deviceFeatures[i].mCurrentPlayState)); } if (device == null) { /*Called because of player state change*/ updatePlayerStateAndPosition(state, currentPosMs); return; } else { int deviceIndex = getIndexForDevice(device); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.w(TAG,"invalid device index" + "Play status change for not connected device"); } else { Log.v(TAG, "old state: " + deviceFeatures[deviceIndex].mCurrentPlayState + " new state: " + state + " device: " + device + " index: " + deviceIndex); updatePlayStatusForDevice(deviceIndex, state); } } } private void updatePlayStatusForDevice(int deviceIndex,int state) { Log.i(TAG,"updatePlayStatusForDevice: device: " + deviceFeatures[deviceIndex].mCurrentDevice); int oldPlayStatus = convertPlayStateToPlayStatus( deviceFeatures[deviceIndex].mCurrentPlayState); int newPlayStatus = convertPlayStateToPlayStatus(state); if (DEBUG) Log.v(TAG, "oldPlayStatus " + oldPlayStatus); if (DEBUG) Log.v(TAG, "newPlayStatus " + newPlayStatus); deviceFeatures[deviceIndex].mCurrentPlayState = state; if ((deviceFeatures[deviceIndex].mPlayStatusChangedNT == NOTIFICATION_TYPE_INTERIM) && (oldPlayStatus != newPlayStatus)) { deviceFeatures[deviceIndex].mPlayStatusChangedNT = NOTIFICATION_TYPE_CHANGED; registerNotificationRspPlayStatusNative( deviceFeatures[deviceIndex].mPlayStatusChangedNT, newPlayStatus, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } } private void updatePlayerStateAndPosition(int state, long currentPosMs) { if (DEBUG) Log.v(TAG, "updatePlayerPlayPauseState, old=" + mCurrentPlayerState + ", state=" + state); boolean oldPosValid = (mCurrentPosMs != RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN); if (DEBUG) Log.v(TAG, "old state = " + mCurrentPlayerState + ", new state= " + state); int oldPlayStatus = convertPlayStateToPlayStatus(mCurrentPlayerState); int newPlayStatus = convertPlayStateToPlayStatus(state); if ((mCurrentPlayerState == RemoteControlClient.PLAYSTATE_PLAYING) && (mCurrentPlayerState != state) && oldPosValid) { mCurrentPosMs = getPlayPosition(null); Log.d(TAG, "Update mCurrentPosMs to " + mCurrentPosMs); } if ((state == RemoteControlClient.PLAYSTATE_PLAYING) && (mCurrentPlayerState != state)) { mPlayStartTimeMs = SystemClock.elapsedRealtime(); Log.d(TAG, "Update mPlayStartTimeMs to " + mPlayStartTimeMs); } mCurrentPlayerState = state; if (!(RemoteControlClient.PLAYSTATE_PLAYING == mCurrentPlayerState && mCurrentPosMs == currentPosMs)) { if (currentPosMs != RemoteControlClient.PLAYBACK_POSITION_INVALID) { mCurrentPosMs = currentPosMs; mPlayStartTimeMs = SystemClock.elapsedRealtime(); Log.d(TAG, "Update mPlayStartTimeMs: " + mPlayStartTimeMs + " mCurrentPosMs: " + mCurrentPosMs); } } boolean newPosValid = (mCurrentPosMs != RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN); long playPosition = getPlayPosition(null); mHandler.removeMessages(MESSAGE_PLAY_INTERVAL_TIMEOUT); for (int deviceIndex = 0; deviceIndex < maxAvrcpConnections; deviceIndex++) { if (deviceFeatures[deviceIndex].mCurrentDevice != null && ((deviceFeatures[deviceIndex].mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM) && ((oldPlayStatus != newPlayStatus) || (oldPosValid != newPosValid) || (newPosValid && ((playPosition >= deviceFeatures[deviceIndex].mNextPosMs) || (playPosition <= deviceFeatures[deviceIndex].mPrevPosMs)))))) { deviceFeatures[deviceIndex].mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED; registerNotificationRspPlayPosNative(deviceFeatures[deviceIndex].mPlayPosChangedNT, (int)playPosition, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } if (deviceFeatures[deviceIndex].mCurrentDevice != null && ((deviceFeatures[deviceIndex].mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM) && newPosValid && (state == RemoteControlClient.PLAYSTATE_PLAYING))) { Message msg = mHandler.obtainMessage(MESSAGE_PLAY_INTERVAL_TIMEOUT, 0, 0, deviceFeatures[deviceIndex].mCurrentDevice); mHandler.sendMessageDelayed(msg, deviceFeatures[deviceIndex].mNextPosMs - playPosition); } /*Discretion is required only when updating play state changed as playing*/ if ((state != RemoteControlClient.PLAYSTATE_PLAYING) || isPlayStateToBeUpdated(deviceIndex)) { updatePlayStatusForDevice(deviceIndex, state); } } } private boolean isPlayStateToBeUpdated(int deviceIndex) { Log.v(TAG, "isPlayStateTobeUpdated: device: " + deviceFeatures[deviceIndex].mCurrentDevice); if (maxAvrcpConnections < 2) { Log.v(TAG, "maxAvrcpConnections: " + maxAvrcpConnections); return true; } else if(mA2dpService.isMulticastFeatureEnabled()) { if (!areMultipleDevicesConnected()) { Log.v(TAG, "Single connection exists"); return true; } else if (mA2dpService.isMulticastEnabled()) { Log.v(TAG, "Multicast is Enabled"); return true; } else { Log.v(TAG, "Multiple connection exists, Multicast not enabled"); if(isDeviceActiveInHandOffNative(getByteAddress( deviceFeatures[deviceIndex].mCurrentDevice))) { Log.v(TAG, "Device Active in handoff scenario"); return true; } else { Log.v(TAG, "Device Not Active in handoff scenario"); return false; } } } else { if (!areMultipleDevicesConnected()) { Log.v(TAG, "Single connection exists"); return true; } else { Log.v(TAG, "Multiple connection exists in handoff"); if(isDeviceActiveInHandOffNative(getByteAddress( deviceFeatures[deviceIndex].mCurrentDevice))) { Log.v(TAG, "Device Active in handoff scenario"); return true; } else { Log.v(TAG, "Device Not Active in handoff scenario"); return false; } } } } private boolean areMultipleDevicesConnected() { for (int deviceIndex = 0; deviceIndex < maxAvrcpConnections; deviceIndex++) { if (deviceFeatures[deviceIndex].mCurrentDevice == null) { return false; } } return true; } private void updateTransportControls(int transportControlFlags) { mTransportControlFlags = transportControlFlags; } private void updateAvailableMediaPlayers() { /* for registerged notification check for all devices which has * registered for change notification */ if (DEBUG) Log.v(TAG, "updateAvailableMediaPlayers"); for (int i = 0; i < maxAvrcpConnections; i++) { if (deviceFeatures[i].mAvailablePlayersChangedNT == NOTIFICATION_TYPE_INTERIM) { deviceFeatures[i].mAvailablePlayersChangedNT = NOTIFICATION_TYPE_CHANGED; if (DEBUG) Log.v(TAG, "send AvailableMediaPlayers to stack"); registerNotificationRspAvailablePlayersChangedNative( deviceFeatures[i].mAvailablePlayersChangedNT, getByteAddress(deviceFeatures[i].mCurrentDevice)); } } } private void updateAddressedMediaPlayer(int playerId) { Log.v(TAG, "updateAddressedMediaPlayer"); Log.v(TAG, "current Player: " + mAddressedPlayerId); Log.v(TAG, "Requested Player: " + playerId); int previousAddressedPlayerId; for (int i = 0; i < maxAvrcpConnections; i++) { if ((deviceFeatures[i].mAddressedPlayerChangedNT == NOTIFICATION_TYPE_INTERIM) && (mAddressedPlayerId != playerId)) { if (DEBUG) Log.v(TAG, "send AddressedMediaPlayer to stack: playerId" + playerId); previousAddressedPlayerId = mAddressedPlayerId; deviceFeatures[i].mAddressedPlayerChangedNT = NOTIFICATION_TYPE_CHANGED; registerNotificationRspAddressedPlayerChangedNative( deviceFeatures[i].mAddressedPlayerChangedNT, playerId, getByteAddress(deviceFeatures[i].mCurrentDevice)); if (previousAddressedPlayerId != INVALID_ADDRESSED_PLAYER_ID) { resetAndSendPlayerStatusReject(); } } else { if (DEBUG) Log.v(TAG, "Do not reset notifications, ADDR_PLAYR_CHNGD not registered"); } } mAddressedPlayerId = playerId; } public void updateResetNotification(int notificationType) { Log.v(TAG,"notificationType " + notificationType); for (int i = 0; i < maxAvrcpConnections; i++) { switch (notificationType) { case PLAY_STATUS_CHANGE_NOTIFICATION: if (deviceFeatures[i].mPlayStatusChangedNT == NOTIFICATION_TYPE_INTERIM) { deviceFeatures[i].mPlayStatusChangedNT = NOTIFICATION_TYPE_REJECT; registerNotificationRspPlayStatusNative( deviceFeatures[i].mPlayStatusChangedNT, PLAYSTATUS_STOPPED, getByteAddress(deviceFeatures[i].mCurrentDevice)); } else { Log.v(TAG,"i " + i + " status is"+ deviceFeatures[i].mPlayStatusChangedNT); } break; case PLAY_POSITION_CHANGE_NOTIFICATION: if (deviceFeatures[i].mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM) { if (DEBUG) Log.v(TAG, "send Play Position reject to stack"); deviceFeatures[i].mPlayPosChangedNT = NOTIFICATION_TYPE_REJECT; registerNotificationRspPlayPosNative( deviceFeatures[i].mPlayPosChangedNT, -1 ,getByteAddress(deviceFeatures[i].mCurrentDevice)); mHandler.removeMessages(MESSAGE_PLAY_INTERVAL_TIMEOUT); } else { Log.v(TAG,"i " + i + " status is"+ deviceFeatures[i].mPlayPosChangedNT); } break; case TRACK_CHANGE_NOTIFICATION: if (deviceFeatures[i].mTrackChangedNT == NOTIFICATION_TYPE_INTERIM) { if (DEBUG) Log.v(TAG, "send Track Changed reject to stack"); deviceFeatures[i].mTrackChangedNT = NOTIFICATION_TYPE_REJECT; byte[] track = new byte[TRACK_ID_SIZE]; /* track is stored in big endian format */ for (int j = 0; j < TRACK_ID_SIZE; ++j) { track[j] = (byte) (mTrackNumber >> (56 - 8 * j)); } registerNotificationRspTrackChangeNative( deviceFeatures[i].mTrackChangedNT , track ,getByteAddress(deviceFeatures[i].mCurrentDevice)); } else { Log.v(TAG,"i " + i + " status is"+ deviceFeatures[i].mTrackChangedNT); } break; case NOW_PALYING_CONTENT_CHANGED_NOTIFICATION: if (deviceFeatures[i].mNowPlayingContentChangedNT == NOTIFICATION_TYPE_INTERIM) { if (DEBUG) Log.v(TAG, "send Now playing changed reject to stack"); deviceFeatures[i].mNowPlayingContentChangedNT = NOTIFICATION_TYPE_REJECT; registerNotificationRspNowPlayingContentChangedNative( deviceFeatures[i].mNowPlayingContentChangedNT , getByteAddress(deviceFeatures[i].mCurrentDevice)); } else { Log.v(TAG,"i " + i + " status is"+ deviceFeatures[i].mNowPlayingContentChangedNT); } break; default : Log.e(TAG,"Invalid Notification type "); } } } private void resetAndSendPlayerStatusReject() { if (DEBUG) Log.v(TAG, "resetAndSendPlayerStatusReject"); updateResetNotification(PLAY_STATUS_CHANGE_NOTIFICATION); updateResetNotification(PLAY_POSITION_CHANGE_NOTIFICATION); updateResetNotification(TRACK_CHANGE_NOTIFICATION); updateResetNotification(NOW_PALYING_CONTENT_CHANGED_NOTIFICATION); } void updateBrowsedPlayerFolder(int numOfItems, int status, String[] folderNames) { Log.v(TAG, "updateBrowsedPlayerFolder: numOfItems = " + numOfItems + " status = " + status); if (mBrowserDevice == null) { Log.e(TAG,"mBrowserDevice is null for music player called api"); } BluetoothDevice device = mBrowserDevice; int deviceIndex = getIndexForDevice(device); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.e(TAG,"invalid index for device"); return; } deviceFeatures[deviceIndex].mCurrentPath = PATH_ROOT; deviceFeatures[deviceIndex].mCurrentPathUid = null; deviceFeatures[deviceIndex].mMediaUri = mMediaUriStatic; mMediaUriStatic = null; setBrowsedPlayerRspNative((byte)status, 0x0, numOfItems, 0x0, CHAR_SET_UTF8, folderNames, getByteAddress(device)); mBrowserDevice = null; } void updateNowPlayingContentChanged() { Log.v(TAG, "updateNowPlayingContentChanged"); for (int i = 0; i < maxAvrcpConnections; i++) { if (deviceFeatures[i].mNowPlayingContentChangedNT == NOTIFICATION_TYPE_INTERIM) { Log.v(TAG, "Notify peer on updateNowPlayingContentChanged"); deviceFeatures[i].mNowPlayingContentChangedNT = NOTIFICATION_TYPE_CHANGED; registerNotificationRspNowPlayingContentChangedNative( deviceFeatures[i].mNowPlayingContentChangedNT , getByteAddress(deviceFeatures[i].mCurrentDevice)); } } } void updatePlayItemResponse(boolean success) { Log.v(TAG, "updatePlayItemResponse: success: " + success); BluetoothDevice device = mBrowserDevice; if (mBrowserDevice == null) { Log.e(TAG, "mBrowserDevice is null for music app called API"); } /* add member for getting current setting get play item pending rsp * and clear it when this is recieved */ if (success) { playItemRspNative(OPERATION_SUCCESSFUL , getByteAddress(device)); } else { playItemRspNative(INTERNAL_ERROR , getByteAddress(device)); } mBrowserDevice = null; } void updateNowPlayingEntriesReceived(long[] playList) { Log.e(TAG,"updateNowPlayingEntriesReceived called"); int status = OPERATION_SUCCESSFUL; int numItems = 0; long reqItems = (mCachedRequest.mEnd - mCachedRequest.mStart) + 1; long availableItems = 0; Cursor cursor = null; int[] itemType = new int[MAX_BROWSE_ITEM_TO_SEND]; long[] uid = new long[MAX_BROWSE_ITEM_TO_SEND]; int[] type = new int[MAX_BROWSE_ITEM_TO_SEND]; byte[] playable = new byte[MAX_BROWSE_ITEM_TO_SEND]; String[] displayName = new String[MAX_BROWSE_ITEM_TO_SEND]; byte[] numAtt = new byte[MAX_BROWSE_ITEM_TO_SEND]; String[] attValues = new String[MAX_BROWSE_ITEM_TO_SEND * 7]; int[] attIds = new int[MAX_BROWSE_ITEM_TO_SEND * 7]; int index; if (mBrowserDevice == null) { Log.e(TAG,"mBrowserDevice is null for music app called API"); } BluetoothDevice device = mBrowserDevice; int deviceIndex = getIndexForDevice(device); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.e(TAG,"invalid device index"); return; } Log.v(TAG, "updateNowPlayingEntriesReceived"); // Item specific attribute's entry starts from index*7, reset all such entries to 0 for now for (int count = 0; count < (MAX_BROWSE_ITEM_TO_SEND * 7); count++) { attValues[count] = ""; attIds[count] = 0; } availableItems = playList.length; if ((mCachedRequest.mStart + 1) > availableItems) { Log.i(TAG, "startIteam exceeds the available item index"); getFolderItemsRspNative((byte)RANGE_OUT_OF_BOUNDS , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); return; } if ((mCachedRequest.mStart < 0) || (mCachedRequest.mEnd < 0) || (mCachedRequest.mStart > mCachedRequest.mEnd)) { Log.i(TAG, "wrong start / end index"); getFolderItemsRspNative((byte)RANGE_OUT_OF_BOUNDS , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); return; } availableItems = availableItems - mCachedRequest.mStart; Log.i(TAG, "start Index: " + mCachedRequest.mStart); Log.i(TAG, "end Index: " + mCachedRequest.mEnd); Log.i(TAG, "availableItems: " + availableItems); if (availableItems > MAX_BROWSE_ITEM_TO_SEND) availableItems = MAX_BROWSE_ITEM_TO_SEND; if (reqItems > availableItems) reqItems = availableItems; Log.i(TAG, "reqItems: " + reqItems); for (index = 0; index < reqItems; index++) { try { cursor = mContext.getContentResolver().query( deviceFeatures[deviceIndex].mMediaUri, mCursorCols, MediaStore.Audio.Media.IS_MUSIC + "=1 AND _id=" + playList[index + (int)mCachedRequest.mStart], null, null); if (cursor != null) { int validAttrib = 0; cursor.moveToFirst(); itemType[index] = TYPE_MEDIA_ELEMENT_ITEM; uid[index] = cursor.getLong(cursor.getColumnIndexOrThrow("_id")); type[index] = MEDIA_TYPE_AUDIO; playable[index] = 0; displayName[index] = cursor.getString(cursor.getColumnIndexOrThrow( MediaStore.Audio.Media.TITLE)); for (int attIndex = 0; attIndex < mCachedRequest.mAttrCnt; attIndex++) { int attr = mCachedRequest.mAttrList.get(attIndex).intValue(); if ((attr <= MEDIA_ATTR_MAX) && (attr >= MEDIA_ATTR_MIN)) { attValues[(7 * index) + attIndex] = getAttributeStringFromCursor( cursor, attr, deviceIndex); attIds[(7 * index) + attIndex] = attr; validAttrib ++; } } numAtt[index] = (byte)validAttrib; } } catch(Exception e) { Log.i(TAG, "Exception e"+ e); getFolderItemsRspNative((byte)INTERNAL_ERROR , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } finally { if (cursor != null) { cursor.close(); } } } numItems = index; getFolderItemsRspNative((byte)OPERATION_SUCCESSFUL , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); mBrowserDevice = null; } class CachedRequest { long mStart; long mEnd; byte mAttrCnt; ArrayList mAttrList; public CachedRequest(long start, long end, byte attrCnt, int[] attrs) { mStart = start; mEnd = end; mAttrCnt = attrCnt; mAttrList = new ArrayList(); for (int i = 0; i < attrCnt; ++i) { mAttrList.add(new Integer(attrs[i])); } } } class FolderListEntries { byte mScope; long mStart; long mEnd; int mAttrCnt; int mNumAttr; String mAddress; ArrayList mAttrList; public FolderListEntries(byte scope, long start, long end, int attrCnt, int numAttr, int[] attrs, String deviceAddress) { mScope = scope; mStart = start; mEnd = end; mAttrCnt = attrCnt; mNumAttr = numAttr; mAddress = deviceAddress; int i; mAttrList = new ArrayList(); for (i = 0; i < numAttr; ++i) { mAttrList.add(new Integer(attrs[i])); } } } class Metadata { private String artist; private String trackTitle; private String albumTitle; private String genre; private long tracknum; public Metadata() { artist = null; trackTitle = null; albumTitle = null; genre = null; tracknum = -1L; } public String toString() { return "Metadata[artist=" + artist + " trackTitle=" + trackTitle + " albumTitle=" + albumTitle + " genre=" + genre + " tracknum=" + Long.toString(tracknum) + "]"; } } private void updateMetadata(MetadataEditor data) { if (DEBUG) Log.v(TAG, "updateMetadata"); String oldMetadata = mMetadata.toString(); mMetadata.artist = data.getString(MediaMetadataRetriever.METADATA_KEY_ARTIST, null); mMetadata.trackTitle = data.getString(MediaMetadataRetriever.METADATA_KEY_TITLE, null); mMetadata.albumTitle = data.getString(MediaMetadataRetriever.METADATA_KEY_ALBUM, null); mMetadata.genre = data.getString(MediaMetadataRetriever.METADATA_KEY_GENRE, null); mTrackNumber = data.getLong(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS, 0L); mMetadata.tracknum = data.getLong(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER, 0L); Log.v(TAG,"old Metadata = " + oldMetadata); Log.v(TAG,"new MetaData " + mMetadata.toString()); if (!oldMetadata.equals(mMetadata.toString())) { Log.v(TAG,"new mMetadata, mTrackNumber update to " + mTrackNumber); for (int i = 0; i < maxAvrcpConnections; i++) { if ((deviceFeatures[i].mCurrentDevice != null) && (deviceFeatures[i].mTrackChangedNT == NOTIFICATION_TYPE_INTERIM)) { deviceFeatures[i].mTrackChangedNT = NOTIFICATION_TYPE_CHANGED; Log.v(TAG,"sending track change for device " + i); sendTrackChangedRsp(deviceFeatures[i].mCurrentDevice); } } if (mCurrentPosMs != RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) { mCurrentPosMs = 0L; for (int i = 0; i < maxAvrcpConnections; i++) { if ((deviceFeatures[i].mCurrentDevice != null) && (deviceFeatures[i].mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING)) { Log.i(TAG,"updated mPlayStartTimeMs"); mPlayStartTimeMs = SystemClock.elapsedRealtime(); break; } } } /* need send play position changed notification when track is changed */ for (int i = 0; i < maxAvrcpConnections; i++) { Log.v(TAG,i + " mCurrentPlayState " + deviceFeatures[i].mCurrentPlayState); if (deviceFeatures[i].mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM && deviceFeatures[i].mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING) { Log.v(TAG,"sending play pos change for device " + i); deviceFeatures[i].mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED; registerNotificationRspPlayPosNative(deviceFeatures[i].mPlayPosChangedNT, (int)getPlayPosition(deviceFeatures[i].mCurrentDevice) , getByteAddress(deviceFeatures[i].mCurrentDevice)); mHandler.removeMessages(MESSAGE_PLAY_INTERVAL_TIMEOUT); } } } if (DEBUG) Log.v(TAG, "mMetadata=" + mMetadata.toString()); mSongLengthMs = data.getLong(MediaMetadataRetriever.METADATA_KEY_DURATION, RemoteControlClient.PLAYBACK_POSITION_INVALID); if (DEBUG) Log.v(TAG, "duration=" + mSongLengthMs); } private void getRcFeatures(byte[] address, int features) { Message msg = mHandler.obtainMessage(MESSAGE_GET_RC_FEATURES, features, 0, Utils.getAddressStringFromByte(address)); mHandler.sendMessage(msg); } private void getPlayStatus(byte[] address) { Message msg = mHandler.obtainMessage(MESSAGE_GET_PLAY_STATUS, 0, 0, Utils.getAddressStringFromByte(address)); mHandler.sendMessage(msg); } private void getElementAttr(byte numAttr, int[] attrs, byte[] address) { int i; ArrayList attrList = new ArrayList(); for (i = 0; i < numAttr; ++i) { attrList.add(attrs[i]); } ItemAttr itemAttr = new ItemAttr(attrList, 0, Utils.getAddressStringFromByte(address)); Message msg = mHandler.obtainMessage(MESSAGE_GET_ELEM_ATTRS, (int)numAttr, 0, itemAttr); mHandler.sendMessage(msg); } private void setBrowsedPlayer(int playerId, byte[] address) { if (DEBUG) Log.v(TAG, "setBrowsedPlayer: PlayerID: " + playerId); Message msg = mHandler.obtainMessage(MESSAGE_SET_BROWSED_PLAYER, playerId, 0, Utils.getAddressStringFromByte(address)); mHandler.sendMessage(msg); } private void processSetBrowsedPlayer(int playerId, String deviceAddress) { String packageName = null; byte retError = INVALID_PLAYER_ID; BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress); int deviceIndex = getIndexForDevice(device); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.v(TAG,"device entry not present, bailing out"); return; } /* Following gets updated if SetBrowsed Player succeeds */ deviceFeatures[deviceIndex].mCurrentPath = PATH_INVALID; deviceFeatures[deviceIndex].mMediaUri = Uri.EMPTY; deviceFeatures[deviceIndex].mCurrentPathUid = null; if (DEBUG) Log.v(TAG, "processSetBrowsedPlayer: PlayerID: " + playerId); if (mMediaPlayers.size() > 0) { final Iterator rccIterator = mMediaPlayers.iterator(); while (rccIterator.hasNext()) { final MediaPlayerInfo di = rccIterator.next(); if (di.RetrievePlayerId() == playerId) { if (di.GetPlayerAvailablility()) { if (DEBUG) Log.v(TAG, "player found and available"); if (di.IsPlayerBrowsable()) { if (di.IsPlayerBrowsableWhenAddressed()) { if (di.GetPlayerFocus()) { packageName = di.RetrievePlayerPackageName(); if (DEBUG) Log.v(TAG, "player addressed hence browsable"); } else { if (DEBUG) Log.v(TAG, "Reject: player browsable only" + "when addressed"); retError = PLAYER_NOT_ADDRESSED; } } else { packageName = di.RetrievePlayerPackageName(); } } else { retError = PLAYER_NOT_BROWSABLE; } } } } } if (packageName != null) { mRemoteController.setRemoteControlClientBrowsedPlayer(); mBrowserDevice = device; } else { if (DEBUG) Log.v(TAG, "player not available for browse"); setBrowsedPlayerRspNative(retError , 0x0, 0x0, 0x0, 0x0, null, getByteAddress(device)); } } private void fastForward(int keyState, String deviceAddress) { BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress); int deviceIndex = getIndexForDevice(device); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.e(TAG,"invalid index for device"); return; } if ((keyState == deviceFeatures[deviceIndex].keyPressState) && (keyState == KEY_STATE_RELEASE)) { Log.e(TAG, "Ignore key release event"); } else { Message msg = mHandler.obtainMessage(MESSAGE_FAST_FORWARD, keyState, 0, deviceAddress); mHandler.sendMessage(msg); deviceFeatures[deviceIndex].keyPressState = keyState; } } private void rewind(int keyState, String deviceAddress) { BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress); int deviceIndex = getIndexForDevice(device); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.e(TAG,"invalid index for device"); return; } if ((keyState == deviceFeatures[deviceIndex].keyPressState) && (keyState == KEY_STATE_RELEASE)) { Log.e(TAG, "Ignore key release event"); } else { Message msg = mHandler.obtainMessage(MESSAGE_REWIND, keyState, 0, deviceAddress); mHandler.sendMessage(msg); deviceFeatures[deviceIndex].keyPressState = keyState; } } private void changePath(byte direction, long uid, byte[] address) { if (DEBUG) Log.v(TAG, "changePath: direction: " + direction + " uid:" + uid); ItemAttr itemAttr = new ItemAttr(null, uid, Utils.getAddressStringFromByte(address)); Message msg = mHandler.obtainMessage(MESSAGE_CHANGE_PATH, direction, 0, itemAttr); mHandler.sendMessage(msg); } private void processChangePath(int direction, long folderUid, String deviceAddress) { if (DEBUG) Log.v(TAG, "processChangePath: direction: " + direction + " uid:" + folderUid); long numberOfItems = 0; int status = OPERATION_SUCCESSFUL; BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress); int deviceIndex = getIndexForDevice(device); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.v(TAG,"device entry not present, bailing out"); return; } if (deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_ROOT)){ switch (direction) { case FOLDER_UP: status = DOES_NOT_EXIST; break; case FOLDER_DOWN: if (folderUid == UID_TITLES) { deviceFeatures[deviceIndex].mCurrentPath = PATH_TITLES; numberOfItems = getNumItems(PATH_TITLES, MediaStore.Audio.Media.TITLE, deviceIndex); } else if (folderUid == UID_ALBUM) { deviceFeatures[deviceIndex].mCurrentPath = PATH_ALBUMS; numberOfItems = getNumItems(PATH_ALBUMS, MediaStore.Audio.Media.ALBUM_ID, deviceIndex); } else if (folderUid == UID_ARTIST) { deviceFeatures[deviceIndex].mCurrentPath = PATH_ARTISTS; numberOfItems = getNumItems(PATH_ARTISTS, MediaStore.Audio.Media.ARTIST_ID, deviceIndex); } else if (folderUid == UID_PLAYLIST) { deviceFeatures[deviceIndex].mCurrentPath = PATH_PLAYLISTS; numberOfItems = getNumPlaylistItems(); } else { status = DOES_NOT_EXIST; } break; default: status = INVALID_DIRECTION; break; } } else if (deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_TITLES)) { switch (direction) { case FOLDER_UP: deviceFeatures[deviceIndex].mCurrentPath = PATH_ROOT; numberOfItems = NUM_ROOT_ELEMENTS; break; case FOLDER_DOWN: Cursor cursor = null; try { cursor = mContext.getContentResolver().query( deviceFeatures[deviceIndex].mMediaUri, new String[] {MediaStore.Audio.Media.TITLE}, MediaStore.Audio.Media.IS_MUSIC + "=1 AND _id=" + folderUid, null, null); if (cursor != null) status = NOT_A_DIRECTORY; else status = DOES_NOT_EXIST; } catch (Exception e) { Log.e(TAG, "Exception " + e); changePathRspNative(INTERNAL_ERROR , numberOfItems , getByteAddress(device)); } finally { if (cursor != null) { cursor.close(); } } break; default: status = INVALID_DIRECTION; break; } } else if (deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_ALBUMS)) { switch (direction) { case FOLDER_UP: if (deviceFeatures[deviceIndex].mCurrentPathUid == null) { // Path @ Album deviceFeatures[deviceIndex].mCurrentPath = PATH_ROOT; numberOfItems = NUM_ROOT_ELEMENTS; } else { // Path @ individual album id deviceFeatures[deviceIndex].mCurrentPath = PATH_ALBUMS; deviceFeatures[deviceIndex].mCurrentPathUid = null; numberOfItems = getNumItems(PATH_ALBUMS, MediaStore.Audio.Media.ALBUM_ID, deviceIndex); } break; case FOLDER_DOWN: if (deviceFeatures[deviceIndex].mCurrentPathUid == null) { // Path @ Album Cursor cursor = null; try { cursor = mContext.getContentResolver().query( deviceFeatures[deviceIndex].mMediaUri, new String[] {MediaStore.Audio.Media.ALBUM}, MediaStore.Audio.Media.IS_MUSIC + "=1 AND " + MediaStore.Audio.Media.ALBUM_ID + "=" + folderUid, null, null); if ((cursor == null) || (cursor.getCount() == 0)) { status = DOES_NOT_EXIST; } else{ numberOfItems = cursor.getCount(); deviceFeatures[deviceIndex].mCurrentPathUid = String.valueOf(folderUid); } } catch (Exception e) { Log.e(TAG, "Exception " + e); changePathRspNative(INTERNAL_ERROR , numberOfItems , getByteAddress(device)); } finally { if (cursor != null) { cursor.close(); } } } else { // Path @ Individual Album id Cursor cursor = null; try { cursor = mContext.getContentResolver().query( deviceFeatures[deviceIndex].mMediaUri, new String[] {MediaStore.Audio.Media.TITLE}, MediaStore.Audio.Media.IS_MUSIC + "=1 AND _id=" + folderUid, null, null); /* As Individual Album path can not have any folder in it hence return * the error as applicable, depending on whether uid passed * exists. */ if (cursor != null) status = NOT_A_DIRECTORY; else status = DOES_NOT_EXIST; } catch (Exception e) { Log.e(TAG, "Exception " + e); changePathRspNative(INTERNAL_ERROR , numberOfItems , getByteAddress(device)); } finally { if (cursor != null) { cursor.close(); } } } break; default: status = INVALID_DIRECTION; break; } } else if (deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_ARTISTS)) { switch(direction) { case FOLDER_UP: if (deviceFeatures[deviceIndex].mCurrentPathUid == null) { deviceFeatures[deviceIndex].mCurrentPath = PATH_ROOT; numberOfItems = NUM_ROOT_ELEMENTS; } else { deviceFeatures[deviceIndex].mCurrentPath = PATH_ARTISTS; deviceFeatures[deviceIndex].mCurrentPathUid = null; numberOfItems = getNumItems(PATH_ARTISTS, MediaStore.Audio.Media.ARTIST_ID, deviceIndex); } break; case FOLDER_DOWN: if (deviceFeatures[deviceIndex].mCurrentPathUid == null) { Cursor cursor = null; try { cursor = mContext.getContentResolver().query( deviceFeatures[deviceIndex].mMediaUri, new String[] {MediaStore.Audio.Media.ARTIST}, MediaStore.Audio.Media.IS_MUSIC + "=1 AND " + MediaStore.Audio.Media.ARTIST_ID + "=" + folderUid, null, null); if ((cursor == null) || (cursor.getCount() == 0)) { status = DOES_NOT_EXIST; } else{ numberOfItems = cursor.getCount(); deviceFeatures[deviceIndex].mCurrentPathUid = String.valueOf(folderUid); deviceFeatures[deviceIndex].mCurrentPath = PATH_ARTISTS; } } catch (Exception e) { Log.e(TAG, "Exception " + e); changePathRspNative(INTERNAL_ERROR , numberOfItems , getByteAddress(device)); } finally { if (cursor != null) { cursor.close(); } } } else { Cursor cursor = null; try { cursor = mContext.getContentResolver().query( deviceFeatures[deviceIndex].mMediaUri, new String[] {MediaStore.Audio.Media.TITLE}, MediaStore.Audio.Media.IS_MUSIC + "=1 AND _id=" + folderUid, null, null); if (cursor != null) status = NOT_A_DIRECTORY; else status = DOES_NOT_EXIST; } catch (Exception e) { Log.e(TAG, "Exception " + e); changePathRspNative(INTERNAL_ERROR , numberOfItems , getByteAddress(device)); } finally { if (cursor != null) { cursor.close(); } } } break; default: status = INVALID_DIRECTION; break; } } else if (deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_PLAYLISTS)) { switch(direction) { case FOLDER_UP: if (deviceFeatures[deviceIndex].mCurrentPathUid == null) { deviceFeatures[deviceIndex].mCurrentPath = PATH_ROOT; numberOfItems = NUM_ROOT_ELEMENTS; } else { deviceFeatures[deviceIndex].mCurrentPath = PATH_PLAYLISTS; deviceFeatures[deviceIndex].mCurrentPathUid = null; numberOfItems = getNumPlaylistItems(); } break; case FOLDER_DOWN: if (deviceFeatures[deviceIndex].mCurrentPathUid == null) { Cursor cursor = null; String[] playlistMemberCols = new String[] { MediaStore.Audio.Playlists.Members._ID, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.DURATION, MediaStore.Audio.Playlists.Members.PLAY_ORDER, MediaStore.Audio.Playlists.Members.AUDIO_ID, MediaStore.Audio.Media.IS_MUSIC }; try { Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", folderUid); StringBuilder where = new StringBuilder(); where.append(MediaStore.Audio.Media.TITLE + " != ''"); cursor = mContext.getContentResolver().query(uri, playlistMemberCols, where.toString(), null, MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER); if (cursor != null) { numberOfItems = cursor.getCount(); deviceFeatures[deviceIndex].mCurrentPathUid = String.valueOf(folderUid); deviceFeatures[deviceIndex].mCurrentPath = PATH_PLAYLISTS; } else { status = DOES_NOT_EXIST; } } catch (Exception e) { Log.e(TAG, "Exception " + e); changePathRspNative(INTERNAL_ERROR , numberOfItems , getByteAddress(device)); } finally { if (cursor != null) { cursor.close(); } } } else { numberOfItems = 0; status = DOES_NOT_EXIST; } break; default: status = INVALID_DIRECTION; break; } } else { Log.i(TAG, "Current Path not set"); status = DOES_NOT_EXIST; } Log.i(TAG, "Number of items " + numberOfItems + ", status: " + status); changePathRspNative(status , numberOfItems , getByteAddress(device)); } private long getNumPlaylistItems() { Cursor cursor = null; String[] cols = new String[] { MediaStore.Audio.Playlists._ID, MediaStore.Audio.Playlists.NAME }; try { cursor = mContext.getContentResolver().query( MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, cols, MediaStore.Audio.Playlists.NAME + " != ''", null, MediaStore.Audio.Playlists.NAME); if ((cursor == null) || (cursor.getCount() == 0)) { return 0; } else { long count = cursor.getCount(); return count; } } catch (Exception e) { Log.e(TAG, "Exception " + e); return 0; } finally { if (cursor != null) { cursor.close(); } } } private long getNumItems(String path, String element, int deviceIndex) { if (path == null || element == null) return 0; Cursor cursor = null; try { cursor = mContext.getContentResolver().query( deviceFeatures[deviceIndex].mMediaUri, new String[] {element}, MediaStore.Audio.Media.IS_MUSIC + "=1", null, element); if ((cursor == null) || (cursor.getCount() == 0)) { return 0; } else if (path.equals(PATH_TITLES)) { long count = cursor.getCount(); return count; } else if (path.equals(PATH_ALBUMS) || path.equals(PATH_ARTISTS)){ long elemCount = 0; cursor.moveToFirst(); long count = cursor.getCount(); long prevElem = 0; long curElem = 0; while (count > 0) { curElem = cursor.getLong(cursor.getColumnIndexOrThrow(element)); Log.i(TAG, "curElem "+ curElem + "preElem " + prevElem); if (curElem != prevElem) { elemCount++; } prevElem = curElem; cursor.moveToNext(); count--; } Log.i(TAG, "element Count is "+ elemCount); return elemCount; } } catch (Exception e) { Log.e(TAG, "Exception " + e); } finally { if (cursor != null) { cursor.close(); } } return 0; } private void playItem(byte scope, long uid, byte[] address) { if (DEBUG) Log.v(TAG, "playItem: scope: " + scope + " uid:" + uid); ItemAttr itemAttr = new ItemAttr(null, uid, Utils.getAddressStringFromByte(address)); Message msg = mHandler.obtainMessage(MESSAGE_PLAY_ITEM, scope, 0, itemAttr); mHandler.sendMessage(msg); } private void processPlayItem(int scope, long uid, String deviceAddress) { if (DEBUG) Log.v(TAG, "processPlayItem: scope: " + scope + " uid:" + uid); BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress); mBrowserDevice = device; int deviceIndex = getIndexForDevice(device); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.v(TAG,"device entry not present, bailing out"); return; } if (uid < 0) { if (DEBUG) Log.v(TAG, "invalid uid"); playItemRspNative(DOES_NOT_EXIST , getByteAddress(device)); return; } if (mMediaPlayers.size() > 0) { final Iterator rccIterator = mMediaPlayers.iterator(); while (rccIterator.hasNext()) { final MediaPlayerInfo di = rccIterator.next(); if (di.GetPlayerFocus()) { if (!di.IsRemoteAddressable()) { playItemRspNative(INTERNAL_ERROR , getByteAddress(device)); if (DEBUG) Log.v(TAG, "Play Item fails: Player not remote" + "addressable"); return; } } } } if (scope == SCOPE_VIRTUAL_FILE_SYS) { if (deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_ROOT)) { playItemRspNative(UID_A_DIRECTORY , getByteAddress(device)); } else if (deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_TITLES)) { Cursor cursor = null; try { cursor = mContext.getContentResolver().query( deviceFeatures[deviceIndex].mMediaUri, new String[] {MediaStore.Audio.Media.TITLE}, MediaStore.Audio.Media.IS_MUSIC + "=1 AND _id=" + uid, null, null); if ((cursor == null) || (cursor.getCount() == 0)) { Log.i(TAG, "No such track"); playItemRspNative(DOES_NOT_EXIST , getByteAddress(device)); } else { Log.i(TAG, "Play uid:" + uid); mRemoteController.setRemoteControlClientPlayItem(uid, scope); } } catch (Exception e) { Log.e(TAG, "Exception " + e); playItemRspNative(INTERNAL_ERROR , getByteAddress(device)); } finally { if (cursor != null) { cursor.close(); } } } else if (deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_ALBUMS)) { if (deviceFeatures[deviceIndex].mCurrentPathUid == null) { playItemRspNative(UID_A_DIRECTORY , getByteAddress(device)); } else { Cursor cursor = null; try { cursor = mContext.getContentResolver().query( deviceFeatures[deviceIndex].mMediaUri, new String[] {MediaStore.Audio.Media.TITLE}, MediaStore.Audio.Media.IS_MUSIC + "=1 AND _id=" + uid + " AND " + MediaStore.Audio.Media.ALBUM_ID + "=" + deviceFeatures[deviceIndex].mCurrentPathUid, null, null); if ((cursor == null) || (cursor.getCount() == 0)) { Log.i(TAG, "No such track"); playItemRspNative(DOES_NOT_EXIST , getByteAddress(device)); } else { Log.i(TAG, "Play uid:" + uid); mRemoteController.setRemoteControlClientPlayItem(uid, scope); } } catch (Exception e) { Log.e(TAG, "Exception " + e); playItemRspNative(INTERNAL_ERROR , getByteAddress(device)); } finally { if (cursor != null) { cursor.close(); } } } } else if (deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_ARTISTS)) { if (deviceFeatures[deviceIndex].mCurrentPathUid == null) { playItemRspNative(UID_A_DIRECTORY , getByteAddress(device)); } else { Cursor cursor = null; try { cursor = mContext.getContentResolver().query( deviceFeatures[deviceIndex].mMediaUri, new String[] {MediaStore.Audio.Media.TITLE}, MediaStore.Audio.Media.IS_MUSIC + "=1 AND _id=" + uid + " AND " + MediaStore.Audio.Media.ARTIST_ID + "=" + deviceFeatures[deviceIndex].mCurrentPathUid, null, null); if ((cursor == null) || (cursor.getCount() == 0)) { Log.i(TAG, "No such track"); playItemRspNative(DOES_NOT_EXIST , getByteAddress(device)); } else { Log.i(TAG, "Play uid:" + uid); mRemoteController.setRemoteControlClientPlayItem(uid, scope); } } catch (Exception e) { Log.e(TAG, "Exception " + e); playItemRspNative(INTERNAL_ERROR , getByteAddress(device)); } finally { if (cursor != null) { cursor.close(); } } } } else if (deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_PLAYLISTS)) { if (deviceFeatures[deviceIndex].mCurrentPathUid == null) { playItemRspNative(UID_A_DIRECTORY , getByteAddress(device)); } else { Cursor cursor = null; try { String[] playlistMemberCols = new String[] { MediaStore.Audio.Playlists.Members._ID, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.DURATION, MediaStore.Audio.Playlists.Members.PLAY_ORDER, MediaStore.Audio.Playlists.Members.AUDIO_ID, MediaStore.Audio.Media.IS_MUSIC }; Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", Long.parseLong(deviceFeatures[deviceIndex].mCurrentPathUid)); StringBuilder where = new StringBuilder(); where.append(MediaStore.Audio.Playlists.Members.AUDIO_ID + "=" + uid); cursor = mContext.getContentResolver().query(uri, playlistMemberCols, where.toString(), null, MediaStore.Audio.Playlists.Members. DEFAULT_SORT_ORDER); if ((cursor == null) || (cursor.getCount() == 0)) { Log.i(TAG, "No such track"); playItemRspNative(DOES_NOT_EXIST , getByteAddress(device)); } else { Log.i(TAG, "Play uid:" + uid); mRemoteController.setRemoteControlClientPlayItem(uid, scope); } } catch (Exception e) { Log.e(TAG, "Exception " + e); playItemRspNative(INTERNAL_ERROR , getByteAddress(device)); } finally { if (cursor != null) { cursor.close(); } } } } else { playItemRspNative(DOES_NOT_EXIST , getByteAddress(device)); } } else if (scope == SCOPE_NOW_PLAYING) { mRemoteController.setRemoteControlClientPlayItem(uid, scope); } else { playItemRspNative(DOES_NOT_EXIST , getByteAddress(device)); Log.v(TAG, "Play Item fails: Invalid scope"); } } private void getItemAttr(byte scope, long uid, byte numAttr, int[] attrs, byte[] address) { if (DEBUG) Log.v(TAG, "getItemAttr: scope: " + scope + " uid:" + uid + " numAttr:" + numAttr); int i; ArrayList attrList = new ArrayList(); for (i = 0; i < numAttr; ++i) { attrList.add(attrs[i]); if (DEBUG) Log.v(TAG, "attrs[" + i + "] = " + attrs[i]); } ItemAttr itemAttr = new ItemAttr(attrList, uid, Utils.getAddressStringFromByte(address)); Message msg = mHandler.obtainMessage(MESSAGE_GET_ITEM_ATTRS, (int)numAttr, (int)scope, itemAttr); mHandler.sendMessage(msg); } private String[] mCursorCols = new String[] { "audio._id AS _id", MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DURATION, MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Media.ALBUM_ID, MediaStore.Audio.Media.ARTIST_ID, MediaStore.Audio.Media.IS_PODCAST, MediaStore.Audio.Media.BOOKMARK }; private void processGetItemAttr(byte scope, long uid, byte numAttr, int[] attrs, String deviceAddress) { if (DEBUG) Log.v(TAG, "processGetItemAttr: scope: " + scope + " uid:" + uid + " numAttr:" + numAttr); String[] textArray; BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress); int deviceIndex = getIndexForDevice(device); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.v(TAG,"device entry not present, bailing out"); return; } textArray = new String[numAttr]; if ((scope == SCOPE_VIRTUAL_FILE_SYS) || (scope == SCOPE_NOW_PLAYING)) { Cursor cursor = null; try { if ((deviceFeatures[deviceIndex].mMediaUri == Uri.EMPTY) || (deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_INVALID))) { if (DEBUG) Log.v(TAG, "Browsed player not set, getItemAttr can not be processed"); getItemAttrRspNative((byte)0 ,attrs , textArray ,getByteAddress(device)); return; } cursor = mContext.getContentResolver().query( deviceFeatures[deviceIndex].mMediaUri, mCursorCols, MediaStore.Audio.Media.IS_MUSIC + "=1 AND _id=" + uid, null, null); if ((cursor == null) || (cursor.getCount() == 0)) { Log.i(TAG, "Invalid track UID"); Log.i(TAG, "cursor is " + cursor); if (cursor != null) Log.i(TAG, "cursor.getCount() " + cursor.getCount()); getItemAttrRspNative((byte)0 ,attrs , textArray ,getByteAddress(device)); } else { int validAttrib = 0; cursor.moveToFirst(); for (int i = 0; i < numAttr; ++i) { if ((attrs[i] <= MEDIA_ATTR_MAX) && (attrs[i] >= MEDIA_ATTR_MIN)) { textArray[i] = getAttributeStringFromCursor( cursor, attrs[i], deviceIndex); validAttrib ++; } } getItemAttrRspNative(numAttr ,attrs , textArray ,getByteAddress(device)); } } catch (Exception e) { Log.e(TAG, "Exception " + e); getItemAttrRspNative((byte)0 ,attrs , textArray ,getByteAddress(device)); } finally { if (cursor != null) { cursor.close(); } } } else { Log.i(TAG, "Invalid scope"); getItemAttrRspNative((byte)0 ,attrs , textArray ,getByteAddress(device)); } } private class ItemAttr { ArrayList mAttrList; long mUid; String mAddress; public ItemAttr (ArrayList attrList, long uid, String deviceAddress) { mAttrList = attrList; mUid = uid; mAddress = deviceAddress; } } private void setAddressedPlayer(int playerId, byte[] address) { if (DEBUG) Log.v(TAG, "setAddressedPlayer: PlayerID: " + playerId); Message msg = mHandler.obtainMessage(MESSAGE_SET_ADDR_PLAYER, playerId, 0, Utils.getAddressStringFromByte(address)); mHandler.sendMessage(msg); } private void processSetAddressedPlayer(int playerId, String deviceAddress) { if (DEBUG) Log.v(TAG, "processSetAddressedPlayer: PlayerID: " + playerId); String packageName = null; BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress); int deviceIndex = getIndexForDevice(device); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.v(TAG,"device entry not present, bailing out"); return; } if (deviceFeatures[deviceIndex].mRequestedAddressedPlayerPackageName != null) { if (DEBUG) Log.v(TAG, "setAddressedPlayer: Request in progress, Reject this Request"); setAdressedPlayerRspNative((byte)PLAYER_NOT_ADDRESSED, getByteAddress(mAdapter.getRemoteDevice(deviceAddress))); return; } if (mMediaPlayers.size() > 0) { final Iterator rccIterator = mMediaPlayers.iterator(); while (rccIterator.hasNext()) { final MediaPlayerInfo di = rccIterator.next(); if (di.RetrievePlayerId() == playerId) { packageName = di.RetrievePlayerPackageName(); } } } if(packageName != null) { if (playerId == mAddressedPlayerId) { if (DEBUG) Log.v(TAG, "setAddressedPlayer: Already addressed, sending success"); setAdressedPlayerRspNative((byte)OPERATION_SUCCESSFUL, getByteAddress(mAdapter.getRemoteDevice(deviceAddress))); return; } String newPackageName = packageName.replace("com.android", "org.codeaurora"); Intent mediaIntent = new Intent(newPackageName + ".setaddressedplayer"); mediaIntent.setPackage(packageName); // This needs to be caught in respective media players mContext.sendBroadcast(mediaIntent); if (DEBUG) Log.v(TAG, "Intent Broadcasted: " + newPackageName + ".setaddressedplayer"); deviceFeatures[deviceIndex].mRequestedAddressedPlayerPackageName = packageName; deviceFeatures[deviceIndex].isMusicAppResponsePending = true; Message msg = mHandler.obtainMessage(MESSAGE_SET_ADDR_PLAYER_REQ_TIMEOUT, 0, 0, deviceAddress); mHandler.sendMessageDelayed(msg, AVRCP_BR_RSP_TIMEOUT); Log.v(TAG, "Post MESSAGE_SET_ADDR_PLAYER_REQ_TIMEOUT"); } else { if (DEBUG) Log.v(TAG, "setAddressedPlayer fails: No such media player available"); setAdressedPlayerRspNative((byte)INVALID_PLAYER_ID, getByteAddress(mAdapter.getRemoteDevice(deviceAddress))); } } private void getFolderItems(byte scope, long start, long end, int attrCnt, int numAttr, int[] attrs, byte[] address) { if (DEBUG) Log.v(TAG, "getFolderItems"); if (DEBUG) Log.v(TAG, "scope: " + scope + " attrCnt: " + attrCnt); if (DEBUG) Log.v(TAG, "start: " + start + " end: " + end); for (int i = 0; i < numAttr; ++i) { if (DEBUG) Log.v(TAG, "attrs[" + i + "] = " + attrs[i]); } FolderListEntries folderListEntries = new FolderListEntries (scope, start, end, attrCnt, numAttr, attrs, Utils.getAddressStringFromByte(address)); Message msg = mHandler.obtainMessage(MESSAGE_GET_FOLDER_ITEMS, 0, 0, folderListEntries); mHandler.sendMessage(msg); } private void processGetFolderItems(byte scope, long start, long end, int size, int numAttr, int[] attrs, String deviceAddress) { if (DEBUG) Log.v(TAG, "processGetFolderItems"); if (DEBUG) Log.v(TAG, "scope: " + scope + " size: " + size); if (DEBUG) Log.v(TAG, "start: " + start + " end: " + end + " numAttr: " + numAttr); if (scope == SCOPE_PLAYER_LIST) { // populate mediaplayer item list here processGetMediaPlayerItems(scope, start, end, size, numAttr, attrs, deviceAddress); } else if ((scope == SCOPE_VIRTUAL_FILE_SYS) || (scope == SCOPE_NOW_PLAYING)) { for (int i = 0; i < numAttr; ++i) { if (DEBUG) Log.v(TAG, "attrs[" + i + "] = " + attrs[i]); } processGetFolderItemsInternal(scope, start, end, size, (byte)numAttr, attrs, deviceAddress); } } private void processGetMediaPlayerItems(byte scope, long start, long end, int size, int numAttr, int[] attrs, String deviceAddress) { byte[] folderItems = new byte[size]; int[] folderItemLengths = new int[32]; int availableMediaPlayers = 0; int count = 0; int positionItemStart = 0; BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress); if (mMediaPlayers.size() > 0) { final Iterator rccIterator = mMediaPlayers.iterator(); while (rccIterator.hasNext()) { final MediaPlayerInfo di = rccIterator.next(); if (di.GetPlayerAvailablility()) { if (start == 0) { byte[] playerEntry = di.RetrievePlayerItemEntry(); int length = di.RetrievePlayerEntryLength(); folderItemLengths[availableMediaPlayers ++] = length; for (count = 0; count < length; count ++) { folderItems[positionItemStart + count] = playerEntry[count]; } positionItemStart += length; // move start to next item start } else if (start > 0) { --start; } } } } if (DEBUG) Log.v(TAG, "Number of available MediaPlayers = " + availableMediaPlayers); getMediaPlayerListRspNative((byte)OPERATION_SUCCESSFUL, 0x1357, availableMediaPlayers, folderItems, folderItemLengths, getByteAddress(device)); } private boolean isCurrentPathValid (int deviceIndex) { if (deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_ROOT) || deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_TITLES) || deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_ALBUMS) || deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_ARTISTS) || deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_PLAYLISTS)) { return true; } return false; } private void processGetFolderItemsInternal(byte scope, long start, long end, int size, byte numAttr, int[] attrs, String deviceAddress) { int status = OPERATION_SUCCESSFUL; long numItems = 0; long reqItems = (end - start) + 1; int[] itemType = new int[MAX_BROWSE_ITEM_TO_SEND]; long[] uid = new long[MAX_BROWSE_ITEM_TO_SEND]; int[] type = new int[MAX_BROWSE_ITEM_TO_SEND]; byte[] playable = new byte[MAX_BROWSE_ITEM_TO_SEND]; String[] displayName = new String[MAX_BROWSE_ITEM_TO_SEND]; byte[] numAtt = new byte[MAX_BROWSE_ITEM_TO_SEND]; String[] attValues = new String[MAX_BROWSE_ITEM_TO_SEND * 7]; int[] attIds = new int[MAX_BROWSE_ITEM_TO_SEND * 7]; BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress); mBrowserDevice = device; int deviceIndex = getIndexForDevice(device); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.v(TAG,"device entry not present, bailing out"); return; } if (DEBUG) Log.v(TAG, "processGetFolderItemsInternal"); if (DEBUG) Log.v(TAG, "requested attribute count" + numAttr); for (int count = 0; count < numAttr; count++) { if (DEBUG) Log.v(TAG, "attr[" + count + "] = " + attrs[count]); } if (scope == SCOPE_VIRTUAL_FILE_SYS) { // Item specific attribute's entry starts from index*7 for (int count = 0; count < (MAX_BROWSE_ITEM_TO_SEND * 7); count++) { attValues[count] = ""; attIds[count] = 0; } if (DEBUG) Log.v(TAG, "mCurrentPath: " + deviceFeatures[deviceIndex].mCurrentPath); if (DEBUG) Log.v(TAG, "mCurrentPathUID: " + deviceFeatures[deviceIndex].mCurrentPathUid); if (!isCurrentPathValid(deviceIndex)) { getFolderItemsRspNative((byte)DOES_NOT_EXIST , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); Log.v(TAG, "Current path not set"); return; } if ((start < 0) || (end < 0) || (start > end)) { getFolderItemsRspNative((byte)RANGE_OUT_OF_BOUNDS , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); Log.e(TAG, "Wrong start/end index"); return; } if (deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_ROOT)) { long availableItems = NUM_ROOT_ELEMENTS; if (start >= availableItems) { Log.i(TAG, "startIteam exceeds the available item index"); getFolderItemsRspNative((byte)RANGE_OUT_OF_BOUNDS , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); return; } if (DEBUG) Log.v(TAG, "availableItems: " + availableItems); if (DEBUG) Log.v(TAG, "reqItems: " + reqItems); availableItems = availableItems - start; if (availableItems > MAX_BROWSE_ITEM_TO_SEND) availableItems = MAX_BROWSE_ITEM_TO_SEND; if (reqItems > availableItems) reqItems = availableItems; if (DEBUG) Log.v(TAG, "revised reqItems: " + reqItems); numItems = reqItems; for (int count = 0; count < reqItems; count ++) { long index = start + count; switch ((int)index) { case ALBUMS_ITEM_INDEX: itemType[count] = TYPE_FOLDER_ITEM; uid[count] = UID_ALBUM; type[count] = FOLDER_TYPE_ALBUMS; playable[count] = 0; displayName[count] = PATH_ALBUMS; numAtt[count] = 0; break; case ARTISTS_ITEM_INDEX: itemType[count] = TYPE_FOLDER_ITEM; uid[count] = UID_ARTIST; type[count] = FOLDER_TYPE_ARTISTS; playable[count] = 0; displayName[count] = PATH_ARTISTS; numAtt[count] = 0; break; case PLAYLISTS_ITEM_INDEX: itemType[count] = TYPE_FOLDER_ITEM; uid[count] = UID_PLAYLIST; type[count] = FOLDER_TYPE_PLAYLISTS; playable[count] = 0; displayName[count] = PATH_PLAYLISTS; numAtt[count] = 0; break; case TITLES_ITEM_INDEX: itemType[count] = TYPE_FOLDER_ITEM; uid[count] = UID_TITLES; type[count] = FOLDER_TYPE_TITLES; playable[count] = 0; displayName[count] = PATH_TITLES; numAtt[count] = 0; break; default: Log.i(TAG, "wrong index"); getFolderItemsRspNative((byte)RANGE_OUT_OF_BOUNDS , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } } for (int count = 0; count < numItems; count++) { Log.v(TAG, itemType[count] + "," + uid[count] + "," + type[count]); } getFolderItemsRspNative((byte)status , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } else if (deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_TITLES)) { long availableItems = 0; Cursor cursor = null; try { cursor = mContext.getContentResolver().query( deviceFeatures[deviceIndex].mMediaUri, mCursorCols, MediaStore.Audio.Media.IS_MUSIC + "=1", null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); if (cursor != null) { availableItems = cursor.getCount(); if (start >= availableItems) { Log.i(TAG, "startIteam exceeds the available item index"); getFolderItemsRspNative((byte)RANGE_OUT_OF_BOUNDS , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); return; } cursor.moveToFirst(); for (int i = 0; i < start; i++) { cursor.moveToNext(); } } else { Log.i(TAG, "Error: could not fetch the elements"); getFolderItemsRspNative((byte)INTERNAL_ERROR , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); return; } if (DEBUG) Log.v(TAG, "availableItems: " + availableItems); if (DEBUG) Log.v(TAG, "reqItems: " + reqItems); availableItems = availableItems - start; if (availableItems > MAX_BROWSE_ITEM_TO_SEND) availableItems = MAX_BROWSE_ITEM_TO_SEND; if (reqItems > availableItems) reqItems = availableItems; if (DEBUG) Log.v(TAG, "revised reqItems: " + reqItems); int attIndex; int index; for (index = 0; index < reqItems; index++) { itemType[index] = TYPE_MEDIA_ELEMENT_ITEM; uid[index] = cursor.getLong(cursor.getColumnIndexOrThrow("_id")); type[index] = MEDIA_TYPE_AUDIO; playable[index] = 0; displayName[index] = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore. Audio.Media.TITLE)); int validAttrib = 0; for (attIndex = 0; attIndex < numAttr; attIndex++) { if ((attrs[attIndex] <= MEDIA_ATTR_MAX) && (attrs[attIndex] >= MEDIA_ATTR_MIN)) { attValues[(7 * index) + attIndex] = getAttributeStringFromCursor( cursor, attrs[attIndex], deviceIndex); attIds[(7 * index) + attIndex] = attrs[attIndex]; validAttrib ++; } } numAtt[index] = (byte)validAttrib; cursor.moveToNext(); } numItems = index; getFolderItemsRspNative((byte)OPERATION_SUCCESSFUL , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } catch(Exception e) { Log.i(TAG, "Exception e" + e); getFolderItemsRspNative((byte)INTERNAL_ERROR , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } finally { if (cursor != null) { cursor.close(); } } } else if (deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_ALBUMS)) { if (deviceFeatures[deviceIndex].mCurrentPathUid == null) { long availableItems = 0; Cursor cursor = null; try { availableItems = getNumItems(PATH_ALBUMS, MediaStore.Audio.Media.ALBUM_ID, deviceIndex); if (start >= availableItems) { Log.i(TAG, "startIteam exceeds the available item index"); getFolderItemsRspNative((byte)RANGE_OUT_OF_BOUNDS , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); return; } if (DEBUG) Log.v(TAG, "availableItems: " + availableItems); if (DEBUG) Log.v(TAG, "reqItems: " + reqItems); availableItems = availableItems - start; if (availableItems > MAX_BROWSE_ITEM_TO_SEND) availableItems = MAX_BROWSE_ITEM_TO_SEND; if (reqItems > availableItems) reqItems = (int)availableItems; Log.i(TAG, "revised reqItems: " + reqItems); cursor = mContext.getContentResolver().query( deviceFeatures[deviceIndex].mMediaUri, mCursorCols, MediaStore.Audio.Media.IS_MUSIC + "=1", null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER); int count = 0; if (cursor != null) { count = cursor.getCount(); } else { Log.i(TAG, "Error: could not fetch the elements"); getFolderItemsRspNative((byte)INTERNAL_ERROR , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); return; } if (count < reqItems) { reqItems = count; } cursor.moveToFirst(); int index = 0; long prevElem = -1; long curElem = -1; while ((reqItems > 0) && (count > 0)) { curElem = cursor.getLong(cursor.getColumnIndexOrThrow( MediaStore.Audio.Media.ALBUM_ID)); if (curElem != prevElem) { if (start > 0) { --start; } else { itemType[index] = TYPE_FOLDER_ITEM; uid[index] = cursor.getLong(cursor.getColumnIndexOrThrow( MediaStore.Audio.Media.ALBUM_ID)); type[index] = FOLDER_TYPE_ALBUMS; playable[index] = 0; displayName[index] = cursor.getString( cursor.getColumnIndexOrThrow( MediaStore.Audio.Media.ALBUM)); numAtt[index] = 0; index++; reqItems--; } } prevElem = curElem; cursor.moveToNext(); count--; } if (index > 0) { numItems = index; getFolderItemsRspNative((byte)OPERATION_SUCCESSFUL , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } else { getFolderItemsRspNative((byte)RANGE_OUT_OF_BOUNDS , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } } catch(Exception e) { Log.i(TAG, "Exception e" + e); getFolderItemsRspNative((byte)INTERNAL_ERROR , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } finally { if (cursor != null) { cursor.close(); } } } else { long folderUid = Long.valueOf(deviceFeatures[deviceIndex].mCurrentPathUid); long availableItems = 0; Cursor cursor = null; try { cursor = mContext.getContentResolver().query( deviceFeatures[deviceIndex].mMediaUri, mCursorCols, MediaStore.Audio.Media.IS_MUSIC + "=1 AND " + MediaStore.Audio.Media.ALBUM_ID + "=" + folderUid, null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER); if (cursor != null) { availableItems = cursor.getCount(); if (start >= availableItems) { Log.i(TAG, "startIteam exceeds the available item index"); getFolderItemsRspNative((byte)RANGE_OUT_OF_BOUNDS , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); return; } cursor.moveToFirst(); for (int i = 0; i < start; i++) { cursor.moveToNext(); } } else { Log.i(TAG, "Error: could not fetch the elements"); getFolderItemsRspNative((byte)INTERNAL_ERROR , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); return; } if (DEBUG) Log.v(TAG, "availableItems: " + availableItems); if (DEBUG) Log.v(TAG, "reqItems: " + reqItems); availableItems = availableItems - start; if (availableItems > MAX_BROWSE_ITEM_TO_SEND) availableItems = MAX_BROWSE_ITEM_TO_SEND; if (reqItems > availableItems) reqItems = availableItems; if (DEBUG) Log.v(TAG, "revised reqItems: " + reqItems); int attIndex; int index; for (index = 0; index < reqItems; index++) { itemType[index] = TYPE_MEDIA_ELEMENT_ITEM; uid[index] = cursor.getLong(cursor.getColumnIndexOrThrow("_id")); type[index] = MEDIA_TYPE_AUDIO; playable[index] = 0; displayName[index] = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore. Audio.Media.TITLE)); int validAttrib = 0; for (attIndex = 0; attIndex < numAttr; attIndex++) { if ((attrs[attIndex] <= MEDIA_ATTR_MAX) && (attrs[attIndex] >= MEDIA_ATTR_MIN)) { attValues[(7 * index) + attIndex] = getAttributeStringFromCursor( cursor, attrs[attIndex], deviceIndex); attIds[(7 * index) + attIndex] = attrs[attIndex]; validAttrib ++; } } numAtt[index] = (byte)validAttrib; cursor.moveToNext(); } numItems = index; getFolderItemsRspNative((byte)OPERATION_SUCCESSFUL , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } catch(Exception e) { Log.i(TAG, "Exception e" + e); getFolderItemsRspNative((byte)INTERNAL_ERROR , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } finally { if (cursor != null) { cursor.close(); } } } } else if (deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_ARTISTS)) { if (deviceFeatures[deviceIndex].mCurrentPathUid == null) { long availableItems = 0; Cursor cursor = null; try { availableItems = getNumItems(PATH_ARTISTS, MediaStore.Audio.Media.ARTIST_ID, deviceIndex); if (start >= availableItems) { Log.i(TAG, "startIteam exceeds the available item index"); getFolderItemsRspNative((byte)RANGE_OUT_OF_BOUNDS , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); return; } if (DEBUG) Log.v(TAG, "availableItems: " + availableItems); if (DEBUG) Log.v(TAG, "reqItems: " + reqItems); availableItems = availableItems - start; if (availableItems > MAX_BROWSE_ITEM_TO_SEND) availableItems = MAX_BROWSE_ITEM_TO_SEND; if (reqItems > availableItems) reqItems = (int)availableItems; if (DEBUG) Log.v(TAG, "revised reqItems: " + reqItems); cursor = mContext.getContentResolver().query( deviceFeatures[deviceIndex].mMediaUri, mCursorCols, MediaStore.Audio.Media.IS_MUSIC + "=1", null, MediaStore.Audio.Artists.DEFAULT_SORT_ORDER); int count = 0; if (cursor != null) { count = cursor.getCount(); } else { Log.i(TAG, "Error: could not fetch the elements"); getFolderItemsRspNative((byte)INTERNAL_ERROR , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); return; } if (count < reqItems) { reqItems = count; } cursor.moveToFirst(); int index = 0; long prevElem = -1; long curElem = -1; while ((reqItems > 0) && (count > 0)) { curElem = cursor.getLong(cursor.getColumnIndexOrThrow( MediaStore.Audio.Media.ARTIST_ID)); if (curElem != prevElem) { if (start > 0) { --start; } else { itemType[index] = TYPE_FOLDER_ITEM; uid[index] = cursor.getLong(cursor.getColumnIndexOrThrow( MediaStore.Audio.Media.ARTIST_ID)); type[index] = FOLDER_TYPE_ARTISTS; playable[index] = 0; displayName[index] = cursor.getString( cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)); numAtt[index] = 0; index++; reqItems--; } } prevElem = curElem; cursor.moveToNext(); count--; } if (index > 0) { numItems = index; getFolderItemsRspNative((byte)OPERATION_SUCCESSFUL , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } else { getFolderItemsRspNative((byte)RANGE_OUT_OF_BOUNDS , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } } catch(Exception e) { Log.i(TAG, "Exception e" + e); getFolderItemsRspNative((byte)INTERNAL_ERROR , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } finally { if (cursor != null) { cursor.close(); } } } else { long folderUid = Long.valueOf(deviceFeatures[deviceIndex].mCurrentPathUid); long availableItems = 0; Cursor cursor = null; try { cursor = mContext.getContentResolver().query( deviceFeatures[deviceIndex].mMediaUri, mCursorCols, MediaStore.Audio.Media.IS_MUSIC + "=1 AND " + MediaStore.Audio.Media.ARTIST_ID + "=" + folderUid, null, MediaStore.Audio.Artists.DEFAULT_SORT_ORDER); if (cursor != null) { availableItems = cursor.getCount(); if (start >= availableItems) { Log.i(TAG, "startIteam exceeds the available item index"); getFolderItemsRspNative((byte)RANGE_OUT_OF_BOUNDS , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); return; } cursor.moveToFirst(); for (int i = 0; i < start; i++) { cursor.moveToNext(); } } else { Log.i(TAG, "Error: could not fetch the elements"); getFolderItemsRspNative((byte)INTERNAL_ERROR , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); return; } if (DEBUG) Log.v(TAG, "availableItems: " + availableItems); if (DEBUG) Log.v(TAG, "reqItems: " + reqItems); availableItems = availableItems - start; if (availableItems > MAX_BROWSE_ITEM_TO_SEND) availableItems = MAX_BROWSE_ITEM_TO_SEND; if (reqItems > availableItems) reqItems = availableItems; if (DEBUG) Log.v(TAG, "revised reqItems: " + reqItems); int attIndex; int index; for (index = 0; index < reqItems; index++) { itemType[index] = TYPE_MEDIA_ELEMENT_ITEM; uid[index] = cursor.getLong(cursor.getColumnIndexOrThrow("_id")); type[index] = MEDIA_TYPE_AUDIO; playable[index] = 0; displayName[index] = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore. Audio.Media.TITLE)); int validAttrib = 0; for (attIndex = 0; attIndex < numAttr; attIndex++) { if ((attrs[attIndex] <= MEDIA_ATTR_MAX) && (attrs[attIndex] >= MEDIA_ATTR_MIN)) { attValues[(7 * index) + attIndex] = getAttributeStringFromCursor( cursor, attrs[attIndex], deviceIndex); attIds[(7 * index) + attIndex] = attrs[attIndex]; validAttrib ++; } } numAtt[index] = (byte)validAttrib; cursor.moveToNext(); } numItems = index; getFolderItemsRspNative((byte)OPERATION_SUCCESSFUL , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } catch(Exception e) { Log.i(TAG, "Exception e" + e); getFolderItemsRspNative((byte)INTERNAL_ERROR , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } finally { if (cursor != null) { cursor.close(); } } } } else if (deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_PLAYLISTS)) { if (deviceFeatures[deviceIndex].mCurrentPathUid == null) { long availableItems = 0; Cursor cursor = null; try { availableItems = getNumPlaylistItems(); if (start >= availableItems) { Log.i(TAG, "startIteam exceeds the available item index"); getFolderItemsRspNative((byte)RANGE_OUT_OF_BOUNDS , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); return; } if (DEBUG) Log.v(TAG, "availableItems: " + availableItems); if (DEBUG) Log.v(TAG, "reqItems: " + reqItems); availableItems = availableItems - start; if (availableItems > MAX_BROWSE_ITEM_TO_SEND) availableItems = MAX_BROWSE_ITEM_TO_SEND; if (reqItems > availableItems) reqItems = (int)availableItems; if (DEBUG) Log.v(TAG, "revised reqItems: " + reqItems); String[] cols = new String[] { MediaStore.Audio.Playlists._ID, MediaStore.Audio.Playlists.NAME }; cursor = mContext.getContentResolver().query( MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, cols, MediaStore.Audio.Playlists.NAME + " != ''", null, MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER); int count = 0; if (cursor != null) { count = cursor.getCount(); } else { Log.i(TAG, "Error: could not fetch the elements"); getFolderItemsRspNative((byte)INTERNAL_ERROR , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); return; } if (count < reqItems) { reqItems = count; } cursor.moveToFirst(); for (int i = 0; i < start; i++) { cursor.moveToNext(); } int index = 0; for (index = 0; index < reqItems; index++) { itemType[index] = TYPE_FOLDER_ITEM; uid[index] = cursor.getLong(cursor.getColumnIndexOrThrow( MediaStore.Audio.Playlists._ID)); type[index] = FOLDER_TYPE_PLAYLISTS; playable[index] = 0; displayName[index] = cursor.getString(cursor.getColumnIndexOrThrow( MediaStore.Audio.Playlists.NAME)); cursor.moveToNext(); } numItems = index; if (index > 0) { numItems = index; getFolderItemsRspNative((byte)OPERATION_SUCCESSFUL , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } else { getFolderItemsRspNative((byte)RANGE_OUT_OF_BOUNDS , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } } catch(Exception e) { Log.i(TAG, "Exception e" + e); getFolderItemsRspNative((byte)INTERNAL_ERROR , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } finally { if (cursor != null) { cursor.close(); } } } else { long folderUid = Long.valueOf(deviceFeatures[deviceIndex].mCurrentPathUid); long availableItems = 0; Cursor cursor = null; String[] playlistMemberCols = new String[] { MediaStore.Audio.Playlists.Members._ID, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.DURATION, MediaStore.Audio.Playlists.Members.PLAY_ORDER, MediaStore.Audio.Playlists.Members.AUDIO_ID, MediaStore.Audio.Media.IS_MUSIC }; try { Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", folderUid); StringBuilder where = new StringBuilder(); where.append(MediaStore.Audio.Media.TITLE + " != ''"); cursor = mContext.getContentResolver().query(uri, playlistMemberCols, where.toString(), null, MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER); if (cursor != null) { availableItems = cursor.getCount(); if (start >= availableItems) { Log.i(TAG, "startIteam exceeds the available item index"); getFolderItemsRspNative((byte)RANGE_OUT_OF_BOUNDS , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); return; } cursor.moveToFirst(); for (int i = 0; i < start; i++) { cursor.moveToNext(); } } else { Log.i(TAG, "Error: could not fetch the elements"); getFolderItemsRspNative((byte)INTERNAL_ERROR , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); return; } if (DEBUG) Log.v(TAG, "availableItems: " + availableItems); if (DEBUG) Log.v(TAG, "reqItems: " + reqItems); availableItems = availableItems - start; if (availableItems > MAX_BROWSE_ITEM_TO_SEND) availableItems = MAX_BROWSE_ITEM_TO_SEND; if (reqItems > availableItems) reqItems = availableItems; if (DEBUG) Log.v(TAG, "revised reqItems: " + reqItems); int attIndex; int index; for (index = 0; index < reqItems; index++) { itemType[index] = TYPE_MEDIA_ELEMENT_ITEM; uid[index] = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore. Audio.Playlists.Members.AUDIO_ID)); type[index] = MEDIA_TYPE_AUDIO; playable[index] = 0; displayName[index] = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore. Audio.Media.TITLE)); int validAttrib = 0; for (attIndex = 0; attIndex < numAttr; attIndex++) { if ((attrs[attIndex] <= MEDIA_ATTR_MAX) && (attrs[attIndex] >= MEDIA_ATTR_MIN)) { attValues[(7 * index) + attIndex] = getAttributeStringFromCursor( cursor, attrs[attIndex], deviceIndex); attIds[(7 * index) + attIndex] = attrs[attIndex]; validAttrib ++; } } numAtt[index] = (byte)validAttrib; cursor.moveToNext(); } numItems = index; getFolderItemsRspNative((byte)OPERATION_SUCCESSFUL , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } catch(Exception e) { Log.e(TAG, "Exception e" + e); getFolderItemsRspNative((byte)INTERNAL_ERROR , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); } finally { if (cursor != null) { cursor.close(); } } } } else { getFolderItemsRspNative((byte)DOES_NOT_EXIST , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); Log.v(TAG, "GetFolderItems fail as player is not browsable"); } } else if (scope == SCOPE_NOW_PLAYING) { if (mMediaPlayers.size() > 0) { final Iterator rccIterator = mMediaPlayers.iterator(); while (rccIterator.hasNext()) { final MediaPlayerInfo di = rccIterator.next(); if (di.GetPlayerFocus()) { if (!di.IsRemoteAddressable() || deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_INVALID)) { getFolderItemsRspNative((byte)INTERNAL_ERROR , numItems, itemType, uid, type, playable, displayName, numAtt, attValues, attIds, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); Log.e(TAG, "GetFolderItems fails: addressed player is not browsable"); return; } } } } mRemoteController.getRemoteControlClientNowPlayingEntries(); mCachedRequest = new CachedRequest(start, end, numAttr, attrs); } } private void registerNotification(int eventId, int param, byte[] address) { Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_NOTIFICATION, eventId, param, Utils.getAddressStringFromByte(address)); mHandler.sendMessage(msg); } private void processRCCStateChange(String callingPackageName, int isFocussed, int isAvailable) { Log.v(TAG, "processRCCStateChange: " + callingPackageName); boolean available = false; boolean focussed = false; boolean isResetFocusRequired = false; BluetoothDevice device = null; if (isFocussed == 1) focussed = true; if (isAvailable == 1) available = true; if (focussed) { isResetFocusRequired = true; for (int i = 0; i < maxAvrcpConnections; i++) { if (deviceFeatures[i].mRequestedAddressedPlayerPackageName != null) { if (callingPackageName.equals( deviceFeatures[i].mRequestedAddressedPlayerPackageName)) { mHandler.removeMessages(MESSAGE_SET_ADDR_PLAYER_REQ_TIMEOUT); if (deviceFeatures[i].isMusicAppResponsePending == true) { device = deviceFeatures[i].mCurrentDevice; deviceFeatures[i].isMusicAppResponsePending = false; } if (device == null) { Log.e(TAG,"ERROR!!!! device is null"); return; } if (DEBUG) Log.v(TAG, "SetAddressedPlayer succeeds for: " + deviceFeatures[i].mRequestedAddressedPlayerPackageName); deviceFeatures[i].mRequestedAddressedPlayerPackageName = null; setAdressedPlayerRspNative((byte)OPERATION_SUCCESSFUL, getByteAddress(deviceFeatures[i].mCurrentDevice)); } else { if (DEBUG) Log.v(TAG, "SetaddressedPlayer package mismatch with: " + deviceFeatures[i].mRequestedAddressedPlayerPackageName); } } else { if (DEBUG) Log.v(TAG, "SetaddressedPlayer request is not in progress"); } } } if (mMediaPlayers.size() > 0) { final Iterator rccIterator = mMediaPlayers.iterator(); while (rccIterator.hasNext()) { final MediaPlayerInfo di = rccIterator.next(); if (di.RetrievePlayerPackageName().equals(callingPackageName)) { isResetFocusRequired = false; if (di.GetPlayerAvailablility() != available) { di.SetPlayerAvailablility(available); if (DEBUG) Log.v(TAG, "setting " + callingPackageName + " availability: " + available); if (mHandler != null) { if (DEBUG) Log.v(TAG, "Send MSG_UPDATE_AVAILABLE_PLAYERS"); mHandler.obtainMessage(MSG_UPDATE_AVAILABLE_PLAYERS, 0, 0, 0).sendToTarget(); } } if (di.GetPlayerFocus() != focussed) { di.SetPlayerFocus(focussed); if (DEBUG) Log.v(TAG, "setting " + callingPackageName + " focus: " + focussed); if(focussed) { if (mHandler != null) { if (DEBUG) Log.v(TAG, "Send MSG_UPDATE_ADDRESSED_PLAYER: " + di.RetrievePlayerId()); mHandler.obtainMessage(MSG_UPDATE_ADDRESSED_PLAYER, di.RetrievePlayerId(), 0, 0).sendToTarget(); } } } break; } } } if (DEBUG) Log.v(TAG, "isResetFocusRequired: " + isResetFocusRequired); if (focussed) { // this is applicable only if list contains more than one media players if (mMediaPlayers.size() > 0) { final Iterator rccIterator = mMediaPlayers.iterator(); while (rccIterator.hasNext()) { final MediaPlayerInfo di = rccIterator.next(); if (!(di.RetrievePlayerPackageName().equals(callingPackageName))) { if (DEBUG) Log.v(TAG, "setting " + callingPackageName + " focus: false"); di.SetPlayerFocus(false); // reset focus for all other players } } } } if(isResetFocusRequired) { for (int i = 0; i < maxAvrcpConnections; i++) { if (mHandler != null) { if (DEBUG) Log.v(TAG, "Send MSG_UPDATE_ADDRESSED_PLAYER: 0"); mHandler.obtainMessage(MSG_UPDATE_ADDRESSED_PLAYER, 0, 0, 0).sendToTarget(); } } } } private void processRegisterNotification(int eventId, int param, String deviceAddress) { BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress); int deviceIndex = getIndexForDevice(device); Log.v(TAG,"processRegisterNotification: eventId" + eventId); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.v(TAG,"device entry not present, bailing out"); return; } switch (eventId) { case EVT_PLAY_STATUS_CHANGED: deviceFeatures[deviceIndex].mPlayStatusChangedNT = NOTIFICATION_TYPE_INTERIM; registerNotificationRspPlayStatusNative( deviceFeatures[deviceIndex].mPlayStatusChangedNT, convertPlayStateToPlayStatus( deviceFeatures[deviceIndex].mCurrentPlayState), getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); break; case EVT_TRACK_CHANGED: deviceFeatures[deviceIndex].mTrackChangedNT = NOTIFICATION_TYPE_INTERIM; sendTrackChangedRsp(device); break; case EVT_PLAY_POS_CHANGED: long songPosition = getPlayPosition(deviceFeatures[deviceIndex].mCurrentDevice); deviceFeatures[deviceIndex].mPlayPosChangedNT = NOTIFICATION_TYPE_INTERIM; deviceFeatures[deviceIndex].mPlaybackIntervalMs = (long)param * 1000L; if (mCurrentPosMs != RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) { deviceFeatures[deviceIndex].mNextPosMs = songPosition + deviceFeatures[deviceIndex].mPlaybackIntervalMs; deviceFeatures[deviceIndex].mPrevPosMs = songPosition - deviceFeatures[deviceIndex].mPlaybackIntervalMs; if (deviceFeatures[deviceIndex].mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING) { Message msg = mHandler.obtainMessage(MESSAGE_PLAY_INTERVAL_TIMEOUT, 0, 0, deviceFeatures[deviceIndex].mCurrentDevice); mHandler.sendMessageDelayed(msg, deviceFeatures[deviceIndex].mPlaybackIntervalMs); } } registerNotificationRspPlayPosNative(deviceFeatures[deviceIndex].mPlayPosChangedNT, (int)getPlayPosition(deviceFeatures[deviceIndex].mCurrentDevice) , getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); if (DEBUG) Log.v(TAG,"mPlayPosChangedNT updated for index " + deviceFeatures[deviceIndex].mPlayPosChangedNT + " index " + deviceIndex); break; case EVT_APP_SETTINGS_CHANGED: deviceFeatures[deviceIndex].mPlayerStatusChangeNT = NOTIFICATION_TYPE_INTERIM; sendPlayerAppChangedRsp(deviceFeatures[deviceIndex].mPlayerStatusChangeNT, device); break; case EVT_ADDRESSED_PLAYER_CHANGED: if (DEBUG) Log.v(TAG, "Process EVT_ADDRESSED_PLAYER_CHANGED Interim: Player ID: " + mAddressedPlayerId); deviceFeatures[deviceIndex].mAddressedPlayerChangedNT = NOTIFICATION_TYPE_INTERIM; registerNotificationRspAddressedPlayerChangedNative( deviceFeatures[deviceIndex].mAddressedPlayerChangedNT , mAddressedPlayerId , getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); break; case EVT_AVAILABLE_PLAYERS_CHANGED: deviceFeatures[deviceIndex].mAvailablePlayersChangedNT = NOTIFICATION_TYPE_INTERIM; registerNotificationRspAvailablePlayersChangedNative( deviceFeatures[deviceIndex].mAvailablePlayersChangedNT, getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); break; case EVT_NOW_PLAYING_CONTENT_CHANGED: deviceFeatures[deviceIndex].mNowPlayingContentChangedNT = NOTIFICATION_TYPE_INTERIM; registerNotificationRspNowPlayingContentChangedNative( deviceFeatures[deviceIndex].mNowPlayingContentChangedNT , getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); break; default: Log.v(TAG, "processRegisterNotification: Unhandled Type: " + eventId); break; } } private void handlePassthroughCmd(int id, int keyState, byte[] address) { switch (id) { case BluetoothAvrcp.PASSTHROUGH_ID_REWIND: rewind(keyState, Utils.getAddressStringFromByte(address)); break; case BluetoothAvrcp.PASSTHROUGH_ID_FAST_FOR: fastForward(keyState, Utils.getAddressStringFromByte(address)); break; } } private void changePositionBy(long amount, String deviceAddress) { long currentPosMs = getPlayPosition(mAdapter.getRemoteDevice(deviceAddress)); if (currentPosMs == -1L) return; long newPosMs = Math.max(0L, currentPosMs + amount); mRemoteController.seekTo(newPosMs); } private int getSkipMultiplier() { long currentTime = SystemClock.elapsedRealtime(); long multi = (long) Math.pow(2, (currentTime - mSkipStartTime)/SKIP_DOUBLE_INTERVAL); return (int) Math.min(MAX_MULTIPLIER_VALUE, multi); } private void sendTrackChangedRsp(BluetoothDevice device) { byte[] track = new byte[TRACK_ID_SIZE]; long TrackNumberRsp = -1L; int deviceIndex = getIndexForDevice(device); if(DEBUG) Log.v(TAG,"mCurrentPlayState" + deviceFeatures[deviceIndex].mCurrentPlayState ); TrackNumberRsp = mMetadata.tracknum ; /* track is stored in big endian format */ for (int i = 0; i < TRACK_ID_SIZE; ++i) { track[i] = (byte) (TrackNumberRsp >> (56 - 8 * i)); } registerNotificationRspTrackChangeNative(deviceFeatures[deviceIndex].mTrackChangedNT , track ,getByteAddress(device)); } private void sendPlayerAppChangedRsp(int rsptype, BluetoothDevice device) { int j = 0; byte i = NUMPLAYER_ATTRIBUTE*2; byte [] retVal = new byte [i]; retVal[j++] = ATTRIBUTE_REPEATMODE; retVal[j++] = settingValues.repeat_value; retVal[j++] = ATTRIBUTE_SHUFFLEMODE; retVal[j++] = settingValues.shuffle_value; registerNotificationPlayerAppRspNative(rsptype, i, retVal, getByteAddress(device)); } private long getPlayPosition(BluetoothDevice device) { long songPosition = -1L; if (device != null) { int deviceIndex = getIndexForDevice(device); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.e(TAG,"Device index is not valid in getPlayPosition"); return songPosition; } if (mCurrentPosMs != RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) { if (deviceFeatures[deviceIndex].mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING) { songPosition = SystemClock.elapsedRealtime() - mPlayStartTimeMs + mCurrentPosMs; } else { songPosition = mCurrentPosMs; } } } else { if (mCurrentPosMs != RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) { if (mCurrentPlayerState == RemoteControlClient.PLAYSTATE_PLAYING) { songPosition = SystemClock.elapsedRealtime() - mPlayStartTimeMs + mCurrentPosMs; } else { songPosition = mCurrentPosMs; } } } if (DEBUG) Log.v(TAG, "getPlayPosition position: " + songPosition + " Device:" + device); return songPosition; } private String getAttributeStringFromCursor(Cursor cursor, int attrId, int deviceIndex) { String attrStr = ""; switch (attrId) { case MEDIA_ATTR_TITLE: attrStr = cursor.getString(cursor.getColumnIndexOrThrow( MediaStore.Audio.Media.TITLE)); break; case MEDIA_ATTR_ARTIST: attrStr = cursor.getString(cursor.getColumnIndexOrThrow( MediaStore.Audio.Media.ARTIST)); break; case MEDIA_ATTR_ALBUM: attrStr = cursor.getString(cursor.getColumnIndexOrThrow( MediaStore.Audio.Media.ALBUM)); break; case MEDIA_ATTR_PLAYING_TIME: attrStr = cursor.getString(cursor.getColumnIndexOrThrow( MediaStore.Audio.Media.DURATION)); break; case MEDIA_ATTR_TRACK_NUM: if (deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_PLAYLISTS)) { attrStr = cursor.getString(cursor.getColumnIndexOrThrow( MediaStore.Audio.Playlists.Members._ID)); } else { attrStr = String.valueOf(cursor.getLong( cursor.getColumnIndexOrThrow("_id"))); } break; case MEDIA_ATTR_NUM_TRACKS: attrStr = String.valueOf(cursor.getCount()); break; case MEDIA_ATTR_GENRE: attrStr = ""; // GENRE is not supported break; default: Log.v(TAG, "getAttributeStringFromCursor: wrong attribute: attrId = " + attrId); break; } if (attrStr == null) { attrStr = new String(); } if (DEBUG) Log.v(TAG, "getAttributeStringFromCursor: attrId = " + attrId + " str = " + attrStr); return attrStr; } private String getAttributeString(int attrId) { String attrStr = null; switch (attrId) { case MEDIA_ATTR_TITLE: attrStr = mMetadata.trackTitle; break; case MEDIA_ATTR_ARTIST: attrStr = mMetadata.artist; break; case MEDIA_ATTR_ALBUM: attrStr = mMetadata.albumTitle; break; case MEDIA_ATTR_PLAYING_TIME: if (mSongLengthMs != 0L) { attrStr = Long.toString(mSongLengthMs); } break; case MEDIA_ATTR_TRACK_NUM: attrStr = Long.toString(mMetadata.tracknum); break; case MEDIA_ATTR_NUM_TRACKS: attrStr = Long.toString(mTrackNumber); break; case MEDIA_ATTR_GENRE: attrStr = mMetadata.genre; break; } if (attrStr == null) { attrStr = new String(); } if (DEBUG) Log.v(TAG, "getAttributeString:attrId=" + attrId + " str=" + attrStr); return attrStr; } private int convertPlayStateToPlayStatus(int playState) { int playStatus = PLAYSTATUS_ERROR; switch (playState) { case RemoteControlClient.PLAYSTATE_PLAYING: case RemoteControlClient.PLAYSTATE_BUFFERING: playStatus = PLAYSTATUS_PLAYING; break; case RemoteControlClient.PLAYSTATE_STOPPED: case RemoteControlClient.PLAYSTATE_NONE: playStatus = PLAYSTATUS_STOPPED; break; case RemoteControlClient.PLAYSTATE_PAUSED: playStatus = PLAYSTATUS_PAUSED; break; case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: playStatus = PLAYSTATUS_FWD_SEEK; break; case RemoteControlClient.PLAYSTATE_REWINDING: case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: playStatus = PLAYSTATUS_REV_SEEK; break; case RemoteControlClient.PLAYSTATE_ERROR: playStatus = PLAYSTATUS_ERROR; break; } return playStatus; } private boolean isPlayingState(int playState) { boolean isPlaying = false; switch (playState) { case RemoteControlClient.PLAYSTATE_PLAYING: case RemoteControlClient.PLAYSTATE_BUFFERING: isPlaying = true; break; default: isPlaying = false; break; } return isPlaying; } /** * This is called from AudioService. It will return whether this device supports abs volume. * NOT USED AT THE MOMENT. * returns true only when both playing devices support absolute volume */ public boolean isAbsoluteVolumeSupported() { List absVolumeSupported = new ArrayList(); for (int i = 0; i < maxAvrcpConnections; i++ ) { if (deviceFeatures[i].mCurrentDevice != null) { // add 1 in byte list if absolute volume is supported // add 0 in byte list if absolute volume not supported if ((deviceFeatures[i].mFeatures & BTRC_FEAT_ABSOLUTE_VOLUME) != 0) { Log.v(TAG, "isAbsoluteVolumeSupported: yes, for dev: " + i); absVolumeSupported.add((byte)1); } else { Log.v(TAG, "isAbsoluteVolumeSupported: no, for dev: " + i); absVolumeSupported.add((byte)0); } } } return !(absVolumeSupported.contains((byte)0) || absVolumeSupported.isEmpty()); } /** * We get this call from AudioService. This will send a message to our handler object, * requesting our handler to call setVolumeNative() */ public void adjustVolume(int direction) { Message msg = mHandler.obtainMessage(MESSAGE_ADJUST_VOLUME, direction, 0); mHandler.sendMessage(msg); } public void setAbsoluteVolume(int volume) { int avrcpVolume = convertToAvrcpVolume(volume); avrcpVolume = Math.min(AVRCP_MAX_VOL, Math.max(0, avrcpVolume)); mHandler.removeMessages(MESSAGE_ADJUST_VOLUME); Message msg = mHandler.obtainMessage(MESSAGE_SET_ABSOLUTE_VOLUME, avrcpVolume, 0); mHandler.sendMessage(msg); } /* Called in the native layer as a btrc_callback to return the volume set on the carkit in the * case when the volume is change locally on the carkit. This notification is not called when * the volume is changed from the phone. * * This method will send a message to our handler to change the local stored volume and notify * AudioService to update the UI */ private void volumeChangeCallback(int volume, int ctype, byte[] address) { Message msg = mHandler.obtainMessage(MESSAGE_VOLUME_CHANGED, volume, ctype, Utils.getAddressStringFromByte(address)); mHandler.sendMessage(msg); } private void notifyVolumeChanged(int volume, BluetoothDevice device) { volume = convertToAudioStreamVolume(volume); mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME); } private int convertToAudioStreamVolume(int volume) { // Rescale volume to match AudioSystem's volume return (int) Math.round((double) volume*mAudioStreamMax/AVRCP_MAX_VOL); } private int convertToAvrcpVolume(int volume) { return (int) Math.ceil((double) volume*AVRCP_MAX_VOL/mAudioStreamMax); } private void updateLocalPlayerSettings( byte[] data) { if (DEBUG) Log.v(TAG, "updateLocalPlayerSettings"); for (int i = 0; i < data.length; i += 2) { if (DEBUG) Log.v(TAG, "ID: " + data[i] + " Value: " + data[i+1]); switch (data[i]) { case ATTRIBUTE_EQUALIZER: settingValues.eq_value = data[i+1]; break; case ATTRIBUTE_REPEATMODE: settingValues.repeat_value = data[i+1]; break; case ATTRIBUTE_SHUFFLEMODE: settingValues.shuffle_value = data[i+1]; break; case ATTRIBUTE_SCANMODE: settingValues.scan_value = data[i+1]; break; } } } private boolean checkPlayerAttributeResponse( byte[] data) { boolean ret = false; if (DEBUG) Log.v(TAG, "checkPlayerAttributeResponse"); for (int i = 0; i < data.length; i += 2) { if (DEBUG) Log.v(TAG, "ID: " + data[i] + " Value: " + data[i+1]); switch (data[i]) { case ATTRIBUTE_EQUALIZER: if (mPendingSetAttributes.contains(new Integer(ATTRIBUTE_EQUALIZER))) { Log.v(TAG, "Pending SetAttribute contains Equalizer"); if(data[i+1] == ATTRIBUTE_NOTSUPPORTED) { ret = false; } else { ret = true; } } break; case ATTRIBUTE_REPEATMODE: if (mPendingSetAttributes.contains(new Integer(ATTRIBUTE_REPEATMODE))) { Log.v(TAG, "Pending SetAttribute contains Repeat"); if(data[i+1] == ATTRIBUTE_NOTSUPPORTED) { ret = false; } else { ret = true; } } break; case ATTRIBUTE_SHUFFLEMODE: if (mPendingSetAttributes.contains(new Integer(ATTRIBUTE_SHUFFLEMODE))) { Log.v(TAG, "Pending SetAttribute contains Shuffle"); if(data[i+1] == ATTRIBUTE_NOTSUPPORTED) { ret = false; } else { ret = true; } } break; } } return ret; } //PDU ID 0x11 private void onListPlayerAttributeRequest(byte[] address) { if (DEBUG) Log.v(TAG, "onListPlayerAttributeRequest"); Intent intent = new Intent(PLAYERSETTINGS_REQUEST); intent.putExtra(COMMAND, CMDGET); intent.putExtra(EXTRA_GET_COMMAND, GET_ATTRIBUTE_IDS); mContext.sendBroadcast(intent, BLUETOOTH_PERM); int deviceIndex = getIndexForDevice(mAdapter.getRemoteDevice( Utils.getAddressStringFromByte(address))); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.e(TAG,"invalid index for device"); return; } deviceFeatures[deviceIndex].isMusicAppResponsePending = true; Message msg = mHandler.obtainMessage(MESSAGE_PLAYERSETTINGS_TIMEOUT, GET_ATTRIBUTE_IDS,0 , Utils.getAddressStringFromByte(address)); mPendingCmds.add(new Integer(msg.arg1)); mHandler.sendMessageDelayed(msg, 500); } //PDU ID 0x12 private void onListPlayerAttributeValues (byte attr, byte[] address) { if (DEBUG)Log.v(TAG, "onListPlayerAttributeValues"); Intent intent = new Intent(PLAYERSETTINGS_REQUEST); intent.putExtra(COMMAND, CMDGET); intent.putExtra(EXTRA_GET_COMMAND, GET_VALUE_IDS); intent.putExtra(EXTRA_ATTRIBUTE_ID, attr); mContext.sendBroadcast(intent, BLUETOOTH_PERM); mPlayerSettings.attr = attr; int deviceIndex = getIndexForDevice(mAdapter.getRemoteDevice( Utils.getAddressStringFromByte(address))); if (deviceIndex == INVALID_DEVICE_INDEX) { Log.e(TAG,"invalid index for device"); return; } deviceFeatures[deviceIndex].isMusicAppResponsePending = true; Message msg = mHandler.obtainMessage(); msg.what = MESSAGE_PLAYERSETTINGS_TIMEOUT; msg.arg1 = GET_VALUE_IDS; msg.arg2 = 0; msg.obj = Utils.getAddressStringFromByte(address); mPendingCmds.add(new Integer(msg.arg1)); mHandler.sendMessageDelayed(msg, 500); } //PDU ID 0x13 private void onGetPlayerAttributeValues (byte attr ,int[] arr , byte[] address) { if (DEBUG) Log.v(TAG, "onGetPlayerAttributeValues: num of attrib " + attr ); int i ; byte[] barray = new byte[attr]; for(i =0 ; i> 8) & 0xff); position++; playerEntry[position] = (byte)mMajorPlayerType; position++; for (count = 0; count < PLAYER_SUBTYPE_FIELD_LENGTH; count++) { playerEntry[position] = (byte)((mPlayerSubType >> (8 * count)) & 0xff); position++; } playerEntry[position] = (byte)convertPlayStateToPlayStatus(mPlayState); position++; for (count = 0; count < FEATURE_BITMASK_FIELD_LENGTH; count++) { playerEntry[position] = (byte)mFeatureMask[count]; position++; } playerEntry[position] = (byte)(mCharsetId & 0xff); position++; playerEntry[position] = (byte)((mCharsetId >> 8) & 0xff); position++; playerEntry[position] = (byte)(mDisplayableNameLength & 0xff); position++; playerEntry[position] = (byte)((mDisplayableNameLength >> 8) & 0xff); position++; for (count = 0; count < mDisplayableNameLength; count++) { playerEntry[position] = (byte)mDisplayableName[count]; position++; } if (position != mEntryLength) { Log.e(TAG, "ERROR populating PlayerItemEntry: position:" + position + "mEntryLength:" + mEntryLength); } if (DEBUG) { Log.v(TAG, "MediaPlayerInfo: mPlayerId=" + mPlayerId); Log.v(TAG, "mMajorPlayerType=" + mMajorPlayerType + " mPlayerSubType=" + mPlayerSubType); Log.v(TAG, "mPlayState=" + mPlayState + " mCharsetId=" + mCharsetId); Log.v(TAG, "mPlayerPackageName=" + mPlayerPackageName + " mDisplayableNameLength=" + mDisplayableNameLength); Log.v(TAG, "mItemLength=" + mItemLength + "mEntryLength=" + mEntryLength); Log.v(TAG, "mFeatureMask="); for (count = 0; count < FEATURE_BITMASK_FIELD_LENGTH; count ++) { Log.v(TAG, "" + mFeatureMask[count]); } Log.v(TAG, "mDisplayableName="); for (count = 0; count < mDisplayableNameLength; count ++) { Log.v(TAG, "" + mDisplayableName[count]); } Log.v(TAG, "playerEntry item is populated as below:="); for (count = 0; count < position; count ++) { Log.v(TAG, "" + playerEntry[count]); } } return playerEntry; } } /** * The media player instances */ private ArrayList mMediaPlayers = new ArrayList(1); }