diff options
author | Brint E. Kriebel <bekit@cyngn.com> | 2014-06-30 19:36:28 -0700 |
---|---|---|
committer | Brint E. Kriebel <bekit@cyngn.com> | 2014-06-30 19:36:28 -0700 |
commit | 1bebd9e564634c7c7feaf9bcbcb1297cfc02f8b6 (patch) | |
tree | 0ff15251db7e77ec08fd889479f32137e0fafb53 /src | |
parent | 4f63bf9904f4babfd928250adc6646c6669bf484 (diff) | |
parent | d4ab3f4d65d816e6185f946b3b29e977262220d3 (diff) | |
download | android_packages_apps_Bluetooth-1bebd9e564634c7c7feaf9bcbcb1297cfc02f8b6.tar.gz android_packages_apps_Bluetooth-1bebd9e564634c7c7feaf9bcbcb1297cfc02f8b6.tar.bz2 android_packages_apps_Bluetooth-1bebd9e564634c7c7feaf9bcbcb1297cfc02f8b6.zip |
Merge branch 'cm-11.0' into stable/cm-11.0cm-11.0-XNPH33R-bacon-3628510d76cm-11.0-XNPH30O-bacon-4f280f505a
Diffstat (limited to 'src')
17 files changed, 738 insertions, 218 deletions
diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java index 22c6081b9..7c5721b60 100644 --- a/src/com/android/bluetooth/a2dp/A2dpService.java +++ b/src/com/android/bluetooth/a2dp/A2dpService.java @@ -37,7 +37,7 @@ import java.util.Map; * @hide */ public class A2dpService extends ProfileService { - private static final boolean DBG = false; + private static final boolean DBG = true; private static final String TAG="A2dpService"; private A2dpStateMachine mStateMachine; @@ -60,8 +60,8 @@ public class A2dpService extends ProfileService { } protected boolean start() { - mStateMachine = A2dpStateMachine.make(this, this); mAvrcp = Avrcp.make(this); + mStateMachine = A2dpStateMachine.make(this, this); setA2dpService(this); return true; } @@ -236,6 +236,10 @@ public class A2dpService extends ProfileService { mAvrcp.setAbsoluteVolume(volume); } + public void setAvrcpAudioState(int state) { + mAvrcp.setA2dpAudioState(state); + } + synchronized boolean isA2dpPlaying(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); diff --git a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java index b88e7b11c..7648acd90 100644 --- a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java +++ b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java @@ -62,7 +62,7 @@ import java.util.List; import java.util.Set; final class A2dpStateMachine extends StateMachine { - private static final boolean DBG = false; + private static final boolean DBG = true; static final int CONNECT = 1; static final int DISCONNECT = 2; @@ -656,14 +656,16 @@ final class A2dpStateMachine extends StateMachine { switch (state) { case AUDIO_STATE_STARTED: if (mPlayingA2dpDevice == null) { - mPlayingA2dpDevice = device; - broadcastAudioState(device, BluetoothA2dp.STATE_PLAYING, - BluetoothA2dp.STATE_NOT_PLAYING); + mPlayingA2dpDevice = device; + mService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING); + broadcastAudioState(device, BluetoothA2dp.STATE_PLAYING, + BluetoothA2dp.STATE_NOT_PLAYING); } break; case AUDIO_STATE_STOPPED: if (mPlayingA2dpDevice != null) { mPlayingA2dpDevice = null; + mService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING); broadcastAudioState(device, BluetoothA2dp.STATE_NOT_PLAYING, BluetoothA2dp.STATE_PLAYING); } diff --git a/src/com/android/bluetooth/a2dp/Avrcp.java b/src/com/android/bluetooth/a2dp/Avrcp.java index f2d559801..6ad7d28b9 100644 --- a/src/com/android/bluetooth/a2dp/Avrcp.java +++ b/src/com/android/bluetooth/a2dp/Avrcp.java @@ -20,6 +20,7 @@ import java.util.Timer; import java.util.TimerTask; import android.app.PendingIntent; +import android.bluetooth.BluetoothA2dp; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -124,8 +125,9 @@ final class Avrcp { 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 = 13; + private static final int MESSAGE_SET_ADDR_PLAYER_REQ_TIMEOUT = 14; private static final int SET_ADDR_PLAYER_TIMEOUT = 2000; private int mAddressedPlayerChangedNT; @@ -777,10 +779,24 @@ final class Avrcp { int isAvailable = msg.arg2; processRCCStateChange(callingPackageName, isFocussed, isAvailable); break; + + case MESSAGE_SET_A2DP_AUDIO_STATE: + if (DEBUG) Log.v(TAG, "MESSAGE_SET_A2DP_AUDIO_STATE:" + msg.arg1); + updateA2dpAudioState(msg.arg1); + break; } } } + private void updateA2dpAudioState(int state) { + boolean isPlaying = (state == BluetoothA2dp.STATE_PLAYING); + if (isPlaying != isPlayingState(mCurrentPlayState)) { + updatePlayPauseState(isPlaying ? RemoteControlClient.PLAYSTATE_PLAYING : + RemoteControlClient.PLAYSTATE_PAUSED, + RemoteControlClient.PLAYBACK_POSITION_INVALID); + } + } + private void updatePlayPauseState(int state, long currentPosMs) { if (DEBUG) Log.v(TAG,"updatePlayPauseState"); boolean oldPosValid = (mCurrentPosMs != @@ -951,9 +967,6 @@ final class Avrcp { while (rccIterator.hasNext()) { final MediaPlayerInfo di = rccIterator.next(); if (di.GetPlayerFocus()) { - if (DEBUG) Log.v(TAG, "incrementing TrackNumber:" + mTrackNumber + "by 1"); - mTrackNumber = di.GetTrackNumber(); - mTrackNumber ++; di.SetTrackNumber(mTrackNumber); break; } @@ -978,6 +991,7 @@ final class Avrcp { mMetadata.trackTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_TITLE); mMetadata.albumTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUM); mMetadata.genre = getMdString(data, MediaMetadataRetriever.METADATA_KEY_GENRE); + mTrackNumber = getMdLong(data, MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS); mMetadata.tracknum = getMdLong(data, MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER); Log.v(TAG,"mMetadata.toString() = " + mMetadata.toString()); @@ -1399,6 +1413,20 @@ final class Avrcp { 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. @@ -1445,7 +1473,7 @@ final class Avrcp { private int convertToAudioStreamVolume(int volume) { // Rescale volume to match AudioSystem's volume - return (int) Math.ceil((double) volume*mAudioStreamMax/AVRCP_MAX_VOL); + return (int) Math.round((double) volume*mAudioStreamMax/AVRCP_MAX_VOL); } private int convertToAvrcpVolume(int volume) { @@ -1587,6 +1615,14 @@ private void updateLocalPlayerSettings( byte[] data) { mHandler.sendMessageDelayed(msg, 130); } + /** + * This is called from A2dpStateMachine to set A2dp audio state. + */ + public void setA2dpAudioState(int state) { + Message msg = mHandler.obtainMessage(MESSAGE_SET_A2DP_AUDIO_STATE, state, 0); + mHandler.sendMessage(msg); + } + // Do not modify without updating the HAL bt_rc.h files. // match up with btrc_play_status_t enum of bt_rc.h diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java index a62a20729..2bb78bcf5 100644 --- a/src/com/android/bluetooth/btservice/AdapterService.java +++ b/src/com/android/bluetooth/btservice/AdapterService.java @@ -504,7 +504,7 @@ public class AdapterService extends Service { /** * The Binder implementation must be declared to be a static class, with * the AdapterService instance passed in the constructor. Furthermore, - * when the AdapterService shuts down, the reference to the AdapterService + * when the AdapterService shuts down, the reference to the AdapterService * must be explicitly removed. * * Otherwise, a memory leak can occur from repeated starting/stopping the @@ -1279,7 +1279,7 @@ public class AdapterService extends Service { } else if((a2dpConnDevList.isEmpty()) && (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_ON) && - (a2dpService.getLastConnectedA2dpSepType(device) == BluetoothProfile.PROFILE_A2DP_SNK)&& + (a2dpService.getLastConnectedA2dpSepType(device) != BluetoothProfile.PROFILE_A2DP_SRC)&& (hsConnected || (hsService.getPriority(device) == BluetoothProfile.PRIORITY_OFF))) { a2dpService.connect(device); } diff --git a/src/com/android/bluetooth/btservice/AdapterState.java b/src/com/android/bluetooth/btservice/AdapterState.java index 1d5eff99f..585f5ae86 100644 --- a/src/com/android/bluetooth/btservice/AdapterState.java +++ b/src/com/android/bluetooth/btservice/AdapterState.java @@ -25,6 +25,7 @@ import android.util.Log; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import android.os.SystemProperties; +import java.lang.RuntimeException; /** * This state machine handles Bluetooth Adapter State. @@ -375,15 +376,19 @@ final class AdapterState extends StateMachine { } void stateChangeCallback(int status) { - if (status == AbstractionLayer.BT_STATE_OFF) { - SystemProperties.set("bluetooth.isEnabled","false"); - sendMessage(DISABLED); - } else if (status == AbstractionLayer.BT_STATE_ON) { - // We should have got the property change for adapter and remote devices. - SystemProperties.set("bluetooth.isEnabled","true"); - sendMessage(ENABLED_READY); - } else { - errorLog("Incorrect status in stateChangeCallback"); + try { + if (status == AbstractionLayer.BT_STATE_OFF) { + SystemProperties.set("bluetooth.isEnabled","false"); + sendMessage(DISABLED); + } else if (status == AbstractionLayer.BT_STATE_ON) { + // We should have got the property change for adapter and remote devices. + SystemProperties.set("bluetooth.isEnabled","true"); + sendMessage(ENABLED_READY); + } else { + errorLog("Incorrect status in stateChangeCallback"); + } + } catch (RuntimeException e) { + Log.e(TAG,"Error setting system prop " + e); } } diff --git a/src/com/android/bluetooth/btservice/ProfileService.java b/src/com/android/bluetooth/btservice/ProfileService.java index e3d9196fe..0c1b70e3f 100644 --- a/src/com/android/bluetooth/btservice/ProfileService.java +++ b/src/com/android/bluetooth/btservice/ProfileService.java @@ -38,6 +38,8 @@ public abstract class ProfileService extends Service { public static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; public static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; + public static final String BLUETOOTH_PRIVILEGED = + android.Manifest.permission.BLUETOOTH_PRIVILEGED; public static interface IProfileServiceBinder extends IBinder { public boolean cleanup(); diff --git a/src/com/android/bluetooth/btservice/RemoteDevices.java b/src/com/android/bluetooth/btservice/RemoteDevices.java index 2898145df..fbd8ecd90 100644 --- a/src/com/android/bluetooth/btservice/RemoteDevices.java +++ b/src/com/android/bluetooth/btservice/RemoteDevices.java @@ -308,6 +308,8 @@ final class RemoteDevices { intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pin); intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN); + // Make intent as foreground + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); mAdapterService.sendOrderedBroadcast(intent, mAdapterService.BLUETOOTH_ADMIN_PERM); // Release wakelock to allow the LCD to go off after the PIN popup notification. mWakeLock.release(); @@ -448,6 +450,8 @@ final class RemoteDevices { intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.PAIRING_VARIANT_PIN); intent.putExtra(BluetoothDevice.EXTRA_SECURE_PAIRING, secure); + //Make intent as foreground + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); mAdapterService.sendOrderedBroadcast(intent, mAdapterService.BLUETOOTH_ADMIN_PERM); // Release wakelock to allow the LCD to go off after the PIN popup notification. mWakeLock.release(); @@ -491,6 +495,8 @@ final class RemoteDevices { Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + //Make intent as foreground + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); if (displayPasskey) { intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, passkey); } diff --git a/src/com/android/bluetooth/gatt/GattService.java b/src/com/android/bluetooth/gatt/GattService.java index 8c94e5285..fa05c984d 100644 --- a/src/com/android/bluetooth/gatt/GattService.java +++ b/src/com/android/bluetooth/gatt/GattService.java @@ -20,28 +20,29 @@ import android.app.Service; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; import android.bluetooth.IBluetoothGatt; import android.bluetooth.IBluetoothGattCallback; import android.bluetooth.IBluetoothGattServerCallback; import android.content.Intent; import android.os.IBinder; -import android.os.IBinder.DeathRecipient; import android.os.ParcelUuid; import android.os.RemoteException; import android.util.Log; +import com.android.bluetooth.btservice.ProfileService; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import com.android.bluetooth.LeScanRequestArbitrator; -import com.android.bluetooth.btservice.ProfileService; -import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder; /** * Provides Bluetooth Gatt profile, as a service in @@ -51,7 +52,30 @@ import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder; public class GattService extends ProfileService { private static final boolean DBG = GattServiceConfig.DBG; private static final String TAG = GattServiceConfig.TAG_PREFIX + "GattService"; - BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter(); + private static final int DEFAULT_SCAN_INTERVAL_MILLIS = 200; + + /** + * Max packet size for ble advertising, defined in Bluetooth Specification Version 4.0 [Vol 3]. + */ + private static final int ADVERTISING_PACKET_MAX_BYTES = 31; + /** + * Size overhead for advertising flag. + */ + private static final int ADVERTISING_FLAGS_BYTES = 3; + /** + * Size overhead per field. Usually it's just one byte of field length and one byte of + * field type. + */ + private static final int FIELD_OVERHEAD_BYTES = 2; + + /** + * Byte size of 16 bit service uuid. + */ + private static final int SHORT_UUID_BYTES = 2; + /** + * Byte size of 128 bit service uuid. + */ + private static final int FULL_UUID_BYTES = 16; /** * Search queue to serialize remote onbject inspection. @@ -75,6 +99,15 @@ public class GattService extends ProfileService { * Server handle map. */ HandleMap mHandleMap = new HandleMap(); + private List<UUID> mAdvertisingServiceUuids = new ArrayList<UUID>(); + + private int mAdvertisingClientIf = 0; + + private byte[] mServiceData = new byte[0]; + private int mManufacturerCode = -1; + private byte[] mManufacturerData = new byte[0]; + private Integer mAdvertisingState = BluetoothAdapter.STATE_ADVERTISE_STOPPED; + private final Object mLock = new Object(); /** * Pending service declaration queue @@ -112,7 +145,7 @@ public class GattService extends ProfileService { } /** - * List of clients intereste in scan results. + * List of clients interested in scan results. */ private List<ScanClient> mScanQueue = new ArrayList<ScanClient>(); @@ -127,7 +160,7 @@ public class GattService extends ProfileService { private void removeScanClient(int appIf, boolean isServer) { for(ScanClient client : mScanQueue) { - if (client.appIf == appIf && client.isServer == isServer) { + if (client.appIf == appIf && client.isServer == isServer) { mScanQueue.remove(client); break; } @@ -197,7 +230,11 @@ public class GattService extends ProfileService { public void binderDied() { if (DBG) Log.d(TAG, "Binder is dead - unregistering client (" + mAppIf + ")!"); - stopScan(mAppIf, false); + if (mAdvertisingClientIf == mAppIf) { + stopAdvertising(true); // force stop advertising. + } else { + stopScan(mAppIf, false); + } unregisterClient(mAppIf); } } @@ -288,12 +325,6 @@ public class GattService extends ProfileService { service.clientDisconnect(clientIf, address); } - public void clientListen(int clientIf, boolean start) { - GattService service = getService(); - if (service == null) return; - service.clientListen(clientIf, start); - } - public void refreshDevice(int clientIf, String address) { GattService service = getService(); if (service == null) return; @@ -412,11 +443,11 @@ public class GattService extends ProfileService { public void beginServiceDeclaration(int serverIf, int srvcType, int srvcInstanceId, int minHandles, - ParcelUuid srvcId) { + ParcelUuid srvcId, boolean advertisePreferred) { GattService service = getService(); if (service == null) return; service.beginServiceDeclaration(serverIf, srvcType, srvcInstanceId, - minHandles, srvcId.getUuid()); + minHandles, srvcId.getUuid(), advertisePreferred); } public void addIncludedService(int serverIf, int srvcType, @@ -479,13 +510,68 @@ public class GattService extends ProfileService { srvcId.getUuid(), charInstanceId, charId.getUuid(), confirm, value); } - public void setAdvData(int serverIf, boolean setScanRsp, boolean inclName, - boolean inclTxPower, int minInterval, int maxInterval, - int appearance, byte[] manufacturerData) { + @Override + public void startAdvertising(int appIf) throws RemoteException { + GattService service = getService(); + if (service == null) return; + service.startAdvertising(appIf); + } + + @Override + public boolean isAdvertising() { + GattService service = getService(); + if (service == null) return false; + return service.isAdvertising(); + } + + @Override + public void stopAdvertising() throws RemoteException { GattService service = getService(); if (service == null) return; - service.setAdvData(serverIf, setScanRsp, inclName, inclTxPower, - minInterval, maxInterval, appearance, manufacturerData); + service.stopAdvertising(); + } + + @Override + public boolean setAdvServiceData(byte[] serviceData) throws RemoteException { + GattService service = getService(); + if (service == null) return false; + return service.setAdvServiceData(serviceData); + } + + @Override + public byte[] getAdvServiceData() throws RemoteException { + GattService service = getService(); + if (service == null) return null; + return service.getAdvServiceData(); + } + + @Override + public boolean setAdvManufacturerCodeAndData(int manufactureCode, byte[] manufacturerData) + throws RemoteException { + GattService service = getService(); + if (service == null) return false; + return service.setAdvManufacturerCodeAndData(manufactureCode, manufacturerData); + } + + @Override + public byte[] getAdvManufacturerData() throws RemoteException { + GattService service = getService(); + if (service == null) return null; + return service.getAdvManufacturerData(); + } + + @Override + public List<ParcelUuid> getAdvServiceUuids() throws RemoteException { + GattService service = getService(); + if (service == null) return null; + return service.getAdvServiceUuids(); + } + + @Override + public void removeAdvManufacturerCodeAndData(int manufacturerCode) throws RemoteException { + GattService service = getService(); + if (service == null) return; + service.removeAdvManufacturerCodeAndData(manufacturerCode); } }; @@ -836,14 +922,52 @@ public class GattService extends ProfileService { } } - void onClientListen(int status, int clientIf) - throws RemoteException { + void onAdvertiseCallback(int status, int clientIf) throws RemoteException { if (DBG) Log.d(TAG, "onClientListen() status=" + status); + synchronized (mLock) { + if (DBG) Log.d(TAG, "state" + mAdvertisingState); + // Invalid advertising state + if (mAdvertisingState == BluetoothAdapter.STATE_ADVERTISE_STARTED || + mAdvertisingState == BluetoothAdapter.STATE_ADVERTISE_STOPPED) { + Log.e(TAG, "invalid callback state " + mAdvertisingState); + return; + } - ClientMap.App app = mClientMap.getById(clientIf); - if (app == null) return; + // Force stop advertising, no callback. + if (mAdvertisingState == BluetoothAdapter.STATE_ADVERTISE_FORCE_STOPPING) { + mAdvertisingState = BluetoothAdapter.STATE_ADVERTISE_STOPPED; + mAdvertisingClientIf = 0; + sendBroadcast(new Intent( + BluetoothAdapter.ACTION_BLUETOOTH_ADVERTISING_STOPPED)); + return; + } - app.callback.onListen(status); + if (mAdvertisingState == BluetoothAdapter.STATE_ADVERTISE_STARTING) { + if (status == 0) { + mAdvertisingClientIf = clientIf; + mAdvertisingState = BluetoothAdapter.STATE_ADVERTISE_STARTED; + sendBroadcast(new Intent( + BluetoothAdapter.ACTION_BLUETOOTH_ADVERTISING_STARTED)); + } else { + mAdvertisingState = BluetoothAdapter.STATE_ADVERTISE_STOPPED; + } + } else if (mAdvertisingState == BluetoothAdapter.STATE_ADVERTISE_STOPPING) { + if (status == 0) { + mAdvertisingState = BluetoothAdapter.STATE_ADVERTISE_STOPPED; + sendBroadcast(new Intent( + BluetoothAdapter.ACTION_BLUETOOTH_ADVERTISING_STOPPED)); + mAdvertisingClientIf = 0; + } else { + mAdvertisingState = BluetoothAdapter.STATE_ADVERTISE_STARTED; + } + } + } + ClientMap.App app = mClientMap.getById(clientIf); + if (app == null || app.callback == null) { + Log.e(TAG, "app or callback is null"); + return; + } + app.callback.onAdvertiseStateChange(mAdvertisingState, status); } /************************************************************************** @@ -987,9 +1111,137 @@ public class GattService extends ProfileService { gattClientDisconnectNative(clientIf, address, connId != null ? connId : 0); } - void clientListen(int clientIf, boolean start) { - if (DBG) Log.d(TAG, "clientListen() - start=" + start); - gattClientListenNative(clientIf, start); + synchronized boolean setAdvServiceData(byte[] serviceData) { + enforcePrivilegedPermission(); + if (serviceData == null) return false; + // Calculate how many more bytes are needed for advertising service data field. + int extraBytes = (mServiceData == null) ? + FIELD_OVERHEAD_BYTES + serviceData.length : + serviceData.length - mServiceData.length; + if (getAvailableSize() < extraBytes) { + Log.e(TAG, "cannot set service data, available size " + getAvailableSize()); + return false; + } + mServiceData = serviceData; + return true; + } + + byte[] getAdvServiceData() { + enforcePrivilegedPermission(); + return mServiceData; + } + + synchronized boolean setAdvManufacturerCodeAndData( + int manufacturerCode, byte[] manufacturerData) { + enforcePrivilegedPermission(); + if (manufacturerCode <= 0 || manufacturerData == null) { + return false; + } + if (mManufacturerCode > 0 && mManufacturerData != null) { + Log.e(TAG, "manufacture data is already set"); + return false; + } + if (getAvailableSize() < + FIELD_OVERHEAD_BYTES + manufacturerData.length) { + Log.e(TAG, "cannot set manu data, available size " + getAvailableSize()); + return false; + } + this.mManufacturerCode = manufacturerCode; + this.mManufacturerData = manufacturerData; + return true; + } + + void removeAdvManufacturerCodeAndData(int manufacturerCode) { + enforcePrivilegedPermission(); + if (mManufacturerCode != manufacturerCode) { + return; + } + mManufacturerCode = -1; + mManufacturerData = new byte[0]; + } + + byte[] getAdvManufacturerData() { + enforcePrivilegedPermission(); + return mManufacturerData; + } + + synchronized List<ParcelUuid> getAdvServiceUuids() { + enforcePrivilegedPermission();; + boolean fullUuidFound = false; + List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>(); + for (HandleMap.Entry entry : mHandleMap.mEntries) { + if (entry.advertisePreferred) { + ParcelUuid parcelUuid = new ParcelUuid(entry.uuid); + if (BluetoothUuid.isShortUuid(parcelUuid)) { + serviceUuids.add(parcelUuid); + } else { + // Allow at most one 128 bit service uuid to be advertised. + if (!fullUuidFound) { + fullUuidFound = true; + serviceUuids.add(parcelUuid); + } + } + } + } + return serviceUuids; + } + + boolean isAdvertising() { + enforcePrivilegedPermission(); + return mAdvertisingState != BluetoothAdapter.STATE_ADVERTISE_STOPPED; + } + + void startAdvertising(int clientIf) { + enforcePrivilegedPermission(); + if (DBG) Log.d(TAG, "start advertising for app - " + clientIf); + List<ParcelUuid> serviceUuids = getAdvServiceUuids(); + int advertisingServiceUuidLength = serviceUuids == null ? 0 : serviceUuids.size(); + + // Note according to Bluetooth Spec Version 4.0, for advertising and scan response data + // "all numerical multi-byte entities and values shall use little-endian byte order". + ByteBuffer advertisingUuidBytes = ByteBuffer.allocate(advertisingServiceUuidLength * 16) + .order(ByteOrder.LITTLE_ENDIAN); + for (ParcelUuid parcelUuid : serviceUuids) { + UUID uuid = parcelUuid.getUuid(); + // Least signifcant bits first as the advertising uuid should be in little-endian. + advertisingUuidBytes.putLong(uuid.getLeastSignificantBits()) + .putLong(uuid.getMostSignificantBits()); + } + + // Set advertising data. + gattSetAdvDataNative(clientIf, + false, // not scan response data + false, // no device name + false, // no tx power included + DEFAULT_SCAN_INTERVAL_MILLIS, + DEFAULT_SCAN_INTERVAL_MILLIS, + 0, // no appearance limit + mManufacturerData, + mServiceData, + advertisingUuidBytes.array()); + + // Start advertising if advertising is not already started. + if (!isAdvertising()) { + gattAdvertiseNative(clientIf, true); + mAdvertisingClientIf = clientIf; + mAdvertisingState = BluetoothAdapter.STATE_ADVERTISE_STARTING; + } + } + + void stopAdvertising() { + stopAdvertising(false); + } + + void stopAdvertising(boolean forceStop) { + enforcePrivilegedPermission(); + gattAdvertiseNative(mAdvertisingClientIf, false); + synchronized (mLock) { + if (forceStop) { + mAdvertisingState = BluetoothAdapter.STATE_ADVERTISE_FORCE_STOPPING; + } else { + mAdvertisingState = BluetoothAdapter.STATE_ADVERTISE_STOPPING; + } + } } List<String> getConnectedDevices() { @@ -1151,15 +1403,6 @@ public class GattService extends ProfileService { gattClientReadRemoteRssiNative(clientIf, address); } - void setAdvData(int serverIf, boolean setScanRsp, boolean inclName, - boolean inclTxPower, int minInterval, int maxInterval, - int appearance, byte[] manufacturerData) { - if (DBG) Log.d(TAG, "setAdvData() - setScanRsp=" + setScanRsp); - if (minInterval == 0) maxInterval = 0; - gattSetAdvDataNative(serverIf, setScanRsp, inclName, inclTxPower, - minInterval, maxInterval, appearance, manufacturerData); - } - /************************************************************************** * Callback functions - SERVER *************************************************************************/ @@ -1183,8 +1426,11 @@ public class GattService extends ProfileService { UUID uuid = new UUID(srvcUuidMsb, srvcUuidLsb); if (DBG) Log.d(TAG, "onServiceAdded() UUID=" + uuid + ", status=" + status + ", handle=" + srvcHandle); - if (status == 0) - mHandleMap.addService(serverIf, srvcHandle, uuid, srvcType, srvcInstId); + if (status == 0) { + mHandleMap.addService(serverIf, srvcHandle, uuid, srvcType, srvcInstId, + mAdvertisingServiceUuids.remove(uuid)); + } + continueServiceDeclaration(serverIf, status, srvcHandle); } @@ -1415,12 +1661,13 @@ public class GattService extends ProfileService { } void beginServiceDeclaration(int serverIf, int srvcType, int srvcInstanceId, - int minHandles, UUID srvcUuid) { + int minHandles, UUID srvcUuid, boolean advertisePreferred) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); if (DBG) Log.d(TAG, "beginServiceDeclaration() - uuid=" + srvcUuid); ServiceDeclaration serviceDeclaration = addDeclaration(); - serviceDeclaration.addService(srvcUuid, srvcType, srvcInstanceId, minHandles); + serviceDeclaration.addService(srvcUuid, srvcType, srvcInstanceId, minHandles, + advertisePreferred); } void addIncludedService(int serverIf, int srvcType, int srvcInstanceId, @@ -1529,6 +1776,33 @@ public class GattService extends ProfileService { return type; } + private synchronized int getAvailableSize() { + enforcePrivilegedPermission(); + int availableSize = ADVERTISING_PACKET_MAX_BYTES - ADVERTISING_FLAGS_BYTES; + + for (ParcelUuid parcelUuid : getAdvServiceUuids()) { + if (BluetoothUuid.isShortUuid(parcelUuid)) { + availableSize -= FIELD_OVERHEAD_BYTES + SHORT_UUID_BYTES; + } else { + availableSize -= FIELD_OVERHEAD_BYTES + FULL_UUID_BYTES; + } + } + if (mManufacturerCode > 0 && mManufacturerData != null) { + availableSize -= (FIELD_OVERHEAD_BYTES + mManufacturerData.length); + } + if (mServiceData != null) { + availableSize -= (FIELD_OVERHEAD_BYTES + mServiceData.length); + } + return availableSize; + } + + // Enforce caller has BLUETOOTH_PRIVILEGED permission. A {@link SecurityException} will be + // thrown if the caller app does not have BLUETOOTH_PRIVILEGED permission. + private void enforcePrivilegedPermission() { + enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, + "Need BLUETOOTH_PRIVILEGED permission"); + } + private void continueSearch(int connId, int status) throws RemoteException { Log.d(TAG, "continueSearch() - connid=" + connId + ", status=" + status); @@ -1577,6 +1851,9 @@ public class GattService extends ProfileService { + entry.type); switch(entry.type) { case ServiceDeclaration.TYPE_SERVICE: + if (entry.advertisePreferred) { + mAdvertisingServiceUuids.add(entry.uuid); + } gattServerAddServiceNative(serverIf, entry.serviceType, entry.instance, entry.uuid.getLeastSignificantBits(), @@ -1621,6 +1898,7 @@ public class GattService extends ProfileService { ServerMap.App app = mServerMap.getById(serverIf); if (app != null) { HandleMap.Entry serviceEntry = mHandleMap.getByHandle(srvcHandle); + if (serviceEntry != null) { app.callback.onServiceAdded(status, serviceEntry.serviceType, serviceEntry.instance, new ParcelUuid(serviceEntry.uuid)); @@ -1801,11 +2079,11 @@ public class GattService extends ProfileService { private native void gattClientReadRemoteRssiNative(int clientIf, String address); - private native void gattClientListenNative(int client_if, boolean start); + private native void gattAdvertiseNative(int client_if, boolean start); private native void gattSetAdvDataNative(int serverIf, boolean setScanRsp, boolean inclName, boolean inclTxPower, int minInterval, int maxInterval, - int appearance, byte[] manufacturerData); + int appearance, byte[] manufacturerData, byte[] serviceData, byte[] serviceUuid); private native void gattServerRegisterAppNative(long app_uuid_lsb, long app_uuid_msb); diff --git a/src/com/android/bluetooth/gatt/HandleMap.java b/src/com/android/bluetooth/gatt/HandleMap.java index 5d45654b9..187625a43 100644 --- a/src/com/android/bluetooth/gatt/HandleMap.java +++ b/src/com/android/bluetooth/gatt/HandleMap.java @@ -42,6 +42,7 @@ class HandleMap { int serviceHandle = 0; int charHandle = 0; boolean started = false; + boolean advertisePreferred = false; Entry(int serverIf, int handle, UUID uuid, int serviceType, int instance) { this.serverIf = serverIf; @@ -52,6 +53,17 @@ class HandleMap { this.serviceType = serviceType; } + Entry(int serverIf, int handle, UUID uuid, int serviceType, int instance, + boolean advertisePreferred) { + this.serverIf = serverIf; + this.type = TYPE_SERVICE; + this.handle = handle; + this.uuid = uuid; + this.instance = instance; + this.serviceType = serviceType; + this.advertisePreferred = advertisePreferred; + } + Entry(int serverIf, int type, int handle, UUID uuid, int serviceHandle) { this.serverIf = serverIf; this.type = type; @@ -86,8 +98,9 @@ class HandleMap { mRequestMap.clear(); } - void addService(int serverIf, int handle, UUID uuid, int serviceType, int instance) { - mEntries.add(new Entry(serverIf, handle, uuid, serviceType, instance)); + void addService(int serverIf, int handle, UUID uuid, int serviceType, int instance, + boolean advertisePreferred) { + mEntries.add(new Entry(serverIf, handle, uuid, serviceType, instance, advertisePreferred)); } void addCharacteristic(int serverIf, int handle, UUID uuid, int serviceHandle) { diff --git a/src/com/android/bluetooth/gatt/ServiceDeclaration.java b/src/com/android/bluetooth/gatt/ServiceDeclaration.java index 0c9a51b2f..1d0bc4e90 100644 --- a/src/com/android/bluetooth/gatt/ServiceDeclaration.java +++ b/src/com/android/bluetooth/gatt/ServiceDeclaration.java @@ -39,6 +39,7 @@ class ServiceDeclaration { int properties = 0; int serviceType = 0; int serviceHandle = 0; + boolean advertisePreferred = false; Entry(UUID uuid, int serviceType, int instance) { this.type = TYPE_SERVICE; @@ -47,6 +48,14 @@ class ServiceDeclaration { this.serviceType = serviceType; } + Entry(UUID uuid, int serviceType, int instance, boolean advertisePreferred) { + this.type = TYPE_SERVICE; + this.uuid = uuid; + this.instance = instance; + this.serviceType = serviceType; + this.advertisePreferred = advertisePreferred; + } + Entry(UUID uuid, int properties, int permissions, int instance) { this.type = TYPE_CHARACTERISTIC; this.uuid = uuid; @@ -69,8 +78,9 @@ class ServiceDeclaration { mEntries = new ArrayList<Entry>(); } - void addService(UUID uuid, int serviceType, int instance, int minHandles) { - mEntries.add(new Entry(uuid, serviceType, instance)); + void addService(UUID uuid, int serviceType, int instance, int minHandles, + boolean advertisePreferred) { + mEntries.add(new Entry(uuid, serviceType, instance, advertisePreferred)); if (minHandles == 0) { ++mNumHandles; } else { @@ -102,6 +112,15 @@ class ServiceDeclaration { return entry; } + boolean isServiceAdvertisePreferred(UUID uuid) { + for (Entry entry : mEntries) { + if (entry.uuid.equals(uuid)) { + return entry.advertisePreferred; + } + } + return false; + } + int getNumHandles() { return mNumHandles; } diff --git a/src/com/android/bluetooth/map/BluetoothMapContent.java b/src/com/android/bluetooth/map/BluetoothMapContent.java index 19447980c..6710f3414 100644 --- a/src/com/android/bluetooth/map/BluetoothMapContent.java +++ b/src/com/android/bluetooth/map/BluetoothMapContent.java @@ -41,6 +41,8 @@ import android.provider.Telephony.Threads; import android.telephony.TelephonyManager; import android.util.Log; import android.text.format.Time; +import android.text.util.Rfc822Token; +import android.text.util.Rfc822Tokenizer; import android.util.TimeFormatException; import com.android.emailcommon.provider.EmailContent; import com.android.emailcommon.provider.EmailContent.Message; @@ -50,10 +52,11 @@ import com.android.emailcommon.provider.EmailContent.SyncColumns; import com.android.bluetooth.map.BluetoothMapUtils.TYPE; import com.google.android.mms.pdu.CharacterSets; +import com.google.android.mms.pdu.PduHeaders; import android.database.sqlite.SQLiteException; -import java.util.List; -import java.util.ArrayList; import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.commons.codec.net.QuotedPrintableCodec; import org.apache.commons.codec.DecoderException; @@ -264,7 +267,7 @@ public class BluetoothMapContent { selection, null, null); - if (c.moveToFirst()) { + if (c !=null && c.moveToFirst()) { do { String add = c.getString(c.getColumnIndex("address")); Integer type = c.getInt(c.getColumnIndex("type")); @@ -313,7 +316,7 @@ public class BluetoothMapContent { null, null); if (D) Log.d(TAG, " parts:"); - if (c.moveToFirst()) { + if (c !=null && c.moveToFirst()) { do { Long partid = c.getLong(c.getColumnIndex(BaseColumns._ID)); String ct = c.getString(c.getColumnIndex("ct")); @@ -357,9 +360,9 @@ public class BluetoothMapContent { printMmsAddr(id); printMmsParts(id); } + c.close(); } else { Log.d(TAG, "query failed"); - c.close(); } } @@ -374,9 +377,9 @@ public class BluetoothMapContent { while (c.moveToNext()) { printSms(c); } + c.close(); } else { Log.d(TAG, "query failed"); - c.close(); } } @@ -561,8 +564,15 @@ public class BluetoothMapContent { private void setPriority(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { + String priority = "no"; if ((ap.getParameterMask() & MASK_PRIORITY) != 0) { - String priority = "no"; + int pri = 0; + if (fi.msgType == FilterInfo.TYPE_MMS) { + pri = c.getInt(c.getColumnIndex(Mms.PRIORITY)); + } + if (pri == PduHeaders.PRIORITY_HIGH) { + priority = "yes"; + } if (D) Log.d(TAG, "setPriority: " + priority); e.setPriority(priority); } @@ -714,7 +724,7 @@ public class BluetoothMapContent { } else { int toIndex = c.getColumnIndex(MessageColumns.TO_LIST); address = c.getString(toIndex); - if (address.contains("")) { + if (address != null && address.contains("")) { String[] recepientAddrStr = address.split(""); if (recepientAddrStr !=null && recepientAddrStr.length > 0) { if (V){ @@ -724,7 +734,7 @@ public class BluetoothMapContent { e.setRecipientAddressing(recepientAddrStr[0].trim()); } } else { if (D) Log.d(TAG, "setRecipientAddressing: " + address); - e.setRecipientAddressing(address.trim()); + e.setRecipientAddressing(address); } return; } @@ -1035,7 +1045,9 @@ public class BluetoothMapContent { name = c.getString(c.getColumnIndex(Contacts.DISPLAY_NAME)); } - c.close(); + if (c != null) { + c.close(); + } return name; } @@ -1450,9 +1462,13 @@ public class BluetoothMapContent { if (!c.isLast()) { where += " OR "; } - p.close(); + if (p != null) { + p.close(); + } + } + if (c != null) { + c.close(); } - c.close(); if (str != null && str.length() > 0) { if (where.length() > 0) { @@ -1499,6 +1515,23 @@ public class BluetoothMapContent { return where; } + private String setWhereFilterPriority(BluetoothMapAppParams ap, FilterInfo fi) { + String where = ""; + int pri = ap.getFilterPriority(); + /*only MMS have priority info */ + if(fi.msgType == FilterInfo.TYPE_MMS) + { + if(pri == 0x0002) + { + where += " AND " + Mms.PRIORITY + "<=" + + Integer.toString(PduHeaders.PRIORITY_NORMAL); + }else if(pri == 0x0001) { + where += " AND " + Mms.PRIORITY + "=" + + Integer.toString(PduHeaders.PRIORITY_HIGH); + } + } + return where; + } private String setWhereFilterRecipient(BluetoothMapAppParams ap, FilterInfo fi) { @@ -1565,6 +1598,7 @@ public class BluetoothMapContent { where += setWhereFilterFolderType(folder, fi); where += setWhereFilterReadStatus(ap, fi); where += setWhereFilterPeriod(ap, fi); + where += setWhereFilterPriority(ap,fi); /* where += setWhereFilterOriginator(ap, fi); */ /* where += setWhereFilterRecipient(ap, fi); */ @@ -1706,6 +1740,76 @@ public class BluetoothMapContent { return folderName; } + /** + * Since email app returns addresses as RFC822 formatted strings and Rfc822Tokens are expected + * this will tear apart the string to return the components we want to put into + * an Rfc822Token + * @param part = what you want to return: "name" or "address": returns null if neither + * @param emailString = raw string from the email provider that is RFC822 formatted + * @return string type of the requested part + */ + private String deTokenizeEmail (String part, String emailString) { + final Matcher sEmailMatcher = + Pattern.compile("\\\"?([^\"<]*?)\\\"?\\s*<(.*)>").matcher(""); + String name, address; + Matcher m = sEmailMatcher.reset(emailString.trim()); + if (m.matches()) { + name = m.group(1); + address = m.group(2); + if (name == null) { + name = ""; + } else { + name = Html.fromHtml(name.trim()).toString(); + } + if (address == null) { + address = ""; + } else { + address = Html.fromHtml(address).toString(); + } + } else { + // Try and tokenize the string + final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(emailString.trim()); + if (tokens.length > 0) { + final String tokenizedName = tokens[0].getName(); + name = tokenizedName != null ? Html.fromHtml(tokenizedName.trim()).toString() : ""; + address = Html.fromHtml(tokens[0].getAddress()).toString(); + } else { + name = ""; + address = Html.fromHtml(emailString.trim()).toString(); + } + } + String output = null; + if (part == "name") { + output = name.replace("'", ""); //single quote from gmail likes to slip through + } + else if (part == "address") { + output = address; + } + return output; + } + + private String getNameFromEmail(String emailAddress) { + String contactName = null; + Cursor p; + + Uri uri = Uri.withAppendedPath(ContactsContract.CommonDataKinds.Email.CONTENT_LOOKUP_URI, + Uri.encode(emailAddress)); + + String[] projection = {Contacts._ID, Contacts.DISPLAY_NAME}; + String selection = Contacts.IN_VISIBLE_GROUP + "=1"; + String orderBy = Contacts._ID + " ASC"; + + // Get the contact _ID and name + p = mResolver.query(uri, projection, selection, null, orderBy); + if (p != null && p.getCount() >= 1) { + //Resolved an email to a contact so lets use it + p.moveToFirst(); + contactName = p.getString(p.getColumnIndex(Contacts.DISPLAY_NAME)); + if(V) Log.v(TAG,"Phone book lookup resolved " + emailAddress + " to " + contactName); + } + p.close(); + return contactName; + } private void extractEmailAddresses(long id, BluetoothMapbMessageMmsEmail message) { if (V) Log.v(TAG, "extractEmailAddresses with id " + id); @@ -1713,23 +1817,34 @@ public class BluetoothMapContent { Uri uriEmail = Uri.parse(urlEmail); StringTokenizer emailId; String tempEmail = null; + String emailComponent, nameComponent = null, contactsName; Cursor c = mResolver.query(uriEmail, EMAIL_PROJECTION, "_id = " + id, null, null); if (c != null && c.moveToFirst()) { String senderName = null; if((senderName = c.getString(c.getColumnIndex(MessageColumns.FROM_LIST))) != null ) { if(V) Log.v(TAG, " senderName " + senderName); if(senderName.contains("")){ - String[] senderStr = senderName.split(""); - if(senderStr !=null && senderStr.length > 0){ - if(V) Log.v(TAG, " senderStr[1] " + senderStr[1].trim()); - if(V) Log.v(TAG, " senderStr[0] " + senderStr[0].trim()); - setVCardFromEmailAddress(message, senderStr[1].trim(), true); - message.addFrom(null, senderStr[0].trim()); - } + String[] senderStr = senderName.split(""); + if(senderStr !=null && senderStr.length > 0){ + emailComponent = senderStr[1].trim(); //should be email + nameComponent = senderStr[0].trim(); //should be name + if(V){ + Log.v(TAG, " senderStr[1] " + emailComponent); + Log.v(TAG, " senderStr[0] " + nameComponent); + } + contactsName = getNameFromEmail(emailComponent); + if (contactsName != null) nameComponent = contactsName; + setVCardFromEmailAddress(message, nameComponent, emailComponent, true); + message.addFrom(nameComponent,emailComponent); + } } else { - if(V) Log.v(TAG, " senderStr is" + senderName.trim()); - setVCardFromEmailAddress(message, senderName.trim(), true); - message.addFrom(null, senderName.trim()); + if(V) Log.v(TAG, " senderStr is" + senderName.trim()); + nameComponent = deTokenizeEmail("name", senderName.trim()); + emailComponent = deTokenizeEmail("address", senderName.trim()); + contactsName = getNameFromEmail(emailComponent); + if (contactsName != null) nameComponent = contactsName; + setVCardFromEmailAddress(message, nameComponent, emailComponent, true); + message.addFrom(nameComponent,emailComponent); } } String recipientName = null; @@ -1737,48 +1852,66 @@ public class BluetoothMapContent { if((recipientName = c.getString(c.getColumnIndex(MessageColumns.TO_LIST))) != null){ if(V) Log.v(TAG, " recipientName " + recipientName); if(recipientName.contains("")){ - String[] recepientStr = recipientName.split(""); - if(recepientStr !=null && recepientStr.length > 0){ - if (V){ - Log.v(TAG, " recepientStr[1] " + recepientStr[1].trim()); - Log.v(TAG, " recepientStr[0] " + recepientStr[0].trim()); - } - setVCardFromEmailAddress(message, recepientStr[1].trim(), false); - message.addTo(recepientStr[1].trim(), recepientStr[0].trim()); - } + String[] recepientStr = recipientName.split(""); + if(recepientStr !=null && recepientStr.length > 0){ + emailComponent = recepientStr[1].trim(); //should be email + nameComponent = recepientStr[0].trim(); //should be name + if (V){ + Log.v(TAG, " recepientStr[1] " + emailComponent); + Log.v(TAG, " recepientStr[0] " + nameComponent); + } + contactsName = getNameFromEmail(emailComponent); + if (contactsName != null) nameComponent = contactsName; + setVCardFromEmailAddress(message, nameComponent, emailComponent, false); + message.addTo(nameComponent,emailComponent); + } } else if(recipientName.contains("")){ - multiRecepients = recipientName.replace('', ';'); - if(multiRecepients != null){ - if (V){ - Log.v(TAG, " Setting ::Recepient name :: " + multiRecepients.trim()); - } - emailId = new StringTokenizer(multiRecepients.trim(),";"); - do { - setVCardFromEmailAddress(message, emailId.nextElement().toString(), false); - } while(emailId.hasMoreElements()); - - message.addTo(multiRecepients.trim(), multiRecepients.trim()); - } + multiRecepients = recipientName.replace('', ';'); + if(multiRecepients != null){ + if (V){ + Log.v(TAG, " Setting::M Recepient name: " + multiRecepients.trim()); + } + emailId = new StringTokenizer(multiRecepients.trim(),";"); + do { + tempEmail = emailId.nextElement().toString(); + nameComponent = deTokenizeEmail("name", tempEmail); + emailComponent = deTokenizeEmail("address", tempEmail); + contactsName = getNameFromEmail(emailComponent); + if (contactsName != null) nameComponent = contactsName; + setVCardFromEmailAddress(message, nameComponent, emailComponent, + false); + message.addTo(nameComponent,emailComponent); + } while(emailId.hasMoreElements()); + } } else if(recipientName.contains(",")){ - multiRecepients = recipientName.replace(',', ';'); - if(multiRecepients != null){ - if (V){ - Log.v(TAG, "Setting ::Recepient name :: " + multiRecepients.trim()); - } - emailId = new StringTokenizer(multiRecepients.trim(),";"); - do { + multiRecepients = recipientName.replace(", \"", "; \""); + if(multiRecepients != null){ + if (V){ + Log.v(TAG, "Setting::M2 Recepient name : " + multiRecepients.trim()); + } + emailId = new StringTokenizer(multiRecepients.trim(),";"); + do { tempEmail = emailId.nextElement().toString(); - setVCardFromEmailAddress(message, tempEmail, false); - message.addTo(null, tempEmail); - } while(emailId.hasMoreElements()); - } + nameComponent = deTokenizeEmail("name", tempEmail); + emailComponent = deTokenizeEmail("address", tempEmail); + contactsName = getNameFromEmail(emailComponent); + if (contactsName != null) nameComponent = contactsName; + setVCardFromEmailAddress(message, nameComponent, emailComponent, + false); + message.addTo(nameComponent,emailComponent); + } while(emailId.hasMoreElements()); + } } else { - Log.v(TAG, " Setting ::Recepient name :: " + recipientName.trim()); - setVCardFromEmailAddress(message, recipientName.trim(), false); - message.addTo(null, recipientName.trim()); - } - } - } + Log.v(TAG, " Setting ::Single Recepient name :: " + recipientName.trim()); + nameComponent = deTokenizeEmail("name", recipientName.trim()); + emailComponent = deTokenizeEmail("address", recipientName.trim()); + contactsName = getNameFromEmail(emailComponent); + if (contactsName != null) nameComponent = contactsName; + setVCardFromEmailAddress(message, nameComponent, emailComponent, false); + message.addTo(nameComponent,emailComponent); + } + } + } } /** @@ -2055,21 +2188,22 @@ public class BluetoothMapContent { if (smsSelected(fi, ap)) { fi.msgType = FilterInfo.TYPE_SMS; + if(ap.getFilterPriority() != 1){ /*SMS cannot have high priority*/ + String where = setWhereFilter(folder, fi, ap); - String where = setWhereFilter(folder, fi, ap); - - Cursor c = mResolver.query(Sms.CONTENT_URI, - SMS_PROJECTION, where, null, "date DESC"); + Cursor c = mResolver.query(Sms.CONTENT_URI, + SMS_PROJECTION, where, null, "date DESC"); - if (c != null) { - while (c.moveToNext()) { - if (matchAddresses(c, fi, ap)) { - printSms(c); - e = element(c, fi, ap); - bmList.add(e); + if (c != null) { + while (c.moveToNext()) { + if (matchAddresses(c, fi, ap)) { + printSms(c); + e = element(c, fi, ap); + bmList.add(e); + } } + c.close(); } - c.close(); } } @@ -2156,7 +2290,7 @@ public class BluetoothMapContent { where += " AND read=0 "; where += setWhereFilterPeriod(ap, fi); Cursor c = mResolver.query(Sms.CONTENT_URI, - SMS_PROJECTION, where, null, "date DESC"); + SMS_PROJECTION, where, null, "date DESC"); if (c != null) { cnt = c.getCount(); @@ -2237,24 +2371,21 @@ public class BluetoothMapContent { throw new IllegalArgumentException("Invalid message handle."); } - private void setVCardFromEmailAddress(BluetoothMapbMessage message, String emailAddr, boolean incoming) { - if(D) Log.d(TAG, "setVCardFromEmailAddress, emailAdress is " +emailAddr); - String contactId = null, contactName = null; + private void setVCardFromEmailAddress(BluetoothMapbMessage message, + String contactName, String emailAddress, boolean incoming) { + if(D) Log.d(TAG, "setVCardFromEmailAddress, emailAdress is " +emailAddress); String[] phoneNumbers = {""}; String[] emailAddresses = new String[1]; - StringTokenizer emailId; - Cursor p; - if(incoming == true) { - emailAddresses[0] = emailAddr; - if(V) Log.v(TAG,"Adding addOriginator " + emailAddresses[0]); - message.addOriginator(emailAddr, phoneNumbers, emailAddresses); + emailAddresses[0] = emailAddress; + if(V) Log.v(TAG,"Adding addOriginator " + contactName + " " + emailAddresses[0]); + message.addOriginator(contactName, phoneNumbers, emailAddresses); } else { - emailAddresses[0] = emailAddr; - if(V) Log.v(TAG,"Adding Receipient " + emailAddresses[0]); - message.addRecipient(emailAddr, phoneNumbers, emailAddresses); + emailAddresses[0] = emailAddress; + if(V) Log.v(TAG,"Adding Receipient " + contactName + " " + emailAddresses[0]); + message.addRecipient(contactName, phoneNumbers, emailAddresses); } } @@ -2278,7 +2409,8 @@ public class BluetoothMapContent { contactId = p.getString(p.getColumnIndex(Contacts._ID)); contactName = p.getString(p.getColumnIndex(Contacts.DISPLAY_NAME)); } - p.close(); + if (p != null) + p.close(); // The phone number we got is the one we will use phoneNumbers = new String[1]; @@ -2377,7 +2509,7 @@ public class BluetoothMapContent { selection, null, null); /* TODO: Change the setVCard...() to return the vCard, and use the name in message.addXxx() */ - if (c.moveToFirst()) { + if (c != null && c.moveToFirst()) { do { String address = c.getString(c.getColumnIndex("address")); Integer type = c.getInt(c.getColumnIndex("type")); @@ -2458,7 +2590,7 @@ public class BluetoothMapContent { selection, null, null); - if (c.moveToFirst()) { + if (c != null && c.moveToFirst()) { do { Long partId = c.getLong(c.getColumnIndex(BaseColumns._ID)); String contentType = c.getString(c.getColumnIndex("ct")); diff --git a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java index 12203a13f..c66a03dbe 100644 --- a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java +++ b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java @@ -1198,7 +1198,7 @@ public class BluetoothMapContentObserver { Cursor cursor = mResolver.query(msgInfo.uri, ID_PROJECTION, null, null, null); try { - if (cursor.moveToFirst()) { + if (cursor != null && cursor.moveToFirst()) { int messageId = cursor.getInt(0); Uri updateUri = ContentUris.withAppendedId(UPDATE_STATUS_URI, messageId); @@ -1216,7 +1216,8 @@ public class BluetoothMapContentObserver { Log.d(TAG, "Can't find message for status update: " + messageUri); } } finally { - cursor.close(); + if (cursor != null) + cursor.close(); } if (status == 0) { diff --git a/src/com/android/bluetooth/map/BluetoothMapService.java b/src/com/android/bluetooth/map/BluetoothMapService.java index e94af3d30..262ab70f4 100644 --- a/src/com/android/bluetooth/map/BluetoothMapService.java +++ b/src/com/android/bluetooth/map/BluetoothMapService.java @@ -273,17 +273,19 @@ public class BluetoothMapService extends ProfileService { Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices(); int connectionState; synchronized (this) { - for (BluetoothDevice device : bondedDevices) { - ParcelUuid[] featureUuids = device.getUuids(); - if (!BluetoothUuid.containsAnyUuid(featureUuids, MAP_UUIDS)) { - continue; - } - connectionState = getConnectionState(device); - for(int i = 0; i < states.length; i++) { - if (connectionState == states[i]) { - deviceList.add(device); - } - } + if (bondedDevices != null) { + for (BluetoothDevice device : bondedDevices) { + ParcelUuid[] featureUuids = device.getUuids(); + if (!BluetoothUuid.containsAnyUuid(featureUuids, MAP_UUIDS)) { + continue; + } + connectionState = getConnectionState(device); + for(int i = 0; i < states.length; i++) { + if (connectionState == states[i]) { + deviceList.add(device); + } + } + } } } return deviceList; diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessageMmsEmail.java b/src/com/android/bluetooth/map/BluetoothMapbMessageMmsEmail.java index 84f9c511e..86692be27 100644 --- a/src/com/android/bluetooth/map/BluetoothMapbMessageMmsEmail.java +++ b/src/com/android/bluetooth/map/BluetoothMapbMessageMmsEmail.java @@ -53,45 +53,29 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { public void encode(StringBuilder sb, String boundaryTag, boolean last) throws UnsupportedEncodingException { - sb.append("--").append(boundaryTag).append("\r\n"); - if(contentType != null) - sb.append("Content-Type: ").append(contentType); - if(charsetName != null) - sb.append("; ").append("charset=\"").append(charsetName).append("\""); - sb.append("\r\n"); - if(contentLocation != null) - sb.append("Content-Location: ").append(contentLocation).append("\r\n"); - if(contentId != null) - sb.append("Content-ID: ").append(contentId).append("\r\n"); - if(contentDisposition != null) - sb.append("Content-Disposition: ").append(contentDisposition).append("\r\n"); - if(data != null) { + if(data != null) { /* TODO: If errata 4176 is adopted in the current form (it is not in either 1.1 or 1.2), the below is not allowed, Base64 should be used for text. */ if(contentType != null && (contentType.toUpperCase().contains("TEXT") || contentType.toUpperCase().contains("SMIL") )) { - sb.append("Content-Transfer-Encoding: 8BIT\r\n\r\n"); // Add the header split empty line sb.append(new String(data,"UTF-8")).append("\r\n"); } else { - sb.append("Content-Transfer-Encoding: Base64\r\n\r\n"); // Add the header split empty line sb.append(Base64.encodeToString(data, Base64.DEFAULT)).append("\r\n"); } } if(last) { - sb.append("--").append(boundaryTag).append("--").append("\r\n"); + sb.append("\r\n"); } } public void encodePlainText(StringBuilder sb) throws UnsupportedEncodingException { if(contentType != null && contentType.toUpperCase().contains("TEXT")) { - sb.append(contentType).append("\r\n"); - sb.append("Content-Transfer-Encoding: 8bit").append("\r\n"); - sb.append("Content-Disposition:inline").append("\r\n") - .append("\r\n"); - sb.append(new String(data,"UTF-8")).append("\r\n"); + if(data != null) { + sb.append(new String(data,"UTF-8")).append("\r\n"); + } } else if(contentType != null && contentType.toUpperCase().contains("/SMIL")) { /* Skip the smil.xml, as no-one knows what it is. */ } else { @@ -388,14 +372,6 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { String boundary = "MessageBoundary."+randomInt; encodeHeaders(sb); - sb.append("Mime-Version: 1.0").append("\r\n"); - sb.append( - "Content-Type: multipart/mixed; boundary=\""+boundary+"\"") - .append("\r\n"); - sb.append("Content-Transfer-Encoding: 8bit").append("\r\n") - .append("\r\n"); - sb.append("MIME Message").append("\r\n"); - sb.append("--"+boundary).append("\r\n"); Log.v(TAG, "after encode header sb is "+ sb.toString()); @@ -403,7 +379,6 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { if(getIncludeAttachments() == false) { for(MimePart part : parts) { part.encodePlainText(sb); /* We call encode on all parts, to include a tag, where an attachment is missing. */ - sb.append("--"+boundary+"--").append("\r\n"); } } else { for(MimePart part : parts) { diff --git a/src/com/android/bluetooth/opp/BluetoothOppNotification.java b/src/com/android/bluetooth/opp/BluetoothOppNotification.java index 28e3fb909..c0061183e 100644 --- a/src/com/android/bluetooth/opp/BluetoothOppNotification.java +++ b/src/com/android/bluetooth/opp/BluetoothOppNotification.java @@ -112,6 +112,7 @@ class BluetoothOppNotification { private int mInboundActiveNotificationId = 0; private int mOutboundActiveNotificationId = 0; + private int mIncomingShownId = 0; /** * This inner class is used to describe some properties for one transfer. @@ -596,27 +597,30 @@ class BluetoothOppNotification { long timeStamp = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP)); Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id); - Notification n = new Notification(); - n.icon = R.drawable.bt_incomming_file_notification; - n.flags |= Notification.FLAG_ONLY_ALERT_ONCE; - n.flags |= Notification.FLAG_ONGOING_EVENT; - n.defaults = Notification.DEFAULT_SOUND; - n.tickerText = title; + if (mIncomingShownId != id) { + Notification n = new Notification(); + n.icon = R.drawable.bt_incomming_file_notification; + n.flags |= Notification.FLAG_ONLY_ALERT_ONCE; + n.flags |= Notification.FLAG_ONGOING_EVENT; + n.defaults = Notification.DEFAULT_SOUND; + n.tickerText = title; - Intent intent = new Intent(Constants.ACTION_INCOMING_FILE_CONFIRM); - intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); - intent.setDataAndNormalize(contentUri); + Intent intent = new Intent(Constants.ACTION_INCOMING_FILE_CONFIRM); + intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); + intent.setDataAndNormalize(contentUri); - n.when = timeStamp; - n.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast(mContext, 0, + n.when = timeStamp; + n.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast(mContext, 0, intent, 0)); - intent = new Intent(Constants.ACTION_HIDE); - intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); - intent.setDataAndNormalize(contentUri); - n.deleteIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); + intent = new Intent(Constants.ACTION_HIDE); + intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); + intent.setDataAndNormalize(contentUri); + n.deleteIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); - mNotificationMgr.notify(id, n); + mNotificationMgr.notify(id, n); + mIncomingShownId = id; + } } cursor.close(); if (V) Log.v(TAG, "Freeing cursor: " + cursor); diff --git a/src/com/android/bluetooth/opp/BluetoothOppService.java b/src/com/android/bluetooth/opp/BluetoothOppService.java index 8e7b836c6..945d5475c 100644 --- a/src/com/android/bluetooth/opp/BluetoothOppService.java +++ b/src/com/android/bluetooth/opp/BluetoothOppService.java @@ -886,11 +886,20 @@ public class BluetoothOppService extends Service { if (V) Log.v(TAG, "Service cancel batch for share " + info.mId); batch.cancelBatch(); } - if (mTransfer != null) { - if (V) Log.v(TAG, "Stop transfer session"); + + /* Server/Client transfer cleanup */ + if ((batch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) + && (mTransfer != null)) { + if (V) Log.v(TAG, "Stop Client Transfer"); mTransfer.stop(); mTransfer = null; + } else if ((batch.mDirection == BluetoothShare.DIRECTION_INBOUND) + && (mServerTransfer != null)) { + if (V) Log.v(TAG, "Stop Server Transfer"); + mServerTransfer.stop(); + mServerTransfer = null; } + if (batch.isEmpty()) { if (V) Log.v(TAG, "Service remove batch " + batch.mId); removeBatch(batch); diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapService.java b/src/com/android/bluetooth/pbap/BluetoothPbapService.java index d73781480..fd53ceb1b 100644 --- a/src/com/android/bluetooth/pbap/BluetoothPbapService.java +++ b/src/com/android/bluetooth/pbap/BluetoothPbapService.java @@ -49,6 +49,7 @@ import android.bluetooth.BluetoothUuid; import android.content.Context; import android.content.Intent; import android.os.Handler; +import android.os.PowerManager; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; @@ -162,6 +163,8 @@ public class BluetoothPbapService extends Service { private static String sRemoteDeviceName = null; + private PowerManager.WakeLock mFullWakeLock = null; + private boolean mHasStarted = false; private volatile boolean mInterrupted; @@ -559,6 +562,7 @@ public class BluetoothPbapService extends Service { sRemoteDeviceName = getString(R.string.defaultname); } + acquirePbapWakeLock(); Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST); intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); @@ -579,6 +583,8 @@ public class BluetoothPbapService extends Service { // phonebook access, while UI still there waiting for user to confirm mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler .obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE); + + releasePbapWakeLock(); stopped = true; // job done ,close this thread; } catch (IOException ex) { stopped=true; @@ -721,6 +727,32 @@ public class BluetoothPbapService extends Service { return sRemoteDeviceName; } + private void acquirePbapWakeLock() { + if (mFullWakeLock == null) { + PowerManager pm = (PowerManager) this.getSystemService(Context.POWER_SERVICE); + mFullWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP + | PowerManager.ON_AFTER_RELEASE, "StartingObexPbapTransaction"); + mFullWakeLock.setReferenceCounted(false); + + mFullWakeLock.acquire(); + if (VERBOSE) Log.v(TAG, "Pbap: mFullWakeLock acquired"); + } else { + Log.e(TAG, "Pbap:mFullWakeLock already acquired"); + } + } + + private void releasePbapWakeLock() { + if (mFullWakeLock != null) { + if (mFullWakeLock.isHeld()) { + mFullWakeLock.release(); + if (VERBOSE) Log.v(TAG, "Pbap: mFullWakeLock released"); + } else { + if (VERBOSE) Log.v(TAG, "Pbap: mFullWakeLock already released"); + } + mFullWakeLock = null; + } + } + /** * Handlers for incoming service calls */ |