diff options
author | AnjaneeDevi Kapparapu <c_akappa@qti.qualcomm.com> | 2016-12-09 12:47:52 +0530 |
---|---|---|
committer | Anjaneedevi Kapparapu <akappa@codeaurora.org> | 2016-12-09 12:52:23 +0530 |
commit | b6fb5c31b88156417c57e9cc17f87ca22e69b495 (patch) | |
tree | 64229ba844aa7011e3875dc48e9f60a4a0225360 /src/com | |
parent | 8edd1a3a518f3858ba517de3bf010a5054de161f (diff) | |
parent | 42db863c7055834c96a7c02a4389d55a7cb4968e (diff) | |
download | android_packages_apps_Bluetooth-b6fb5c31b88156417c57e9cc17f87ca22e69b495.tar.gz android_packages_apps_Bluetooth-b6fb5c31b88156417c57e9cc17f87ca22e69b495.tar.bz2 android_packages_apps_Bluetooth-b6fb5c31b88156417c57e9cc17f87ca22e69b495.zip |
To Backport the N-Mr1 Changes
To Backport the N-Mr1 Changes
Merge remote-tracking branch 'origin/bt.lnx.2.1.c1-rel' into bt.lnx.2.1.c1-dev
Change-Id: Id00f000afbc330b368fa2b19d6c9fe468e86f1d6
CRs-Fixed: 1099415
Diffstat (limited to 'src/com')
28 files changed, 855 insertions, 198 deletions
diff --git a/src/com/android/bluetooth/OolConnManager.java b/src/com/android/bluetooth/OolConnManager.java index 034e9da57..c0b56e143 100644 --- a/src/com/android/bluetooth/OolConnManager.java +++ b/src/com/android/bluetooth/OolConnManager.java @@ -40,6 +40,7 @@ public class OolConnManager { static int channel = 0; static boolean sdpDone = false; static String mAddress; + public static boolean interruptSdp = false; public static BluetoothSocket CreateL2capConnection(BluetoothDevice remBtDev,UUID uuid ) { @@ -66,7 +67,7 @@ public class OolConnManager { int waitCount = 0; int channelNo = -1; - while(!sdpDone && waitCount < 100) { + while(!sdpDone && waitCount < 100 && (!interruptSdp)) { try { Thread.sleep(100); @@ -77,6 +78,7 @@ public class OolConnManager { } waitCount = 0; sdpDone = false; + interruptSdp = false; Log.d(TAG,"returning l2c channel as "+channel); channelNo = channel; @@ -86,8 +88,9 @@ public class OolConnManager { public static void saveOppSdpRecord(SdpOppOpsRecord sdpRec, BluetoothDevice btDevice) { - Log.v(TAG,"saveOppSdpRecord"+ btDevice.getAddress()); - if ((mAddress != null) && mAddress.equalsIgnoreCase(btDevice.getAddress())) { + Log.i(TAG, "saveOppSdpRecord" + btDevice.getAddress() + " sdpRec:" + sdpRec); + if (sdpRec != null && (mAddress != null) && mAddress. + equalsIgnoreCase(btDevice.getAddress())) { channel = sdpRec.getL2capPsm(); sdpDone = true; Log.d(TAG,"saveOppSdpRecord channel "+ channel); diff --git a/src/com/android/bluetooth/avrcp/Avrcp.java b/src/com/android/bluetooth/avrcp/Avrcp.java index 50c7ece8c..8d7a77dc7 100644 --- a/src/com/android/bluetooth/avrcp/Avrcp.java +++ b/src/com/android/bluetooth/avrcp/Avrcp.java @@ -123,6 +123,7 @@ public final class Avrcp { private static boolean updatePlayTime; private static boolean updateValues; private int mAddressedPlayerId; + private int mBrowsedPlayerId; /* BTRC features */ public static final int BTRC_FEAT_METADATA = 0x01; @@ -439,6 +440,7 @@ public final class Avrcp { maxAvrcpConnections = maxConnections; deviceFeatures = new DeviceDependentFeature[maxAvrcpConnections]; mAddressedPlayerId = INVALID_ADDRESSED_PLAYER_ID; + mBrowsedPlayerId = INVALID_ADDRESSED_PLAYER_ID; for(int i = 0; i < maxAvrcpConnections; i++) { deviceFeatures[i] = new DeviceDependentFeature(); } @@ -716,7 +718,7 @@ public final class Avrcp { featureMasks2[FEATURE_MASK_REWIND_OFFSET] | FEATURE_MASK_REWIND_MASK; featureMasks2[FEATURE_MASK_FAST_FWD_OFFSET] = featureMasks2[FEATURE_MASK_FAST_FWD_OFFSET] | FEATURE_MASK_FAST_FWD_MASK; - mediaPlayerInfo1 = new MediaPlayerInfo ((short)0x0001, + mediaPlayerInfo1 = new MediaPlayerInfo ((short)0x0000, MAJOR_TYPE_AUDIO, SUB_TYPE_NONE, (byte)PlaybackState.STATE_PAUSED, @@ -726,7 +728,7 @@ public final class Avrcp { "com.android.music", true, featureMasks); - mediaPlayerInfo2 = new MediaPlayerInfo ((short)0x0000, + mediaPlayerInfo2 = new MediaPlayerInfo ((short)0x0001, MAJOR_TYPE_AUDIO, SUB_TYPE_NONE, (byte)PlaybackState.STATE_PAUSED, @@ -820,42 +822,6 @@ public final class Avrcp { } @Override - public void onUpdateFolderInfoBrowsedPlayer(String stringUri) { - Log.v(TAG, "onClientFolderInfoBrowsedPlayer: stringUri: " + stringUri); - if (stringUri != null) { - String[] ExternalPath = stringUri.split("/"); - if (ExternalPath.length < 4) { - Log.d(TAG, "Wrong entries."); - mHandler.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 (mHandler != null) { - // Don't send the complete path to CK as few gets confused by that - // Send only the name of the root folder - mHandler.obtainMessage(MSG_UPDATE_BROWSED_PLAYER_FOLDER, NUM_ROOT_ELEMENTS, - OPERATION_SUCCESSFUL, SplitPath).sendToTarget(); - } - } else { - mHandler.obtainMessage(MSG_UPDATE_BROWSED_PLAYER_FOLDER, 0, INTERNAL_ERROR, - null).sendToTarget(); - } - Log.d(TAG, "Exit onUpdateFolderInfoBrowsedPlayer()"); - } - - @Override public void onUpdateNowPlayingEntries(long[] playList) { Log.v(TAG, "onClientUpdateNowPlayingEntries"); if (mHandler != null) { @@ -1023,11 +989,6 @@ public final class Avrcp { 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(); @@ -1082,6 +1043,19 @@ public final class Avrcp { deviceFeatures[deviceIndex].mVolumeMapping.clear(); if ((deviceFeatures[deviceIndex].mFeatures & + BTRC_FEAT_BROWSE) != 0) + { + Log.v(TAG,"BTRC_FEAT_BROWSE support is present on remote side"); + deviceFeatures[deviceIndex].mCurrentPath = PATH_ROOT; + deviceFeatures[deviceIndex].mCurrentPathUid = null; + deviceFeatures[deviceIndex].mMediaUri = Uri.parse("content://media/external/audio/media"); + Log.v(TAG," update current path to root folder before browse"); + deviceFeatures[deviceIndex].isBrowsingSupported = true; + mBrowserDevice = device; + Log.v(TAG,"Browsing supported by remote : mBrowserDevice = " + mBrowserDevice); + } + + if ((deviceFeatures[deviceIndex].mFeatures & BTRC_FEAT_AVRC_UI_UPDATE) != 0) { int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth; @@ -1597,8 +1571,8 @@ public final class Avrcp { 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.mSize, itemAttr.mAddress); + processGetItemAttrInternal((byte)msg.arg2, itemAttr.mUid, (byte)msg.arg1, + attrIds, itemAttr.mSize, itemAttr.mAddress); break; case MESSAGE_GET_FOLDER_ITEMS: FolderListEntries folderListEntries = (FolderListEntries)msg.obj; @@ -1936,7 +1910,22 @@ public final class Avrcp { long TrackNumberRsp = -1L; try { TrackNumberRsp = Long.parseLong(mMediaAttributes.getString - (MediaAttributes.ATTR_MEDIA_NUMBER)); + (MediaAttributes.ATTR_MEDIA_NUMBER)); + //if (deviceFeatures[i].isBrowsingSupported) {//Track num + if (((deviceFeatures[i].mFeatures & BTRC_FEAT_BROWSE) != 0) && + (deviceFeatures[i].mCurrentPath != PATH_INVALID)) {//Track num + Log.e(TAG,"TRACK_CHANGE_NOTIFICATION device suppports browsing"); + if (mMediaPlayers.size() > 0) { + final Iterator<MediaPlayerInfo> rccIterator = mMediaPlayers.iterator(); + while (rccIterator.hasNext()) { + final MediaPlayerInfo di = rccIterator.next(); + if (di.IsPlayerBrowsable() && (di.GetPlayerFocus() == true)) { + TrackNumberRsp = Long.parseLong(mMediaAttributes.getString + (MediaAttributes.ATTR_TRACK_NUM)); + } + } + } + } } catch (Exception e) { Log.e(TAG, "sendTrackChangedRsp Exception e" + e); TrackNumberRsp = -1L; @@ -2005,27 +1994,6 @@ public final class Avrcp { updateResetNotification(PLAYER_STATUS_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)); - } - void updateNowPlayingContentChanged() { Log.v(TAG, "updateNowPlayingContentChanged"); for (int i = 0; i < maxAvrcpConnections; i++) { @@ -2090,6 +2058,11 @@ public final class Avrcp { } Log.v(TAG, "updateNowPlayingEntriesReceived"); + if (mCachedRequest.mIsGetItemAttr) { + Log.v(TAG,"calling processGetItemAttrdummy"); + processGetItemAttrdummy(playList); + return; + } if (!mCachedRequest.mIsGetFolderItems) { Log.v(TAG, "getTotalNumberOfItemsRspNative for NowPlaying List"); @@ -2186,13 +2159,22 @@ public final class Avrcp { ArrayList<Integer> mAttrList; int mSize; boolean mIsGetFolderItems; + boolean mIsGetItemAttr; + long mUid; + byte mScope; + String mDeviceAddress; public CachedRequest(long start, long end, byte attrCnt, int[] attrs, - int size, boolean isGetFolderItems) { + int size, boolean isGetFolderItems, long uid, byte scope, + String deviceAddress, boolean isGetItemAttr) { mStart = start; mEnd = end; mAttrCnt = attrCnt; mSize = size; mIsGetFolderItems = isGetFolderItems; + mUid = uid; + mScope = scope; + mDeviceAddress = deviceAddress; + mIsGetItemAttr = isGetItemAttr; mAttrList = new ArrayList<Integer>(); for (int i = 0; i < attrCnt; ++i) { mAttrList.add(new Integer(attrs[i])); @@ -2236,6 +2218,7 @@ public final class Avrcp { private String coverArt; private String tracknum; + private static final int ATTR_TRACK_NUM = 0; private static final int ATTR_TITLE = 1; private static final int ATTR_ARTIST_NAME = 2; private static final int ATTR_ALBUM_NAME = 3; @@ -2253,12 +2236,12 @@ public final class Avrcp { artistName = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_ARTIST)); albumName = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_ALBUM)); - mediaNumber = longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER)); + /* playlist array starts with 0*/ + mediaNumber = longStringOrBlank((data.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER) + 1L)); mediaTotalNumber = longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS)); genre = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_GENRE)); playingTimeMs = longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_DURATION)); tracknum = longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_DISC_NUMBER)); - // Try harder for the title. title = data.getString(MediaMetadata.METADATA_KEY_TITLE); @@ -2300,6 +2283,8 @@ public final class Avrcp { return new String(); switch (attrId) { + case ATTR_TRACK_NUM: + return tracknum; case ATTR_TITLE: return title; case ATTR_ARTIST_NAME: @@ -2409,6 +2394,9 @@ public final class Avrcp { private void processSetBrowsedPlayer(int playerId, String deviceAddress) { String packageName = null; + int folder_depth = 0; + long num_attributes = 0; + ArrayList <String> folderPath = new ArrayList<String>(); byte retError = INVALID_PLAYER_ID; BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress); int deviceIndex = getIndexForDevice(device); @@ -2416,10 +2404,7 @@ public final class Avrcp { 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) { @@ -2452,15 +2437,200 @@ public final class Avrcp { } } } - if (packageName != null) { - mMediaController.getTransportControls().setRemoteControlClientBrowsedPlayer(); - mBrowserDevice = device; - } else { + if (packageName == null) { if (DEBUG) Log.v(TAG, "player not available for browse"); setBrowsedPlayerRspNative(retError , 0x0, 0x0, 0x0, 0x0, null, getByteAddress(device)); + } else { + String CurrentPath = deviceFeatures[deviceIndex].mCurrentPath; + String CurrentPathUid = deviceFeatures[deviceIndex].mCurrentPathUid; + Uri CurrentUri = deviceFeatures[deviceIndex].mMediaUri; + long folderUid = (CurrentPathUid != null) ? Long.valueOf(CurrentPathUid):(long)0; + retError = OPERATION_SUCCESSFUL; + folderPath.add(PATH_ROOT); + + if (deviceFeatures[deviceIndex].mCurrentPath.equals(PATH_ROOT)) { + num_attributes = NUM_ROOT_ELEMENTS; + folder_depth = 0; + + } else if (CurrentPath.equals(PATH_TITLES)) { + folderPath.add(CurrentPath); + num_attributes = getNumItems(PATH_TITLES, + MediaStore.Audio.Media.TITLE, deviceIndex); + + } else if (CurrentPath.equals(PATH_ALBUMS)) { + folderPath.add(CurrentPath); + if (CurrentPathUid == null) { + num_attributes = getNumItems(PATH_ALBUMS, + MediaStore.Audio.Media.ALBUM_ID, deviceIndex); + } else { + Cursor cursor = null; + try { + cursor = mContext.getContentResolver().query(CurrentUri, + mCursorCols, MediaStore.Audio.Media.IS_MUSIC + "=1 AND " + + MediaStore.Audio.Media.ALBUM_ID + "=" + folderUid, null, + MediaStore.Audio.Albums.DEFAULT_SORT_ORDER); + if (cursor != null) { + num_attributes = cursor.getCount(); + String FolderName; + cursor.moveToFirst(); + FolderName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM)); + Log.i(TAG,"ALBUM =" + FolderName); + folderPath.add(FolderName); + } else { + Log.i(TAG, "Error: could not fetch the elements"); + retError = INTERNAL_ERROR; + setBrowsedPlayerRspNative(retError, + 0x0, 0x0, 0x0, 0x0, + null, getByteAddress(device)); + } + } + catch(Exception e) { + Log.e(TAG, "Exception e" + e); + retError = INTERNAL_ERROR; + setBrowsedPlayerRspNative(retError, + 0x0, 0x0, 0x0, 0x0, + null, getByteAddress(device)); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + + } else if (CurrentPath.equals(PATH_ARTISTS)) { + folderPath.add(CurrentPath); + if (CurrentPathUid == null) { + num_attributes = getNumItems(PATH_ARTISTS, + MediaStore.Audio.Media.ARTIST_ID, deviceIndex); + } else { + Cursor cursor = null; + try { + cursor = mContext.getContentResolver().query(CurrentUri, + mCursorCols, MediaStore.Audio.Media.IS_MUSIC + "=1 AND " + + MediaStore.Audio.Media.ARTIST_ID + "=" + folderUid, null, + MediaStore.Audio.Artists.DEFAULT_SORT_ORDER); + if (cursor != null) { + num_attributes = cursor.getCount(); + String FolderName; + cursor.moveToFirst(); + FolderName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST)); + Log.i(TAG,"ARTIST =" + FolderName); + folderPath.add(FolderName); + } else { + Log.i(TAG, "Error: could not fetch the elements"); + retError = INTERNAL_ERROR; + setBrowsedPlayerRspNative(retError, + 0x0, 0x0, 0x0, 0x0, + null, getByteAddress(device)); + } + } catch(Exception e) { + Log.e(TAG, "Exception e" + e); + retError = INTERNAL_ERROR; + setBrowsedPlayerRspNative(retError, + 0x0, 0x0, 0x0, 0x0, + null, getByteAddress(device)); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + + } else if (CurrentPath.equals(PATH_PLAYLISTS)) { + folderPath.add(CurrentPath); + if (CurrentPathUid == null) { + num_attributes = getNumPlaylistItems(); + } else { + 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) { + num_attributes = cursor.getCount(); + } + } catch (Exception e) { + Log.e(TAG, "Exception " + e); + retError = INTERNAL_ERROR; + setBrowsedPlayerRspNative(retError, + 0x0, 0x0, 0x0, 0x0, + null, getByteAddress(device)); + } finally { + if (cursor != null) { + cursor.close(); + } + } + Cursor tempcursor = null; + String[] cols = new String[] { + MediaStore.Audio.Playlists._ID, + MediaStore.Audio.Playlists.NAME + }; + try { + tempcursor = mContext.getContentResolver().query( + MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, + cols, MediaStore.Audio.Playlists._ID + "=" + folderUid, + null, MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER); + if (tempcursor != null) { + tempcursor.moveToFirst(); + String FolderName; + FolderName = tempcursor.getString(tempcursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.NAME)); + Log.i(TAG,"PLAYLIST = " + FolderName); + folderPath.add(FolderName); + } + } catch (Exception e) { + Log.e(TAG, "Exception " + e); + retError = INTERNAL_ERROR; + setBrowsedPlayerRspNative(retError, + 0x0, 0x0, 0x0, 0x0, + null, getByteAddress(device)); + } finally { + if (tempcursor != null) { + tempcursor.close(); + } + } + } + + } else { + folderPath.clear(); + retError = INTERNAL_ERROR; + num_attributes = 0; + } + folder_depth = folderPath.size() - 1; + String [] folderNames = new String[folderPath.size()]; + folderNames = folderPath.toArray(folderNames); + Log.i(TAG,"SetBrowsedplayer for playerid = " + playerId + " and status code" + retError); + for (int i = 0; i < folderPath.size(); i++) { + Log.i(TAG,"folderNames[" + i + "] = " + folderNames[i]); + } + setBrowsedPlayerRspNative(retError , + 0x0, (int)num_attributes, folder_depth, (int)CHAR_SET_UTF8, + folderNames, getByteAddress(device)); + } + + if (retError == OPERATION_SUCCESSFUL) { + mBrowsedPlayerId = playerId; + Log.i(TAG,"Set Browsed player id = " + mBrowsedPlayerId); + } else { + mBrowsedPlayerId = INVALID_ADDRESSED_PLAYER_ID; + Log.i(TAG,"Set Browsed player failed with error = " + retError); } } @@ -3157,7 +3327,8 @@ public final class Avrcp { } if (mMediaController != null) { mMediaController.getTransportControls().getRemoteControlClientNowPlayingEntries(); - mCachedRequest = new CachedRequest((long)0, (long)0, (byte)0, null, (int)0, false); + mCachedRequest = new CachedRequest((long)0, (long)0, (byte)0, null, (int)0, false, + (long)0, (byte)0, null, false); } else { Log.e(TAG, "processGetNowPlayingTotalItems fails: mMediaController is null"); getTotalNumberOfItemsRspNative((byte)INTERNAL_ERROR, virtualFileTotalItems, @@ -3394,9 +3565,42 @@ public final class Avrcp { MediaStore.Audio.Media.IS_PODCAST, MediaStore.Audio.Media.BOOKMARK }; + private void processGetItemAttrInternal(byte scope, long uid, byte numAttr, int[] attrs, + int size, String deviceAddress) { + boolean cachereq = false; + Log.v(TAG,"processGetItemAttrInternal uid = " + uid); + for (int i = 0; i < numAttr; i++) { + if (attrs[i] == MEDIA_ATTR_TRACK_NUM || + attrs[i] == MEDIA_ATTR_NUM_TRACKS) + cachereq = true; + } + if (scope == SCOPE_NOW_PLAYING && cachereq) { + Log.v(TAG,"scope now playing, caching req"); + mMediaController.getTransportControls().getRemoteControlClientNowPlayingEntries(); + mCachedRequest = new CachedRequest((long)0, (long)0, numAttr, attrs, size, false, + uid, scope, deviceAddress, true); + } + else + processGetItemAttr(scope, uid, numAttr, attrs, size, deviceAddress, null); + } + private void processGetItemAttrdummy(long [] playlist) { + byte scope = mCachedRequest.mScope; + long uid = mCachedRequest.mUid; + byte numAttr = mCachedRequest.mAttrCnt; + int[] attrs = new int[numAttr]; + int size = mCachedRequest.mSize; + String deviceAddress = mCachedRequest.mDeviceAddress; + for (int i = 0; i < numAttr; ++i) + attrs[i] = mCachedRequest.mAttrList.get(i).intValue(); + + Log.v(TAG,"processGetItemAttrdummy"); + processGetItemAttr(scope, uid, numAttr, attrs, size, deviceAddress, playlist); + mCachedRequest.mIsGetItemAttr = false; + //mCachedGetItemAttrReq = null; + } private void processGetItemAttr(byte scope, long uid, byte numAttr, int[] attrs, - int size, String deviceAddress) { + int size, String deviceAddress, long[] playlist) { if (DEBUG) Log.v(TAG, "processGetItemAttr: scope: " + scope + " uid:" + uid + " numAttr:" + numAttr + " size: " + size); @@ -3440,6 +3644,31 @@ public final class Avrcp { validAttrib ++; } } + if (scope == SCOPE_NOW_PLAYING && playlist != null) { + long arraylength = playlist.length; + Log.i(TAG,"arraylength = " + arraylength); + for (int i = 0; i < numAttr; ++i) { + if (attrs[i] == MEDIA_ATTR_NUM_TRACKS) + textArray[i] = String.valueOf(arraylength); + if (attrs[i] == MEDIA_ATTR_TRACK_NUM) { + for (int j = 0; j < playlist.length; ++j) { + cursor = mContext.getContentResolver().query( + deviceFeatures[deviceIndex].mMediaUri, mCursorCols, + MediaStore.Audio.Media.IS_MUSIC + "=1 AND _id=" + + playlist[j], null, null); + if (cursor != null) { + cursor.moveToFirst(); + long muid = cursor.getLong(cursor.getColumnIndexOrThrow("_id")); + Log.i(TAG,"uid = " + uid + "muid = " + muid); + if (muid == uid) { + textArray[i] = String.valueOf(j+1); //j starts from 0 + break; + } + } + } + } + } + } getItemAttrRspNative(numAttr ,attrs , textArray, size, getByteAddress(device)); } @@ -3660,6 +3889,15 @@ public final class Avrcp { attIds[count] = 0; } + if (!deviceFeatures[deviceIndex].isBrowsingSupported || mBrowsedPlayerId != 0) { + getFolderItemsRspNative((byte)INTERNAL_ERROR , + numItems, itemType, uid, type, + playable, displayName, numAtt, attValues, attIds, size, + getByteAddress(deviceFeatures[deviceIndex].mCurrentDevice)); + Log.v(TAG, "Browsed player is yet not set"); + return; + } + if (DEBUG) Log.v(TAG, "mCurrentPath: " + deviceFeatures[deviceIndex].mCurrentPath); @@ -4428,7 +4666,8 @@ public final class Avrcp { } } mMediaController.getTransportControls().getRemoteControlClientNowPlayingEntries(); - mCachedRequest = new CachedRequest(start, end, numAttr, attrs, size, true); + mCachedRequest = new CachedRequest(start, end, numAttr, attrs, size, true, (long)0, + (byte)0, null, false); } } @@ -4660,12 +4899,34 @@ public final class Avrcp { try { TrackNumberRsp = Long.parseLong(mMediaAttributes.getString - (MediaAttributes.ATTR_MEDIA_NUMBER)); + (MediaAttributes.ATTR_MEDIA_NUMBER)); + if ((((deviceFeatures[deviceIndex].mFeatures & BTRC_FEAT_BROWSE) != 0) && + (deviceFeatures[deviceIndex].mCurrentPath != PATH_INVALID)) || + ((deviceFeatures[deviceIndex].mTrackChangedNT == NOTIFICATION_TYPE_INTERIM) && + (((deviceFeatures[deviceIndex].mFeatures & BTRC_FEAT_BROWSE) != 0) && + deviceFeatures[deviceIndex].mCurrentPath == PATH_INVALID))) { + /* First time when media playback is resumed after AVRCP connection, + * track changed rsp is not sent, So send track num for interim resp + * if browse channel is not connected yet to avoid metadata not getting + * updated properly. + */ + Log.e(TAG,"sendTrackChangedRsp: device supports browsing"); + if (mMediaPlayers.size() > 0) { + final Iterator<MediaPlayerInfo> rccIterator = mMediaPlayers.iterator(); + while (rccIterator.hasNext()) { + final MediaPlayerInfo di = rccIterator.next(); + if (di.IsPlayerBrowsable() && (di.GetPlayerFocus() == true)) { + TrackNumberRsp = Long.parseLong(mMediaAttributes.getString + (MediaAttributes.ATTR_TRACK_NUM)); + Log.e(TAG,"sendTrackChangedRsp: in focus tracnum = " + TrackNumberRsp); + } + } + } + } } catch (Exception e) { Log.e(TAG, "sendTrackChangedRsp Exception e" + e); TrackNumberRsp = -1; } - /* track is stored in big endian format */ for (int i = 0; i < TRACK_ID_SIZE; ++i) { track[i] = (byte) (TrackNumberRsp >> (56 - 8 * i)); @@ -4783,16 +5044,17 @@ public final class Avrcp { int playStatus = PLAYSTATUS_ERROR; switch (state.getState()) { case PlaybackState.STATE_PLAYING: - case PlaybackState.STATE_BUFFERING: playStatus = PLAYSTATUS_PLAYING; break; case PlaybackState.STATE_STOPPED: + case PlaybackState.STATE_CONNECTING: case PlaybackState.STATE_NONE: playStatus = PLAYSTATUS_STOPPED; break; case PlaybackState.STATE_PAUSED: + case PlaybackState.STATE_BUFFERING: playStatus = PLAYSTATUS_PAUSED; break; @@ -4832,6 +5094,14 @@ public final class Avrcp { return; } long playPositionMs = getPlayPosition(deviceFeatures[i].mCurrentDevice); + int currPlayStatus = convertPlayStateToPlayStatus(deviceFeatures[i].mCurrentPlayState); + + // Some remote devices are going to bad state when sending play position + // as ffff for non-playing state + if (playPositionMs == -1L && currPlayStatus != PLAYSTATUS_PLAYING) { + if (DEBUG) Log.d(TAG, " Don't send invalid play position notification for non-playing state"); + return; + } // mNextPosMs is set to -1 when the previous position was invalid // so this will be true if the new position is valid & old was invalid. diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java index 09f03808f..f4d029796 100644 --- a/src/com/android/bluetooth/btservice/AdapterService.java +++ b/src/com/android/bluetooth/btservice/AdapterService.java @@ -89,7 +89,8 @@ import android.os.SystemProperties; public class AdapterService extends Service { private static final String TAG = "BluetoothAdapterService"; - private static final boolean DBG = false; + private static final boolean DBG = true; + private static final boolean VERBOSE = false; private static final boolean TRACE_REF = false; private static final int MIN_ADVT_INSTANCES_FOR_MA = 5; private static final int MIN_OFFLOADED_FILTERS = 10; @@ -539,6 +540,8 @@ public class AdapterService extends Service { mProfileObserver = new ProfileObserver(getApplicationContext(), this, new Handler()); mProfileObserver.start(); mVendor.init(); + + setAdapterService(this); } @Override @@ -594,9 +597,6 @@ public class AdapterService extends Service { // Ignore. } - //FIXME: Set static instance here??? - setAdapterService(this); - //Start Gatt service setGattProfileServiceState(supportedProfileServices,BluetoothAdapter.STATE_ON); } @@ -1643,7 +1643,10 @@ public class AdapterService extends Service { Log.i(TAG,"A2dp Multicast is Ongoing, ignore discovery"); return false; } - + if (mAdapterProperties.isDiscovering()) { + Log.i(TAG,"discovery already active, ignore startDiscovery"); + return false; + } return startDiscoveryNative(); } @@ -1651,6 +1654,10 @@ public class AdapterService extends Service { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); + if (!mAdapterProperties.isDiscovering()) { + Log.i(TAG,"discovery not active, ignore cancelDiscovery"); + return false; + } return cancelDiscoveryNative(); } @@ -1754,6 +1761,12 @@ public class AdapterService extends Service { } } + private void cancelDiscoveryforautoConnect(){ + if (mAdapterProperties.isDiscovering() == true) { + cancelDiscovery(); + } + } + private void autoConnectHeadset(){ HeadsetService hsService = HeadsetService.getHeadsetService(); @@ -1763,6 +1776,7 @@ public class AdapterService extends Service { } for (BluetoothDevice device : bondedDevices) { if (hsService.getPriority(device) == BluetoothProfile.PRIORITY_AUTO_CONNECT ){ + cancelDiscoveryforautoConnect(); debugLog("autoConnectHeadset() - Connecting HFP with " + device.toString()); hsService.connect(device); } @@ -1777,6 +1791,7 @@ public class AdapterService extends Service { } for (BluetoothDevice device : bondedDevices) { if (a2dpSservice.getPriority(device) == BluetoothProfile.PRIORITY_AUTO_CONNECT ){ + cancelDiscoveryforautoConnect(); debugLog("autoConnectA2dp() - Connecting A2DP with " + device.toString()); a2dpSservice.connect(device); } @@ -1792,9 +1807,10 @@ public class AdapterService extends Service { for (BluetoothDevice device : bondedDevices) { if (headsetClientService.getPriority(device) == BluetoothProfile.PRIORITY_AUTO_CONNECT){ - debugLog("autoConnectHeadsetClient() - Connecting Headset Client with " + - device.toString()); - headsetClientService.connect(device); + cancelDiscoveryforautoConnect(); + debugLog("autoConnectHeadsetClient() - Connecting Headset Client with " + + device.toString()); + headsetClientService.connect(device); } } } @@ -1808,6 +1824,7 @@ public class AdapterService extends Service { for (BluetoothDevice device : bondedDevices) { if (a2dpSinkService.getPriority(device) == BluetoothProfile.PRIORITY_AUTO_CONNECT) { + cancelDiscoveryforautoConnect(); debugLog("autoConnectA2dpSink() - Connecting A2DP Sink with " + device.toString()); a2dpSinkService.connect(device); } @@ -1822,6 +1839,7 @@ public class AdapterService extends Service { } for (BluetoothDevice device : bondedDevices) { if (pbapClientService.getPriority(device) == BluetoothProfile.PRIORITY_AUTO_CONNECT) { + cancelDiscoveryforautoConnect(); debugLog("autoConnectPbapClient() - Connecting PBAP Client with " + device.toString()); pbapClientService.connect(device); @@ -2101,9 +2119,9 @@ public class AdapterService extends Service { case BluetoothProfile.A2DP: A2dpService a2dpService = A2dpService.getA2dpService(); - deviceList = a2dpService.getConnectedDevices(); if ((a2dpService != null) && (BluetoothProfile.PRIORITY_AUTO_CONNECT != a2dpService.getPriority(device))){ + deviceList = a2dpService.getConnectedDevices(); adjustOtherSinkPriorities(a2dpService, deviceList); a2dpService.setPriority(device,BluetoothProfile.PRIORITY_AUTO_CONNECT); } @@ -2662,11 +2680,12 @@ public class AdapterService extends Service { } } - debugLog("energyInfoCallback() status = " + status + - "tx_time = " + tx_time + "rx_time = " + rx_time + - "idle_time = " + idle_time + "energy_used = " + energy_used + - "ctrl_state = " + ctrl_state + - "traffic = " + Arrays.toString(data)); + verboseLog("energyInfoCallback() status = " + status + + "tx_time = " + tx_time + "rx_time = " + rx_time + + "idle_time = " + idle_time + + "energy_used = " + energy_used + + "ctrl_state = " + ctrl_state + + "traffic = " + Arrays.toString(data)); } private int getIdleCurrentMa() { @@ -2712,8 +2731,8 @@ public class AdapterService extends Service { enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); if (args.length > 0) { - debugLog("dumpsys arguments, check for protobuf output: " + - TextUtils.join(" ", args)); + verboseLog("dumpsys arguments, check for protobuf output: " + + TextUtils.join(" ", args)); if (args[0].startsWith("--proto")) { if (args[0].equals("--proto-java-bin")) { dumpJava(fd); @@ -2761,6 +2780,7 @@ public class AdapterService extends Service { private void dumpJava(FileDescriptor fd) { BluetoothProto.BluetoothLog log = new BluetoothProto.BluetoothLog(); + log.setNumBondedDevices(getBondedDevices().length); for (ProfileService profile : mProfiles) { profile.dumpProto(log); @@ -2788,6 +2808,10 @@ public class AdapterService extends Service { if (DBG) Log.d(TAG, msg); } + private void verboseLog(String msg) { + if (VERBOSE) Log.v(TAG, msg); + } + private void errorLog(String msg) { Log.e(TAG, msg); } diff --git a/src/com/android/bluetooth/btservice/RemoteDevices.java b/src/com/android/bluetooth/btservice/RemoteDevices.java index 19089a742..94c469d8f 100644 --- a/src/com/android/bluetooth/btservice/RemoteDevices.java +++ b/src/com/android/bluetooth/btservice/RemoteDevices.java @@ -30,12 +30,15 @@ import com.android.bluetooth.Utils; import java.util.concurrent.atomic.AtomicInteger; import java.util.ArrayList; import java.util.HashMap; - +import java.util.LinkedList; +import java.util.Queue; final class RemoteDevices { private static final boolean DBG = false; private static final String TAG = "BluetoothRemoteDevices"; + // Maximum number of device properties to remember + private static final int MAX_DEVICE_QUEUE_SIZE = 200; private static BluetoothAdapter mAdapter; private static AdapterService mAdapterService; @@ -45,13 +48,15 @@ final class RemoteDevices { private static final int UUID_INTENT_DELAY = 6000; private static final int MESSAGE_UUID_INTENT = 1; - private HashMap<BluetoothDevice, DeviceProperties> mDevices; + private HashMap<String, DeviceProperties> mDevices; + private Queue<String> mDeviceQueue; RemoteDevices(AdapterService service) { mAdapter = BluetoothAdapter.getDefaultAdapter(); mAdapterService = service; mSdpTracker = new ArrayList<BluetoothDevice>(); - mDevices = new HashMap<BluetoothDevice, DeviceProperties>(); + mDevices = new HashMap<String, DeviceProperties>(); + mDeviceQueue = new LinkedList<String>(); } @@ -61,6 +66,9 @@ final class RemoteDevices { if (mDevices != null) mDevices.clear(); + + if (mDeviceQueue != null) + mDeviceQueue.clear(); } @Override @@ -70,28 +78,36 @@ final class RemoteDevices { DeviceProperties getDeviceProperties(BluetoothDevice device) { synchronized (mDevices) { - return mDevices.get(device); + return mDevices.get(device.getAddress()); } } BluetoothDevice getDevice(byte[] address) { - synchronized (mDevices) { - for (BluetoothDevice dev : mDevices.keySet()) { - if (dev.getAddress().equals(Utils.getAddressStringFromByte(address))) { - return dev; - } - } - } - return null; + DeviceProperties prop = mDevices.get(Utils.getAddressStringFromByte(address)); + if (prop == null) + return null; + return prop.getDevice(); } DeviceProperties addDeviceProperties(byte[] address) { synchronized (mDevices) { DeviceProperties prop = new DeviceProperties(); - BluetoothDevice device = - mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address)); + prop.mDevice = mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address)); prop.mAddress = address; - mDevices.put(device, prop); + String key = Utils.getAddressStringFromByte(address); + DeviceProperties pv = mDevices.put(key, prop); + + if (pv == null) { + mDeviceQueue.offer(key); + if (mDeviceQueue.size() > MAX_DEVICE_QUEUE_SIZE) { + String deleteKey = mDeviceQueue.poll(); + for (BluetoothDevice device : mAdapterService.getBondedDevices()) { + if (device.getAddress().equals(deleteKey)) return prop; + } + debugLog("Removing device " + deleteKey + " from property map"); + mDevices.remove(deleteKey); + } + } return prop; } } @@ -105,6 +121,7 @@ final class RemoteDevices { private int mDeviceType; private String mAlias; private int mBondState; + private BluetoothDevice mDevice; DeviceProperties() { mBondState = BluetoothDevice.BOND_NONE; @@ -147,6 +164,15 @@ final class RemoteDevices { } /** + * @return the mDevice + */ + BluetoothDevice getDevice() { + synchronized (mObject) { + return mDevice; + } + } + + /** * @return mRssi */ short getRssi() { @@ -235,6 +261,7 @@ final class RemoteDevices { BluetoothDevice bdDevice = getDevice(address); DeviceProperties device; if (bdDevice == null) { + debugLog("Added new device property"); device = addDeviceProperties(address); bdDevice = getDevice(address); } else { @@ -244,11 +271,12 @@ final class RemoteDevices { for (int j = 0; j < types.length && device != null; j++) { type = types[j]; val = values[j]; - if(val.length <= 0) + if (val.length <= 0) errorLog("devicePropertyChangedCallback: bdDevice: " + bdDevice + ", value is empty for type: " + type); else { synchronized(mObject) { + debugLog("Property type: " + type); switch (type) { case AbstractionLayer.BT_PROPERTY_BDNAME: device.mName = new String(val); diff --git a/src/com/android/bluetooth/btservice/bluetooth.proto b/src/com/android/bluetooth/btservice/bluetooth.proto index 11311ea58..77ded7807 100644 --- a/src/com/android/bluetooth/btservice/bluetooth.proto +++ b/src/com/android/bluetooth/btservice/bluetooth.proto @@ -25,6 +25,9 @@ message BluetoothLog { // Scan event information. repeated ScanEvent scan_event = 4; + + // Number of bonded devices. + optional int32 num_bonded_devices = 5; } // The information about the device. diff --git a/src/com/android/bluetooth/gatt/AdvertiseManager.java b/src/com/android/bluetooth/gatt/AdvertiseManager.java index 058bf4b59..3fb4aa945 100644 --- a/src/com/android/bluetooth/gatt/AdvertiseManager.java +++ b/src/com/android/bluetooth/gatt/AdvertiseManager.java @@ -194,7 +194,7 @@ class AdvertiseManager { private void handleStartAdvertising(AdvertiseClient client) { Utils.enforceAdminPermission(mService); int clientIf = client.clientIf; - if (mAdvertiseClients.contains(clientIf)) { + if (mAdvertiseClients.contains(client)) { postCallback(clientIf, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED); return; } diff --git a/src/com/android/bluetooth/gatt/ContextMap.java b/src/com/android/bluetooth/gatt/ContextMap.java index e3044d586..024572792 100644 --- a/src/com/android/bluetooth/gatt/ContextMap.java +++ b/src/com/android/bluetooth/gatt/ContextMap.java @@ -190,6 +190,7 @@ import com.android.bluetooth.btservice.BluetoothProto; while (i.hasNext()) { App entry = i.next(); if (entry.id == id) { + removeConnectionsByAppId(id); entry.unlinkToDeath(); entry.appScanStats.isRegistered = false; i.remove(); @@ -205,7 +206,7 @@ import com.android.bluetooth.btservice.BluetoothProto; void addConnection(int id, int connId, String address) { synchronized (mConnections) { App entry = getById(id); - if (entry != null){ + if (entry != null) { mConnections.add(new Connection(connId, address, id)); } } @@ -228,6 +229,19 @@ import com.android.bluetooth.btservice.BluetoothProto; } /** + * Remove all connections for a given application ID. + */ + void removeConnectionsByAppId(int appId) { + Iterator<Connection> i = mConnections.iterator(); + while (i.hasNext()) { + Connection connection = i.next(); + if (connection.appId == appId) { + i.remove(); + } + } + } + + /** * Get an application context by ID. */ App getById(int id) { diff --git a/src/com/android/bluetooth/gatt/GattService.java b/src/com/android/bluetooth/gatt/GattService.java index e1e5e5728..f8bef759d 100644 --- a/src/com/android/bluetooth/gatt/GattService.java +++ b/src/com/android/bluetooth/gatt/GattService.java @@ -237,6 +237,7 @@ public class GattService extends ProfileService { boolean permissionCheck(int connId, int handle) { List<BluetoothGattService> db = gattClientDatabases.get(connId); + if (db == null) return true; for (BluetoothGattService service : db) { for (BluetoothGattCharacteristic characteristic: service.getCharacteristics()) { @@ -759,7 +760,15 @@ public class GattService extends ProfileService { void onSearchCompleted(int connId, int status) throws RemoteException { if (DBG) Log.d(TAG, "onSearchCompleted() - connId=" + connId+ ", status=" + status); // Gatt DB is ready! - gattClientGetGattDbNative(connId); + + // This callback was called from the jni_workqueue thread. If we make request to the stack + // on the same thread, it might cause deadlock. Schedule request on a new thread instead. + Thread t = new Thread(new Runnable() { + public void run() { + gattClientGetGattDbNative(connId); + } + }); + t.start(); } GattDbElement GetSampleGattDbElement() { @@ -1391,9 +1400,20 @@ public class GattService extends ProfileService { } void unregAll() { - for(ClientMap.App app:mClientMap.mApps){ - if (DBG) Log.d(TAG, "unreg:" + app.id); - unregisterClient(app.id); + int clientCount = mClientMap.mApps.size(); + int counter = 0; + while ((counter < clientCount) && (mClientMap.mApps.size() > 0)) { + if (DBG) Log.d(TAG, "unreg client:" + mClientMap.mApps.get(0).id); + unregisterClient(mClientMap.mApps.get(0).id); + counter ++; + } + + int serverCount = mServerMap.mApps.size(); + counter = 0; + while ((counter < serverCount) && (mServerMap.mApps.size() > 0)) { + if (DBG) Log.d(TAG, "unreg server:" + mServerMap.mApps.get(0).id); + unregisterServer(mServerMap.mApps.get(0).id); + counter ++; } } @@ -1627,12 +1647,11 @@ public class GattService extends ProfileService { void connectionParameterUpdate(int clientIf, String address, int connectionPriority) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - // Default spec recommended interval is 30->50 ms - int minInterval = 24; // 24 * 1.25ms = 30ms - int maxInterval = 40; // 40 * 1.25ms = 50ms + int minInterval; + int maxInterval; // Slave latency - int latency = 0; + int latency; // Link supervision timeout is measured in N * 10ms int timeout = 2000; // 20s @@ -1642,12 +1661,22 @@ public class GattService extends ProfileService { case BluetoothGatt.CONNECTION_PRIORITY_HIGH: minInterval = getResources().getInteger(R.integer.gatt_high_priority_min_interval); maxInterval = getResources().getInteger(R.integer.gatt_high_priority_max_interval); + latency = getResources().getInteger(R.integer.gatt_high_priority_latency); break; case BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER: minInterval = getResources().getInteger(R.integer.gatt_low_power_min_interval); maxInterval = getResources().getInteger(R.integer.gatt_low_power_max_interval); - latency = 2; + latency = getResources().getInteger(R.integer.gatt_low_power_latency); + break; + + default: + // Using the values for CONNECTION_PRIORITY_BALANCED. + minInterval = + getResources().getInteger(R.integer.gatt_balanced_priority_min_interval); + maxInterval = + getResources().getInteger(R.integer.gatt_balanced_priority_max_interval); + latency = getResources().getInteger(R.integer.gatt_balanced_priority_latency); break; } diff --git a/src/com/android/bluetooth/gatt/HandleMap.java b/src/com/android/bluetooth/gatt/HandleMap.java index 4a2063984..27a7a7194 100644 --- a/src/com/android/bluetooth/gatt/HandleMap.java +++ b/src/com/android/bluetooth/gatt/HandleMap.java @@ -69,7 +69,6 @@ class HandleMap { this.type = type; this.handle = handle; this.uuid = uuid; - this.instance = instance; this.serviceHandle = serviceHandle; } @@ -78,7 +77,6 @@ class HandleMap { this.type = type; this.handle = handle; this.uuid = uuid; - this.instance = instance; this.serviceHandle = serviceHandle; this.charHandle = charHandle; } diff --git a/src/com/android/bluetooth/gatt/ScanManager.java b/src/com/android/bluetooth/gatt/ScanManager.java index 7712997db..9ed405901 100644 --- a/src/com/android/bluetooth/gatt/ScanManager.java +++ b/src/com/android/bluetooth/gatt/ScanManager.java @@ -40,11 +40,13 @@ import com.android.bluetooth.btservice.AdapterService; import com.android.internal.app.IBatteryStats; import java.util.ArrayDeque; +import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -95,8 +97,8 @@ public class ScanManager { private CountDownLatch mLatch; ScanManager(GattService service) { - mRegularScanClients = new HashSet<ScanClient>(); - mBatchClients = new HashSet<ScanClient>(); + mRegularScanClients = Collections.newSetFromMap(new ConcurrentHashMap<ScanClient, Boolean>()); + mBatchClients = Collections.newSetFromMap(new ConcurrentHashMap<ScanClient, Boolean>()); mService = service; mScanNative = new ScanNative(); curUsedTrackableAdvertisements = 0; @@ -238,11 +240,11 @@ public class ScanManager { if (!mScanNative.isOpportunisticScanClient(client)) { mScanNative.configureRegularScanParams(); - if (!mScanNative.isFirstMatchScanClient(client)) { + if (!mScanNative.isExemptFromScanDowngrade(client)) { Message msg = mHandler.obtainMessage(MSG_SCAN_TIMEOUT); msg.obj = client; // Only one timeout message should exist at any time - mHandler.removeMessages(SCAN_TIMEOUT_MS); + mHandler.removeMessages(MSG_SCAN_TIMEOUT); mHandler.sendMessageDelayed(msg, SCAN_TIMEOUT_MS); } } @@ -261,10 +263,6 @@ public class ScanManager { if (client == null) return; if (mRegularScanClients.contains(client)) { - // The ScanClient passed in just holds the clientIf. We retrieve the real client, - // which may have workSource set. - client = mScanNative.getRegularScanClient(client.clientIf); - if (client == null) return; mScanNative.stopRegularScan(client); @@ -278,7 +276,11 @@ public class ScanManager { // Update BatteryStats with this workload. try { - mBatteryStats.noteBleScanStopped(client.workSource); + // The ScanClient passed in just holds the clientIf. We retrieve the real client, + // which may have workSource set. + ScanClient workClient = mScanNative.getRegularScanClient(client.clientIf); + if (workClient != null) + mBatteryStats.noteBleScanStopped(workClient.workSource); } catch (RemoteException e) { /* ignore */ } @@ -534,6 +536,12 @@ public class ScanManager { } } + private boolean isExemptFromScanDowngrade(ScanClient client) { + return isOpportunisticScanClient(client) + || isFirstMatchScanClient(client) + || !shouldUseAllPassFilter(client); + } + private boolean isOpportunisticScanClient(ScanClient client) { return client.settings.getScanMode() == ScanSettings.SCAN_MODE_OPPORTUNISTIC; } @@ -683,8 +691,9 @@ public class ScanManager { void regularScanTimeout() { for (ScanClient client : mRegularScanClients) { - if (!isOpportunisticScanClient(client) && !isFirstMatchScanClient(client)) { - logd("clientIf set to scan opportunisticly: " + client.clientIf); + if (!isExemptFromScanDowngrade(client)) { + Log.w(TAG, "Moving scan client to opportunistic (clientIf " + + client.clientIf + ")"); setOpportunisticScanClient(client); client.stats.setScanTimeout(); } diff --git a/src/com/android/bluetooth/hfp/HeadsetHalConstants.java b/src/com/android/bluetooth/hfp/HeadsetHalConstants.java index 0742917c1..dee633f2d 100644 --- a/src/com/android/bluetooth/hfp/HeadsetHalConstants.java +++ b/src/com/android/bluetooth/hfp/HeadsetHalConstants.java @@ -64,4 +64,8 @@ final public class HeadsetHalConstants { final static int CALL_STATE_INCOMING = 4; final static int CALL_STATE_WAITING = 5; final static int CALL_STATE_IDLE = 6; + + // Match up with bthf_hf_ind_type_t of bt_hf.h + final static int HF_INDICATOR_ENHANCED_DRIVER_SAFETY = 1; + final static int HF_INDICATOR_BATTERY_LEVEL_STATUS = 2; } diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java index 05806fa59..76ef46301 100644..100755 --- a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java +++ b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java @@ -339,6 +339,7 @@ final class HeadsetClientStateMachine extends StateMachine { Log.d(TAG, "Enter sendCallChangedIntent()"); Log.d(TAG, "sendCallChangedIntent " + c); Intent intent = new Intent(BluetoothHeadsetClient.ACTION_CALL_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); intent.putExtra(BluetoothHeadsetClient.EXTRA_CALL, c); mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM); Log.d(TAG, "Exit sendCallChangedIntent()"); diff --git a/src/com/android/bluetooth/hid/HidService.java b/src/com/android/bluetooth/hid/HidService.java index ba6ca2ca4..23178d04b 100644 --- a/src/com/android/bluetooth/hid/HidService.java +++ b/src/com/android/bluetooth/hid/HidService.java @@ -109,6 +109,12 @@ public class HidService extends ProfileService { } if(mInputDevices != null) { + for (BluetoothDevice device: mInputDevices.keySet()) { + int inputDeviceState = getConnectionState(device); + if (inputDeviceState != BluetoothProfile.STATE_DISCONNECTED) { + broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED); + } + } mInputDevices.clear(); } clearHidService(); diff --git a/src/com/android/bluetooth/map/BluetoothMapContent.java b/src/com/android/bluetooth/map/BluetoothMapContent.java index 2370a4d35..4f42bfb58 100755 --- a/src/com/android/bluetooth/map/BluetoothMapContent.java +++ b/src/com/android/bluetooth/map/BluetoothMapContent.java @@ -950,10 +950,14 @@ public class BluetoothMapContent { address = c.getString(c.getColumnIndex(Sms.ADDRESS)); } if ((address == null) && msgType == Sms.MESSAGE_TYPE_DRAFT) { - //Fetch address for Drafts folder from "canonical_address" table + // Fetch address for Drafts folder from "canonical_address" table int threadIdInd = c.getColumnIndex(Sms.THREAD_ID); String threadIdStr = c.getString(threadIdInd); - address = getCanonicalAddressSms(mResolver, Integer.valueOf(threadIdStr)); + // If a draft message has no recipient, it has no thread ID + // hence threadIdStr could possibly be null + if (threadIdStr != null) { + address = getCanonicalAddressSms(mResolver, Integer.valueOf(threadIdStr)); + } if(V) Log.v(TAG, "threadId = " + threadIdStr + " adress:" + address +"\n"); } } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { @@ -961,7 +965,7 @@ public class BluetoothMapContent { address = getAddressMms(mResolver, id, MMS_TO); } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) { /* Might be another way to handle addresses */ - address = getRecipientAddressingEmail(e, c,fi); + address = getRecipientAddressingEmail(e, c, fi); } if (V) Log.v(TAG, "setRecipientAddressing: " + address); if(address == null) @@ -1603,7 +1607,8 @@ public class BluetoothMapContent { } else if (BluetoothMapContract.FOLDER_NAME_SENT.equalsIgnoreCase(folder)) { where = Sms.TYPE + " = 2 AND " + Sms.THREAD_ID + " <> -1"; } else if (BluetoothMapContract.FOLDER_NAME_DRAFT.equalsIgnoreCase(folder)) { - where = Sms.TYPE + " = 3 AND " + Sms.THREAD_ID + " <> -1"; + where = Sms.TYPE + " = 3 AND " + + "(" + Sms.THREAD_ID + " IS NULL OR " + Sms.THREAD_ID + " <> -1 )"; } else if (BluetoothMapContract.FOLDER_NAME_DELETED.equalsIgnoreCase(folder)) { where = Sms.THREAD_ID + " = -1"; } @@ -1620,7 +1625,8 @@ public class BluetoothMapContent { } else if (BluetoothMapContract.FOLDER_NAME_SENT.equalsIgnoreCase(folder)) { where = Mms.MESSAGE_BOX + " = 2 AND " + Mms.THREAD_ID + " <> -1"; } else if (BluetoothMapContract.FOLDER_NAME_DRAFT.equalsIgnoreCase(folder)) { - where = Mms.MESSAGE_BOX + " = 3 AND " + Mms.THREAD_ID + " <> -1"; + where = Mms.MESSAGE_BOX + " = 3 AND " + + "(" + Mms.THREAD_ID + " IS NULL OR " + Mms.THREAD_ID + " <> -1 )"; } else if (BluetoothMapContract.FOLDER_NAME_DELETED.equalsIgnoreCase(folder)) { where = Mms.THREAD_ID + " = -1"; } diff --git a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java index 878f5bedf..c70408ccb 100755 --- a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java +++ b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java @@ -37,6 +37,7 @@ import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; +import android.os.UserManager; import android.provider.Telephony; import android.provider.Telephony.Mms; import android.provider.Telephony.MmsSms; @@ -172,7 +173,9 @@ public class BluetoothMapContentObserver { public static final String EXTRA_MESSAGE_SENT_TIMESTAMP = "timestamp"; private SmsBroadcastReceiver mSmsBroadcastReceiver = new SmsBroadcastReceiver(); + private CeBroadcastReceiver mCeBroadcastReceiver = new CeBroadcastReceiver(); + private boolean mStorageUnlocked = false; private boolean mInitialized = false; @@ -480,6 +483,12 @@ public class BluetoothMapContentObserver { Log.w(TAG, "onChange() with URI == null - not handled."); return; } + + if (!mStorageUnlocked) { + Log.v(TAG, "Ignore events until storage is completely unlocked"); + return; + } + if (V) Log.d(TAG, "onChange on thread: " + Thread.currentThread().getId() + " Uri: " + uri.toString() + " selfchange: " + selfChange); @@ -1184,9 +1193,10 @@ public class BluetoothMapContentObserver { private void initMsgList() throws RemoteException { if (V) Log.d(TAG, "initMsgList"); + UserManager manager = UserManager.get(mContext); + if (manager == null || !manager.isUserUnlocked()) return; - if(mEnableSmsMms) { - + if (mEnableSmsMms) { HashMap<Long, Msg> msgListSms = new HashMap<Long, Msg>(); Cursor c = mResolver.query(Sms.CONTENT_URI, @@ -2361,21 +2371,19 @@ public class BluetoothMapContentObserver { /* Approved MAP spec errata 3445 states that read status initiated * by the MCE shall change the MSE read status. */ if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) { - Uri uri = Sms.Inbox.CONTENT_URI; + Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, handle); ContentValues contentValues = new ContentValues(); contentValues.put(Sms.READ, statusValue); contentValues.put(Sms.SEEN, statusValue); - String where = Sms._ID+"="+handle; String values = contentValues.toString(); - if (D) Log.d(TAG, " -> SMS Uri: " + uri.toString() + - " Where " + where + " values " + values); + if (D) Log.d(TAG, " -> SMS Uri: " + uri.toString() + " values " + values); synchronized(getMsgListSms()) { Msg msg = getMsgListSms().get(handle); if(msg != null) { // This will always be the case msg.flagRead = statusValue; } } - count = mResolver.update(uri, contentValues, where, null); + count = mResolver.update(uri, contentValues, null, null); if (D) Log.d(TAG, " -> "+count +" rows updated!"); } else if (type == TYPE.MMS) { @@ -2455,8 +2463,16 @@ public class BluetoothMapContentObserver { long folderId = -1; if (recipientList == null) { - if (D) Log.d(TAG, "empty recipient list"); - return -1; + if (folderElement.getName().equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT)) { + BluetoothMapbMessage.vCard empty = + new BluetoothMapbMessage.vCard("", "", null, null, 0); + recipientList = new ArrayList<BluetoothMapbMessage.vCard>(); + recipientList.add(empty); + Log.w(TAG, "Added empty recipient to draft message"); + } else { + Log.e(TAG, "Trying to send a message with no recipients"); + return -1; + } } if ( msg.getType().equals(TYPE.EMAIL) ) { @@ -3190,6 +3206,52 @@ public class BluetoothMapContentObserver { } } + private class CeBroadcastReceiver extends BroadcastReceiver { + public void register() { + UserManager manager = UserManager.get(mContext); + if (manager == null || manager.isUserUnlocked()) { + mStorageUnlocked = true; + return; + } + + Handler handler = new Handler(Looper.getMainLooper()); + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_BOOT_COMPLETED); + mContext.registerReceiver(this, intentFilter, null, handler); + } + + public void unregister() { + try { + mContext.unregisterReceiver(this); + } catch (IllegalArgumentException e) { + /* do nothing */ + } + } + + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + Log.d(TAG, "onReceive: action" + action); + + if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { + try { + initMsgList(); + } catch (RemoteException e) { + Log.e(TAG, "Error initializing SMS/MMS message lists."); + } + + for (String folder : FOLDER_SMS_MAP.values()) { + Event evt = new Event(EVENT_TYPE_NEW, -1, folder, mSmsType); + sendEvent(evt); + } + mStorageUnlocked = true; + /* After unlock this BroadcastReceiver is never needed */ + unregister(); + } else { + Log.d(TAG, "onReceive: Unknown action " + action); + } + } + } + /** * Handle MMS sent intents in disconnected(MNS) state, where we do not need to send any * notifications. @@ -3331,6 +3393,8 @@ public class BluetoothMapContentObserver { private void resendPendingMessages() { /* Send pending messages in outbox */ String where = "type = " + Sms.MESSAGE_TYPE_OUTBOX; + UserManager manager = UserManager.get(mContext); + if (manager == null || !manager.isUserUnlocked()) return; Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null, null); try { @@ -3398,6 +3462,11 @@ public class BluetoothMapContentObserver { if (mSmsBroadcastReceiver != null) { mSmsBroadcastReceiver.register(); } + + if (mCeBroadcastReceiver != null) { + mCeBroadcastReceiver.register(); + } + registerPhoneServiceStateListener(); mInitialized = true; } diff --git a/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java index 88e5605de..832060e86 100644 --- a/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java +++ b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java @@ -27,6 +27,7 @@ import android.util.Log; import android.util.Xml; import com.android.bluetooth.map.BluetoothMapUtils.TYPE; +import com.android.bluetooth.util.Interop; public class BluetoothMapMessageListingElement implements Comparable<BluetoothMapMessageListingElement> { @@ -276,9 +277,17 @@ public class BluetoothMapMessageListingElement BluetoothMapUtils.getMapHandle(mCpHandle, mType)); if(mSubject != null){ String stripped = BluetoothMapUtils.stripInvalidChars(mSubject); + + if (Interop.matchByAddress(Interop.INTEROP_MAP_ASCIIONLY, + BluetoothMapService.getRemoteDevice().getAddress())) { + stripped = stripped.replaceAll("[\\P{ASCII}&\"><]", ""); + if (stripped.isEmpty()) stripped = "---"; + } + xmlMsgElement.attribute(null, "subject", stripped.substring(0, stripped.length() < 256 ? stripped.length() : 256)); } + if(mDateTime != 0) xmlMsgElement.attribute(null, "datetime", this.getDateTimeString()); if(mSenderName != null) diff --git a/src/com/android/bluetooth/map/BluetoothMapObexServer.java b/src/com/android/bluetooth/map/BluetoothMapObexServer.java index f0c26b0de..00b290215 100644 --- a/src/com/android/bluetooth/map/BluetoothMapObexServer.java +++ b/src/com/android/bluetooth/map/BluetoothMapObexServer.java @@ -24,6 +24,7 @@ import android.os.Handler; import android.os.Message; import android.os.ParcelUuid; import android.os.RemoteException; +import android.os.UserManager; import android.text.format.DateUtils; import android.util.Log; @@ -402,6 +403,11 @@ public class BluetoothMapObexServer extends ServerRequestHandler { return ResponseCodes.OBEX_HTTP_OK; } + private boolean isUserUnlocked() { + UserManager manager = UserManager.get(mContext); + return (manager == null || manager.isUserUnlocked()); + } + @Override public int onPut(final Operation op) { if (D) Log.d(TAG, "onPut(): enter"); @@ -431,26 +437,34 @@ public class BluetoothMapObexServer extends ServerRequestHandler { return ResponseCodes.OBEX_HTTP_OK; } return updateInbox(); - }else if(type.equals(TYPE_SET_NOTIFICATION_REGISTRATION)) { + } else if (type.equals(TYPE_SET_NOTIFICATION_REGISTRATION)) { if(V) { Log.d(TAG,"TYPE_SET_NOTIFICATION_REGISTRATION: NotificationStatus: " + appParams.getNotificationStatus()); } return mObserver.setNotificationRegistration(appParams.getNotificationStatus()); - }else if(type.equals(TYPE_SET_NOTIFICATION_FILTER)) { + } else if (type.equals(TYPE_SET_NOTIFICATION_FILTER)) { if(V) { Log.d(TAG,"TYPE_SET_NOTIFICATION_FILTER: NotificationFilter: " + appParams.getNotificationFilter()); } + if (!isUserUnlocked()) { + Log.e(TAG, "Storage locked, " + type + " failed"); + return ResponseCodes.OBEX_HTTP_UNAVAILABLE; + } mObserver.setNotificationFilter(appParams.getNotificationFilter()); return ResponseCodes.OBEX_HTTP_OK; - } else if(type.equals(TYPE_SET_MESSAGE_STATUS)) { + } else if (type.equals(TYPE_SET_MESSAGE_STATUS)) { if(V) { Log.d(TAG,"TYPE_SET_MESSAGE_STATUS: " + "StatusIndicator: " + appParams.getStatusIndicator() + ", StatusValue: " + appParams.getStatusValue() + ", ExtentedData: " + "" ); // TODO: appParams.getExtendedImData()); } + if (!isUserUnlocked()) { + Log.e(TAG, "Storage locked, " + type + " failed"); + return ResponseCodes.OBEX_HTTP_UNAVAILABLE; + } return setMessageStatus(name, appParams); } else if (type.equals(TYPE_MESSAGE)) { if(V) { @@ -458,6 +472,10 @@ public class BluetoothMapObexServer extends ServerRequestHandler { + ", retry: " + appParams.getRetry() + ", charset: " + appParams.getCharset()); } + if (!isUserUnlocked()) { + Log.e(TAG, "Storage locked, " + type + " failed"); + return ResponseCodes.OBEX_HTTP_UNAVAILABLE; + } return pushMessage(op, name, appParams, mMessageVersion); } else if (type.equals(TYPE_SET_OWNER_STATUS)) { if(V) { @@ -916,6 +934,10 @@ public class BluetoothMapObexServer extends ServerRequestHandler { Log.d(TAG,"FilterConvoId = " + ((tmpLongLong == null) ? "" : Long.toHexString(tmpLongLong.getLeastSignificantBits()) ) ); } + if (!isUserUnlocked()) { + Log.e(TAG, "Storage locked, " + type + " failed"); + return ResponseCodes.OBEX_HTTP_UNAVAILABLE; + } // Block until all packets have been send. return sendMessageListingRsp(op, appParams, name); @@ -930,6 +952,10 @@ public class BluetoothMapObexServer extends ServerRequestHandler { Log.d(TAG,"FilterReadStatus = " + appParams.getFilterReadStatus()); Log.d(TAG,"FilterRecipient = " + appParams.getFilterRecipient()); } + if (!isUserUnlocked()) { + Log.e(TAG, "Storage locked, " + type + " failed"); + return ResponseCodes.OBEX_HTTP_UNAVAILABLE; + } // Block until all packets have been send. return sendConvoListingRsp(op, appParams,name); } else if (type.equals(TYPE_GET_MAS_INSTANCE_INFORMATION)) { @@ -947,6 +973,10 @@ public class BluetoothMapObexServer extends ServerRequestHandler { ", Charset = " + appParams.getCharset() + ", FractionRequest = " + appParams.getFractionRequest()); } + if (!isUserUnlocked()) { + Log.e(TAG, "Storage locked, " + type + " failed"); + return ResponseCodes.OBEX_HTTP_UNAVAILABLE; + } // Block until all packets have been send. return sendGetMessageRsp(op, name, appParams, mMessageVersion); } else { @@ -1028,8 +1058,8 @@ public class BluetoothMapObexServer extends ServerRequestHandler { return ResponseCodes.OBEX_HTTP_BAD_REQUEST; } Log.v(TAG,"sendMessageListingRsp: has sms " + folderToList.hasSmsMmsContent() + - "has email " + folderToList.hasEmailContent() + - "has IM " + folderToList.hasImContent() ); + ", has email " + folderToList.hasEmailContent() + + ", has IM " + folderToList.hasImContent() ); } try { diff --git a/src/com/android/bluetooth/map/BluetoothMapService.java b/src/com/android/bluetooth/map/BluetoothMapService.java index 552bb09eb..addebf0c5 100755 --- a/src/com/android/bluetooth/map/BluetoothMapService.java +++ b/src/com/android/bluetooth/map/BluetoothMapService.java @@ -867,6 +867,7 @@ public class BluetoothMapService extends ProfileService { sendShutdownMessage(); } mStartError = true; + setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED); return true; } diff --git a/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java b/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java index 2a51aa067..38873da12 100644 --- a/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java +++ b/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java @@ -42,31 +42,31 @@ public class BluetoothOppHandoverReceiver extends BroadcastReceiver { if (D) Log.d(TAG, "No device attached to handover intent."); return; } + + String mimeType = intent.getType(); + ArrayList<Uri> uris = new ArrayList<Uri>(); if (action.equals(Constants.ACTION_HANDOVER_SEND)) { - String type = intent.getType(); Uri stream = (Uri)intent.getParcelableExtra(Intent.EXTRA_STREAM); - if (stream != null && type != null) { - // Save type/stream, will be used when adding transfer - // session to DB. - BluetoothOppManager.getInstance(context).saveSendingFileInfo(type, - stream.toString(), true); - } else { - if (D) Log.d(TAG, "No mimeType or stream attached to handover request"); - } + if (stream != null) uris.add(stream); } else if (action.equals(Constants.ACTION_HANDOVER_SEND_MULTIPLE)) { - ArrayList<Uri> uris = new ArrayList<Uri>(); - String mimeType = intent.getType(); uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); - if (mimeType != null && uris != null) { - BluetoothOppManager.getInstance(context).saveSendingFileInfo(mimeType, - uris, true); - } else { - if (D) Log.d(TAG, "No mimeType or stream attached to handover request"); - return; - } } - // we already know where to send to - BluetoothOppManager.getInstance(context).startTransfer(device); + + if (mimeType != null && uris != null && !uris.isEmpty()) { + final String finalType = mimeType; + final ArrayList<Uri> finalUris = uris; + Thread t = new Thread(new Runnable() { + public void run() { + BluetoothOppManager.getInstance(context).saveSendingFileInfo(finalType, + finalUris, true); + BluetoothOppManager.getInstance(context).startTransfer(device); + } + }); + t.start(); + } else { + if (D) Log.d(TAG, "No mimeType or stream attached to handover request"); + return; + } } else if (action.equals(Constants.ACTION_WHITELIST_DEVICE)) { BluetoothDevice device = (BluetoothDevice)intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); diff --git a/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java b/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java index 0f23bd382..59e7848b6 100644 --- a/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java +++ b/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java @@ -476,13 +476,13 @@ public class BluetoothOppObexClientSession implements BluetoothOppObexSession { outputStream.write(buffer, 0, readLength); position += readLength; + /* check remote accept or reject */ + responseCode = putOperation.getResponseCode(); mCallback.removeMessages(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT); synchronized (this) { mWaitingForRemote = false; } - /* check remote accept or reject */ - responseCode = putOperation.getResponseCode(); if (responseCode == ResponseCodes.OBEX_HTTP_CONTINUE || responseCode == ResponseCodes.OBEX_HTTP_OK) { @@ -595,7 +595,7 @@ public class BluetoothOppObexClientSession implements BluetoothOppObexSession { } finally { try { if (outputStream != null) { - outputStream.close(); + outputStream.close(); } // Close InputStream and remove SendFileInfo from map diff --git a/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java b/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java index 2a9cadb8b..f60f06cab 100644 --- a/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java +++ b/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java @@ -123,6 +123,7 @@ public class BluetoothOppSendFileInfo { Log.e(TAG, "generateFileInfo: " + e); return new BluetoothOppSendFileInfo(fileName, contentType, length, null, 0); } + if (metadataCursor != null) { try { if (metadataCursor.moveToFirst()) { diff --git a/src/com/android/bluetooth/opp/BluetoothOppService.java b/src/com/android/bluetooth/opp/BluetoothOppService.java index 8c10ab7e9..89e5a0d4b 100644 --- a/src/com/android/bluetooth/opp/BluetoothOppService.java +++ b/src/com/android/bluetooth/opp/BluetoothOppService.java @@ -142,6 +142,8 @@ public class BluetoothOppService extends Service { private ObexTransport mPendingConnection = null; private int mOppSdpHandle = -1; + private boolean isScreenOff = false; + /* * TODO No support for queue incoming from multiple devices. * Make an array list of server session to support receiving queue from @@ -167,6 +169,8 @@ public class BluetoothOppService extends Service { mNotifier = new BluetoothOppNotification(this); mNotifier.mNotificationMgr.cancelAll(); mNotifier.updateNotification(); + mPowerManager = (PowerManager)getSystemService(POWER_SERVICE); + isScreenOff = !mPowerManager.isInteractive(); final ContentResolver contentResolver = getContentResolver(); new Thread("trimDatabase") { @@ -176,6 +180,8 @@ public class BluetoothOppService extends Service { }.start(); IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(Intent.ACTION_SCREEN_ON); registerReceiver(mBluetoothReceiver, filter); synchronized (BluetoothOppService.this) { @@ -262,8 +268,19 @@ public class BluetoothOppService extends Service { mTransfer =null; } synchronized (BluetoothOppService.this) { + if (D) Log.d(TAG, "STOP_LISTENER :" + mUpdateThread); if (mUpdateThread == null) { stopSelf(); + } else { + try { + mUpdateThread.interrupt(); + mUpdateThread.join(); + if (D) Log.d(TAG, "Stop after join"); + } catch (InterruptedException e) { + Log.e(TAG, "Interrupted", e); + } + mUpdateThread = null; + stopSelf(); } } // Update Notification @@ -466,6 +483,12 @@ public class BluetoothOppService extends Service { break; } + } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { + isScreenOff = true; + if (V) Log.v(TAG, "ACTION_SCREEN_OFF "); + } else if (action.equals(Intent.ACTION_SCREEN_ON)) { + isScreenOff = false; + if (V) Log.v(TAG, "ACTION_SCREEN_ON "); } } }; @@ -475,7 +498,10 @@ public class BluetoothOppService extends Service { mPendingUpdate = true; if ((mUpdateThread == null) && (mAdapter != null) && mAdapter.isEnabled()) { - mPowerManager = (PowerManager)getSystemService(POWER_SERVICE); + if (mPowerManager == null) { + mPowerManager = (PowerManager)getSystemService(POWER_SERVICE); + isScreenOff = !mPowerManager.isInteractive(); + } if (V) Log.v(TAG, "Starting a new thread"); mUpdateThread = new UpdateThread(); mUpdateThread.start(); @@ -484,8 +510,17 @@ public class BluetoothOppService extends Service { } private class UpdateThread extends Thread { + private boolean isInterrupted ; public UpdateThread() { super("Bluetooth Share Service"); + isInterrupted = false; + } + + @Override + public void interrupt() { + isInterrupted = true; + if (D) Log.d(TAG, "Interrupted :" + isInterrupted); + super.interrupt(); } @Override @@ -493,14 +528,15 @@ public class BluetoothOppService extends Service { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); boolean keepService = false; - for (;;) { + for (; !isInterrupted;) { synchronized (BluetoothOppService.this) { if (mUpdateThread != this) { throw new IllegalStateException( "multiple UpdateThreads in BluetoothOppService"); } if (V) Log.v(TAG, "pendingUpdate is " + mPendingUpdate + " keepUpdateThread is " - + keepService + " sListenStarted is " + mListenStarted); + + keepService + " sListenStarted is " + mListenStarted + " isInterrupted :" + + isInterrupted + " isScreenOff:" + isScreenOff); if (!mPendingUpdate) { mUpdateThread = null; if (!keepService && !mListenStarted) { @@ -512,12 +548,12 @@ public class BluetoothOppService extends Service { return; } try { - if (!mPowerManager.isInteractive()) - Thread.sleep(10); + if (isScreenOff && !isInterrupted) { + Thread.sleep(1000); + } } catch (InterruptedException e) { Log.e(TAG, "Interrupted", e); } - mPendingUpdate = false; } Cursor cursor; diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransfer.java b/src/com/android/bluetooth/opp/BluetoothOppTransfer.java index d74cbd67b..bc2510faf 100644 --- a/src/com/android/bluetooth/opp/BluetoothOppTransfer.java +++ b/src/com/android/bluetooth/opp/BluetoothOppTransfer.java @@ -416,8 +416,9 @@ public class BluetoothOppTransfer implements BluetoothOppBatch.BluetoothOppBatch if (mConnectThread != null) { try { mConnectThread.interrupt(); - if (V) Log.v(TAG, "waiting for connect thread to terminate"); + if (D) Log.v(TAG, "waiting for connect thread to terminate"); mConnectThread.join(); + if (D) Log.d(TAG, "connect thread to terminated"); } catch (InterruptedException e) { if (V) Log.v(TAG, "Interrupted waiting for connect thread to join"); } @@ -435,6 +436,7 @@ public class BluetoothOppTransfer implements BluetoothOppBatch.BluetoothOppBatch mHandlerThread = null; } } + if (V) Log.v(TAG, "exit stop :"+mConnectThread); } private void startObexSession() { @@ -603,6 +605,8 @@ public class BluetoothOppTransfer implements BluetoothOppBatch.BluetoothOppBatch private boolean mSdpInitiated = false; + private boolean isInterrupted = false; + /* create a TCP socket */ public SocketConnectThread(String host, int port, int dummy) { super("Socket Connect Thread"); @@ -611,6 +615,7 @@ public class BluetoothOppTransfer implements BluetoothOppBatch.BluetoothOppBatch this.device = null; isConnected = false; mSdpInitiated = false; + isInterrupted = false; } /* create a Rfcomm/L2CAP Socket */ @@ -623,6 +628,7 @@ public class BluetoothOppTransfer implements BluetoothOppBatch.BluetoothOppBatch isConnected = false; mRetry = retry; mSdpInitiated = false; + isInterrupted = false; } /* create a Rfcomm/L2CAP Socket */ @@ -649,7 +655,10 @@ public class BluetoothOppTransfer implements BluetoothOppBatch.BluetoothOppBatch } public void interrupt() { + Log.d(TAG, "start interrupt :" + btSocket); if (!Constants.USE_TCP_DEBUG) { + isInterrupted = true; + OolConnManager.interruptSdp= true; if (btSocket != null) { try { btSocket.close(); @@ -664,6 +673,11 @@ public class BluetoothOppTransfer implements BluetoothOppBatch.BluetoothOppBatch if (V) Log.v(TAG, "connectRfcommSocket"); try { + if (isInterrupted) { + Log.d(TAG, "connectRfcommSocket interrupted"); + markConnectionFailed(btSocket); + return; + } btSocket = device.createInsecureRfcommSocketToServiceRecord(BluetoothUuid.ObexObjectPush.getUuid()); } catch (IOException e1) { Log.e(TAG, "Rfcomm socket create error",e1); @@ -786,6 +800,13 @@ public class BluetoothOppTransfer implements BluetoothOppBatch.BluetoothOppBatch l2cChannel = 0; try { l2cChannel = OolConnManager.getL2cPSM(device); + if (isInterrupted) { + Log.e(TAG, "btSocket connect interrupted "); + markConnectionFailed(btSocket); + return; + } else { + btSocket = device.createInsecureL2capSocket(l2cChannel); + } if (l2cChannel > 0) { Log.d(TAG, "Connecting to l2cap psm = " + l2cChannel); btSocket = device.createInsecureL2capSocket(l2cChannel); diff --git a/src/com/android/bluetooth/opp/BluetoothOppUtility.java b/src/com/android/bluetooth/opp/BluetoothOppUtility.java index 8294246f0..99777a5ae 100644 --- a/src/com/android/bluetooth/opp/BluetoothOppUtility.java +++ b/src/com/android/bluetooth/opp/BluetoothOppUtility.java @@ -367,6 +367,9 @@ public class BluetoothOppUtility { static void putSendFileInfo(Uri uri, BluetoothOppSendFileInfo sendFileInfo) { if (D) Log.d(TAG, "putSendFileInfo: uri=" + uri + " sendFileInfo=" + sendFileInfo); + if (sendFileInfo == BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR) { + Log.e(TAG, "putSendFileInfo: bad sendFileInfo, URI: " + uri); + } sSendFileMap.put(uri, sendFileInfo); } diff --git a/src/com/android/bluetooth/pan/PanService.java b/src/com/android/bluetooth/pan/PanService.java index 05f92ffe0..6a43b9342 100755 --- a/src/com/android/bluetooth/pan/PanService.java +++ b/src/com/android/bluetooth/pan/PanService.java @@ -440,8 +440,8 @@ public class PanService extends ProfileService { if (prevState == state) return; if (remote_role == BluetoothPan.LOCAL_PANU_ROLE) { if (state == BluetoothProfile.STATE_CONNECTED) { - if((!mTetherOn)||(local_role == BluetoothPan.LOCAL_PANU_ROLE)){ - if(DBG) Log.d(TAG, "handlePanDeviceStateChange BT tethering is off/Local role" + if ((!mTetherOn) || (local_role == BluetoothPan.LOCAL_PANU_ROLE)) { + if (DBG) Log.d(TAG, "handlePanDeviceStateChange BT tethering is off/Local role" + " is PANU drop the connection"); mPanDevices.remove(device); disconnectPanNative(Utils.getByteAddress(device)); diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapReceiver.java b/src/com/android/bluetooth/pbap/BluetoothPbapReceiver.java index a35ae5659..75388ee64 100644 --- a/src/com/android/bluetooth/pbap/BluetoothPbapReceiver.java +++ b/src/com/android/bluetooth/pbap/BluetoothPbapReceiver.java @@ -42,6 +42,8 @@ public class BluetoothPbapReceiver extends BroadcastReceiver { private static final String TAG = "BluetoothPbapReceiver"; + private static final boolean D = BluetoothPbapService.DEBUG; + private static final boolean V = Log.isLoggable(BluetoothPbapService.LOG_TAG, Log.VERBOSE); @Override @@ -52,12 +54,13 @@ public class BluetoothPbapReceiver extends BroadcastReceiver { in.setClass(context, BluetoothPbapService.class); String action = intent.getAction(); in.putExtra("action", action); - Log.i(TAG, "Enter - onReceive for intent:" + action); + if (D) Log.d(TAG, "PbapReceiver onReceive action = " + action); + boolean startService = true; if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); in.putExtra(BluetoothAdapter.EXTRA_STATE, state); - Log.i(TAG, "State :" + state); + if (D) Log.d(TAG, "state = " + state); if ((state == BluetoothAdapter.STATE_TURNING_ON) || (state == BluetoothAdapter.STATE_OFF)) { //FIX: We turn on PBAP after BluetoothAdapter.STATE_ON, @@ -68,11 +71,13 @@ public class BluetoothPbapReceiver extends BroadcastReceiver { // Don't forward intent unless device has bluetooth and bluetooth is enabled. BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); if (adapter == null || !adapter.isEnabled()) { + if (D) Log.d(TAG, "BluetoothAdapter is not enabled (" + + adapter + "). Would not start service."); startService = false; } } if (startService) { - if (V) Log.v(TAG, "Calling start service!!!! with action = " + in.getAction()); + if (D) Log.d(TAG, "Calling start service with action = " + in.getAction()); context.startService(in); } Log.i(TAG, "Exit - onReceive for intent:" + action); diff --git a/src/com/android/bluetooth/sdp/SdpManager.java b/src/com/android/bluetooth/sdp/SdpManager.java index 86823a027..d6db4ed77 100644 --- a/src/com/android/bluetooth/sdp/SdpManager.java +++ b/src/com/android/bluetooth/sdp/SdpManager.java @@ -85,7 +85,7 @@ public class SdpManager { static Object mTrackerLock = new Object(); /* The timeout to wait for reply from native. Should never fire. */ - private static final int SDP_INTENT_DELAY = 6000; + private static final int SDP_INTENT_DELAY = 11000; private static final int MESSAGE_SDP_INTENT = 2; // We need a reference to the adapter service, to be able to send intents diff --git a/src/com/android/bluetooth/util/Interop.java b/src/com/android/bluetooth/util/Interop.java new file mode 100644 index 000000000..4861c154e --- /dev/null +++ b/src/com/android/bluetooth/util/Interop.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 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.util; + +import java.util.ArrayList; +import java.util.List; + +/** + * Centralized Bluetooth Interoperability workaround utilities and database. + * This is the Java version. An analagous native version can be found + * in /system/bt/devices/include/interop_database.h. + */ +public class Interop { + + /** + * Simple interop entry consisting of a workarond id (see below) + * and a (partial or complete) Bluetooth device address string + * to match against. + */ + private static class Entry { + String address; + int workaround_id; + + public Entry(int workaround_id, String address) { + this.workaround_id = workaround_id; + this.address = address; + } + } + + /** + * The actual "database" of interop entries. + */ + private static List<Entry> entries = null; + + /** + * Workaround ID for deivces which do not accept non-ASCII + * characters in SMS messages. + */ + public static final int INTEROP_MAP_ASCIIONLY = 1; + + /** + * Initializes the interop datbase with the relevant workaround + * entries. + * When adding entries, please provide a description for each + * device as to what problem the workaround addresses. + */ + private static void lazyInitInteropDatabase() { + if (entries != null) return; + entries = new ArrayList<Entry>(); + + /** Mercedes Benz NTG 4.5 does not handle non-ASCII characters in SMS */ + entries.add(new Entry(INTEROP_MAP_ASCIIONLY, "00:26:e8")); + } + + /** + * Checks wheter a given device identified by |address| is a match + * for a given workaround identified by |workaround_id|. + * Return true if the address matches, false otherwise. + */ + public static boolean matchByAddress(int workaround_id, String address) { + if (address == null || address.isEmpty()) return false; + + lazyInitInteropDatabase(); + for (Entry entry : entries) { + if (entry.workaround_id == workaround_id && + entry.address.startsWith(address.toLowerCase())) { + return true; + } + } + + return false; + } +} |