From fe3807a5b23f54f6539436d71aa0cd931a2b76f0 Mon Sep 17 00:00:00 2001 From: Matthew Xie Date: Thu, 18 Jul 2013 17:31:50 -0700 Subject: Bluetooth MAP profile - sms and mms support initial check-in bug:10116530 Change-Id: I57d022005bcff5bc3e56438a81ac92566f957744 --- Android.mk | 1 + core/java/android/bluetooth/BluetoothDevice.java | 3 + core/java/android/bluetooth/BluetoothMap.java | 300 ++++++++++++++++++++++ core/java/android/bluetooth/BluetoothProfile.java | 6 + core/java/android/bluetooth/BluetoothUuid.java | 13 +- core/java/android/bluetooth/IBluetoothMap.aidl | 32 +++ obex/javax/obex/ClientOperation.java | 3 + obex/javax/obex/HeaderSet.java | 2 + obex/javax/obex/Operation.java | 2 + obex/javax/obex/ServerOperation.java | 35 ++- 10 files changed, 386 insertions(+), 11 deletions(-) create mode 100644 core/java/android/bluetooth/BluetoothMap.java create mode 100644 core/java/android/bluetooth/IBluetoothMap.aidl diff --git a/Android.mk b/Android.mk index df10876..921566f 100644 --- a/Android.mk +++ b/Android.mk @@ -95,6 +95,7 @@ LOCAL_SRC_FILES += \ core/java/android/bluetooth/IBluetoothManager.aidl \ core/java/android/bluetooth/IBluetoothManagerCallback.aidl \ core/java/android/bluetooth/IBluetoothPbap.aidl \ + core/java/android/bluetooth/IBluetoothMap.aidl \ core/java/android/bluetooth/IBluetoothStateChangeCallback.aidl \ core/java/android/bluetooth/IBluetoothGatt.aidl \ core/java/android/bluetooth/IBluetoothGattCallback.aidl \ diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 3ee7142..3acd9b0 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -343,6 +343,9 @@ public final class BluetoothDevice implements Parcelable { /**@hide*/ public static final int REQUEST_TYPE_PHONEBOOK_ACCESS = 2; + /**@hide*/ + public static final int REQUEST_TYPE_MESSAGE_ACCESS = 3; + /** * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intents, * Contains package name to return reply intent to. diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java new file mode 100644 index 0000000..7de309f --- /dev/null +++ b/core/java/android/bluetooth/BluetoothMap.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2008 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 android.bluetooth; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.ServiceManager; +import android.util.Log; + +/** + * This class provides the APIs to control the Bluetooth MAP + * Profile. + *@hide + */ +public class BluetoothMap { + + private static final String TAG = "BluetoothMap"; + private static final boolean DBG = true; + private static final boolean VDBG = false; + + /** int extra for MAP_STATE_CHANGED_ACTION */ + public static final String MAP_STATE = + "android.bluetooth.map.intent.MAP_STATE"; + /** int extra for MAP_STATE_CHANGED_ACTION */ + public static final String MAP_PREVIOUS_STATE = + "android.bluetooth.map.intent.MAP_PREVIOUS_STATE"; + + /** Indicates the state of a Map connection state has changed. + * This intent will always contain MAP_STATE, MAP_PREVIOUS_STATE and + * BluetoothIntent.ADDRESS extras. + */ + public static final String MAP_STATE_CHANGED_ACTION = + "android.bluetooth.map.intent.action.MAP_STATE_CHANGED"; + + private IBluetoothMap mService; + private final Context mContext; + private ServiceListener mServiceListener; + private BluetoothAdapter mAdapter; + + /** There was an error trying to obtain the state */ + public static final int STATE_ERROR = -1; + /** No client currently connected */ + public static final int STATE_DISCONNECTED = 0; + /** Connection attempt in progress */ + public static final int STATE_CONNECTING = 1; + /** Client is currently connected */ + public static final int STATE_CONNECTED = 2; + + public static final int RESULT_FAILURE = 0; + public static final int RESULT_SUCCESS = 1; + /** Connection canceled before completion. */ + public static final int RESULT_CANCELED = 2; + + /** + * An interface for notifying Bluetooth PCE IPC clients when they have + * been connected to the BluetoothMap service. + */ + public interface ServiceListener { + /** + * Called to notify the client when this proxy object has been + * connected to the BluetoothMap service. Clients must wait for + * this callback before making IPC calls on the BluetoothMap + * service. + */ + public void onServiceConnected(BluetoothMap proxy); + + /** + * Called to notify the client that this proxy object has been + * disconnected from the BluetoothMap service. Clients must not + * make IPC calls on the BluetoothMap service after this callback. + * This callback will currently only occur if the application hosting + * the BluetoothMap service, but may be called more often in future. + */ + public void onServiceDisconnected(); + } + + final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback = + new IBluetoothStateChangeCallback.Stub() { + public void onBluetoothStateChange(boolean up) { + if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); + if (!up) { + if (VDBG) Log.d(TAG,"Unbinding service..."); + synchronized (mConnection) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } else { + synchronized (mConnection) { + try { + if (mService == null) { + if (VDBG) Log.d(TAG,"Binding service..."); + if (!mContext.bindService( + new Intent(IBluetoothMap.class.getName()), + mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth MAP Service"); + } + } + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + } + }; + + /** + * Create a BluetoothMap proxy object. + */ + public BluetoothMap(Context context, ServiceListener l) { + mContext = context; + mServiceListener = l; + mAdapter = BluetoothAdapter.getDefaultAdapter(); + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); + } catch (RemoteException e) { + Log.e(TAG,"",e); + } + } + if (!context.bindService(new Intent(IBluetoothMap.class.getName()), mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth Map Service"); + } + } + + protected void finalize() throws Throwable { + try { + close(); + } finally { + super.finalize(); + } + } + + /** + * Close the connection to the backing service. + * Other public functions of BluetoothMap will return default error + * results once close() has been called. Multiple invocations of close() + * are ok. + */ + public synchronized void close() { + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); + } catch (Exception e) { + Log.e(TAG,"",e); + } + } + + synchronized (mConnection) { + if (mService != null) { + try { + mService = null; + mContext.unbindService(mConnection); + mConnection = null; + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + mServiceListener = null; + } + + /** + * Get the current state of the BluetoothMap service. + * @return One of the STATE_ return codes, or STATE_ERROR if this proxy + * object is currently not connected to the Map service. + */ + public int getState() { + if (VDBG) log("getState()"); + if (mService != null) { + try { + return mService.getState(); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) log(Log.getStackTraceString(new Throwable())); + } + return BluetoothMap.STATE_ERROR; + } + + /** + * Get the currently connected remote Bluetooth device (PCE). + * @return The remote Bluetooth device, or null if not in connected or + * connecting state, or if this proxy object is not connected to + * the Map service. + */ + public BluetoothDevice getClient() { + if (VDBG) log("getClient()"); + if (mService != null) { + try { + return mService.getClient(); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) log(Log.getStackTraceString(new Throwable())); + } + return null; + } + + /** + * Returns true if the specified Bluetooth device is connected (does not + * include connecting). Returns false if not connected, or if this proxy + * object is not currently connected to the Map service. + */ + public boolean isConnected(BluetoothDevice device) { + if (VDBG) log("isConnected(" + device + ")"); + if (mService != null) { + try { + return mService.isConnected(device); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) log(Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Disconnects the current Map Client. Currently this call blocks, + * it may soon be made asynchronous. Returns false if this proxy object is + * not currently connected to the Map service. + */ + public boolean disconnect() { + if (DBG) log("disconnect()"); + if (mService != null) { + try { + mService.disconnect(); + return true; + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) log(Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Check class bits for possible Map support. + * This is a simple heuristic that tries to guess if a device with the + * given class bits might support Map. It is not accurate for all + * devices. It tries to err on the side of false positives. + * @return True if this device might support Map. + */ + public static boolean doesClassMatchSink(BluetoothClass btClass) { + // TODO optimize the rule + switch (btClass.getDeviceClass()) { + case BluetoothClass.Device.COMPUTER_DESKTOP: + case BluetoothClass.Device.COMPUTER_LAPTOP: + case BluetoothClass.Device.COMPUTER_SERVER: + case BluetoothClass.Device.COMPUTER_UNCATEGORIZED: + return true; + default: + return false; + } + } + + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + if (DBG) log("Proxy object connected"); + mService = IBluetoothMap.Stub.asInterface(service); + if (mServiceListener != null) { + mServiceListener.onServiceConnected(BluetoothMap.this); + } + } + public void onServiceDisconnected(ComponentName className) { + if (DBG) log("Proxy object disconnected"); + mService = null; + if (mServiceListener != null) { + mServiceListener.onServiceDisconnected(); + } + } + }; + + private static void log(String msg) { + Log.d(TAG, msg); + } +} diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index 43079f4..1574090 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -98,6 +98,12 @@ public interface BluetoothProfile { static public final int GATT_SERVER = 8; /** + * MAP Profile + * @hide + */ + public static final int MAP = 9; + + /** * Default priority for devices that we try to auto-connect to and * and allow incoming connections for the profile * @hide diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java index 5962235..fe66fbd 100644 --- a/core/java/android/bluetooth/BluetoothUuid.java +++ b/core/java/android/bluetooth/BluetoothUuid.java @@ -64,10 +64,14 @@ public final class BluetoothUuid { ParcelUuid.fromString("0000000f-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid PBAP_PSE = ParcelUuid.fromString("0000112f-0000-1000-8000-00805F9B34FB"); + public static final ParcelUuid MAP = + ParcelUuid.fromString("00001132-0000-1000-8000-00805F9B34FB"); + public static final ParcelUuid MNS = + ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid[] RESERVED_UUIDS = { AudioSink, AudioSource, AdvAudioDist, HSP, Handsfree, AvrcpController, AvrcpTarget, - ObexObjectPush, PANU, NAP}; + ObexObjectPush, PANU, NAP, MAP, MNS}; public static boolean isAudioSource(ParcelUuid uuid) { return uuid.equals(AudioSource); @@ -112,6 +116,13 @@ public final class BluetoothUuid { public static boolean isBnep(ParcelUuid uuid) { return uuid.equals(BNEP); } + public static boolean isMap(ParcelUuid uuid) { + return uuid.equals(MAP); + } + public static boolean isMns(ParcelUuid uuid) { + return uuid.equals(MNS); + } + /** * Returns true if ParcelUuid is present in uuidArray * diff --git a/core/java/android/bluetooth/IBluetoothMap.aidl b/core/java/android/bluetooth/IBluetoothMap.aidl new file mode 100644 index 0000000..0c18e06 --- /dev/null +++ b/core/java/android/bluetooth/IBluetoothMap.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2008 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 android.bluetooth; + +import android.bluetooth.BluetoothDevice; + +/** + * System private API for Bluetooth MAP service + * + * {@hide} + */ +interface IBluetoothMap { + int getState(); + BluetoothDevice getClient(); + boolean connect(in BluetoothDevice device); + void disconnect(); + boolean isConnected(in BluetoothDevice device); +} diff --git a/obex/javax/obex/ClientOperation.java b/obex/javax/obex/ClientOperation.java index 05b498c..294b502 100644 --- a/obex/javax/obex/ClientOperation.java +++ b/obex/javax/obex/ClientOperation.java @@ -723,4 +723,7 @@ public final class ClientOperation implements Operation, BaseStream { } } } + + public void noBodyHeader(){ + } } diff --git a/obex/javax/obex/HeaderSet.java b/obex/javax/obex/HeaderSet.java index b89b707..2b3066f 100644 --- a/obex/javax/obex/HeaderSet.java +++ b/obex/javax/obex/HeaderSet.java @@ -464,6 +464,8 @@ public final class HeaderSet { return mHttpHeader; case WHO: return mWho; + case CONNECTION_ID: + return mConnectionID; case OBJECT_CLASS: return mObjectClass; case APPLICATION_PARAMETER: diff --git a/obex/javax/obex/Operation.java b/obex/javax/obex/Operation.java index 25656ed..5b4d5ace 100644 --- a/obex/javax/obex/Operation.java +++ b/obex/javax/obex/Operation.java @@ -178,4 +178,6 @@ public interface Operation { void close() throws IOException; int getMaxPacketSize(); + + public void noBodyHeader(); } diff --git a/obex/javax/obex/ServerOperation.java b/obex/javax/obex/ServerOperation.java index d1476d2..fc441e0 100644 --- a/obex/javax/obex/ServerOperation.java +++ b/obex/javax/obex/ServerOperation.java @@ -88,6 +88,8 @@ public final class ServerOperation implements Operation, BaseStream { private boolean mHasBody; + private boolean mSendBodyHeader = true; + /** * Creates new ServerOperation * @param p the parent that created this object @@ -364,24 +366,33 @@ public final class ServerOperation implements Operation, BaseStream { * (End of Body) otherwise, we need to send 0x48 (Body) */ if ((finalBitSet) || (mPrivateOutput.isClosed())) { - out.write(0x49); + if(mSendBodyHeader == true) { + out.write(0x49); + bodyLength += 3; + out.write((byte)(bodyLength >> 8)); + out.write((byte)bodyLength); + out.write(body); + } } else { + if(mSendBodyHeader == true) { out.write(0x48); + bodyLength += 3; + out.write((byte)(bodyLength >> 8)); + out.write((byte)bodyLength); + out.write(body); + } } - bodyLength += 3; - out.write((byte)(bodyLength >> 8)); - out.write((byte)bodyLength); - out.write(body); } } if ((finalBitSet) && (type == ResponseCodes.OBEX_HTTP_OK) && (orginalBodyLength <= 0)) { - out.write(0x49); - orginalBodyLength = 3; - out.write((byte)(orginalBodyLength >> 8)); - out.write((byte)orginalBodyLength); - + if(mSendBodyHeader == true) { + out.write(0x49); + orginalBodyLength = 3; + out.write((byte)(orginalBodyLength >> 8)); + out.write((byte)orginalBodyLength); + } } mResponseSize = 3; @@ -711,4 +722,8 @@ public final class ServerOperation implements Operation, BaseStream { public void streamClosed(boolean inStream) throws IOException { } + + public void noBodyHeader(){ + mSendBodyHeader = false; + } } -- cgit v1.1