From 442407a15b12fcc1795b1e1df388408b134cdf96 Mon Sep 17 00:00:00 2001 From: Erin Yueh Date: Tue, 20 Oct 2009 12:16:52 +0800 Subject: [PATCH] apply Bluetooth FTP & OPP patch from aurora. --- Android.mk | 6 +- core/java/android/bluetooth/obex/BluetoothFtp.java | 254 +++ .../bluetooth/obex/BluetoothObexIntent.java | 138 ++ core/java/android/bluetooth/obex/BluetoothOpp.java | 194 +++ .../java/android/bluetooth/obex/IBluetoothFtp.aidl | 57 + .../bluetooth/obex/IBluetoothFtpCallback.aidl | 102 ++ .../java/android/bluetooth/obex/IBluetoothOpp.aidl | 47 + core/java/android/os/Parcel.java | 88 + core/java/android/server/BluetoothA2dpService.java | 26 +- .../android/server/BluetoothDeviceService.java | 2 +- core/java/android/server/BluetoothFtpService.java | 763 +++++++++ .../java/android/server/BluetoothObexDatabase.java | 421 +++++ core/java/android/server/BluetoothOppService.java | 447 +++++ core/jni/Android.mk | 12 +- core/jni/AndroidRuntime.cpp | 5 + core/jni/android_bluetooth_Database.cpp | 6 + core/jni/android_bluetooth_common.cpp | 85 +- core/jni/android_bluetooth_common.h | 121 ++- core/jni/android_server_BluetoothA2dpService.cpp | 75 +- core/jni/android_server_BluetoothDeviceService.cpp | 96 +- core/jni/android_server_BluetoothEventLoop.cpp | 393 ++++- core/jni/android_server_BluetoothFtpService.cpp | 1797 ++++++++++++++++++++ core/jni/android_server_BluetoothOppService.cpp | 1201 +++++++++++++ services/java/com/android/server/SystemServer.java | 14 + tools/aidl/Type.cpp | 19 + tools/aidl/Type.h | 4 + 26 files changed, 6217 insertions(+), 156 deletions(-) create mode 100755 core/java/android/bluetooth/obex/BluetoothFtp.java create mode 100755 core/java/android/bluetooth/obex/BluetoothObexIntent.java create mode 100755 core/java/android/bluetooth/obex/BluetoothOpp.java create mode 100755 core/java/android/bluetooth/obex/IBluetoothFtp.aidl create mode 100755 core/java/android/bluetooth/obex/IBluetoothFtpCallback.aidl create mode 100755 core/java/android/bluetooth/obex/IBluetoothOpp.aidl create mode 100755 core/java/android/server/BluetoothFtpService.java create mode 100755 core/java/android/server/BluetoothObexDatabase.java create mode 100755 core/java/android/server/BluetoothOppService.java create mode 100755 core/jni/android_server_BluetoothFtpService.cpp create mode 100755 core/jni/android_server_BluetoothOppService.cpp diff --git a/Android.mk b/Android.mk index 1b5c70b..3b6c25a 100644 --- a/Android.mk +++ b/Android.mk @@ -1,5 +1,6 @@ # # Copyright (C) 2008 The Android Open Source Project +# Copyright (c) 2009, Code Aurora Forum. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -65,7 +66,7 @@ endif ## READ ME: ######################################################## LOCAL_SRC_FILES += \ core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl \ - core/java/android/accessibilityservice/IEventListener.aidl \ + core/java/android/accessibilityservice/IEventListener.aidl \ core/java/android/accounts/IAccountsService.aidl \ core/java/android/app/IActivityController.aidl \ core/java/android/app/IActivityPendingResult.aidl \ @@ -89,6 +90,9 @@ LOCAL_SRC_FILES += \ core/java/android/bluetooth/IBluetoothDevice.aidl \ core/java/android/bluetooth/IBluetoothDeviceCallback.aidl \ core/java/android/bluetooth/IBluetoothHeadset.aidl \ + core/java/android/bluetooth/obex/IBluetoothOpp.aidl \ + core/java/android/bluetooth/obex/IBluetoothFtp.aidl \ + core/java/android/bluetooth/obex/IBluetoothFtpCallback.aidl \ core/java/android/content/IContentService.aidl \ core/java/android/content/IIntentReceiver.aidl \ core/java/android/content/IIntentSender.aidl \ diff --git a/core/java/android/bluetooth/obex/BluetoothFtp.java b/core/java/android/bluetooth/obex/BluetoothFtp.java new file mode 100755 index 0000000..6483212 --- /dev/null +++ b/core/java/android/bluetooth/obex/BluetoothFtp.java @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Code Aurora nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package android.bluetooth.obex; + +import android.bluetooth.obex.IBluetoothFtpCallback; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.server.BluetoothFtpService; +import android.util.Log; + +/** + * Public API for controlling the Bluetooth FTP Profile Service. + * + * BluetoothFtp is a proxy object for controlling the Bluetooth FTP + * Service via IPC. + * + * Currently the BluetoothFtp service runs in the system server and this + * proxy object will be immediately bound to the service on construction. + * However this may change in future releases, and error codes such as + * BluetoothError.ERROR_IPC_NOT_READY will be returned from this API when the + * proxy object is not yet attached. + * + * @hide + */ +public class BluetoothFtp { + private static final String TAG = "BluetoothFtp"; + + private IBluetoothFtp mService; + private String mDestBtAddr = null; + + /** + * Create a BluetoothFtp proxy object for interacting with the local + * Bluetooth FTP service. Currently, only one FTP connection is allowed at a time, + * so close() should be called when FTP session is complete. + */ + public BluetoothFtp() { + IBinder b = ServiceManager.getService(BluetoothFtpService.BLUETOOTH_FTP_SERVICE); + if (b == null) { + throw new RuntimeException("Bluetooth FTP service not available!"); + } + mService = IBluetoothFtp.Stub.asInterface(b); + } + + protected void finalize() throws Throwable { + try { + close(); + } finally { + super.finalize(); + } + } + + /** + * Connect to a FTP server. This is required before most other operations. + * This is an asynchronous call. + * + * @param address Bluetooth address of remote device to connect to + * @param callback Class instance implementing IBluetoothFtpCallback. + * This callback is used for all asynchronous calls. + * + * @return false indicates immediate error + */ + public boolean connect(String address, IBluetoothFtpCallback callback) { + mDestBtAddr = address; + + // Create FTP session (we currently restrict to one simultaneous FTP session) + try { + return mService.createSession(address, callback); + } catch (RemoteException e) {Log.e(TAG, "", e);} + + return false; + } + + /** + * Determine if a given connection is active. + * + * @param address Address of connection to query + * + * @return true indicates an existing connection with address + * false indicates unknown/closed connection. + */ + public boolean isConnectionActive(String address) { + try { + return mService.isConnectionActive(address); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Close the connection to the backing service. + * Other public functions of BluetoothFtp will return default error + * results once close() has been called. Multiple invocations of close() + * are ok. + */ + public synchronized void close() { + if (mDestBtAddr != null) { + try { + mService.closeSession(mDestBtAddr); + } catch (RemoteException e) {Log.e(TAG, "", e);} + + mDestBtAddr = null; + } + } + + /** + * Change the current folder on the remote device + * This is an asynchronous call. + * + * @param folder Name of folder to change to. The name should not contain + * any delimiters. The empty name ("") will change to the + * home/root directory of the remote device. The special + * name ".." will change to the parent directory. + * + * @return false indicates immediate error + */ + public boolean changeFolder(String folder) { + try { + return mService.changeFolder(mDestBtAddr, folder); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Create a folder on the remote device + * This is an asynchronous call. + * + * @param folder Name of folder to create + * + * @return false indicates immediate error + */ + public boolean createFolder(String folder) { + try { + return mService.createFolder(mDestBtAddr, folder); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Delete the specified file/folder from the remote device + * This is an asynchronous call. + * + * @param name Name of file/folder to delete + * + * @return false indicates immediate error + */ + public boolean delete(String name) { + try { + return mService.delete(mDestBtAddr, name); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Request a list of the current folder contents on the remote device. + * This is an asynchronous call. + * + * @return false indicates immediate error + */ + public boolean listFolder() { + try { + return mService.listFolder(mDestBtAddr); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Get a file from the remote device + * This is an asynchronous call. + * + * @param localFilename Filename to place fetched file on local device + * @param remoteFilename Filename to fetch from remote device + * + * @return false indicates immediate error + */ + public boolean getFile(String localFilename, String remoteFilename) { + try { + return mService.getFile(mDestBtAddr, localFilename, remoteFilename); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Copy a file to the remote device + * This is an asynchronous call. + * + * @param localFilename Filename, on local device, to copy to remote device + * @param remoteFilename Filename to place file in on remote device + * + * @return false indicates immediate error + */ + public boolean putFile(String localFilename, String remoteFilename) { + try { + return mService.putFile(mDestBtAddr, localFilename, remoteFilename); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Cancel an ongoing FTP transfer + * + * @param name Name of FTP object being transferred + * + * @return false indicates immediate error + */ + public boolean cancelTransfer(String name) { + try { + return mService.cancelTransfer(mDestBtAddr, name); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Determine if a given transfer is active. + * + * @param filename Filename of FTP object being transferred + * + * @return true indicates an ongoing transfer with filename. + * false indicates unknown/completed transfer. + */ + public boolean isTransferActive(String filename) { + try { + return mService.isTransferActive(filename); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + // TODO: update with any new functionality from BM3 obexd +} diff --git a/core/java/android/bluetooth/obex/BluetoothObexIntent.java b/core/java/android/bluetooth/obex/BluetoothObexIntent.java new file mode 100755 index 0000000..408189e --- /dev/null +++ b/core/java/android/bluetooth/obex/BluetoothObexIntent.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Code Aurora nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package android.bluetooth.obex; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; + +/** + * Bluetooth intents for OBEX operations: authorization, transfer progress, transfer completions. + * + * @hide + */ +public interface BluetoothObexIntent { + /** + * OBEX profile identifiers for PROFILE extra + */ + public static final int PROFILE_OPP = 0; + public static final int PROFILE_FTP = 1; + + /** + * OBEX intent extras (parameters) + */ + public static final String OBJECT_FILENAME = + "android.bluetooth.obex.intent.OBJECT_FILENAME"; + public static final String OBJECT_TYPE = + "android.bluetooth.obex.intent.OBJECT_TYPE"; + public static final String OBJECT_SIZE = + "android.bluetooth.obex.intent.OBJECT_SIZE"; + public static final String ADDRESS = + "android.bluetooth.obex.intent.ADDRESS"; + public static final String BYTES_TRANSFERRED = + "android.bluetooth.obex.intent.BYTES_TRANSFERRED"; + public static final String SUCCESS = + "android.bluetooth.obex.intent.SUCCESS"; + public static final String ERROR_MESSAGE = + "android.bluetooth.obex.intent.ERROR_MESSAGE"; + public static final String PROFILE = + "android.bluetooth.obex.intent.PROFILE"; + /** + * Broadcasted to update listeners on OPP connection status. + *

+ * Extras: + *

+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String CONNECT_STATUS_ACTION = + "android.bluetooth.obex.intent.action.CONNECT_STATUS"; + + /** + * Broadcasted to update listeners on OBEX transfer progress. + *

+ * Extras: + *

+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String PROGRESS_ACTION = + "android.bluetooth.obex.intent.action.PROGRESS"; + + /** + * Broadcasted to indicate that an OBEX object receive is complete. + *

+ * Extras: + *

+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String RX_COMPLETE_ACTION = + "android.bluetooth.obex.intent.action.RX_COMPLETE"; + + /** + * Broadcasted to indicate that an OBEX object transmit is complete. + *

+ * Extras: + *

+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String TX_COMPLETE_ACTION = + "android.bluetooth.obex.intent.action.TX_COMPLETE"; + + /** + * Broadcasted to request authorization for an incoming OBEX transfer. + *

+ * A receiver (e.g., the Bluetooth OPP 'controller') should act according to the user's + * wishes, perhaps by prompting them to approve/reject the transfer. obexAuthorizeComplete() + * should be called with the authorization decision. + *

+ * Extras: + *

+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String AUTHORIZE_ACTION = + "android.bluetooth.obex.intent.action.AUTHORIZE"; +} diff --git a/core/java/android/bluetooth/obex/BluetoothOpp.java b/core/java/android/bluetooth/obex/BluetoothOpp.java new file mode 100755 index 0000000..361aa96 --- /dev/null +++ b/core/java/android/bluetooth/obex/BluetoothOpp.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Code Aurora nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package android.bluetooth.obex; + +import android.server.BluetoothOppService; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +/** + * Public API for controlling the Bluetooth OPP Profile Service. + * + * BluetoothOpp is a proxy object for controlling the Bluetooth OPP + * Service via IPC. + * + * Currently the BluetoothOpp service runs in the system server and this + * proxy object will be immediately bound to the service on construction. + * However this may change in future releases, and error codes such as + * BluetoothError.ERROR_IPC_NOT_READY will be returned from this API when the + * proxy object is not yet attached. + * + * @hide + */ +public class BluetoothOpp { + private static final String TAG = "BluetoothOpp"; + private final IBluetoothOpp mService; + + /** + * Create a BluetoothOpp proxy object for interacting with the local + * Bluetooth OPP service. + */ + public BluetoothOpp() { + IBinder b = ServiceManager.getService(BluetoothOppService.BLUETOOTH_OPP_SERVICE); + if (b == null) { + throw new RuntimeException("Bluetooth OPP service not available!"); + } + mService = IBluetoothOpp.Stub.asInterface(b); + } + + /** + * Push an object using the OPP Bluetooth profile. + * + * @param address Destination BT address. + * @param txFilename Name of file to send + * + * @return false indicates immediate error + */ + public boolean pushObject(String address, String txFilename) { + try { + return mService.pushObject(address, txFilename); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Pull a business card using the OPP Bluetooth profile. + * + * @param address Remote BT address. + * @param rxFilename Filename to store retrieved business card in. + * Passing in null is treated as a "don't care"-- + * BluetoothOppService will generate a filename that + * the caller will receive in various intents + * (e.g., RX_COMPLETE) + * + * @return The filename the pulled business card will be placed in. + * null indicated an immediate error + */ + public String pullBusinessCard(String address, String rxFilename) { + if (rxFilename == null) + { + // TODO: generate a filename for the caller here. Until then, + // this is an unsupported use case. + return null; + } + + boolean ret = false; + try { + ret = mService.pullBusinessCard(address, rxFilename); + } catch (RemoteException e) {Log.e(TAG, "", e);} + + if (ret) { + return rxFilename; + } + + return null; + } + + /** + * Exchange business cards using the OPP Bluetooth profile. + * + * @param address Remote BT address. + * @param rxFilename Filename to store retrieved business card in. + * Passing in null is treated as a "don't care"-- + * BluetoothOppService will generate a filename that + * the caller will receive in various intents + * (e.g., RX_COMPLETE) + * @param txFilename Filename of business card to send + * + * @return The filename the pulled business card will be placed in. + * null indicated an immediate error + */ + public String exchangeBusinessCards(String address, String rxFilename, String txFilename) { + if (rxFilename == null) + { + // TODO: generate a filename for the caller here. Until then, + // this is an unsupported use case. + return null; + } + + String retRxFilename = pullBusinessCard(address, rxFilename); + + if (retRxFilename != null) { + if (pushObject(address, txFilename)) { + return retRxFilename; + } + } + + return null; + } + + /** + * Cancel an ongoing OPP transfer + * + * @param filename Filename of OPP object being transferred + * + * @return false indicates immediate error + */ + public boolean cancelTransfer(String filename) { + try { + return mService.cancelTransfer(filename); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Determine if a given transfer is active. + * + * @param filename Filename of OPP object being transferred + * + * @return true indicates an ongoing transfer with filename. + * false indicates unknown/completed transfer. + */ + public boolean isTransferActive(String filename) { + try { + return mService.isTransferActive(filename); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Approve/reject an incoming OPP push. This is a response to the AUTHORIZE_ACTION intent. + * + * @param proposedFilename Filename identifying transfer from the AUTHORIZE_ACTION + * intent OBJECT_FILENAME extra + * @param accept true to authorize incoming OPP push, false to reject incoming OPP push + * @param newFilename Filename to place received file in. + * null indicates proposedFilename should be used. + * + * @return false indicates immediate error + */ + public boolean obexAuthorizeComplete(String proposedFilename, boolean accept, String newFilename) { + try { + return mService.obexAuthorizeComplete(proposedFilename, accept, newFilename); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } +} diff --git a/core/java/android/bluetooth/obex/IBluetoothFtp.aidl b/core/java/android/bluetooth/obex/IBluetoothFtp.aidl new file mode 100755 index 0000000..d9299db --- /dev/null +++ b/core/java/android/bluetooth/obex/IBluetoothFtp.aidl @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Code Aurora nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package android.bluetooth.obex; + +import android.bluetooth.obex.IBluetoothFtpCallback; + +/** + * System private API for talking with the Bluetooth FTP service. + *

+ * The BluetoothFtp class method documentation should be used as a reference. + * Actions resulting from these procedures are broadcast using intents defined + * in android.bluetooth.obex.BluetoothObexIntent. + * + * {@hide} + */ +interface IBluetoothFtp +{ + boolean createSession(in String address, in IBluetoothFtpCallback callback); // async + boolean closeSession(in String address); + boolean isConnectionActive(in String address); + + boolean changeFolder(in String address, in String folder); // async + boolean createFolder(in String address, in String folder); // async + boolean delete(in String address, in String name); // async + boolean listFolder(in String address); // async + + boolean getFile(in String address, in String localFilename, in String remoteFilename); // async + boolean putFile(in String address, in String localFilename, in String remoteFilename); // async + boolean cancelTransfer(in String address, in String name); + boolean isTransferActive(in String filename); +} diff --git a/core/java/android/bluetooth/obex/IBluetoothFtpCallback.aidl b/core/java/android/bluetooth/obex/IBluetoothFtpCallback.aidl new file mode 100755 index 0000000..71a83b3 --- /dev/null +++ b/core/java/android/bluetooth/obex/IBluetoothFtpCallback.aidl @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Code Aurora nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package android.bluetooth.obex; + +/** + * System private API for Bluetooth FTP service callbacks (async returns). + *

+ * The BluetoothFtp class method documentation should be used as a reference. + * Actions resulting from these procedures are broadcast using intents defined + * in android.bluetooth.obex.BluetoothObexIntent. + * + * {@hide} + */ +interface IBluetoothFtpCallback +{ + /** + * @param isError true if error executing createSession, false otherwise + */ + void onCreateSessionComplete(in boolean isError); + + /** + * Notification that this session has been closed. The client will + * need to reconnect if future FTP operations are required. + */ + void onObexSessionClosed(); + + /** + * @param folder name of folder to create + * @param isError true if error executing createFolder, false otherwise + */ + void onCreateFolderComplete(in String folder, in boolean isError); + + /** + * @param folder name of folder to change to + * @param isError true if error executing changeFolder, false otherwise + */ + void onChangeFolderComplete(in String folder, in boolean isError); + + /** + * @param result List of Map object containing information about the current folder contents. + *

+ *

+ * @param isError true if error executing listFolder, false otherwise + */ + void onListFolderComplete(in List result, in boolean isError); + + /** + * @param localFilename Filename on local device + * @param remoteFilename Filename on remote device + * @param isError true if error executing getFile, false otherwise + */ + void onGetFileComplete(in String localFilename, in String remoteFilename, in boolean isError); + + /** + * @param localFilename Filename on local device + * @param remoteFilename Filename on remote device + * @param isError true if error executing putFile, false otherwise + */ + void onPutFileComplete(in String localFilename, in String remoteFilename, in boolean isError); + + /** + * @param name name of file/folder to delete + * @param isError true if error executing delete, false otherwise + */ + void onDeleteComplete(in String name, in boolean isError); + + /* TODO: update with any new functionality from BM3 obexd */ +} diff --git a/core/java/android/bluetooth/obex/IBluetoothOpp.aidl b/core/java/android/bluetooth/obex/IBluetoothOpp.aidl new file mode 100755 index 0000000..568f83c --- /dev/null +++ b/core/java/android/bluetooth/obex/IBluetoothOpp.aidl @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Code Aurora nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package android.bluetooth.obex; + +/** + * System private API for talking with the Bluetooth OPP service. + * + * The BluetoothOpp class method documentation should be used as a reference. + * Actions resulting from these procedures are broadcast using intents defined + * in android.bluetooth.obex.BluetoothObexIntent. + * + * {@hide} + */ +interface IBluetoothOpp +{ + boolean pushObject(in String address, in String txFilename); + boolean pullBusinessCard(in String address, in String rxFilename); + boolean cancelTransfer(in String filename); + boolean isTransferActive(in String filename); + boolean obexAuthorizeComplete(in String proposedFilename, in boolean accept, in String newFilename); +} diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 6cfccee..eec3de7 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2006 The Android Open Source Project + * Copyright (c) 2009, Code Aurora Forum, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -911,6 +912,33 @@ public final class Parcel { } /** + * Flatten a List containing Map objects into the parcel, at + * the current dataPosition() and growing dataCapacity() if needed. They + * can later be retrieved with {@link #createMapArrayList} or + * {@link #readMapList}. + * + * @param val The list of maps to be written. + * + * @see #createMapArrayList + * @see #readMapList + * + * @hide + */ + public final void writeMapList(List val) { + if (val == null) { + writeInt(-1); + return; + } + int N = val.size(); + int i=0; + writeInt(N); + while (i < N) { + writeMap(val.get(i)); + i++; + } + } + + /** * Flatten a List containing IBinder objects into the parcel, at * the current dataPosition() and growing dataCapacity() if needed. They * can later be retrieved with {@link #createBinderArrayList} or @@ -1541,6 +1569,34 @@ public final class Parcel { } /** + * Read and return a new ArrayList containing Map objects from + * the parcel that was written with {@link #writeMapList} at the + * current dataPosition(). Returns null if the + * previously written list object was null. + * + * @return A newly created ArrayList containing maps with the same data + * as those that were previously written. + * + * @see #writeMapList + * + * @hide + */ + public final ArrayList createMapArrayList() { + int N = readInt(); + if (N < 0) { + return null; + } + ArrayList l = new ArrayList(N); + while (N > 0) { + Map t = new HashMap(); + readMap(t, null); + l.add(t); + N--; + } + return l; + } + + /** * Read and return a new ArrayList containing IBinder objects from * the parcel that was written with {@link #writeBinderList} at the * current dataPosition(). Returns null if the @@ -1589,6 +1645,38 @@ public final class Parcel { } /** + * Read into the given List items Map objects that were written with + * {@link #writeMapList} at the current dataPosition(). + * + * @return A newly created ArrayList containing maps with the same data + * as those that were previously written. + * + * @see #writeMapList + * + * @hide + */ + public final void readMapList(List list) { + int M = list.size(); + int N = readInt(); + int i = 0; + for (; i < M && i < N; i++) { + Map t = new HashMap(); + readMap(t, null); + + list.set(i, t); + } + for (; i mAudioDevices; private final AudioManager mAudioManager; private final BluetoothDevice mBluetooth; + private final BluetoothHeadset mBluetoothHeadset; + // list of disconnected sinks to process after a delay private final ArrayList mPendingDisconnects = new ArrayList(); // number of active sinks - private int mSinkCount = 0; + private int mSinkCount = 0; private class SinkState { public String address; @@ -91,6 +95,14 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { throw new RuntimeException("Could not init BluetoothA2dpService"); } + /** + * Instance of the bluetooth headset helper. This needs to be created + * early because there is a delay before it actually 'connects', as + * noted by its javadoc. Otherwise, a service listener needs to be + * implemented to detect and synchronize the connection. + */ + mBluetoothHeadset = new BluetoothHeadset(mContext, null); + mIntentFilter = new IntentFilter(BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION); mIntentFilter.addAction(BluetoothIntent.BOND_STATE_CHANGED_ACTION); mIntentFilter.addAction(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION); @@ -157,10 +169,14 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { switch (msg.what) { case MESSAGE_CONNECT_TO: String address = (String)msg.obj; - // check bluetooth is still on, device is still preferred, and - // nothing is currently connected + int headsetState = mBluetoothHeadset.getState(); + + // check bluetooth is still on, device is still preferred, a Bluetooth connection + // still exists and nothing is currently connected to A2DP if (mBluetooth.isEnabled() && getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF && + (headsetState == BluetoothHeadset.STATE_CONNECTING || + headsetState == BluetoothHeadset.STATE_CONNECTED) && lookupSinksMatchingStates(new int[] { BluetoothA2dp.STATE_CONNECTING, BluetoothA2dp.STATE_CONNECTED, @@ -375,7 +391,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { } private synchronized void onSinkDisconnected(String path) { - // This is to work around a problem in bluez that results + // This is to work around a problem in bluez that results // sink disconnect events being sent, immediately followed by a reconnect. // To avoid unnecessary audio routing changes, we defer handling // sink disconnects until after a short delay. @@ -459,7 +475,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { if (state != prevState) { if (DBG) log("state " + address + " (" + path + ") " + prevState + "->" + state); - + // keep track of the number of active sinks if (prevState == BluetoothA2dp.STATE_DISCONNECTED) { mSinkCount++; diff --git a/core/java/android/server/BluetoothDeviceService.java b/core/java/android/server/BluetoothDeviceService.java index 8c843ef..b2a3bc0 100644 --- a/core/java/android/server/BluetoothDeviceService.java +++ b/core/java/android/server/BluetoothDeviceService.java @@ -270,7 +270,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { if (!disable(false)) { mRestart = false; } - } + } private synchronized void setBluetoothState(int state) { if (state == mBluetoothState) { diff --git a/core/java/android/server/BluetoothFtpService.java b/core/java/android/server/BluetoothFtpService.java new file mode 100755 index 0000000..1a34ee4 --- /dev/null +++ b/core/java/android/server/BluetoothFtpService.java @@ -0,0 +1,763 @@ +/* + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Code Aurora nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package android.server; + +import android.bluetooth.obex.BluetoothObexIntent; +import android.bluetooth.obex.IBluetoothFtp; +import android.bluetooth.obex.IBluetoothFtpCallback; +import android.content.Context; +import android.content.Intent; +import android.os.RemoteException; +import android.util.Log; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @hide + */ +public class BluetoothFtpService extends IBluetoothFtp.Stub { + public static final String BLUETOOTH_FTP_SERVICE = "bluetooth_ftp"; + private static final String TAG = "BluetoothFtpService"; + private static final boolean DBG = false; + private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; + private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; + + protected BluetoothObexDatabase mSessionDb; + private final Context mContext; + + public BluetoothFtpService(Context context) { + mContext = context; + + if (!initNative()) { + throw new RuntimeException("Could not init BluetoothFtpService"); + } + + mSessionDb = new BluetoothObexDatabase(); + } + protected native boolean initNative(); + + /** + * Called when object is garbage-collected: trigger cleanup in JNI + */ + @Override + protected void finalize() throws Throwable { + try { + cleanupNative(); + } finally { + super.finalize(); + } + } + protected native void cleanupNative(); + + /** + * Helper logging function for debug messages + */ + private static void log(String msg) { + Log.d(TAG, msg); + } + + /** + * Getter functions for unit testing purposes + * @return + */ + public final BluetoothObexDatabase getSessionDb() { + return mSessionDb; + } + + /** + * Create an OBEX session for FTP (async call) + * + * @param address Destination BT address. + * @param callback Class instance implementing IBluetoothFtpCallback + * + * @return false indicates immediate error + */ + public synchronized boolean createSession(String address, IBluetoothFtpCallback callback) { + /* + * Need to register callback before calling native code + * (which could potentially call callback) + */ + BluetoothObexDatabase.SessionDbItem dbItem = + mSessionDb.new SessionDbItem(address,null,callback); + mSessionDb.insert(dbItem); + + boolean ret = createSessionNative(address); + + if (!ret) { + mSessionDb.deleteByAddress(address); + } + + return ret; + } + protected native boolean createSessionNative(String address); + + /** + * Close an OBEX session. + * + * @param address Destination BT address + * + * @return false indicates immediate error + */ + public synchronized boolean closeSession(String address) { + BluetoothObexDatabase.SessionDbItem dbItem = mSessionDb.getByAddress(address); + + if (dbItem == null) { + if (DBG) log("closeSession() could not find session with address: " + address); + return false; + } + + boolean ret = closeSessionNative(dbItem.mSession); + + if (ret) { + mSessionDb.deleteByAddress(address); + // TODO: any orphan transfer cleanup + } else { + if (DBG) { + log("closeSessionNative() failed for session: " + dbItem.mSession + + ". Retaining."); + } + } + + return ret; + } + protected native boolean closeSessionNative(String session); + + /** + * Determine if a given connection is active. + * + * @param address Address of connection to query + * + * @return true indicates an existing connection with address + * false indicates unknown/closed connection. + */ + public synchronized boolean isConnectionActive(String address) { + return (mSessionDb.getByAddress(address) != null); + } + + /** + * Change the current folder of the remote device (async call) + * + * @param address Destination BT address + * @param folder Name of folder to change to + * + * @return false indicates immediate error + */ + public synchronized boolean changeFolder(String address, String folder) { + BluetoothObexDatabase.SessionDbItem dbItem = mSessionDb.getByAddress(address); + + if (dbItem == null) { + if (DBG) log("changeFolder() could not find session with address: " + address); + return false; + } + + return changeFolderNative(dbItem.mSession,folder); + } + protected native boolean changeFolderNative(String session, String folder); + + /** + * Create a new folder on the remote device (async call) + * + * @param address Destination BT address + * @param folder Name of folder to create + * + * @return false indicates immediate error + */ + public synchronized boolean createFolder(String address, String folder) { + BluetoothObexDatabase.SessionDbItem dbItem = mSessionDb.getByAddress(address); + + if (dbItem == null) { + if (DBG) log("createFolder() could not find session with address: " + address); + return false; + } + + return createFolderNative(dbItem.mSession,folder); + } + protected native boolean createFolderNative(String session, String folder); + + /** + * Delete the specified file/folder on the remote device (aync call) + * + * @param address Destination BT address + * @param name Name of file/folder to delete + * + * @return false indicates immediate error + */ + public synchronized boolean delete(String address, String name) { + BluetoothObexDatabase.SessionDbItem dbItem = mSessionDb.getByAddress(address); + + if (dbItem == null) { + if (DBG) log("delete() could not find session with address: " + address); + return false; + } + + return deleteNative(dbItem.mSession,name); + } + protected native boolean deleteNative(String session, String name); + + /** + * List the contents of the current folder on the remote device (async call) + * + * @param address Destination BT address + * + * @return false indicates immediate error + */ + public synchronized boolean listFolder(String address) { + BluetoothObexDatabase.SessionDbItem dbItem = mSessionDb.getByAddress(address); + + if (dbItem == null) { + if (DBG) log("listFolder() could not find session with address: " + address); + return false; + } + + return listFolderNative(dbItem.mSession); + } + protected native boolean listFolderNative(String session); + + /** + * Copy a file from the remote device to this device (async call) + * + * @param address Destination BT address + * @param localFilename Filename to save retrieved file in + * @param remoteFilename Name of file on remote device to get + * + * @return false indicates immediate error + */ + public synchronized boolean getFile(String address, String localFilename, + String remoteFilename) { + BluetoothObexDatabase.SessionDbItem dbItem = mSessionDb.getByAddress(address); + + if (dbItem == null) { + if (DBG) log("getFile() could not find session with address: " + address); + return false; + } + + boolean ret = getFileNative(dbItem.mSession, localFilename, remoteFilename); + if (ret) { + BluetoothObexDatabase.TransferDbItem dbTransItem = + mSessionDb.new TransferDbItem(localFilename,remoteFilename,null,dbItem); + dbTransItem.mDirection = BluetoothObexDatabase.TransferDirection.RX; + mSessionDb.insert(dbTransItem); + } + + return ret; + } + protected native boolean getFileNative(String session, String localFilename, + String remoteFilename); + + /** + * Copy a file from this device to a remote device (async call) + * + * @param address Destination BT address + * @param localFilename Name of file on local device to send + * @param remoteFilename Filename to save file in on remote device + * + * @return false indicates immediate error + */ + public synchronized boolean putFile(String address, String localFilename, + String remoteFilename) { + BluetoothObexDatabase.SessionDbItem dbItem = mSessionDb.getByAddress(address); + + if (dbItem == null) { + if (DBG) log("putFile() could not find session with address: " + address); + return false; + } + + boolean ret = putFileNative(dbItem.mSession, localFilename, remoteFilename); + if (ret) { + BluetoothObexDatabase.TransferDbItem dbTransItem = + mSessionDb.new TransferDbItem(localFilename,remoteFilename,null,dbItem); + dbTransItem.mDirection = BluetoothObexDatabase.TransferDirection.TX; + mSessionDb.insert(dbTransItem); + } + + return ret; + } + protected native boolean putFileNative(String session, String localFilename, + String remoteFilename); + + /** + * Cancel an ongoing FTP transfer + * + * @param address Destination BT address + * @param filename Filename of FTP object being transferred + * + * @return false indicates immediate error + */ + public synchronized boolean cancelTransfer(String address, String name) { + BluetoothObexDatabase.TransferDbItem dbItem = mSessionDb.getByFilename(name); + + if (dbItem == null) { + if (DBG) log("cancelTransfer() could not find transfer with filename: " + name); + } else { + if (dbItem.mSession.mAddress.equals(address)) { + mSessionDb.deleteByFilename(name); + + // It's possible to not have a defined transfer name yet + // (i.e., onObexRequest() hasn't been called). + if (dbItem.mTransfer != null) { + return cancelTransferNative(dbItem.mTransfer); + } else { + return true; + } + } else { + if (DBG) { + log("cancelTransfer() could not find transfer with filename: " + + name + " address: " + address); + } + } + } + + return false; + } + protected native boolean cancelTransferNative(String transfer); + + /** + * Determine if a given transfer is active. + * + * @param filename Filename of FTP object being transferred + * + * @return true indicates an ongoing transfer with filename. + * false indicates unknown/completed transfer. + */ + public synchronized boolean isTransferActive(String filename) { + return (mSessionDb.getByFilename(filename) != null); + } + + /** + * Report of FTP session creation completion + * + * @param session D-Bus object name representing session + * @param address Remote device Bluetooth address + * @param isError true if error during createSession() execution, false otherwise + * + * @see createSession() + */ + public synchronized void onCreateSessionComplete(String session, String address, + boolean isError) { + BluetoothObexDatabase.SessionDbItem dbItem = mSessionDb.getByAddress(address); + + if (dbItem == null) { + if (DBG) { + log("onCreateSessionComplete() could not find session with address: " + + address); + } + + return; + } + + if (isError) { + // Remove session + mSessionDb.deleteByAddress(address); + } else { + // Update session object + mSessionDb.updateSessionByAddress(address, session); + } + + // Send callback + try { + ((IBluetoothFtpCallback)dbItem.mCallback).onCreateSessionComplete(isError); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + + /** + * Report of change folder completion + * + * @param session D-Bus object name representing session + * @param folder Name of folder to change to + * @param isError true if error during changeFolder() execution, false otherwise + * + * @see changeFolder() + */ + public synchronized void onChangeFolderComplete(String session, String folder, + boolean isError) { + BluetoothObexDatabase.SessionDbItem dbItem = mSessionDb.getBySession(session); + + if (dbItem == null) { + if (DBG) log("onChangeFolderComplete() could not find session: " + session); + return; + } + + // Send callback + try { + ((IBluetoothFtpCallback)dbItem.mCallback).onChangeFolderComplete(folder, isError); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + + /** + * Report of create folder completion + * + * @param session D-Bus object name representing session + * @param folder Name of folder to create + * @param isError true if error during createFolder() execution, false otherwise + * + * @see createFolder() + */ + public synchronized void onCreateFolderComplete(String session, String folder, + boolean isError) { + BluetoothObexDatabase.SessionDbItem dbItem = mSessionDb.getBySession(session); + + if (dbItem == null) { + if (DBG) log("onCreateFolderComplete() could not find session: " + session); + return; + } + + // Send callback + try { + ((IBluetoothFtpCallback)dbItem.mCallback).onCreateFolderComplete(folder, isError); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + + /** + * Report of get file completion + * + * @param session D-Bus object name representing session + * @param localFilename Filename to save retrieved file in + * @param remoteFilename Name of file on remote device to get + * @param isError true if error during getFile() execution, false otherwise + * + * @see getFile() + */ + public synchronized void onGetFileComplete(String session, String localFilename, + String remoteFilename, boolean isError) { + if (isError) { + if (DBG) { + log("onGetFileComplete() reported an error for local file: " + + localFilename + ". Removing pending transfer."); + } + mSessionDb.deleteByFilename(localFilename); + } + + BluetoothObexDatabase.SessionDbItem dbItem = mSessionDb.getBySession(session); + if (dbItem == null) { + if (DBG) log("onGetFileComplete() could not find session: " + session); + return; + } + + // Send callback + try { + ((IBluetoothFtpCallback)dbItem.mCallback).onGetFileComplete(localFilename, + remoteFilename, isError); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + + /** + * Report of put file completion + * + * @param session D-Bus object name representing session + * @param localFilename Filename to save retrieved file in + * @param remoteFilename Name of file on remote device to get + * @param isError true if error during putFile() execution, false otherwise + * + * @see putFile() + */ + public synchronized void onPutFileComplete(String session, String localFilename, + String remoteFilename, boolean isError) { + if (isError) { + if (DBG) { + log("onPutFileComplete() reported an error for local file: " + + localFilename + ". Removing pending transfer."); + } + mSessionDb.deleteByFilename(localFilename); + } + + BluetoothObexDatabase.SessionDbItem dbItem = mSessionDb.getBySession(session); + if (dbItem == null) { + if (DBG) log("onPutFileComplete() could not find session: " + session); + return; + } + + // Send callback + try { + ((IBluetoothFtpCallback)dbItem.mCallback).onPutFileComplete(localFilename, + remoteFilename, isError); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + + /** + * Report of delete completion + * + * @param session D-Bus object name representing session + * @param name Name of file/folder to delete + * @param isError true if error during delete() execution, false otherwise + * + * @see delete() + */ + public synchronized void onDeleteComplete(String session, String name, boolean isError) { + BluetoothObexDatabase.SessionDbItem dbItem = mSessionDb.getBySession(session); + + if (dbItem == null) { + if (DBG) log("onDeleteComplete() could not find session: " + session); + return; + } + + // Send callback + try { + ((IBluetoothFtpCallback)dbItem.mCallback).onDeleteComplete(name, isError); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + + /** + * Request for a filename to show the remote party. + * + * @param transfer OBEX transfer name + * + * @return String giving filename to show the remote party. A null string + * indicates that the transfer was rejected/canceled at higher levels. + */ + public synchronized String onObexRequest(String transfer) { + // Use transfer to get properties + TransferProperties tp = obexTransferGetPropertiesNative(transfer); + if (tp == null) { + if (DBG) { + log("onObexRequest() could not retrieve properties for transfer: " + + transfer); + } + return null; + } else { + // FIXME: Work on getting this fixed "upstream"? + // + // The obexd "OBEX client API" defines the filename returned in org.openobex.Transfer + // GetProperties() to be the "complete name of the file being received or sent." In the + // obexd implementation, however, this isn't always the fully qualified filename on the + // the local device: it is implemented as the remote filename in the case of GetFile(). In + // the case of GetFile() it is actually the same thing that obexd "opens"--a counterintuitive + // implementation choice. + // + // We therefore need to check pending filenames (for PutFile() operations) as well as + // pending remote/object names (for GetFile() operations). This could present a problem + // with multiple transfers using identical filenames--no effort is being put towards + // these pathological corner cases. + BluetoothObexDatabase.TransferDbItem dbItem = mSessionDb.getByFilename(tp.mFilename); + if (dbItem == null) dbItem = mSessionDb.getByObjectName(tp.mFilename); + + if (dbItem == null) { + if (DBG) log("onObexRequest() could not find a session using name: " + tp.mFilename); + return null; + } else { + // Update the database to add the transfer reference... + mSessionDb.updateTransferByFilename(dbItem.mFilename, transfer); + + // ...and the object size (refetch to get current record) + dbItem = mSessionDb.getByFilename(dbItem.mFilename); + if (dbItem == null) { + if (DBG) log("onObexRequest() could not match filename: " + dbItem.mFilename); + return null; + } else { + dbItem.mObjectSize = tp.mSize; + } + + // For an incoming file, return the pathname where we want + // the file to go. For an outgoing file, return the name + // to display to remote device. + if (dbItem.mDirection == BluetoothObexDatabase.TransferDirection.TX) { + File file = new File(dbItem.mFilename); + return file.getName(); + } else { + return dbItem.mFilename; + } + } + } + } + public class TransferProperties { + public String mName; // Transferred object name + public int mSize; // Tranferred object size (bytes) + public String mFilename; // Fully-specified object filename + + public TransferProperties(String name, int size, String filename) { + mName = name; + mSize = size; + mFilename = filename; + } + } + protected native TransferProperties obexTransferGetPropertiesNative(String transfer); + + /** + * Report of OBEX transfer progress + * + * @param transfer OBEX transfer name + * @param bytes Number of bytes transferred + */ + public synchronized void onObexProgress(String transfer, int bytes) { + BluetoothObexDatabase.TransferDbItem dbItem = mSessionDb.getByTransfer(transfer); + + if (dbItem == null) { + if (DBG) log("onObexProgress() could not match transfer: " + transfer); + } else { + Intent intent = new Intent(BluetoothObexIntent.PROGRESS_ACTION); + intent.putExtra(BluetoothObexIntent.OBJECT_FILENAME, dbItem.mFilename); + intent.putExtra(BluetoothObexIntent.OBJECT_SIZE, dbItem.mObjectSize); + intent.putExtra(BluetoothObexIntent.BYTES_TRANSFERRED, bytes); + + mContext.sendBroadcast(intent, BLUETOOTH_PERM); + } + } + + /** + * Report of object transfer completion + * + * @param transfer OBEX transfer name + * @param success true if push successful, else false + * @param error if success is false, should hold a user-readable error message + */ + public synchronized void onObexTransferComplete(String transfer, boolean success, + String error) { + BluetoothObexDatabase.TransferDbItem dbItem = mSessionDb.getByTransfer(transfer); + + if (dbItem == null) { + if (DBG) log("onObexTransferComplete() could not match transfer: " + transfer); + } else { + Intent intent = null; + if (dbItem.mDirection == BluetoothObexDatabase.TransferDirection.TX) { + intent = new Intent(BluetoothObexIntent.TX_COMPLETE_ACTION); + intent.putExtra(BluetoothObexIntent.ERROR_MESSAGE, error); + } else { // (dbItem.mDirection == TransferDirection.RX) + intent = new Intent(BluetoothObexIntent.RX_COMPLETE_ACTION); + } + + if (intent != null) { + intent.putExtra(BluetoothObexIntent.OBJECT_FILENAME, dbItem.mFilename); + intent.putExtra(BluetoothObexIntent.PROFILE, BluetoothObexIntent.PROFILE_FTP); + intent.putExtra(BluetoothObexIntent.SUCCESS, success); + mContext.sendBroadcast(intent, BLUETOOTH_PERM); + } + + // Remove transfer database entry + mSessionDb.deleteByFilename(dbItem.mFilename); + } + } + + /** + * Report of OBEX session close (timeout, remotely initiated, etc...) + * + * @param session D-Bus object name representing session + */ + public synchronized void onObexSessionClosed(String session) { + BluetoothObexDatabase.SessionDbItem dbItem = mSessionDb.getBySession(session); + + if (dbItem == null) { + if (DBG) log("onObexSessionClosed() could not find session: " + session); + return; + } + + mSessionDb.deleteByAddress(dbItem.mAddress); + + // Send callback + try { + ((IBluetoothFtpCallback)dbItem.mCallback).onObexSessionClosed(); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + + /** + * listFolder() completion callback. + * + * @param session OBEX session name + * @param result An array of ObjectProperties + * @param isError true if error, else false + * + * @see listFolder() + */ + public synchronized void onListFolderComplete(String session, ObjectProperties[] result, + boolean isError) { + BluetoothObexDatabase.SessionDbItem dbItem = mSessionDb.getBySession(session); + + if (dbItem == null) { + if (DBG) log("onListFolderComplete() could not find session: " + session); + return; + } + + // create the map generation here + List resultListMap = null; + if (!isError) { + resultListMap = (List)(new ArrayList>()); + if (result != null) { + for (int i=0;i hm = new HashMap(); + hm.put("Name", result[i].mName); + hm.put("Type", result[i].mType); + hm.put("Size", result[i].mSize); + hm.put("Permission", result[i].mPermission); + hm.put("Modified", result[i].mModified); + hm.put("Accessed", result[i].mAccessed); + hm.put("Created", result[i].mCreated); + resultListMap.add(hm); + } + } + } + + // Send callback + try { + ((IBluetoothFtpCallback)dbItem.mCallback).onListFolderComplete(resultListMap, + isError); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + public class ObjectProperties { + public String mName; // object name + public String mType; // object type: 'folder' or 'file' + public int mSize; // object size (bytes), or number of items in folder + public String mPermission; // object permissions (group, owner, other) + public int mModified; // last object change timestamp + public int mAccessed; // last object access timestamp + public int mCreated; // object created timestamp + + public ObjectProperties(String name, String type, + int size, String permission, + int modified, int accessed, + int created) { + mName = name; + mType = type; + mSize = size; + mPermission = permission; + mModified = modified; + mAccessed = accessed; + mCreated = created; + } + } + + /* TODO: Update with any new BM3-based obexd OBEX authorization (and other) enhancements. */ +} diff --git a/core/java/android/server/BluetoothObexDatabase.java b/core/java/android/server/BluetoothObexDatabase.java new file mode 100755 index 0000000..8c35109 --- /dev/null +++ b/core/java/android/server/BluetoothObexDatabase.java @@ -0,0 +1,421 @@ +/* + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Code Aurora nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package android.server; + +import android.util.Log; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; + +/** + * @hide + */ + +/** + * Local transfer "database" to provide filename<->transfer mappings + * along with transfer-specific data + */ +public class BluetoothObexDatabase { + public static enum TransferDirection {TX, RX}; + + private static final String TAG = "BluetoothObexDatabase"; + private static final boolean DBG = false; + + private HashMap mFilenameIdx; + private HashMap mObjectNameIdx; + private HashMap mTransferIdx; + private HashMap mSessionIdx; + private HashMap mAddressIdx; + + public class SessionDbItem implements Cloneable { + /** Callback for async complete */ + public Object mCallback; + /** Session object name associated with the transfer. */ + protected String mSession; + /** Destination BT address associated with the transfer. */ + protected String mAddress; + /** List of ongoing transfers associated with the session */ + private List mTransfers; + + /** + * Create a new database item + * + * @param address Address associated with this transfer + * @param session Session object name associated with this transfer + * @param callback Callback object to use for asynchronous calls. + */ + public SessionDbItem(String address, String session, Object callback) { + mAddress = address; + mSession = session; + // TODO: throw if both address and session are null? + mCallback = callback; + mTransfers = new LinkedList(); + } + + /** Copy the SessionDbItem */ + @Override + public Object clone() { + try { + // TODO: verify this does the right thing. + // Use the default object clone implementation (shallow copy) + return super.clone(); + } catch (CloneNotSupportedException e) { + Log.e(TAG, "SessionDbItem clone() exception!", e); + return null; + } + } + + /** + * Getters for unit testing purposes + */ + public final String getAddress() { + return mAddress; + } + + public final String getSession() { + return mSession; + } + } + + public class TransferDbItem implements Cloneable { + public int mObjectSize; + public TransferDirection mDirection; + public boolean mIsServer; + public int mNativeData; + protected SessionDbItem mSession; + + /** Object name associated with the transfer (remote device filename) */ + protected String mObjectName; + /** Filename associated with the transfer. */ + protected String mFilename; + /** Transfer object name associated with the transfer. */ + protected String mTransfer; + + /** + * Create a new database item + * + * @param filename Filename associated with this transfer + * @param objectName Object name associated with this transfer (remote device filename) + * @param transfer Transfer object name associated with this transfer + * @param session Session to associate with this transfer [for FTP] + */ + public TransferDbItem(String filename, String objectName, String transfer, SessionDbItem session) { + mFilename = filename; + mObjectName = objectName; + mTransfer = transfer; + // TODO: throw if both filename and transfer are null? + mObjectSize = 0; + mDirection = TransferDirection.TX; + mIsServer = false; + mNativeData = 0; + mSession = session; + } + + /** + * Copy the TransferDbItem + */ + @Override + public Object clone() { + try { + // Use the default object clone implementation (shallow copy) + return super.clone(); + } catch (CloneNotSupportedException e) { + Log.e(TAG, "TransferDbItem clone() exception!", e); + return null; + } + } + + /** + * Getters for unit testing purposes + */ + public final String getFilename() { + return mFilename; + } + + public final String getTransfer() { + return mTransfer; + } + + public final SessionDbItem getSession() { + return mSession; + } + } + + + /** + * Create an empty transfer database + */ + public BluetoothObexDatabase() { + mFilenameIdx = new HashMap(); + mObjectNameIdx = new HashMap(); + mTransferIdx = new HashMap(); + mSessionIdx = new HashMap(); + mAddressIdx = new HashMap(); + } + + /** + * Insert a new SessionDbItem into the database keyed with a session object name + * and/or a bluetooth address + * + * @param item SessionDbItem to insert + */ + public void insert(SessionDbItem item) { + if (item.mSession != null) { + mSessionIdx.put(item.mSession, item); + } + + if (item.mAddress != null) { + mAddressIdx.put(item.mAddress, item); + } + + // Update references to item from transfers + ListIterator i = item.mTransfers.listIterator(); + while (i.hasNext()) { + i.next().mSession = item; + } + + // Not cataloging any associated transfers here--association of sessions + // and transfers happens when inserting TransferDbItem + } + + /** + * Get a reference to the SessionDbItem keyed/associated with an address. + * + * @param address Address to lookup item with + * + * @return item, if found, otherwise null + */ + public SessionDbItem getByAddress(String address) { + return mAddressIdx.get(address); + } + + /** + * Get a reference to the SessionDbItem keyed/associated with a session object name. + * + * @param session Session object name to lookup item with + * + * @return item, if found, otherwise null + */ + public SessionDbItem getBySession(String session) { + return mSessionIdx.get(session); + } + + /** + * SessionDbItem delete helper function. + */ + private void deleteItem(SessionDbItem dbItem) { + if (dbItem != null) { + mSessionIdx.remove(dbItem.mSession); + mAddressIdx.remove(dbItem.mAddress); + + // Remove references to dbItem from transfers + ListIterator i = dbItem.mTransfers.listIterator(); + while (i.hasNext()) { + i.next().mSession = null; + } + + // Not removing any associated transfers + } + } + + /** + * Delete the SessionDbItem entry associated with an address + * + * @param address Address to lookup item with + */ + public void deleteByAddress(String address) { + deleteItem(getByAddress(address)); + } + + /** + * Update the SessionDbItem entry associated with an address to associate with a new + * session. + * + * @param curAddress Address to lookup item with + * @param newSession New session object name to associate with the session + */ + public void updateSessionByAddress(String curAddress, String newSession) { + SessionDbItem dbItem = getByAddress(curAddress); + + SessionDbItem newDbItem = (SessionDbItem)dbItem.clone(); + // TODO: figure out a relationship here that doesn't do this + newDbItem.mSession = newSession; + + deleteByAddress(curAddress); + insert(newDbItem); + } + + /** + * Insert a TransferDbItem into the database keyed with a filename and/or a transfer + * object name + * + * @param item TransferDbItem to insert + */ + public void insert(TransferDbItem item) { + if (item.mFilename != null) { + mFilenameIdx.put(item.mFilename, item); + } + + if (item.mObjectName != null) { + mObjectNameIdx.put(item.mObjectName, item); + } + + if (item.mTransfer != null) { + mTransferIdx.put(item.mTransfer, item); + } + + // If specified, associate transfer the session + if (item.mSession != null) { + item.mSession.mTransfers.add(item); + } + } + + /** + * Get a reference to the TransferDbItem keyed/associated with a filename. + * + * @param filename Filename to lookup item with + * + * @return item, if found, otherwise null + */ + public TransferDbItem getByFilename(String filename) { + return mFilenameIdx.get(filename); + } + + /** + * Get a reference to the TransferDbItem keyed/associated with an object name. + * + * @param objectName Object name to lookup item with + * + * @return item, if found, otherwise null + */ + public TransferDbItem getByObjectName(String objectName) { + return mObjectNameIdx.get(objectName); + } + + /** + * Get a reference to the TransferDbItem keyed/associated with a transfer object name. + * + * @param transfer Transfer object name to lookup item with + * + * @return item, if found, otherwise null + */ + public TransferDbItem getByTransfer(String transfer) { + return mTransferIdx.get(transfer); + } + + /** + * Get a reference to the TransferDbItem keyed/associated with an address. + * + * @param address Address to lookup item with + * + * @return item, if found, otherwise null + */ + public TransferDbItem getTransferByAddress(String address) { + TransferDbItem ret = null; + SessionDbItem session = mAddressIdx.get(address); + + if ((session != null) && !session.mTransfers.isEmpty()) { + // Currently hardcoded to first linked transfer-- + // this limits the client to starting transfers sequentially + // (one at a time). + // + // TODO: remove this limitation by checking other parameters + ret = session.mTransfers.get(0); + } + + return ret; + } + + + /** + * TransferDbItem delete helper function. + */ + private void deleteItem(TransferDbItem dbItem) { + if (dbItem != null) { + mFilenameIdx.remove(dbItem.mFilename); + mObjectNameIdx.remove(dbItem.mObjectName); + mTransferIdx.remove(dbItem.mTransfer); + + // If session specified, remove link to transfer + if (dbItem.mSession != null) { + SessionDbItem assocSession = mSessionIdx.get(dbItem.mSession); + if (assocSession != null) { + assocSession.mTransfers.remove(dbItem); + } + } + } + } + + /** + * Delete the database entry associated with a filename + * + * @param filename Filename to lookup item with + */ + public void deleteByFilename(String filename) { + deleteItem(getByFilename(filename)); + } + + /** + * Update the database entry associated with a filename to associate with a new + * filename. + * + * @param curFilename Filename to lookup item with + * @param newFilename New filename to associate with the transfer + */ + public void updateFilenameByFilename(String curFilename, String newFilename) { + TransferDbItem dbItem = getByFilename(curFilename); + + TransferDbItem newDbItem = (TransferDbItem)dbItem.clone(); + // TODO: figure out a relationship here that doesn't do this + newDbItem.mFilename = newFilename; + + deleteByFilename(curFilename); + insert(newDbItem); + } + + /** + * Update the database entry associated with a filename to associate with a new + * transfer. + * + * @param curFilename Filename to lookup item with + * @param newTransfer New transfer object name to associate with the transfer + */ + public void updateTransferByFilename(String curFilename, String newTransfer) { + TransferDbItem dbItem = getByFilename(curFilename); + + TransferDbItem newDbItem = (TransferDbItem)dbItem.clone(); + // TODO: figure out a relationship here that doesn't do this + newDbItem.mTransfer = newTransfer; + + deleteByFilename(curFilename); + insert(newDbItem); + } +} diff --git a/core/java/android/server/BluetoothOppService.java b/core/java/android/server/BluetoothOppService.java new file mode 100755 index 0000000..6973ed5 --- /dev/null +++ b/core/java/android/server/BluetoothOppService.java @@ -0,0 +1,447 @@ +/* + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Code Aurora nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package android.server; + +import android.bluetooth.obex.BluetoothObexIntent; +import android.bluetooth.obex.IBluetoothOpp; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import java.io.File; + + +/** + * @hide + */ +public class BluetoothOppService extends IBluetoothOpp.Stub { + public static final String BLUETOOTH_OPP_SERVICE = "bluetooth_opp"; + private static final String TAG = "BluetoothOppService"; + private static final boolean DBG = true; + private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; + private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; + + protected BluetoothObexDatabase mTransferDb; + private final Context mContext; + + public BluetoothOppService(Context context) { + mContext = context; + + if (!initNative()) { + throw new RuntimeException("Could not init BluetoothOppService"); + } + + mTransferDb = new BluetoothObexDatabase(); + } + protected native boolean initNative(); + + /** + * Called when object is garbage-collected: trigger cleanup in JNI + */ + @Override + protected void finalize() throws Throwable { + try { + cleanupNative(); + } finally { + super.finalize(); + } + } + protected native void cleanupNative(); + + /** + * Helper logging function for debug messages + */ + private static void log(String msg) { + Log.e(TAG, msg); + } + + /** Push an object using the OPP Bluetooth profile (async) + * + * @param address Destination BT address. + * @param txFilename Name of file to send + * + * @return false indicates immediate error + * + * @see onSendFilesComplete() + */ + public synchronized boolean pushObject(String address, String txFilename) { + String[] txFilenames = { txFilename }; + + boolean ret = sendFilesNative(address, txFilenames); + if (ret) { + BluetoothObexDatabase.SessionDbItem sessionDbItem = + mTransferDb.new SessionDbItem(address,null,null); + BluetoothObexDatabase.TransferDbItem transferDbItem = + mTransferDb.new TransferDbItem(txFilename,null,null,sessionDbItem); + transferDbItem.mDirection = BluetoothObexDatabase.TransferDirection.TX; + mTransferDb.insert(sessionDbItem); + mTransferDb.insert(transferDbItem); + } + + return ret; + } + protected native boolean sendFilesNative(String address, String[] txFilenames); + + /** + * Pull a business card using the OPP Bluetooth profile (async) + * + * @param address Remote BT address. + * @param rxFilename Filename to store retrieved business card in. + * + * @return false indicates immediate error + * + * @see onPullBusinessCardComplete() + */ + public synchronized boolean pullBusinessCard(String address, String rxFilename) { + boolean ret = pullBusinessCardNative(address, rxFilename); + if (ret) { + BluetoothObexDatabase.SessionDbItem sessionDbItem = + mTransferDb.new SessionDbItem(address,null,null); + BluetoothObexDatabase.TransferDbItem transferDbItem = + mTransferDb.new TransferDbItem(rxFilename,null,null,sessionDbItem); + transferDbItem.mDirection = BluetoothObexDatabase.TransferDirection.RX; + mTransferDb.insert(sessionDbItem); + mTransferDb.insert(transferDbItem); + } + + return ret; + } + protected native boolean pullBusinessCardNative(String address, String rxFilename); + + /** Cancel an ongoing OPP transfer + * + * @param filename Filename of OPP object being transferred + * + * @return false indicates immediate error + */ + public synchronized boolean cancelTransfer(String filename) { + BluetoothObexDatabase.TransferDbItem dbItem = mTransferDb.getByFilename(filename); + + if (dbItem == null) { + if (DBG) log("cancelTransfer() could not find transfer with filename: " + filename); + } else { + mTransferDb.deleteByFilename(filename); + + // It's possible to not have a defined transfer name yet + // (i.e., onObexRequest() hasn't been called). + if (dbItem.mTransfer != null) { + return cancelTransferNative(dbItem.mTransfer, dbItem.mIsServer); + } else { + return true; + } + } + + return false; + } + protected native boolean cancelTransferNative(String transfer, boolean isServer); + + /** + * Determine if a given transfer is active. + * + * @param filename Filename of OPP object being transferred + * + * @return true indicates an ongoing transfer with filename. + * false indicates unknown/completed transfer. + */ + public synchronized boolean isTransferActive(String filename) { + return (mTransferDb.getByFilename(filename) != null); + } + + /** + * Approve/reject an incoming OPP push. This is a response to onObexAuthorize(). + * + * @param proposedFilename Filename identifying transfer from the AUTHORIZE_ACTION + * intent OBJECT_FILENAME extra + * @param accept true to authorize incoming OPP push, false to reject incoming OPP push + * @param newFilename Filename to place received file in. + * null indicates proposedFilename should be used. + * + * @return false indicates immediate error + */ + public synchronized boolean obexAuthorizeComplete(String proposedFilename, + boolean accept, String newFilename) { + BluetoothObexDatabase.TransferDbItem dbItem = + mTransferDb.getByFilename(proposedFilename); + + if (dbItem == null) { + if (DBG) { + log("obexAuthorizeComplete() could not find pending transfer with filename: " + + proposedFilename); + } + return false; + } + + if (newFilename == null) { + // Continue to use proposed filename, per function spec + newFilename = proposedFilename; + } + + if (accept) { + // Update the database to reflect the new filename + mTransferDb.updateFilenameByFilename(proposedFilename, newFilename); + } else { + // Remove the transfer from the transfer database + mTransferDb.deleteByFilename(proposedFilename); + } + + return obexAuthorizeCompleteNative(accept, newFilename, dbItem.mNativeData); + } + protected native boolean obexAuthorizeCompleteNative(boolean accept, + String newFilename, int nativeData); + + /** + * Request to authorize a push to this device (async). + * + * @param transfer OBEX transfer name + * @param address Bluetooth address of remote device + * @param name Name of incoming object + * @param type MIME type of incoming object + * @param length Length (in bytes) of incoming object + * @param nativeData Optional native data that should be passed back + * in obexAuthorizeCompleteNative() + * + * @return false indicates immediate error + * + * @see obexAuthorizeComplete() + */ + public synchronized boolean onObexAuthorize(String transfer, String address, + String name, String type, int length, int nativeData) { + // Add filename/transfer pair to map (cache authorization) + BluetoothObexDatabase.TransferDbItem dbItem = // TODO: make name into a filename? + mTransferDb.new TransferDbItem(name,name,transfer,null); + dbItem.mObjectSize = length; + dbItem.mDirection = BluetoothObexDatabase.TransferDirection.RX; + dbItem.mIsServer = true; + dbItem.mNativeData = nativeData; + mTransferDb.insert(dbItem); + + Intent intent = new Intent(BluetoothObexIntent.AUTHORIZE_ACTION); + intent.putExtra(BluetoothObexIntent.ADDRESS, address); + intent.putExtra(BluetoothObexIntent.OBJECT_FILENAME, name); + intent.putExtra(BluetoothObexIntent.OBJECT_TYPE, type); + intent.putExtra(BluetoothObexIntent.OBJECT_SIZE, length); + mContext.sendBroadcast(intent, BLUETOOTH_PERM); + + return true; + } + + /** + * Request to cancel a pending authorization to this device + * + * @param transfer OBEX transfer name + * + * @return false indicates immediate error + * + * @see onObexAuthorize() + */ + public synchronized boolean onObexAuthorizeCancel(String transfer) { + BluetoothObexDatabase.TransferDbItem dbItem = mTransferDb.getByTransfer(transfer); + + if (dbItem == null) { + if (DBG) log("onObexAuthorizeCancel() could not find pending transfer: " + transfer); + return false; + } + + // Remove the transfer from the transfer database + mTransferDb.deleteByFilename(dbItem.mFilename); + + // Send a transfer complete (failure) intent towards the application + Intent intent = new Intent(BluetoothObexIntent.RX_COMPLETE_ACTION); + intent.putExtra(BluetoothObexIntent.OBJECT_FILENAME, dbItem.mFilename); + intent.putExtra(BluetoothObexIntent.PROFILE, BluetoothObexIntent.PROFILE_OPP); + intent.putExtra(BluetoothObexIntent.SUCCESS, false); + mContext.sendBroadcast(intent, BLUETOOTH_PERM); + + return true; + } + + /** + * Request, in response to a push, for a filename to show the remote party. + * + * @param transfer OBEX transfer name + * + * @return String giving filename to show the remote party. A null string + * indicates that the transfer was rejected/canceled at higher levels. + */ + public synchronized String onObexRequest(String transfer) { + // Use transfer to get properties + TransferProperties tp = obexTransferGetPropertiesNative(transfer); + if (tp == null) { + if (DBG) log("onObexRequest() could not retrieve properties for transfer: " + + transfer); + return null; + } else { + BluetoothObexDatabase.TransferDbItem dbItem = + mTransferDb.getByFilename(tp.mFilename); + + if (dbItem == null) { + if (DBG) log("onObexRequest() could not find a transfer using filename: " + tp.mFilename); + return null; + } else { + // Update the database to add the transfer reference... + mTransferDb.updateTransferByFilename(tp.mFilename, transfer); + + // ...and the object size (refetch to get current record) + dbItem = mTransferDb.getByFilename(tp.mFilename); + if (dbItem == null) { + if (DBG) log("onObexRequest() could not match filename: " + tp.mFilename); + return null; + } else { + dbItem.mObjectSize = tp.mSize; + } + + File file = new File(tp.mFilename); + return file.getName(); + } + } + } + + public class TransferProperties { + public String mName; // Transferred object name + public int mSize; // Tranferred object size (bytes) + public String mFilename; // Fully-specified object filename + + public TransferProperties(String name, int size, String filename) { + mName = name; + mSize = size; + mFilename = filename; + } + } + protected native TransferProperties obexTransferGetPropertiesNative(String transfer); + + /** + * Report of OBEX transfer progress + * + * @param transfer OBEX transfer name + * @param bytes Number of bytes transferred + */ + public synchronized void onObexProgress(String transfer, int bytes) { + BluetoothObexDatabase.TransferDbItem dbItem = mTransferDb.getByTransfer(transfer); + + if (dbItem == null) { + if (DBG) log("onObexProgress() could not match transfer: " + transfer); + } else { + Intent intent = new Intent(BluetoothObexIntent.PROGRESS_ACTION); + intent.putExtra(BluetoothObexIntent.OBJECT_FILENAME, dbItem.mFilename); + intent.putExtra(BluetoothObexIntent.OBJECT_SIZE, dbItem.mObjectSize); + intent.putExtra(BluetoothObexIntent.BYTES_TRANSFERRED, bytes); + + mContext.sendBroadcast(intent, BLUETOOTH_PERM); + } + } + + /** + * Report of sendFilesNative() completion (async) + * + * @param address BT addr of destination device + * @param isError true if error condition, else success + */ + public synchronized void onSendFilesComplete(String address, boolean isError) { + BluetoothObexDatabase.TransferDbItem dbItem = mTransferDb.getTransferByAddress(address); + if (dbItem == null) { + if (DBG) log("onSendFilesComplete() could not find a pending transfer with address: " + + address); + } else { + // Remove transfer from pending operation list. + mTransferDb.deleteByAddress(address); + + // Notify application layer of connection status + Intent intent = new Intent(BluetoothObexIntent.CONNECT_STATUS_ACTION); + intent.putExtra(BluetoothObexIntent.SUCCESS, isError ? false : true); + + if (isError == true) { + // Remove transfer from transfer database + mTransferDb.deleteByFilename(dbItem.mFilename); + } + + mContext.sendBroadcast(intent, BLUETOOTH_PERM); + } + } + + /** + * Report of pullBusinessCardNative() completion (async) + * + * @param address BT addr of destination device + * @param isError true if error condition, else success + */ + public synchronized void onPullBusinessCardComplete(String address, boolean isError) { + BluetoothObexDatabase.TransferDbItem dbItem = mTransferDb.getTransferByAddress(address); + if (dbItem == null) { + if (DBG) { + log("onPullBusinessCardComplete() could not find a pending transfer with address: " + + address); + } + } else { + + // Remove transfer from transfer database + mTransferDb.deleteByFilename(dbItem.mFilename); + + // Notify application layer of success/failure + Intent intent = new Intent(BluetoothObexIntent.RX_COMPLETE_ACTION); + intent.putExtra(BluetoothObexIntent.OBJECT_FILENAME, dbItem.mFilename); + intent.putExtra(BluetoothObexIntent.PROFILE, BluetoothObexIntent.PROFILE_OPP); + intent.putExtra(BluetoothObexIntent.SUCCESS, !isError); + mContext.sendBroadcast(intent, BLUETOOTH_PERM); + } + } + + /** + * Report of object transfer completion (client, or server) + * + * @param transfer OBEX transfer name + * @param success true if transfer successful, else false + * @param error if success is false, should hold a user-readable error message. + */ + public synchronized void onObexTransferComplete(String transfer, + boolean success, String error) { + BluetoothObexDatabase.TransferDbItem dbItem = mTransferDb.getByTransfer(transfer); + + if (dbItem == null) { + if (DBG) log("onObexTransferComplete() could not match transfer: " + transfer); + } else { + Intent intent = null; + if (dbItem.mDirection == BluetoothObexDatabase.TransferDirection.TX) { + intent = new Intent(BluetoothObexIntent.TX_COMPLETE_ACTION); + intent.putExtra(BluetoothObexIntent.ERROR_MESSAGE, error); + } else { // (dbItem.mDirection == TransferDirection.RX) + intent = new Intent(BluetoothObexIntent.RX_COMPLETE_ACTION); + } + + if (intent != null) { + intent.putExtra(BluetoothObexIntent.OBJECT_FILENAME, dbItem.mFilename); + intent.putExtra(BluetoothObexIntent.PROFILE, BluetoothObexIntent.PROFILE_OPP); + intent.putExtra(BluetoothObexIntent.SUCCESS, success); + mContext.sendBroadcast(intent, BLUETOOTH_PERM); + } + + // Remove transfer database entry + mTransferDb.deleteByFilename(dbItem.mFilename); + } + } +} diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 888cb11..425412f 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -113,6 +113,8 @@ LOCAL_SRC_FILES:= \ android_server_BluetoothDeviceService.cpp \ android_server_BluetoothEventLoop.cpp \ android_server_BluetoothA2dpService.cpp \ + android_server_BluetoothFtpService.cpp \ + android_server_BluetoothOppService.cpp \ android_message_digest_sha1.cpp \ android_ddm_DdmHandleNativeHeap.cpp \ android_location_GpsLocationProvider.cpp \ @@ -143,7 +145,8 @@ LOCAL_C_INCLUDES += \ external/tremor/Tremor \ external/icu4c/i18n \ external/icu4c/common \ - frameworks/opt/emoji + frameworks/opt/emoji \ + kernel/include LOCAL_SHARED_LIBRARIES := \ libexpat \ @@ -172,8 +175,11 @@ LOCAL_SHARED_LIBRARIES := \ ifeq ($(BOARD_HAVE_BLUETOOTH),true) LOCAL_C_INCLUDES += \ - external/dbus \ - system/bluetooth/bluez-clean-headers + external/dbus \ + external/bluez/libs/include \ + system/bluetooth/bluedroid/include \ + system/bluetooth/bluez-clean-headers + LOCAL_CFLAGS += -DHAVE_BLUETOOTH LOCAL_SHARED_LIBRARIES += libbluedroid libdbus endif diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index c815301..b094c56 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -1,6 +1,7 @@ /* //device/libs/android_runtime/AndroidRuntime.cpp ** ** Copyright 2006, The Android Open Source Project +** Copyright (c) 2009, Code Aurora Forum, Inc. All rights reserved. ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. @@ -151,6 +152,8 @@ extern int register_android_bluetooth_ScoSocket(JNIEnv *env); extern int register_android_server_BluetoothDeviceService(JNIEnv* env); extern int register_android_server_BluetoothEventLoop(JNIEnv *env); extern int register_android_server_BluetoothA2dpService(JNIEnv* env); +extern int register_android_server_BluetoothFtpService(JNIEnv* env); +extern int register_android_server_BluetoothOppService(JNIEnv* env); extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env); extern int register_com_android_internal_os_ZygoteInit(JNIEnv* env); extern int register_android_util_Base64(JNIEnv* env); @@ -1125,6 +1128,8 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_server_BluetoothDeviceService), REG_JNI(register_android_server_BluetoothEventLoop), REG_JNI(register_android_server_BluetoothA2dpService), + REG_JNI(register_android_server_BluetoothFtpService), + REG_JNI(register_android_server_BluetoothOppService), REG_JNI(register_android_message_digest_sha1), REG_JNI(register_android_ddm_DdmHandleNativeHeap), REG_JNI(register_android_util_Base64), diff --git a/core/jni/android_bluetooth_Database.cpp b/core/jni/android_bluetooth_Database.cpp index 73b8efd..70c73a8 100644 --- a/core/jni/android_bluetooth_Database.cpp +++ b/core/jni/android_bluetooth_Database.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2007 The Android Open Source Project + * Copyright (c) 2009, Code Aurora Forum, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -70,6 +71,7 @@ static jint addServiceRecordNative(JNIEnv *env, jobject object, jbyte* c_record = env->GetByteArrayElements(record, NULL); DBusMessage *reply = dbus_func_args(env, conn, + BLUEZ_DBUS_BASE_SVC, BLUEZ_DBUS_BASE_PATH, DBUS_CLASS_NAME, "AddServiceRecord", @@ -92,6 +94,7 @@ static jint addServiceRecordFromXmlNative(JNIEnv *env, jobject object, const char *c_record = env->GetStringUTFChars(record, NULL); DBusMessage *reply = dbus_func_args(env, conn, + BLUEZ_DBUS_BASE_SVC, BLUEZ_DBUS_BASE_PATH, DBUS_CLASS_NAME, "AddServiceRecordFromXML", @@ -113,6 +116,7 @@ static void updateServiceRecordNative(JNIEnv *env, jobject object, jbyte* c_record = env->GetByteArrayElements(record, NULL); DBusMessage *reply = dbus_func_args(env, conn, + BLUEZ_DBUS_BASE_SVC, BLUEZ_DBUS_BASE_PATH, DBUS_CLASS_NAME, "UpdateServiceRecord", @@ -135,6 +139,7 @@ static void updateServiceRecordFromXmlNative(JNIEnv *env, jobject object, const char *c_record = env->GetStringUTFChars(record, NULL); DBusMessage *reply = dbus_func_args(env, conn, + BLUEZ_DBUS_BASE_SVC, BLUEZ_DBUS_BASE_PATH, DBUS_CLASS_NAME, "UpdateServiceRecordFromXML", @@ -154,6 +159,7 @@ static void removeServiceRecordNative(JNIEnv *env, jobject object, if (conn != NULL) { DBusMessage *reply = dbus_func_args(env, conn, + BLUEZ_DBUS_BASE_SVC, BLUEZ_DBUS_BASE_PATH, DBUS_CLASS_NAME, "RemoveServiceRecord", diff --git a/core/jni/android_bluetooth_common.cpp b/core/jni/android_bluetooth_common.cpp index 0b8a604..73f6e43 100644 --- a/core/jni/android_bluetooth_common.cpp +++ b/core/jni/android_bluetooth_common.cpp @@ -1,5 +1,6 @@ /* ** Copyright 2006, The Android Open Source Project +** Copyright (c) 2009, Code Aurora Forum, Inc. All rights reserved. ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. @@ -45,13 +46,6 @@ jfieldID get_field(JNIEnv *env, jclass clazz, const char *member, return field; } -typedef struct { - void (*user_cb)(DBusMessage *, void *, void *); - void *user; - void *nat; - JNIEnv *env; -} dbus_async_call_t; - void dbus_func_args_async_callback(DBusPendingCall *call, void *data) { dbus_async_call_t *req = (dbus_async_call_t *)data; @@ -83,6 +77,7 @@ static dbus_bool_t dbus_func_args_async_valist(JNIEnv *env, void*), void *user, void *nat, + const char *dest, const char *path, const char *ifc, const char *func, @@ -94,7 +89,7 @@ static dbus_bool_t dbus_func_args_async_valist(JNIEnv *env, dbus_bool_t reply = FALSE; /* Compose the command */ - msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC, path, ifc, func); + msg = dbus_message_new_method_call(dest, path, ifc, func); if (msg == NULL) { LOGE("Could not allocate D-Bus message object!"); @@ -126,6 +121,8 @@ static dbus_bool_t dbus_func_args_async_valist(JNIEnv *env, dbus_func_args_async_callback, pending, NULL); + } else { + free(pending); } } @@ -140,6 +137,7 @@ dbus_bool_t dbus_func_args_async(JNIEnv *env, void (*reply)(DBusMessage *, void *, void*), void *user, void *nat, + const char *dest, const char *path, const char *ifc, const char *func, @@ -151,7 +149,7 @@ dbus_bool_t dbus_func_args_async(JNIEnv *env, ret = dbus_func_args_async_valist(env, conn, timeout_ms, reply, user, nat, - path, ifc, func, + dest, path, ifc, func, first_arg_type, lst); va_end(lst); return ret; @@ -167,6 +165,7 @@ DBusMessage * dbus_func_args_timeout_valist(JNIEnv *env, DBusConnection *conn, int timeout_ms, DBusError *err, + const char *dest, const char *path, const char *ifc, const char *func, @@ -183,7 +182,7 @@ DBusMessage * dbus_func_args_timeout_valist(JNIEnv *env, } /* Compose the command */ - msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC, path, ifc, func); + msg = dbus_message_new_method_call(dest, path, ifc, func); if (msg == NULL) { LOGE("Could not allocate D-Bus message object!"); @@ -213,6 +212,7 @@ done: DBusMessage * dbus_func_args_timeout(JNIEnv *env, DBusConnection *conn, int timeout_ms, + const char *dest, const char *path, const char *ifc, const char *func, @@ -222,7 +222,7 @@ DBusMessage * dbus_func_args_timeout(JNIEnv *env, va_list lst; va_start(lst, first_arg_type); ret = dbus_func_args_timeout_valist(env, conn, timeout_ms, NULL, - path, ifc, func, + dest, path, ifc, func, first_arg_type, lst); va_end(lst); return ret; @@ -230,6 +230,7 @@ DBusMessage * dbus_func_args_timeout(JNIEnv *env, DBusMessage * dbus_func_args(JNIEnv *env, DBusConnection *conn, + const char *dest, const char *path, const char *ifc, const char *func, @@ -239,7 +240,7 @@ DBusMessage * dbus_func_args(JNIEnv *env, va_list lst; va_start(lst, first_arg_type); ret = dbus_func_args_timeout_valist(env, conn, -1, NULL, - path, ifc, func, + dest, path, ifc, func, first_arg_type, lst); va_end(lst); return ret; @@ -248,6 +249,7 @@ DBusMessage * dbus_func_args(JNIEnv *env, DBusMessage * dbus_func_args_error(JNIEnv *env, DBusConnection *conn, DBusError *err, + const char *dest, const char *path, const char *ifc, const char *func, @@ -257,7 +259,7 @@ DBusMessage * dbus_func_args_error(JNIEnv *env, va_list lst; va_start(lst, first_arg_type); ret = dbus_func_args_timeout_valist(env, conn, -1, err, - path, ifc, func, + dest, path, ifc, func, first_arg_type, lst); va_end(lst); return ret; @@ -390,6 +392,63 @@ jbyteArray dbus_returns_array_of_bytes(JNIEnv *env, DBusMessage *reply) { return byteArray; } +bool dbus_append_ss_dict_entry(DBusMessageIter *array_iter, const char *key, + const char *value) { + LOGI(__FUNCTION__); + + DBusMessageIter dict_entry, variant; + bool result = FALSE; + + if (!dbus_message_iter_open_container(array_iter, DBUS_TYPE_DICT_ENTRY, + NULL, &dict_entry)) { + LOGE("Could not open D-Bus container!"); + return FALSE; + } + + LOGI("Opened dictionary container"); + + if (!dbus_message_iter_append_basic(&dict_entry, DBUS_TYPE_STRING, + &key)) { + LOGE("Could not append key in D-Bus DICT_ENTRY!"); + goto close_dict_entry; + } + + LOGI("Appended key"); + + if (!dbus_message_iter_open_container(&dict_entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, &variant)) { + LOGE("Could not open D-Bus container!"); + goto close_dict_entry; + } + + LOGI("Opened variant container"); + + if (!dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, + &value)) { + LOGE("Could not append value in D-Bus DICT_ENTRY!"); + } + + LOGI("Appended variant value"); + +close_variant: + if (!dbus_message_iter_close_container(&dict_entry, &variant)) { + LOGE("Could not close D-Bus container!"); + return FALSE; + } + + LOGI("Closed variant container"); + +close_dict_entry: + if (!dbus_message_iter_close_container(array_iter, &dict_entry)) { + LOGE("Could not close D-Bus container!"); + return FALSE; + } + + LOGI("Closed dictionary container"); + + return TRUE; +} + void get_bdaddr(const char *str, bdaddr_t *ba) { char *d = ((char *)ba) + 5, *endp; int i; diff --git a/core/jni/android_bluetooth_common.h b/core/jni/android_bluetooth_common.h index 69092dd..7a0ee87 100644 --- a/core/jni/android_bluetooth_common.h +++ b/core/jni/android_bluetooth_common.h @@ -1,5 +1,6 @@ /* ** Copyright 2006, The Android Open Source Project +** Copyright (c) 2009, Code Aurora Forum, Inc. All rights reserved. ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. @@ -17,8 +18,10 @@ #ifndef ANDROID_BLUETOOTH_COMMON_H #define ANDROID_BLUETOOTH_COMMON_H -// Set to 0 to enable verbose bluetooth logging +// Set to 0 to enable verbose, debug, and/or info bluetooth logging #define LOG_NDEBUG 1 +#define LOG_NDDEBUG 1 +#define LOG_NIDEBUG 1 #include "jni.h" #include "utils/Log.h" @@ -36,9 +39,98 @@ namespace android { #ifdef HAVE_BLUETOOTH +#define BLUEZ_DBUS_BASE_SVC "org.bluez" #define BLUEZ_DBUS_BASE_PATH "/org/bluez" #define BLUEZ_DBUS_BASE_IFC "org.bluez" +/* + * OBEXD DBUS API + */ +#define OBEXD_DBUS_BASE_SVC "org.openobex" +#define OBEXD_DBUS_BASE_IFC "org.openobex" +#define OBEXD_DBUS_BASE_ERROR "org.openobex.Error" +#define OBEXD_DBUS_SGNL_RULE(ifc, path) "type='signal',interface='"(ifc)"'" \ + ",path='"(path)"'" + +/* OBEXD Client DBUS API */ +#define OBEXD_DBUS_CLIENT_SVC "org.openobex.client" + +#define OBEXD_DBUS_CLIENT_IFC OBEXD_DBUS_BASE_IFC".Client" +#define OBEXD_DBUS_CLIENT_PATH "/" +#define OBEXD_DBUS_CLIENT_SENDFILES "SendFiles" +#define OBEXD_DBUS_CLIENT_PULLCARD "PullBusinessCard" +#define OBEXD_DBUS_CLIENT_EXCHANGE "ExchangeBusinessCards" +#define OBEXD_DBUS_CLIENT_CREATE "CreateSession" + +#define OBEXD_DBUS_CLIENT_SESSION_IFC OBEXD_DBUS_BASE_IFC".Session" +#define OBEXD_DBUS_CLIENT_SESSION_GETPROPS "GetProperties" +#define OBEXD_DBUS_CLIENT_SESSION_ASSIGN "AssignAgent" +#define OBEXD_DBUS_CLIENT_SESSION_RELEASE "ReleaseAgent" +#define OBEXD_DBUS_CLIENT_SESSION_CLOSE "Close" + +#define OBEXD_DBUS_CLIENT_FTP_IFC OBEXD_DBUS_BASE_IFC".FileTransfer" +#define OBEXD_DBUS_CLIENT_FTP_CHANGE_FOLDER "ChangeFolder" +#define OBEXD_DBUS_CLIENT_FTP_CREATE_FOLDER "CreateFolder" +#define OBEXD_DBUS_CLIENT_FTP_LIST_FOLDER "ListFolder" +#define OBEXD_DBUS_CLIENT_FTP_GET_FILE "GetFile" +#define OBEXD_DBUS_CLIENT_FTP_PUT_FILE "PutFile" +#define OBEXD_DBUS_CLIENT_FTP_COPY_FILE "CopyFile" +#define OBEXD_DBUS_CLIENT_FTP_MOVE_FILE "MoveFile" +#define OBEXD_DBUS_CLIENT_FTP_DELETE "Delete" + +#define OBEXD_DBUS_CLIENT_TRANS_IFC OBEXD_DBUS_BASE_IFC".Transfer" +#define OBEXD_DBUS_CLIENT_TRANS_GETPROPS "GetProperties" +#define OBEXD_DBUS_CLIENT_TRANS_CANCEL "Cancel" + +#define OBEXD_DBUS_CLIENT_AGENT_IFC OBEXD_DBUS_BASE_IFC".Agent" +#define OBEXD_DBUS_CLIENT_AGENT_RELEASE "Release" +#define OBEXD_DBUS_CLIENT_AGENT_REQUEST "Request" +#define OBEXD_DBUS_CLIENT_AGENT_PROGRESS "Progress" +#define OBEXD_DBUS_CLIENT_AGENT_COMPLETE "Complete" +#define OBEXD_DBUS_CLIENT_AGENT_ERROR "Error" + +/* OBEXD Server DBUS API */ +#define OBEXD_DBUS_SRV_SVC "org.openobex" + +#define OBEXD_DBUS_SRV_MGR_IFC OBEXD_DBUS_BASE_IFC".Manager" +#define OBEXD_DBUS_SRV_MGR_PATH "/" +#define OBEXD_DBUS_SRV_MGR_REG_AGENT "RegisterAgent" +#define OBEXD_DBUS_SRV_MGR_UNREG_AGENT "UnregisterAgent" + +#define OBEXD_DBUS_SRV_MGR_SGNL_RULE \ + OBEXD_DBUS_SGNL_RULE(OBEXD_DBUS_SRV_MGR_IFC, OBEXD_DBUS_SRV_MGR_PATH) +#define OBEXD_DBUS_SRV_MGR_SGNL_FTP_SESS_CREATED "SessionCreated" +#define OBEXD_DBUS_SRV_MGR_SGNL_FTP_SESS_REMOVED "SessionRemoved" +#define OBEXD_DBUS_SRV_MGR_SGNL_FTP_TRANS_STARTED "FTPTransferStarted" +#define OBEXD_DBUS_SRV_MGR_SGNL_FTP_TRANS_COMPLETED "FTPTransferCompleted" +#define OBEXD_DBUS_SRV_MGR_SGNL_OPP_SESS_CREATED "OPPSessionCreated" +#define OBEXD_DBUS_SRV_MGR_SGNL_OPP_SESS_REMOVED "OPPSessionRemoved" +#define OBEXD_DBUS_SRV_MGR_SGNL_OPP_TRANS_STARTED "TransferStarted" +#define OBEXD_DBUS_SRV_MGR_SGNL_OPP_TRANS_COMPLETED "TransferCompleted" + +#define OBEXD_DBUS_SRV_TRANS_IFC OBEXD_DBUS_BASE_IFC".Transfer" +#define OBEXD_DBUS_SRV_TRANS_CANCEL "Cancel" + +#define OBEXD_DBUS_SRV_TRANS_SGNL_PROGRESS "Progress" + +#define OBEXD_DBUS_SRV_SESS_IFC OBEXD_DBUS_BASE_IFC".Session" +#define OBEXD_DBUS_SRV_SESS_GETPROPS "GetProperties" + +#define OBEXD_DBUS_SRV_AGENT_IFC OBEXD_DBUS_BASE_IFC".Agent" +#define OBEXD_DBUS_SRV_AGENT_AUTHORIZE "Authorize" +#define OBEXD_DBUS_SRV_AGENT_CANCEL "Cancel" + +/* OBEXD Errors*/ +#define OBEXD_DBUS_ERROR_CANCELLED OBEXD_DBUS_BASE_ERROR".Cancelled" +#define OBEXD_DBUS_ERROR_AGENT_EXIST OBEXD_DBUS_BASE_ERROR".AlreadyExists" +#define OBEXD_DBUS_ERROR_REJECTED OBEXD_DBUS_BASE_ERROR".Rejected" + +#define ANDROID_DBUS_AGENT_BASE_PATH "/android/bluetooth" +#define ANDROID_PASSKEY_AGENT_PATH ANDROID_DBUS_AGENT_BASE_PATH"/Agent" +#define ANDROID_OPPCLIENT_AGENT_PATH ANDROID_DBUS_AGENT_BASE_PATH"/OPPClient" +#define ANDROID_OPPSRV_AGENT_PATH ANDROID_DBUS_AGENT_BASE_PATH"/OPPServer" +#define ANDROID_FTPCLIENT_AGENT_PATH ANDROID_DBUS_AGENT_BASE_PATH"/FTPClient" + // It would be nicer to retrieve this from bluez using GetDefaultAdapter, // but this is only possible when the adapter is up (and hcid is running). // It is much easier just to hardcode bluetooth adapter to hci0 @@ -50,6 +142,13 @@ namespace android { // size of the dbus event loops pollfd structure, hopefully never to be grown #define DEFAULT_INITIAL_POLLFD_COUNT 8 +typedef struct { + void (*user_cb)(DBusMessage *, void *, void *); + void *user; + void *nat; + JNIEnv *env; +} dbus_async_call_t; + jfieldID get_field(JNIEnv *env, jclass clazz, const char *member, @@ -89,12 +188,25 @@ struct event_loop_native_data_t { jobject me; }; +dbus_bool_t dbus_func_args_async_valist(JNIEnv *env, + DBusConnection *conn, + int timeout_ms, + void (*reply)(DBusMessage *, void *), + void *user, + const char *dest, + const char *path, + const char *ifc, + const char *func, + int first_arg_type, + va_list args); + dbus_bool_t dbus_func_args_async(JNIEnv *env, DBusConnection *conn, int timeout_ms, void (*reply)(DBusMessage *, void *, void *), void *user, void *nat, + const char *dest, const char *path, const char *ifc, const char *func, @@ -103,6 +215,7 @@ dbus_bool_t dbus_func_args_async(JNIEnv *env, DBusMessage * dbus_func_args(JNIEnv *env, DBusConnection *conn, + const char *dest, const char *path, const char *ifc, const char *func, @@ -112,6 +225,7 @@ DBusMessage * dbus_func_args(JNIEnv *env, DBusMessage * dbus_func_args_error(JNIEnv *env, DBusConnection *conn, DBusError *err, + const char *dest, const char *path, const char *ifc, const char *func, @@ -121,6 +235,7 @@ DBusMessage * dbus_func_args_error(JNIEnv *env, DBusMessage * dbus_func_args_timeout(JNIEnv *env, DBusConnection *conn, int timeout_ms, + const char *dest, const char *path, const char *ifc, const char *func, @@ -131,6 +246,7 @@ DBusMessage * dbus_func_args_timeout_valist(JNIEnv *env, DBusConnection *conn, int timeout_ms, DBusError *err, + const char *dest, const char *path, const char *ifc, const char *func, @@ -144,6 +260,9 @@ jboolean dbus_returns_boolean(JNIEnv *env, DBusMessage *reply); jobjectArray dbus_returns_array_of_strings(JNIEnv *env, DBusMessage *reply); jbyteArray dbus_returns_array_of_bytes(JNIEnv *env, DBusMessage *reply); +bool dbus_append_ss_dict_entry(DBusMessageIter *array_iter, const char *key, + const char *value); + void get_bdaddr(const char *str, bdaddr_t *ba); void get_bdaddr_as_string(const bdaddr_t *ba, char *str); diff --git a/core/jni/android_server_BluetoothA2dpService.cpp b/core/jni/android_server_BluetoothA2dpService.cpp index 91a8e8e..35f463a 100644 --- a/core/jni/android_server_BluetoothA2dpService.cpp +++ b/core/jni/android_server_BluetoothA2dpService.cpp @@ -1,5 +1,6 @@ /* ** Copyright 2008, The Android Open Source Project +** Copyright (c) 2009, Code Aurora Forum, Inc. All rights reserved. ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. @@ -105,7 +106,7 @@ static jobjectArray listHeadsetsNative(JNIEnv *env, jobject object) { LOGV(__FUNCTION__); if (nat) { DBusMessage *reply = - dbus_func_args(env, nat->conn, "/org/bluez/audio", + dbus_func_args(env, nat->conn, "org.bluez", "/org/bluez/audio", "org.bluez.audio.Manager", "ListHeadsets", DBUS_TYPE_INVALID); return reply ? dbus_returns_array_of_strings(env, reply) : NULL; @@ -122,7 +123,7 @@ static jstring createHeadsetNative(JNIEnv *env, jobject object, const char *c_address = env->GetStringUTFChars(address, NULL); LOGV("... address = %s\n", c_address); DBusMessage *reply = - dbus_func_args(env, nat->conn, "/org/bluez/audio", + dbus_func_args(env, nat->conn, "org.bluez", "/org/bluez/audio", "org.bluez.audio.Manager", "CreateHeadset", DBUS_TYPE_STRING, &c_address, DBUS_TYPE_INVALID); @@ -139,7 +140,7 @@ static jstring removeHeadsetNative(JNIEnv *env, jobject object, jstring path) { if (nat) { const char *c_path = env->GetStringUTFChars(path, NULL); DBusMessage *reply = - dbus_func_args(env, nat->conn, "/org/bluez/audio", + dbus_func_args(env, nat->conn, "org.bluez", "/org/bluez/audio", "org.bluez.audio.Manager", "RemoveHeadset", DBUS_TYPE_STRING, &c_path, DBUS_TYPE_INVALID); @@ -156,7 +157,7 @@ static jstring getAddressNative(JNIEnv *env, jobject object, jstring path) { if (nat) { const char *c_path = env->GetStringUTFChars(path, NULL); DBusMessage *reply = - dbus_func_args(env, nat->conn, c_path, + dbus_func_args(env, nat->conn, "org.bluez", c_path, "org.bluez.audio.Device", "GetAddress", DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(path, c_path); @@ -178,7 +179,7 @@ static jboolean connectSinkNative(JNIEnv *env, jobject object, jstring path) { bool ret = dbus_func_args_async(env, nat->conn, -1, onConnectSinkResult, (void *)c_path_copy, nat, - c_path, + "org.bluez", c_path, "org.bluez.audio.Sink", "Connect", DBUS_TYPE_INVALID); @@ -206,7 +207,7 @@ static jboolean disconnectSinkNative(JNIEnv *env, jobject object, bool ret = dbus_func_args_async(env, nat->conn, -1, onDisconnectSinkResult, (void *)c_path_copy, nat, - c_path, + "org.bluez", c_path, "org.bluez.audio.Sink", "Disconnect", DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(path, c_path); @@ -226,7 +227,7 @@ static jboolean isSinkConnectedNative(JNIEnv *env, jobject object, jstring path) if (nat) { const char *c_path = env->GetStringUTFChars(path, NULL); DBusMessage *reply = - dbus_func_args(env, nat->conn, c_path, + dbus_func_args(env, nat->conn, "org.bluez", c_path, "org.bluez.audio.Sink", "IsConnected", DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(path, c_path); @@ -256,9 +257,15 @@ static void onConnectSinkResult(DBusMessage *msg, void *user, void *natData) { /* if (!strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.AuthenticationFailed")) */ LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message); dbus_error_free(&err); + + jstring path = env->NewStringUTF(c_path); + env->CallVoidMethod(nat->me, method_onSinkDisconnected, - env->NewStringUTF(c_path)); + path); + + env->DeleteLocalRef(path); + if (env->ExceptionCheck()) { LOGE("VM Exception occurred in native function %s (%s:%d)", __FUNCTION__, __FILE__, __LINE__); @@ -286,17 +293,23 @@ static void onDisconnectSinkResult(DBusMessage *msg, void *user, void *natData) if (dbus_set_error_from_message(&err, msg)) { /* if (!strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.AuthenticationFailed")) */ LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message); + + jstring path = env->NewStringUTF(c_path); + if (strcmp(err.name, "org.bluez.Error.NotConnected") == 0) { // we were already disconnected, so report disconnect env->CallVoidMethod(nat->me, method_onSinkDisconnected, - env->NewStringUTF(c_path)); + path); } else { // Assume it is still connected env->CallVoidMethod(nat->me, method_onSinkConnected, - env->NewStringUTF(c_path)); + path); } + + env->DeleteLocalRef(path); + dbus_error_free(&err); if (env->ExceptionCheck()) { LOGE("VM Exception occurred in native function %s (%s:%d)", @@ -332,9 +345,14 @@ DBusHandlerResult a2dp_event_filter(DBusMessage *msg, JNIEnv *env) { DBUS_TYPE_STRING, &c_path, DBUS_TYPE_INVALID)) { LOGV("... path = %s", c_path); + + jstring path = env->NewStringUTF(c_path); + env->CallVoidMethod(nat->me, method_onHeadsetCreated, - env->NewStringUTF(c_path)); + path); + + env->DeleteLocalRef(path); } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); result = DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, @@ -345,9 +363,14 @@ DBusHandlerResult a2dp_event_filter(DBusMessage *msg, JNIEnv *env) { DBUS_TYPE_STRING, &c_path, DBUS_TYPE_INVALID)) { LOGV("... path = %s", c_path); + + jstring path = env->NewStringUTF(c_path); + env->CallVoidMethod(nat->me, method_onHeadsetRemoved, - env->NewStringUTF(c_path)); + path); + + env->DeleteLocalRef(path); } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); result = DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, @@ -355,36 +378,56 @@ DBusHandlerResult a2dp_event_filter(DBusMessage *msg, JNIEnv *env) { "Connected")) { const char *c_path = dbus_message_get_path(msg); LOGV("... path = %s", c_path); + + jstring path = env->NewStringUTF(c_path); + env->CallVoidMethod(nat->me, method_onSinkConnected, - env->NewStringUTF(c_path)); + path); + + env->DeleteLocalRef(path); result = DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, "org.bluez.audio.Sink", "Disconnected")) { const char *c_path = dbus_message_get_path(msg); LOGV("... path = %s", c_path); + + jstring path = env->NewStringUTF(c_path); + env->CallVoidMethod(nat->me, method_onSinkDisconnected, - env->NewStringUTF(c_path)); + path); + + env->DeleteLocalRef(path); result = DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, "org.bluez.audio.Sink", "Playing")) { const char *c_path = dbus_message_get_path(msg); LOGV("... path = %s", c_path); + + jstring path = env->NewStringUTF(c_path); + env->CallVoidMethod(nat->me, method_onSinkPlaying, - env->NewStringUTF(c_path)); + path); + + env->DeleteLocalRef(path); result = DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, "org.bluez.audio.Sink", "Stopped")) { const char *c_path = dbus_message_get_path(msg); LOGV("... path = %s", c_path); + + jstring path = env->NewStringUTF(c_path); + env->CallVoidMethod(nat->me, method_onSinkStopped, - env->NewStringUTF(c_path)); + path); + + env->DeleteLocalRef(path); result = DBUS_HANDLER_RESULT_HANDLED; } diff --git a/core/jni/android_server_BluetoothDeviceService.cpp b/core/jni/android_server_BluetoothDeviceService.cpp index 58ae4f6..449d7aa 100644 --- a/core/jni/android_server_BluetoothDeviceService.cpp +++ b/core/jni/android_server_BluetoothDeviceService.cpp @@ -1,5 +1,6 @@ /* ** Copyright 2006, The Android Open Source Project +** Copyright (c) 2009, Code Aurora Forum, Inc. All rights reserved. ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. @@ -42,6 +43,10 @@ #include +#define DBUS_SVC_NAME BLUEZ_DBUS_BASE_SVC +#define DBUS_CLASS_NAME BLUEZ_DBUS_BASE_IFC ".Adapter" +#define LOG_TAG "BluetoothDeviceService.cpp" + namespace android { #define BLUETOOTH_CLASS_ERROR 0xFF000000 @@ -133,9 +138,9 @@ static jstring getNameNative(JNIEnv *env, jobject object){ #ifdef HAVE_BLUETOOTH native_data_t *nat = get_native_data(env, object); if (nat) { - DBusMessage *reply = dbus_func_args(env, nat->conn, nat->adapter, - DBUS_CLASS_NAME, "GetName", - DBUS_TYPE_INVALID); + DBusMessage *reply = dbus_func_args(env, nat->conn, DBUS_SVC_NAME, + nat->adapter, DBUS_CLASS_NAME, + "GetName", DBUS_TYPE_INVALID); return reply ? dbus_returns_string(env, reply) : NULL; } #endif @@ -348,9 +353,9 @@ static jboolean isPeriodicDiscoveryNative(JNIEnv *env, jobject object) { native_data_t *nat = get_native_data(env, object); if (nat) { DBusMessage *reply = - dbus_func_args(env, nat->conn, nat->adapter, - DBUS_CLASS_NAME, "IsPeriodicDiscovery", - DBUS_TYPE_INVALID); + dbus_func_args(env, nat->conn,DBUS_SVC_NAME, + nat->adapter, DBUS_CLASS_NAME, + "IsPeriodicDiscovery", DBUS_TYPE_INVALID); return reply ? dbus_returns_boolean(env, reply) : JNI_FALSE; } #endif @@ -368,8 +373,9 @@ static jboolean setDiscoverableTimeoutNative(JNIEnv *env, jobject object, jint t native_data_t *nat = get_native_data(env, object); if (nat) { DBusMessage *reply = - dbus_func_args(env, nat->conn, nat->adapter, - DBUS_CLASS_NAME, "SetDiscoverableTimeout", + dbus_func_args(env, nat->conn, DBUS_SVC_NAME, + nat->adapter, DBUS_CLASS_NAME, + "SetDiscoverableTimeout", DBUS_TYPE_UINT32, &timeout_s, DBUS_TYPE_INVALID); if (reply != NULL) { @@ -387,9 +393,9 @@ static jint getDiscoverableTimeoutNative(JNIEnv *env, jobject object) { native_data_t *nat = get_native_data(env, object); if (nat) { DBusMessage *reply = - dbus_func_args(env, nat->conn, nat->adapter, - DBUS_CLASS_NAME, "GetDiscoverableTimeout", - DBUS_TYPE_INVALID); + dbus_func_args(env, nat->conn, DBUS_SVC_NAME, + nat->adapter, DBUS_CLASS_NAME, + "GetDiscoverableTimeout", DBUS_TYPE_INVALID); return reply ? dbus_returns_uint32(env, reply) : -1; } #endif @@ -403,8 +409,8 @@ static jboolean isConnectedNative(JNIEnv *env, jobject object, jstring address) if (nat) { const char *c_address = env->GetStringUTFChars(address, NULL); DBusMessage *reply = - dbus_func_args(env, nat->conn, nat->adapter, - DBUS_CLASS_NAME, "IsConnected", + dbus_func_args(env, nat->conn, DBUS_SVC_NAME, + nat->adapter, DBUS_CLASS_NAME, "IsConnected", DBUS_TYPE_STRING, &c_address, DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(address, c_address); @@ -426,8 +432,9 @@ static void disconnectRemoteDeviceNative(JNIEnv *env, jobject object, jstring ad // delay of 2 seconds, after which the actual disconnect takes // place. DBusMessage *reply = - dbus_func_args_timeout(env, nat->conn, 60000, nat->adapter, - DBUS_CLASS_NAME, "DisconnectRemoteDevice", + dbus_func_args_timeout(env, nat->conn, 60000, DBUS_SVC_NAME, + nat->adapter, DBUS_CLASS_NAME, + "DisconnectRemoteDevice", DBUS_TYPE_STRING, &c_address, DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(address, c_address); @@ -442,8 +449,8 @@ static jstring getModeNative(JNIEnv *env, jobject object) { native_data_t *nat = get_native_data(env, object); if (nat) { DBusMessage *reply = - dbus_func_args(env, nat->conn, nat->adapter, - DBUS_CLASS_NAME, "GetMode", + dbus_func_args(env, nat->conn, DBUS_SVC_NAME, + nat->adapter, DBUS_CLASS_NAME, "GetMode", DBUS_TYPE_INVALID); return reply ? dbus_returns_string(env, reply) : NULL; } @@ -458,8 +465,8 @@ static jboolean setModeNative(JNIEnv *env, jobject object, jstring mode) { if (nat) { const char *c_mode = env->GetStringUTFChars(mode, NULL); DBusMessage *reply = - dbus_func_args(env, nat->conn, nat->adapter, - DBUS_CLASS_NAME, "SetMode", + dbus_func_args(env, nat->conn, DBUS_SVC_NAME, + nat->adapter, DBUS_CLASS_NAME, "SetMode", DBUS_TYPE_STRING, &c_mode, DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(mode, c_mode); @@ -489,8 +496,9 @@ static jboolean createBondingNative(JNIEnv *env, jobject object, strlcpy(context_address, c_address, BTADDR_SIZE); // for callback bool ret = dbus_func_args_async(env, nat->conn, (int)timeout_ms, onCreateBondingResult, // callback - context_address, + context_address, // user data eventLoopNat, + DBUS_SVC_NAME, nat->adapter, DBUS_CLASS_NAME, "CreateBonding", DBUS_TYPE_STRING, &c_address, @@ -512,7 +520,7 @@ static jboolean cancelBondingProcessNative(JNIEnv *env, jobject object, const char *c_address = env->GetStringUTFChars(address, NULL); LOGV("... address = %s", c_address); DBusMessage *reply = - dbus_func_args_timeout(env, nat->conn, -1, nat->adapter, + dbus_func_args_timeout(env, nat->conn, -1, DBUS_SVC_NAME, nat->adapter, DBUS_CLASS_NAME, "CancelBondingProcess", DBUS_TYPE_STRING, &c_address, DBUS_TYPE_INVALID); @@ -537,7 +545,7 @@ static jboolean removeBondingNative(JNIEnv *env, jobject object, jstring address DBusError err; dbus_error_init(&err); DBusMessage *reply = - dbus_func_args_error(env, nat->conn, &err, nat->adapter, + dbus_func_args_error(env, nat->conn, &err, DBUS_SVC_NAME, nat->adapter, DBUS_CLASS_NAME, "RemoveBonding", DBUS_TYPE_STRING, &c_address, DBUS_TYPE_INVALID); @@ -569,8 +577,8 @@ static jobjectArray listBondingsNative(JNIEnv *env, jobject object) { native_data_t *nat = get_native_data(env, object); if (nat) { DBusMessage *reply = - dbus_func_args(env, nat->conn, nat->adapter, - DBUS_CLASS_NAME, "ListBondings", + dbus_func_args(env, nat->conn, DBUS_SVC_NAME, + nat->adapter, DBUS_CLASS_NAME, "ListBondings", DBUS_TYPE_INVALID); // return String[] return reply ? dbus_returns_array_of_strings(env, reply) : NULL; @@ -585,8 +593,8 @@ static jobjectArray listConnectionsNative(JNIEnv *env, jobject object) { native_data_t *nat = get_native_data(env, object); if (nat) { DBusMessage *reply = - dbus_func_args(env, nat->conn, nat->adapter, - DBUS_CLASS_NAME, "ListConnections", + dbus_func_args(env, nat->conn, DBUS_SVC_NAME, + nat->adapter, DBUS_CLASS_NAME, "ListConnections", DBUS_TYPE_INVALID); // return String[] return reply ? dbus_returns_array_of_strings(env, reply) : NULL; @@ -601,8 +609,8 @@ static jobjectArray listRemoteDevicesNative(JNIEnv *env, jobject object) { native_data_t *nat = get_native_data(env, object); if (nat) { DBusMessage *reply = - dbus_func_args(env, nat->conn, nat->adapter, - DBUS_CLASS_NAME, "ListRemoteDevices", + dbus_func_args(env, nat->conn, DBUS_SVC_NAME, + nat->adapter, DBUS_CLASS_NAME, "ListRemoteDevices", DBUS_TYPE_INVALID); return reply ? dbus_returns_array_of_strings(env, reply) : NULL; } @@ -618,8 +626,8 @@ static jstring common_Get(JNIEnv *env, jobject object, const char *func) { DBusError err; dbus_error_init(&err); DBusMessage *reply = - dbus_func_args_error(env, nat->conn, &err, nat->adapter, - DBUS_CLASS_NAME, func, + dbus_func_args_error(env, nat->conn, &err, DBUS_SVC_NAME, + nat->adapter, DBUS_CLASS_NAME, func, DBUS_TYPE_INVALID); if (reply) { return dbus_returns_string(env, reply); @@ -658,8 +666,9 @@ static jboolean setNameNative(JNIEnv *env, jobject obj, jstring name) { native_data_t *nat = get_native_data(env, obj); if (nat) { const char *c_name = env->GetStringUTFChars(name, NULL); - DBusMessage *reply = dbus_func_args(env, nat->conn, nat->adapter, - DBUS_CLASS_NAME, "SetName", + DBusMessage *reply = dbus_func_args(env, nat->conn, DBUS_SVC_NAME, + nat->adapter, DBUS_CLASS_NAME, + "SetName", DBUS_TYPE_STRING, &c_name, DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(name, c_name); @@ -685,8 +694,8 @@ static jstring common_getRemote(JNIEnv *env, jobject object, const char *func, LOGV("... address = %s", c_address); DBusMessage *reply = - dbus_func_args_error(env, nat->conn, &err, nat->adapter, - DBUS_CLASS_NAME, func, + dbus_func_args_error(env, nat->conn, &err, DBUS_SVC_NAME, + nat->adapter, DBUS_CLASS_NAME, func, DBUS_TYPE_STRING, &c_address, DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(address, c_address); @@ -747,8 +756,8 @@ static jint getRemoteClassNative(JNIEnv *env, jobject object, jstring address) { LOGV("... address = %s", c_address); DBusMessage *reply = - dbus_func_args(env, nat->conn, nat->adapter, - DBUS_CLASS_NAME, "GetRemoteClass", + dbus_func_args(env, nat->conn, DBUS_SVC_NAME, + nat->adapter, DBUS_CLASS_NAME, "GetRemoteClass", DBUS_TYPE_STRING, &c_address, DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(address, c_address); @@ -779,8 +788,8 @@ static jbyteArray getRemoteFeaturesNative(JNIEnv *env, jobject object, LOGV("... address = %s", c_address); DBusMessage *reply = - dbus_func_args(env, nat->conn, nat->adapter, - DBUS_CLASS_NAME, "GetRemoteFeatures", + dbus_func_args(env, nat->conn, DBUS_SVC_NAME, + nat->adapter, DBUS_CLASS_NAME, "GetRemoteFeatures", DBUS_TYPE_STRING, &c_address, DBUS_TYPE_INVALID); env->ReleaseStringUTFChars(address, c_address); @@ -804,8 +813,9 @@ static jintArray getRemoteServiceHandlesNative(JNIEnv *env, jobject object, LOGV("... address = %s match = %s", c_address, c_match); DBusMessage *reply = - dbus_func_args(env, nat->conn, nat->adapter, - DBUS_CLASS_NAME, "GetRemoteServiceHandles", + dbus_func_args(env, nat->conn, DBUS_SVC_NAME, + nat->adapter, DBUS_CLASS_NAME, + "GetRemoteServiceHandles", DBUS_TYPE_STRING, &c_address, DBUS_TYPE_STRING, &c_match, DBUS_TYPE_INVALID); @@ -850,8 +860,9 @@ static jbyteArray getRemoteServiceRecordNative(JNIEnv *env, jobject object, LOGV("... address = %s", c_address); DBusMessage *reply = - dbus_func_args(env, nat->conn, nat->adapter, - DBUS_CLASS_NAME, "GetRemoteServiceRecord", + dbus_func_args(env, nat->conn, DBUS_SVC_NAME, + nat->adapter, DBUS_CLASS_NAME, + "GetRemoteServiceRecord", DBUS_TYPE_STRING, &c_address, DBUS_TYPE_UINT32, &handle, DBUS_TYPE_INVALID); @@ -881,6 +892,7 @@ static jboolean getRemoteServiceChannelNative(JNIEnv *env, jobject object, bool ret = dbus_func_args_async(env, nat->conn, 20000, // ms onGetRemoteServiceChannelResult, context_address, eventLoopNat, + DBUS_SVC_NAME, nat->adapter, DBUS_CLASS_NAME, "GetRemoteServiceChannel", DBUS_TYPE_STRING, &c_address, diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp index ad24136..b9fdcba 100644 --- a/core/jni/android_server_BluetoothEventLoop.cpp +++ b/core/jni/android_server_BluetoothEventLoop.cpp @@ -1,5 +1,6 @@ /* ** Copyright 2008, The Android Open Source Project +** Copyright (c) 2009, Code Aurora Forum, Inc. All rights reserved. ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. @@ -18,6 +19,7 @@ #include "android_bluetooth_common.h" #include "android_runtime/AndroidRuntime.h" +#include "cutils/properties.h" #include "cutils/sockets.h" #include "JNIHelp.h" #include "jni.h" @@ -158,10 +160,26 @@ static DBusHandlerResult agent_event_filter(DBusConnection *conn, DBusMessage *msg, void *data); +extern DBusHandlerResult oppclient_agent(DBusConnection *conn, + DBusMessage *msg, + void *data); + +extern DBusHandlerResult oppserver_agent(DBusConnection *conn, + DBusMessage *msg, + void *data); + static const DBusObjectPathVTable agent_vtable = { NULL, agent_event_filter, NULL, NULL, NULL, NULL }; +static const DBusObjectPathVTable oppclient_agent_vtable = { + NULL, oppclient_agent, NULL, NULL, NULL, NULL +}; + +static const DBusObjectPathVTable oppserver_agent_vtable = { + NULL, oppserver_agent, NULL, NULL, NULL, NULL +}; + static unsigned int unix_events_to_dbus_flags(short events) { return (events & DBUS_WATCH_READABLE ? POLLIN : 0) | (events & DBUS_WATCH_WRITABLE ? POLLOUT : 0) | @@ -196,6 +214,7 @@ static jboolean setUpEventLoop(native_data_t *nat) { LOG_AND_FREE_DBUS_ERROR(&err); return JNI_FALSE; } + dbus_bus_add_match(nat->conn, "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".Adapter'", &err); @@ -203,6 +222,7 @@ static jboolean setUpEventLoop(native_data_t *nat) { LOG_AND_FREE_DBUS_ERROR(&err); return JNI_FALSE; } + dbus_bus_add_match(nat->conn, "type='signal',interface='org.bluez.audio.Manager'", &err); @@ -210,6 +230,7 @@ static jboolean setUpEventLoop(native_data_t *nat) { LOG_AND_FREE_DBUS_ERROR(&err); return JNI_FALSE; } + dbus_bus_add_match(nat->conn, "type='signal',interface='org.bluez.audio.Device'", &err); @@ -217,6 +238,7 @@ static jboolean setUpEventLoop(native_data_t *nat) { LOG_AND_FREE_DBUS_ERROR(&err); return JNI_FALSE; } + dbus_bus_add_match(nat->conn, "type='signal',interface='org.bluez.audio.Sink'", &err); @@ -225,8 +247,25 @@ static jboolean setUpEventLoop(native_data_t *nat) { return JNI_FALSE; } + dbus_bus_add_match(nat->conn, + "type='signal',interface='"OBEXD_DBUS_SRV_MGR_IFC"'", + &err); + if (dbus_error_is_set(&err)) { + LOG_AND_FREE_DBUS_ERROR(&err); + return JNI_FALSE; + } + + dbus_bus_add_match(nat->conn, + "type='signal',interface='"OBEXD_DBUS_SRV_TRANS_IFC"'", + &err); + if (dbus_error_is_set(&err)) { + LOG_AND_FREE_DBUS_ERROR(&err); + return JNI_FALSE; + } + + const char *path = ANDROID_PASSKEY_AGENT_PATH; + // Add an object handler for passkey agent method calls - const char *path = "/android/bluetooth/Agent"; if (!dbus_connection_register_object_path(nat->conn, path, &agent_vtable, nat)) { LOGE("%s: Can't register object path %s for agent!", @@ -234,15 +273,19 @@ static jboolean setUpEventLoop(native_data_t *nat) { return JNI_FALSE; } + LOGV("Added Object Path %s", path); + // RegisterDefaultPasskeyAgent() will fail until hcid is up, so keep // trying for 10 seconds. int attempt; + DBusMessage *reply = NULL; + for (attempt = 0; attempt < 1000; attempt++) { - DBusMessage *reply = dbus_func_args_error(NULL, nat->conn, &err, - BLUEZ_DBUS_BASE_PATH, + reply = dbus_func_args_error(NULL, nat->conn, &err, + BLUEZ_DBUS_BASE_SVC, BLUEZ_DBUS_BASE_PATH, "org.bluez.Security", "RegisterDefaultPasskeyAgent", - DBUS_TYPE_STRING, &path, - DBUS_TYPE_INVALID); + DBUS_TYPE_STRING, &path, DBUS_TYPE_INVALID); + if (reply) { // Success dbus_message_unref(reply); @@ -259,15 +302,18 @@ static jboolean setUpEventLoop(native_data_t *nat) { return JNI_FALSE; } } + if (attempt == 1000) { LOGE("Time-out trying to call RegisterDefaultPasskeyAgent(), " "is hcid running?"); return JNI_FALSE; } + LOGV("Registered Passkey Agent Path %s", path); + // Now register the Auth agent - DBusMessage *reply = dbus_func_args_error(NULL, nat->conn, &err, - BLUEZ_DBUS_BASE_PATH, + reply = dbus_func_args_error(NULL, nat->conn, &err, + BLUEZ_DBUS_BASE_SVC, BLUEZ_DBUS_BASE_PATH, "org.bluez.Security", "RegisterDefaultAuthorizationAgent", DBUS_TYPE_STRING, &path, DBUS_TYPE_INVALID); @@ -276,7 +322,77 @@ static jboolean setUpEventLoop(native_data_t *nat) { return JNI_FALSE; } + LOGV("Registered Authorization Agent Path %s", path); + dbus_message_unref(reply); + + char proprietary_obex_enabled_str[PROPERTY_VALUE_MAX] = ""; + property_get("ro.qualcomm.proprietary_obex", proprietary_obex_enabled_str, ""); + if (!strncmp("true", proprietary_obex_enabled_str, PROPERTY_VALUE_MAX) || + !strncmp("1", proprietary_obex_enabled_str, PROPERTY_VALUE_MAX)) { + LOGD("Qualcomm proprietary Bluetooth OBEX: enabled."); + + path = ANDROID_OPPCLIENT_AGENT_PATH; + + // Add an object handler for OPP client agent method calls + if (!dbus_connection_register_object_path(nat->conn, path, + &oppclient_agent_vtable, + NULL)) { + LOGE("%s: Can't register object path %s for agent!", + __FUNCTION__, path); + return JNI_FALSE; + } + + LOGV("Added Object Path %s", path); + + path = ANDROID_OPPSRV_AGENT_PATH; + + // Add an object handler for OPP server agent method calls + if (!dbus_connection_register_object_path(nat->conn, path, + &oppserver_agent_vtable, + NULL)) { + LOGE("%s: Can't register object path %s for agent!", + __FUNCTION__, path); + return JNI_FALSE; + } + + LOGV("Added Object Path %s", path); + + for (attempt = 0; attempt < 1000; attempt++) { + // Register OPP Server Agent + reply = dbus_func_args_error(NULL, nat->conn, &err, + OBEXD_DBUS_SRV_SVC, OBEXD_DBUS_SRV_MGR_PATH, + OBEXD_DBUS_SRV_MGR_IFC, OBEXD_DBUS_SRV_MGR_REG_AGENT, + DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); + + if (reply) { + // Success + dbus_message_unref(reply); + LOGV("Registered agent on attempt %d of 1000\n", attempt); + break; + } else if (dbus_error_has_name(&err, + "org.freedesktop.DBus.Error.ServiceUnknown")) { + // dbus_bt is still down, retry + dbus_error_free(&err); + usleep(10000); // 10 ms + } else { + // Some other error we weren't expecting + LOG_AND_FREE_DBUS_ERROR(&err); + return JNI_FALSE; + } + } + + if (attempt == 1000) { + LOGE("Time-out trying to call"OBEXD_DBUS_SRV_MGR_REG_AGENT", " + "is dbus_bt running?"); + return JNI_FALSE; + } + + LOGV("Registered OPP Server Agent Path %s", path); + } else { + LOGD("Qualcomm proprietary Bluetooth OBEX: disabled."); + } + return JNI_TRUE; } @@ -290,23 +406,40 @@ static void tearDownEventLoop(native_data_t *nat) { DBusError err; dbus_error_init(&err); - const char *path = "/android/bluetooth/Agent"; + const char *path = ANDROID_PASSKEY_AGENT_PATH; + DBusMessage *reply = - dbus_func_args(NULL, nat->conn, BLUEZ_DBUS_BASE_PATH, - "org.bluez.Security", "UnregisterDefaultPasskeyAgent", + dbus_func_args(NULL, nat->conn, BLUEZ_DBUS_BASE_SVC, + BLUEZ_DBUS_BASE_PATH, "org.bluez.Security", + "UnregisterDefaultPasskeyAgent", DBUS_TYPE_STRING, &path, DBUS_TYPE_INVALID); if (reply) dbus_message_unref(reply); - reply = - dbus_func_args(NULL, nat->conn, BLUEZ_DBUS_BASE_PATH, - "org.bluez.Security", "UnregisterDefaultAuthorizationAgent", + reply = dbus_func_args(NULL, nat->conn, BLUEZ_DBUS_BASE_SVC, + BLUEZ_DBUS_BASE_PATH, "org.bluez.Security", + "UnregisterDefaultAuthorizationAgent", DBUS_TYPE_STRING, &path, DBUS_TYPE_INVALID); if (reply) dbus_message_unref(reply); dbus_connection_unregister_object_path(nat->conn, path); + path = ANDROID_OPPSRV_AGENT_PATH; + + reply = dbus_func_args(NULL, nat->conn, OBEXD_DBUS_SRV_SVC, + OBEXD_DBUS_SRV_MGR_PATH, + OBEXD_DBUS_SRV_MGR_IFC, + OBEXD_DBUS_SRV_MGR_UNREG_AGENT, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + + if (reply) dbus_message_unref(reply); + + dbus_connection_unregister_object_path(nat->conn, path); + + dbus_connection_unregister_object_path(nat->conn, ANDROID_OPPCLIENT_AGENT_PATH); + dbus_bus_remove_match(nat->conn, "type='signal',interface='org.bluez.audio.Sink'", &err); @@ -332,6 +465,18 @@ static void tearDownEventLoop(native_data_t *nat) { LOG_AND_FREE_DBUS_ERROR(&err); } dbus_bus_remove_match(nat->conn, + "type='signal',interface='"OBEXD_DBUS_SRV_MGR_IFC"'", + &err); + if (dbus_error_is_set(&err)) { + LOG_AND_FREE_DBUS_ERROR(&err); + } + dbus_bus_remove_match(nat->conn, + "type='signal',interface='"OBEXD_DBUS_SRV_TRANS_IFC"'", + &err); + if (dbus_error_is_set(&err)) { + LOG_AND_FREE_DBUS_ERROR(&err); + } + dbus_bus_remove_match(nat->conn, "type='signal',interface='org.freedesktop.DBus'", &err); if (dbus_error_is_set(&err)) { @@ -514,7 +659,7 @@ static void *eventLoopMain(void *ptr) { break; } } - while (dbus_connection_dispatch(nat->conn) == + while (dbus_connection_dispatch(nat->conn) == DBUS_DISPATCH_DATA_REMAINS) { } @@ -643,6 +788,8 @@ static jboolean isEventLoopRunningNative(JNIEnv *env, jobject object) { #ifdef HAVE_BLUETOOTH extern DBusHandlerResult a2dp_event_filter(DBusMessage *msg, JNIEnv *env); +extern DBusHandlerResult opp_event_filter(DBusMessage *msg, JNIEnv *env); +extern DBusHandlerResult ftp_event_filter(DBusMessage *msg, JNIEnv *env); // Called by dbus during WaitForAndDispatchEventNative() static DBusHandlerResult event_filter(DBusConnection *conn, DBusMessage *msg, @@ -677,11 +824,16 @@ static DBusHandlerResult event_filter(DBusConnection *conn, DBusMessage *msg, DBUS_TYPE_INVALID)) { LOGV("... address = %s class = %#X rssi = %hd", c_address, n_class, n_rssi); + + jstring address = env->NewStringUTF(c_address); + env->CallVoidMethod(nat->me, method_onRemoteDeviceFound, - env->NewStringUTF(c_address), + address, (jint)n_class, (jshort)n_rssi); + + env->DeleteLocalRef(address); } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, @@ -704,8 +856,13 @@ static DBusHandlerResult event_filter(DBusConnection *conn, DBusMessage *msg, DBUS_TYPE_STRING, &c_address, DBUS_TYPE_INVALID)) { LOGV("... address = %s", c_address); + + jstring address = env->NewStringUTF(c_address); + env->CallVoidMethod(nat->me, method_onRemoteDeviceDisappeared, - env->NewStringUTF(c_address)); + address); + + env->DeleteLocalRef(address); } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, @@ -718,8 +875,13 @@ static DBusHandlerResult event_filter(DBusConnection *conn, DBusMessage *msg, DBUS_TYPE_UINT32, &n_class, DBUS_TYPE_INVALID)) { LOGV("... address = %s", c_address); + + jstring address = env->NewStringUTF(c_address); + env->CallVoidMethod(nat->me, method_onRemoteClassUpdated, - env->NewStringUTF(c_address), (jint)n_class); + address, (jint)n_class); + + env->DeleteLocalRef(address); } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, @@ -732,10 +894,17 @@ static DBusHandlerResult event_filter(DBusConnection *conn, DBusMessage *msg, DBUS_TYPE_STRING, &c_name, DBUS_TYPE_INVALID)) { LOGV("... address = %s, name = %s", c_address, c_name); + + jstring address = env->NewStringUTF(c_address); + jstring name = env->NewStringUTF(c_name); + env->CallVoidMethod(nat->me, method_onRemoteNameUpdated, - env->NewStringUTF(c_address), - env->NewStringUTF(c_name)); + address, + name); + + env->DeleteLocalRef(address); + env->DeleteLocalRef(name); } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, @@ -746,9 +915,14 @@ static DBusHandlerResult event_filter(DBusConnection *conn, DBusMessage *msg, DBUS_TYPE_STRING, &c_address, DBUS_TYPE_INVALID)) { LOGV("... address = %s", c_address); + + jstring address = env->NewStringUTF(c_address); + env->CallVoidMethod(nat->me, method_onRemoteNameFailed, - env->NewStringUTF(c_address)); + address); + + env->DeleteLocalRef(address); } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, @@ -759,9 +933,14 @@ static DBusHandlerResult event_filter(DBusConnection *conn, DBusMessage *msg, DBUS_TYPE_STRING, &c_address, DBUS_TYPE_INVALID)) { LOGV("... address = %s", c_address); + + jstring address = env->NewStringUTF(c_address); + env->CallVoidMethod(nat->me, method_onRemoteDeviceConnected, - env->NewStringUTF(c_address)); + address); + + env->DeleteLocalRef(address); } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, @@ -772,9 +951,14 @@ static DBusHandlerResult event_filter(DBusConnection *conn, DBusMessage *msg, DBUS_TYPE_STRING, &c_address, DBUS_TYPE_INVALID)) { LOGV("... address = %s", c_address); + + jstring address = env->NewStringUTF(c_address); + env->CallVoidMethod(nat->me, method_onRemoteDeviceDisconnectRequested, - env->NewStringUTF(c_address)); + address); + + env->DeleteLocalRef(address); } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, @@ -785,9 +969,14 @@ static DBusHandlerResult event_filter(DBusConnection *conn, DBusMessage *msg, DBUS_TYPE_STRING, &c_address, DBUS_TYPE_INVALID)) { LOGV("... address = %s", c_address); + + jstring address = env->NewStringUTF(c_address); + env->CallVoidMethod(nat->me, method_onRemoteDeviceDisconnected, - env->NewStringUTF(c_address)); + address); + + env->DeleteLocalRef(address); } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, @@ -798,9 +987,14 @@ static DBusHandlerResult event_filter(DBusConnection *conn, DBusMessage *msg, DBUS_TYPE_STRING, &c_address, DBUS_TYPE_INVALID)) { LOGV("... address = %s", c_address); + + jstring address = env->NewStringUTF(c_address); + env->CallVoidMethod(nat->me, method_onBondingCreated, - env->NewStringUTF(c_address)); + address); + + env->DeleteLocalRef(address); } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, @@ -811,9 +1005,14 @@ static DBusHandlerResult event_filter(DBusConnection *conn, DBusMessage *msg, DBUS_TYPE_STRING, &c_address, DBUS_TYPE_INVALID)) { LOGV("... address = %s", c_address); + + jstring address = env->NewStringUTF(c_address); + env->CallVoidMethod(nat->me, method_onBondingRemoved, - env->NewStringUTF(c_address)); + address); + + env->DeleteLocalRef(address); } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, @@ -824,9 +1023,14 @@ static DBusHandlerResult event_filter(DBusConnection *conn, DBusMessage *msg, DBUS_TYPE_STRING, &c_mode, DBUS_TYPE_INVALID)) { LOGV("... mode = %s", c_mode); + + jstring mode = env->NewStringUTF(c_mode); + env->CallVoidMethod(nat->me, method_onModeChanged, - env->NewStringUTF(c_mode)); + mode); + + env->DeleteLocalRef(mode); } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, @@ -837,9 +1041,14 @@ static DBusHandlerResult event_filter(DBusConnection *conn, DBusMessage *msg, DBUS_TYPE_STRING, &c_name, DBUS_TYPE_INVALID)) { LOGV("... name = %s", c_name); + + jstring name = env->NewStringUTF(c_name); + env->CallVoidMethod(nat->me, method_onNameChanged, - env->NewStringUTF(c_name)); + name); + + env->DeleteLocalRef(name); } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(msg, @@ -864,9 +1073,13 @@ static DBusHandlerResult event_filter(DBusConnection *conn, DBusMessage *msg, } } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); return DBUS_HANDLER_RESULT_HANDLED; + } else if (a2dp_event_filter(msg, env) == DBUS_HANDLER_RESULT_HANDLED) { + return DBUS_HANDLER_RESULT_HANDLED; + } else if (opp_event_filter(msg, env) == DBUS_HANDLER_RESULT_HANDLED) { + return DBUS_HANDLER_RESULT_HANDLED; + } else { + return ftp_event_filter (msg, env); } - - return a2dp_event_filter(msg, env); } // Called by dbus during WaitForAndDispatchEventNative() @@ -885,42 +1098,50 @@ static DBusHandlerResult agent_event_filter(DBusConnection *conn, if (dbus_message_is_method_call(msg, "org.bluez.PasskeyAgent", "Request")) { - const char *adapter; - const char *address; + const char *c_adapter; + const char *c_address; if (!dbus_message_get_args(msg, NULL, - DBUS_TYPE_STRING, &adapter, - DBUS_TYPE_STRING, &address, + DBUS_TYPE_STRING, &c_adapter, + DBUS_TYPE_STRING, &c_address, DBUS_TYPE_INVALID)) { LOGE("%s: Invalid arguments for Request() method", __FUNCTION__); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } - LOGV("... address = %s", address); + LOGV("... address = %s", c_address); dbus_message_ref(msg); // increment refcount because we pass to java + jstring address = env->NewStringUTF(c_address); + env->CallVoidMethod(nat->me, method_onPasskeyAgentRequest, - env->NewStringUTF(address), (int)msg); + address, (int)msg); + + env->DeleteLocalRef(address); return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_method_call(msg, "org.bluez.PasskeyAgent", "Cancel")) { - const char *adapter; - const char *address; + const char *c_adapter; + const char *c_address; if (!dbus_message_get_args(msg, NULL, - DBUS_TYPE_STRING, &adapter, - DBUS_TYPE_STRING, &address, + DBUS_TYPE_STRING, &c_adapter, + DBUS_TYPE_STRING, &c_address, DBUS_TYPE_INVALID)) { LOGE("%s: Invalid arguments for Cancel() method", __FUNCTION__); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } - LOGV("... address = %s", address); + LOGV("... address = %s", c_address); + + jstring address = env->NewStringUTF(c_address); env->CallVoidMethod(nat->me, method_onPasskeyAgentCancel, - env->NewStringUTF(address)); + address); + + env->DeleteLocalRef(address); // reply DBusMessage *reply = dbus_message_new_method_return(msg); @@ -947,27 +1168,34 @@ static DBusHandlerResult agent_event_filter(DBusConnection *conn, return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_method_call(msg, "org.bluez.AuthorizationAgent", "Authorize")) { - const char *adapter; - const char *address; - const char *service; - const char *uuid; + const char *c_adapter; + const char *c_address; + const char *c_service; + const char *c_uuid; if (!dbus_message_get_args(msg, NULL, - DBUS_TYPE_STRING, &adapter, - DBUS_TYPE_STRING, &address, - DBUS_TYPE_STRING, &service, - DBUS_TYPE_STRING, &uuid, + DBUS_TYPE_STRING, &c_adapter, + DBUS_TYPE_STRING, &c_address, + DBUS_TYPE_STRING, &c_service, + DBUS_TYPE_STRING, &c_uuid, DBUS_TYPE_INVALID)) { LOGE("%s: Invalid arguments for Authorize() method", __FUNCTION__); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } - LOGV("... address = %s", address); - LOGV("... service = %s", service); - LOGV("... uuid = %s", uuid); + LOGV("... address = %s", c_address); + LOGV("... service = %s", c_service); + LOGV("... uuid = %s", c_uuid); + + jstring address = env->NewStringUTF(c_address); + jstring service = env->NewStringUTF(c_service); + jstring uuid = env->NewStringUTF(c_uuid); bool auth_granted = env->CallBooleanMethod(nat->me, - method_onAuthAgentAuthorize, env->NewStringUTF(address), - env->NewStringUTF(service), env->NewStringUTF(uuid)); + method_onAuthAgentAuthorize, address, service, uuid); + + env->DeleteLocalRef(address); + env->DeleteLocalRef(service); + env->DeleteLocalRef(uuid); // reply if (auth_granted) { @@ -991,27 +1219,34 @@ static DBusHandlerResult agent_event_filter(DBusConnection *conn, return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_method_call(msg, "org.bluez.AuthorizationAgent", "Cancel")) { - const char *adapter; - const char *address; - const char *service; - const char *uuid; + const char *c_adapter; + const char *c_address; + const char *c_service; + const char *c_uuid; if (!dbus_message_get_args(msg, NULL, - DBUS_TYPE_STRING, &adapter, - DBUS_TYPE_STRING, &address, - DBUS_TYPE_STRING, &service, - DBUS_TYPE_STRING, &uuid, + DBUS_TYPE_STRING, &c_adapter, + DBUS_TYPE_STRING, &c_address, + DBUS_TYPE_STRING, &c_service, + DBUS_TYPE_STRING, &c_uuid, DBUS_TYPE_INVALID)) { LOGE("%s: Invalid arguments for Cancel() method", __FUNCTION__); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } - LOGV("... address = %s", address); - LOGV("... service = %s", service); - LOGV("... uuid = %s", uuid); + LOGV("... address = %s", c_address); + LOGV("... service = %s", c_service); + LOGV("... uuid = %s", c_uuid); + + jstring address = env->NewStringUTF(c_address); + jstring service = env->NewStringUTF(c_service); + jstring uuid = env->NewStringUTF(c_uuid); env->CallVoidMethod(nat->me, - method_onAuthAgentCancel, env->NewStringUTF(address), - env->NewStringUTF(service), env->NewStringUTF(uuid)); + method_onAuthAgentCancel, address, service, uuid); + + env->DeleteLocalRef(address); + env->DeleteLocalRef(service); + env->DeleteLocalRef(uuid); // reply DBusMessage *reply = dbus_message_new_method_return(msg); @@ -1059,13 +1294,15 @@ void onCreateBondingResult(DBusMessage *msg, void *user, void *n) { LOGV(__FUNCTION__); native_data_t *nat = (native_data_t *)n; - const char *address = (const char *)user; + const char *c_address = (const char *)user; DBusError err; dbus_error_init(&err); JNIEnv *env; nat->vm->GetEnv((void**)&env, nat->envVer); - LOGV("... address = %s", address); + jstring address = env->NewStringUTF(c_address); + + LOGV("... address = %s", c_address); jint result = BOND_RESULT_SUCCESS; if (dbus_set_error_from_message(&err, msg)) { @@ -1107,9 +1344,11 @@ void onCreateBondingResult(DBusMessage *msg, void *user, void *n) { env->CallVoidMethod(nat->me, method_onCreateBondingResult, - env->NewStringUTF(address), + address, result); + done: + env->DeleteLocalRef(address); dbus_error_free(&err); free(user); } @@ -1117,7 +1356,7 @@ done: void onGetRemoteServiceChannelResult(DBusMessage *msg, void *user, void *n) { LOGV(__FUNCTION__); - const char *address = (const char *) user; + const char *c_address = (const char *) user; native_data_t *nat = (native_data_t *) n; DBusError err; @@ -1127,7 +1366,7 @@ void onGetRemoteServiceChannelResult(DBusMessage *msg, void *user, void *n) { jint channel = -2; - LOGV("... address = %s", address); + LOGV("... address = %s", c_address); if (dbus_set_error_from_message(&err, msg) || !dbus_message_get_args(msg, &err, @@ -1138,11 +1377,15 @@ void onGetRemoteServiceChannelResult(DBusMessage *msg, void *user, void *n) { dbus_error_free(&err); } -done: + jstring address = env->NewStringUTF(c_address); + env->CallVoidMethod(nat->me, method_onGetRemoteServiceChannelResult, - env->NewStringUTF(address), + address, channel); + + env->DeleteLocalRef(address); + free(user); } #endif @@ -1158,6 +1401,8 @@ static JNINativeMethod sMethods[] = { }; int register_android_server_BluetoothEventLoop(JNIEnv *env) { + LOGV(__FUNCTION__); + return AndroidRuntime::registerNativeMethods(env, "android/server/BluetoothEventLoop", sMethods, NELEM(sMethods)); } diff --git a/core/jni/android_server_BluetoothFtpService.cpp b/core/jni/android_server_BluetoothFtpService.cpp new file mode 100755 index 0000000..dc7f991 --- /dev/null +++ b/core/jni/android_server_BluetoothFtpService.cpp @@ -0,0 +1,1797 @@ +/* + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Code Aurora nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#define LOG_TAG "BluetoothFTPService.cpp" + +#include "android_bluetooth_common.h" +#include "android_runtime/AndroidRuntime.h" +#include "JNIHelp.h" +#include "jni.h" +#include "utils/Log.h" +#include "utils/misc.h" + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_BLUETOOTH +#include +#endif + + +namespace android { + +#ifdef HAVE_BLUETOOTH + +static jmethodID method_onCreateSessionComplete; +static jmethodID method_onChangeFolderComplete; +static jmethodID method_onCreateFolderComplete; +static jmethodID method_onListFolderComplete; +static jmethodID method_onGetFileComplete; +static jmethodID method_onPutFileComplete; +static jmethodID method_onDeleteComplete; +static jmethodID method_onObexRequest; +static jmethodID method_onObexProgress; +static jmethodID method_onObexTransferComplete; +static jmethodID method_onObexSessionClosed; + + +typedef struct { + DBusConnection *conn; + jobject me; // for callbacks to java + /* our vm and env Version for future env generation */ + JavaVM *vm; + int envVer; +} native_data_t; + +typedef struct { + char *session; + char *sourcefile; + char *targetfile; + char *folder; +} callback_data_t; + +static native_data_t *nat = NULL; // global native data + +extern void dbus_func_args_async_callback(DBusPendingCall *call, void *data); +extern DBusHandlerResult ftpclient_agent(DBusConnection *conn, + DBusMessage *msg, + void *data); + +static const DBusObjectPathVTable ftpclient_agent_vtable = { + NULL, ftpclient_agent, NULL, NULL, NULL, NULL +}; + + +static void onCreateSessionComplete(DBusMessage *msg, void *user, void *nat_cb) +{ + LOGI(__FUNCTION__); + + char* c_address = (char *)user; + DBusError err; + jboolean is_error = JNI_FALSE; + char *c_obj_path = NULL; + jstring obj_path = NULL; + + JNIEnv *env = NULL; + nat->vm->GetEnv((void**)&env, nat->envVer); + + dbus_error_init(&err); + + LOGV("... address = %s", c_address); + + jstring address = env->NewStringUTF(c_address); + + dbus_error_init(&err); + + if (dbus_set_error_from_message(&err, msg) || + !dbus_message_get_args(msg, &err, + DBUS_TYPE_OBJECT_PATH, &c_obj_path, + DBUS_TYPE_INVALID)) { + LOG_AND_FREE_DBUS_ERROR(&err); + + is_error = JNI_TRUE; + + goto done; + } + + LOGV(" object path = %s\n", c_obj_path); + + /* Add an object handler for FTP client agent method calls*/ + if (!dbus_connection_register_object_path(nat->conn, + c_obj_path, + &ftpclient_agent_vtable, + NULL)) { + LOGE("%s: Can't register object path %s for agent!", + __FUNCTION__, c_obj_path); + + is_error = JNI_TRUE; + + goto done; + } else { + LOGV("Registered Object Path %s with dbus", c_obj_path); + DBusMessage *reply = dbus_func_args_error(env, nat->conn, &err, + OBEXD_DBUS_CLIENT_SVC, + c_obj_path, + OBEXD_DBUS_CLIENT_SESSION_IFC, + OBEXD_DBUS_CLIENT_SESSION_ASSIGN, + DBUS_TYPE_OBJECT_PATH, + &c_obj_path, DBUS_TYPE_INVALID); + + if (reply) { + LOGV("Added Object Path %s with BM3", c_obj_path); + + is_error = JNI_FALSE; + + dbus_message_unref(reply); + } else { + LOG_AND_FREE_DBUS_ERROR(&err); + + is_error = JNI_TRUE; + } + } + + done: + if (is_error == JNI_FALSE) { + obj_path = env->NewStringUTF(c_obj_path); + } + + env->CallVoidMethod(nat->me, + method_onCreateSessionComplete, + obj_path, + address, + is_error); + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + } + + env->DeleteLocalRef(address); + + if (obj_path != NULL) { + env->DeleteLocalRef(obj_path); + } + + free(c_address); + + return; +} + +static void onChangeFolderComplete(DBusMessage *msg, void *user, void *nat_cb) +{ + LOGI(__FUNCTION__); + + callback_data_t *callback_userdata = (callback_data_t *)user; + DBusError err; + jboolean is_error = JNI_FALSE; + JNIEnv *env = NULL; + nat->vm->GetEnv((void**)&env, nat->envVer); + + dbus_error_init(&err); + + LOGV("... session = %s ...folder = %s\n", callback_userdata->session, callback_userdata->folder); + if (dbus_set_error_from_message(&err, msg)) + { + LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message); + + dbus_error_free(&err); + + is_error = JNI_TRUE; + } + + jstring session = env->NewStringUTF(callback_userdata->session); + jstring folder = env->NewStringUTF(callback_userdata->folder); + + env->CallVoidMethod(nat->me, + method_onChangeFolderComplete, + session, + folder, + is_error); + + if (env->ExceptionCheck()) + { + env->ExceptionDescribe(); + } + + env->DeleteLocalRef(session); + env->DeleteLocalRef(folder); + + free(callback_userdata->session); + free(callback_userdata->folder); + free(callback_userdata); + + return; +} + +static void onCreateFolderComplete(DBusMessage *msg, void *user, void *nat_cb) +{ + LOGI(__FUNCTION__); + + callback_data_t *callback_userdata = (callback_data_t *)user; + DBusError err; + jboolean is_error = JNI_FALSE; + JNIEnv *env = NULL; + nat->vm->GetEnv((void**)&env, nat->envVer); + + dbus_error_init(&err); + + LOGV("... session = %s", callback_userdata->session); + if (dbus_set_error_from_message(&err, msg)) + { + LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message); + + dbus_error_free(&err); + + is_error = JNI_TRUE; + } + + jstring session = env->NewStringUTF(callback_userdata->session); + jstring folder = env->NewStringUTF(callback_userdata->folder); + + env->CallVoidMethod(nat->me, + method_onCreateFolderComplete, + session, + folder, + is_error); + + if (env->ExceptionCheck()) + { + env->ExceptionDescribe(); + } + + env->DeleteLocalRef(session); + env->DeleteLocalRef(folder); + + free(callback_userdata->session); + free(callback_userdata->folder); + free(callback_userdata); + + return; +} + + +static void onListFolderComplete(DBusMessage *msg, void *user, void *nat_cb) +{ + LOGI(__FUNCTION__); + + char* c_session = (char *)user; + DBusError err; + jboolean is_error = JNI_FALSE; + jobjectArray result = NULL; + jobject obj; + JNIEnv *env = NULL; + nat->vm->GetEnv((void**)&env, nat->envVer); + jclass clazz = NULL; + jmethodID constructor = NULL; + DBusMessageIter iter, array; + const char *c_name = NULL, *c_type = NULL, *c_permission = NULL; + uint64_t size = 0, modified = 0, accessed = 0, created = 0; + int array_count = 0; + + dbus_error_init(&err); + + LOGV("onListFolderComplete... session = %s", c_session); + + jstring session = env->NewStringUTF(c_session); + + if (dbus_set_error_from_message(&err, msg)) + { + LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message); + + dbus_error_free(&err); + + goto fail; + + return; + } + + if(!dbus_message_iter_init(msg, &iter)) + { + LOGE("%s: D-Bus msg not valid\n", __FUNCTION__); + + goto fail; + + return; + } + + dbus_message_iter_recurse(&iter, &array); + + while( dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_INVALID ) + { + array_count++; + dbus_message_iter_next(&array); + } + + LOGV(" No. of array element = %d\n", array_count ); + + if(!dbus_message_iter_init(msg, &iter)) + { + LOGE("%s: D-Bus msg not valid\n", __FUNCTION__); + + goto fail; + + return; + } + + dbus_message_iter_recurse(&iter, &array); + + clazz = env->FindClass("android/server/BluetoothFtpService$ObjectProperties"); + + if(!clazz) + { + LOGE("%s:%d Cannot FindClass", __FUNCTION__, __LINE__); + + goto fail; + + return; + } + + constructor = env->GetMethodID(clazz,"", + "(Landroid/server/BluetoothFtpService;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;III)V"); + + if (constructor == NULL) + { + LOGE("%s:%d Cannot Get Constructor", __FUNCTION__, __LINE__); + + goto fail; + + return; + } + + result = env->NewObjectArray(array_count, clazz, NULL); + + if (result == NULL) + { + LOGE("%s:%d Cannot Construct an Array", __FUNCTION__, __LINE__); + + goto fail; + + return; + } + + for( int i = 0; i < array_count; i++) + { + DBusMessageIter arr_element; + + dbus_message_iter_recurse(&array, &arr_element); + + while (dbus_message_iter_get_arg_type(&arr_element) == DBUS_TYPE_DICT_ENTRY) + { + const char *dict_key; + DBusMessageIter dict_entry, dict_variant; + + dbus_message_iter_recurse(&arr_element, &dict_entry); + + dbus_message_iter_get_basic(&dict_entry, &dict_key); + + dbus_message_iter_next(&dict_entry); + + dbus_message_iter_recurse(&dict_entry, &dict_variant); + + int32_t itr_type; + + itr_type = dbus_message_iter_get_arg_type(&dict_variant); + if ((itr_type == DBUS_TYPE_STRING) || (itr_type == DBUS_TYPE_UINT64)) + { + + if (!strncmp(dict_key, "Name", sizeof("Name"))) + { + dbus_message_iter_get_basic(&dict_variant, &c_name); + LOGV("...Name...%s", c_name ); + } + else if (!strncmp(dict_key, "Type", sizeof("Type"))) + { + dbus_message_iter_get_basic(&dict_variant, &c_type); + LOGV("...Type...%s", c_type ); + } + else if (!strncmp(dict_key, "Size", sizeof("Size"))) + { + dbus_message_iter_get_basic(&dict_variant, &size); + LOGV("...Size...%d", (int32_t)size ); + } + else if (!strncmp(dict_key, "Permission", sizeof("Permission"))) + { + dbus_message_iter_get_basic(&dict_variant, &c_permission); + LOGV("...Permission... %s", c_permission); + } + else if (!strncmp(dict_key, "Modified", sizeof("Modified"))) + { + dbus_message_iter_get_basic(&dict_variant, &modified); + LOGV("...Modified...%d", (int32_t)modified); + } + else if (!strncmp(dict_key, "Accessed", sizeof("Accessed"))) + { + dbus_message_iter_get_basic(&dict_variant, &accessed); + LOGV("...Accessed...%d", (int32_t)accessed ); + } + else if (!strncmp(dict_key, "Created", sizeof("Created"))) + { + dbus_message_iter_get_basic(&dict_variant, &created); + LOGV("...Created...%d", (int32_t)created); + } + } + + dbus_message_iter_next(&arr_element); + } + + LOGV("Folder info: Name = %s Type = %s Size = %d", c_name , c_type, (int32_t)size); + LOGV(" Permission = %s Modified = %d Accessed = %d Created = %d", c_permission,(int32_t) modified, (int32_t)accessed, (int32_t)created); + + jstring name = env->NewStringUTF(c_name); + jstring type = env->NewStringUTF(c_type); + jstring permission = env->NewStringUTF(c_permission); + + /* Creating new object "ObjectProperties" and initializing */ + obj = env->NewObject(clazz, constructor, nat->me, + name, type, (jint) size, permission, + (jint)modified, (jint)accessed, (jint)created); + + env->DeleteLocalRef(name); + env->DeleteLocalRef(type); + env->DeleteLocalRef(permission); + + if (env->ExceptionCheck()) + { + LOGE("VM Exception occurred in native function %s (%s:%d)", + __FUNCTION__, __FILE__, __LINE__); + } + + /* Populating the array with object */ + env->SetObjectArrayElement(result, i, obj); + + env->DeleteLocalRef(obj); + + dbus_message_iter_next(&array); + }// for loop + + env->CallVoidMethod(nat->me, + method_onListFolderComplete, + session, + result, + is_error); + goto done; + +fail: + + env->CallVoidMethod(nat->me, + method_onListFolderComplete, + session, + NULL, + JNI_TRUE); + +done: + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + } + + if (clazz) { + env->DeleteLocalRef(clazz); + } + + if (result) { + env->DeleteLocalRef(result); + } + + if (session) { + env->DeleteLocalRef(session); + } + + free(c_session); + + return; +} + +static void onGetFileComplete(DBusMessage *msg, void *user, void *nat_cb) +{ + LOGI(__FUNCTION__); + + callback_data_t *callback_userdata = (callback_data_t *)user; + DBusError err; + jboolean is_error = JNI_FALSE; + JNIEnv *env = NULL; + nat->vm->GetEnv((void**)&env, nat->envVer); + + dbus_error_init(&err); + + LOGV("... session = %s", callback_userdata->session); + if (dbus_set_error_from_message(&err, msg)) + { + LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message); + + dbus_error_free(&err); + + is_error = JNI_TRUE; + } + + jstring session = env->NewStringUTF(callback_userdata->session); + jstring targetfile = env->NewStringUTF(callback_userdata->targetfile); + jstring sourcefile = env->NewStringUTF(callback_userdata->sourcefile); + + env->CallVoidMethod(nat->me, + method_onGetFileComplete, + session, + targetfile, + sourcefile, + is_error); + + if (env->ExceptionCheck()) + { + env->ExceptionDescribe(); + } + + env->DeleteLocalRef(session); + env->DeleteLocalRef(targetfile); + env->DeleteLocalRef(sourcefile); + + free(callback_userdata->session); + free(callback_userdata->targetfile); + free(callback_userdata->sourcefile); + free(callback_userdata); + + return; +} + +static void onPutFileComplete(DBusMessage *msg, void *user, void* nat_cb) +{ + LOGI(__FUNCTION__); + + callback_data_t *callback_userdata = (callback_data_t *)user; + DBusError err; + jboolean is_error = JNI_FALSE; + JNIEnv *env = NULL; + nat->vm->GetEnv((void**)&env, nat->envVer); + + dbus_error_init(&err); + + LOGV("... session = %s", callback_userdata->session); + if (dbus_set_error_from_message(&err, msg)) + { + LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message); + + dbus_error_free(&err); + + is_error = JNI_TRUE; + } + + jstring session = env->NewStringUTF(callback_userdata->session); + jstring targetfile = env->NewStringUTF(callback_userdata->targetfile); + jstring sourcefile = env->NewStringUTF(callback_userdata->sourcefile); + + env->CallVoidMethod(nat->me, + method_onPutFileComplete, + session, + sourcefile, + targetfile, + is_error); + + if (env->ExceptionCheck()) + { + env->ExceptionDescribe(); + } + + env->DeleteLocalRef(session); + env->DeleteLocalRef(targetfile); + env->DeleteLocalRef(sourcefile); + + free(callback_userdata->session); + free(callback_userdata->targetfile); + free(callback_userdata->sourcefile); + free(callback_userdata); + + return; +} + +static void onDeleteComplete(DBusMessage *msg, void *user, void *nat_cb) +{ + LOGI(__FUNCTION__); + + callback_data_t *callback_userdata = (callback_data_t *)user; + DBusError err; + jboolean is_error = JNI_FALSE; + JNIEnv *env = NULL; + nat->vm->GetEnv((void**)&env, nat->envVer); + + dbus_error_init(&err); + + LOGV("... session = %s", callback_userdata->session); + if (dbus_set_error_from_message(&err, msg)) + { + LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message); + + dbus_error_free(&err); + + is_error = JNI_TRUE; + } + + jstring session = env->NewStringUTF(callback_userdata->session); + jstring folder = env->NewStringUTF(callback_userdata->folder); + + env->CallVoidMethod(nat->me, + method_onDeleteComplete, + session, + folder, + is_error); + + if (env->ExceptionCheck()) + { + env->ExceptionDescribe(); + } + + env->DeleteLocalRef(session); + env->DeleteLocalRef(folder); + + free(callback_userdata->session); + free(callback_userdata->folder); + free(callback_userdata); + + return; +} + +static DBusHandlerResult ftp_agent_request_handler(DBusConnection *conn, + DBusMessage *msg) +{ + LOGI(__FUNCTION__); + const char *c_transfer; + JNIEnv *env = NULL; + nat->vm->GetEnv((void**)&env, nat->envVer); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &c_transfer, + DBUS_TYPE_INVALID)) + { + LOGE("%s: Invalid arguments for %s() method", __FUNCTION__, + OBEXD_DBUS_CLIENT_AGENT_REQUEST); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + LOGV("... transfer = %s\n", c_transfer); + + jstring transfer = env->NewStringUTF(c_transfer); + + jstring filename = (jstring) env->CallObjectMethod(nat->me, + method_onObexRequest, + transfer); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + } + + env->DeleteLocalRef(transfer); + + DBusMessage *reply = NULL; + const char *c_filename; + + if (filename != NULL) + { + reply = dbus_message_new_method_return(msg); + } + else + { + reply = dbus_message_new_error(msg, OBEXD_DBUS_ERROR_CANCELLED, NULL); + + } + + if( reply != NULL ) + { + if(filename != NULL) + { + c_filename = env->GetStringUTFChars(filename, NULL); + dbus_message_append_args(reply, + DBUS_TYPE_STRING, &c_filename, + DBUS_TYPE_INVALID); + } + + dbus_connection_send(nat->conn, reply, NULL); + dbus_message_unref(reply); + + if (filename != NULL) { + env->ReleaseStringUTFChars(filename, c_filename); + env->DeleteLocalRef(filename); + } + + return DBUS_HANDLER_RESULT_HANDLED; + } else if (filename != NULL) { + env->DeleteLocalRef(filename); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult ftp_agent_progress_handler(DBusConnection *conn, + DBusMessage *msg) +{ + LOGI(__FUNCTION__); + const char *c_transfer; + uint64_t transferred; + JNIEnv *env = NULL; + nat->vm->GetEnv((void**)&env, nat->envVer); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &c_transfer, + DBUS_TYPE_UINT64, &transferred, + DBUS_TYPE_INVALID)) + { + LOGE("%s: Invalid arguments for %s() method", __FUNCTION__, + OBEXD_DBUS_CLIENT_AGENT_PROGRESS); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + LOGV("... transfer = %s\n... bytes transferred = %d", c_transfer, + (int32_t) transferred); + + jstring transfer = env->NewStringUTF(c_transfer); + + env->CallVoidMethod(nat->me, method_onObexProgress, + transfer,(jint) transferred); + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + } + + env->DeleteLocalRef(transfer); + + DBusMessage *reply = dbus_message_new_method_return(msg); + + if (reply) + { + dbus_message_append_args(reply, DBUS_TYPE_INVALID); + + dbus_connection_send(nat->conn, reply, NULL); + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult ftp_agent_complete_handler(DBusConnection *conn, + DBusMessage *msg) +{ + LOGI(__FUNCTION__); + const char *c_transfer; + JNIEnv *env = NULL; + nat->vm->GetEnv((void**)&env, nat->envVer); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &c_transfer, + DBUS_TYPE_INVALID)) + { + LOGE("%s: Invalid arguments for %s() method", __FUNCTION__, + OBEXD_DBUS_CLIENT_AGENT_COMPLETE); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + LOGV("... transfer = %s", c_transfer); + + jstring transfer = env->NewStringUTF(c_transfer); + + env->CallVoidMethod(nat->me, method_onObexTransferComplete, + transfer, JNI_TRUE, NULL); + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + } + + env->DeleteLocalRef(transfer); + + DBusMessage *reply = dbus_message_new_method_return(msg); + + if (reply) + { + dbus_message_append_args(reply, DBUS_TYPE_INVALID); + + dbus_connection_send(nat->conn, reply, NULL); + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult ftp_agent_error_handler(DBusConnection *conn, + DBusMessage *msg) +{ + LOGI(__FUNCTION__); + const char *c_transfer, *c_message; + JNIEnv *env = NULL; + nat->vm->GetEnv((void**)&env, nat->envVer); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &c_transfer, + DBUS_TYPE_STRING, &c_message, + DBUS_TYPE_INVALID)) + { + LOGE("%s: Invalid arguments for %s() method", __FUNCTION__, + OBEXD_DBUS_CLIENT_AGENT_ERROR); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + LOGV("... transfer = %s\n... message = %s", c_transfer, c_message); + + jstring transfer = env->NewStringUTF(c_transfer); + jstring message = env->NewStringUTF(c_message); + + env->CallVoidMethod(nat->me, method_onObexTransferComplete, + transfer, + JNI_FALSE, + message); + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + } + + env->DeleteLocalRef(transfer); + env->DeleteLocalRef(message); + + DBusMessage *reply = dbus_message_new_method_return(msg); + + if (reply) + { + dbus_message_append_args(reply, DBUS_TYPE_INVALID); + + dbus_connection_send(nat->conn, reply, NULL); + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult ftp_agent_release_handler(DBusConnection *conn, + DBusMessage *msg) +{ + LOGI(__FUNCTION__); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_INVALID)) { + LOGE("%s: Invalid arguments for %s() method", __FUNCTION__, + OBEXD_DBUS_CLIENT_AGENT_RELEASE); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + JNIEnv *env = NULL; + nat->vm->GetEnv((void**)&env, nat->envVer); + const char* c_session = dbus_message_get_path(msg); + + if(c_session) + { + LOGV(" %s: Session %s", __FUNCTION__, c_session); + + jstring session = env->NewStringUTF(c_session); + + env->CallVoidMethod(nat->me, method_onObexSessionClosed, + session); + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + } + + env->DeleteLocalRef(session); + + // Unregister object handler for FTP client agent method calls + if (!dbus_connection_unregister_object_path(nat->conn, + c_session)) + { + LOGE("%s: Can't unregister register object path %s for agent!", + __FUNCTION__, c_session); + } + + } + + DBusMessage *reply = dbus_message_new_method_return(msg); + + if (reply) { + dbus_message_append_args(reply, DBUS_TYPE_INVALID); + + dbus_connection_send(nat->conn, reply, NULL); + + LOGV("ftp client agent released"); + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +/* Called by dbus during WaitForAndDispatchEventNative() */ +DBusHandlerResult ftpclient_agent(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + LOGI(__FUNCTION__); + if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_METHOD_CALL) { + LOGE("%s: not interested (not a method call).", __FUNCTION__); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + LOGE("%s: Received method %s:%s", __FUNCTION__, + dbus_message_get_interface(msg), dbus_message_get_member(msg)); + + if (dbus_message_is_method_call(msg, OBEXD_DBUS_CLIENT_AGENT_IFC, + OBEXD_DBUS_CLIENT_AGENT_RELEASE)) { + + return ftp_agent_release_handler(conn, msg); + + } else if (dbus_message_is_method_call(msg, OBEXD_DBUS_CLIENT_AGENT_IFC, + OBEXD_DBUS_CLIENT_AGENT_REQUEST)) { + + return ftp_agent_request_handler(conn, msg); + + } else if (dbus_message_is_method_call(msg, OBEXD_DBUS_CLIENT_AGENT_IFC, + OBEXD_DBUS_CLIENT_AGENT_PROGRESS)) { + + return ftp_agent_progress_handler(conn, msg); + + } else if (dbus_message_is_method_call(msg, OBEXD_DBUS_CLIENT_AGENT_IFC, + OBEXD_DBUS_CLIENT_AGENT_COMPLETE)) { + + return ftp_agent_complete_handler(conn, msg); + + } else if (dbus_message_is_method_call(msg, OBEXD_DBUS_CLIENT_AGENT_IFC, + OBEXD_DBUS_CLIENT_AGENT_ERROR)) { + + return ftp_agent_error_handler(conn, msg); + + } else { + LOGE("... ignored"); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} +DBusHandlerResult ftp_event_filter(DBusMessage *msg, JNIEnv *env) +{ + LOGI(__FUNCTION__); + DBusError err; + + if (!nat) { + LOGE("... skipping %s\n", __FUNCTION__); + LOGE("... ignored\n"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + dbus_error_init(&err); + + if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + DBusHandlerResult result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (dbus_message_is_signal(msg, OBEXD_DBUS_SRV_MGR_IFC, + OBEXD_DBUS_SRV_MGR_SGNL_FTP_SESS_CREATED)) { + char *c_session; + if (dbus_message_get_args(msg, &err, + DBUS_TYPE_OBJECT_PATH, &c_session, + DBUS_TYPE_INVALID)) { + LOGV("... Session Created = %s", c_session); + + } else { + LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); + } + + result = DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_signal(msg, OBEXD_DBUS_SRV_MGR_IFC, + OBEXD_DBUS_SRV_MGR_SGNL_FTP_SESS_REMOVED)) { + char *c_session; + if (dbus_message_get_args(msg, &err, + DBUS_TYPE_OBJECT_PATH, &c_session, + DBUS_TYPE_INVALID)) { + LOGV("... Session Removed = %s", c_session); + + } else { + LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); + } + + result = DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_signal(msg, OBEXD_DBUS_SRV_MGR_IFC, + OBEXD_DBUS_SRV_MGR_SGNL_FTP_TRANS_STARTED)) { + + char *c_transfer; + + if (dbus_message_get_args(msg, &err, + DBUS_TYPE_OBJECT_PATH, &c_transfer, + DBUS_TYPE_INVALID)) { + LOGV("... Transfer Started = %s", c_transfer); + } else { + LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); + } + + result = DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_signal(msg, OBEXD_DBUS_SRV_MGR_IFC, + OBEXD_DBUS_SRV_MGR_SGNL_FTP_TRANS_COMPLETED)) { + char *c_transfer; + dbus_bool_t c_success; + + if (dbus_message_get_args(msg, &err, + DBUS_TYPE_OBJECT_PATH, &c_transfer, + DBUS_TYPE_BOOLEAN, &c_success, + DBUS_TYPE_INVALID)) { + LOGV("... Transfer Completed = %s Success = %d", c_transfer, + c_success); + } else { + LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); + } + + result = DBUS_HANDLER_RESULT_HANDLED; + } + + if (result == DBUS_HANDLER_RESULT_NOT_YET_HANDLED) { + LOGV("... ignored"); + } + + if (env->ExceptionCheck()) { + LOGE("VM Exception occurred while handling %s.%s (%s) in %s," + " leaving for VM", + dbus_message_get_interface(msg), dbus_message_get_member(msg), + dbus_message_get_path(msg), __FUNCTION__); + } + + return result; +} +#endif +/** +* Connects to system D-Bus and registers with it. + * @returns true on success (even if adapter is present but disabled). + * @return false if dbus is down, or another serious error (out of memory) + */ +static bool initNative(JNIEnv* env, jobject object) +{ + LOGI(__FUNCTION__); + +#ifdef HAVE_BLUETOOTH + nat = (native_data_t *)calloc(1, sizeof(native_data_t)); + if (nat == NULL) + { + LOGE("%s: out of memory!", __FUNCTION__); + return JNI_FALSE; + } + nat->me = env->NewGlobalRef(object); + + env->GetJavaVM( &(nat->vm) ); + nat->envVer = env->GetVersion(); + + DBusError err; + dbus_error_init(&err); + dbus_threads_init_default(); + nat->conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err); + if (dbus_error_is_set(&err)) + { + LOGE("Could not get onto the system bus %s", err.message); + dbus_error_free(&err); + return JNI_FALSE; + } + dbus_connection_set_exit_on_disconnect(nat->conn, FALSE); +#endif + return JNI_TRUE; +} +static void cleanupNative(JNIEnv* env, jobject object) +{ + LOGI(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + if (nat) + { + dbus_connection_close(nat->conn); + env->DeleteGlobalRef(nat->me); + free(nat); + nat = NULL; + } + LOGI(__FUNCTION__); +#endif +} +/** +* Creates a new OBEX session. +* +* Device is configured as follows +* target as "ftp" and destination as the given address. +* +* This is an asynchronous call as it invokes methods on a remote +* object. +* The object path is obtained in the onCreateSessionComplete method. +* @params destination bt address +* @return FALSE if send fails, TRUE otherwise +*/ +static jboolean createSessionNative(JNIEnv* env, jobject object, jstring address ) +{ + LOGI(__FUNCTION__); + jboolean result = JNI_FALSE; +#ifdef HAVE_BLUETOOTH + if( nat && address) + { + DBusMessage *msg = dbus_message_new_method_call(OBEXD_DBUS_CLIENT_SVC, + OBEXD_DBUS_CLIENT_PATH, + OBEXD_DBUS_CLIENT_IFC, + OBEXD_DBUS_CLIENT_CREATE); + + if (msg == NULL) + { + return JNI_FALSE; + } + + DBusMessageIter iter, dict; + + dbus_message_iter_init_append(msg, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + const char *c_address = env->GetStringUTFChars(address, NULL); + LOGV(" createSessionNative ... address = %s", c_address); + + dbus_append_ss_dict_entry(&dict, "Target", "ftp"); + dbus_append_ss_dict_entry(&dict, "Destination", c_address); + + dbus_message_iter_close_container(&iter, &dict); + + dbus_async_call_t *pending = NULL; + + pending = (dbus_async_call_t *) malloc(sizeof(dbus_async_call_t)); + + if (pending) + { + DBusPendingCall *call; + + char *context_address = (char *) calloc(BTADDR_SIZE, + sizeof(char)); + + strlcpy(context_address, c_address, BTADDR_SIZE); // for callback + + pending->env = env; + pending->user_cb = onCreateSessionComplete; + pending->user = context_address; + pending->nat = nat; + + dbus_bool_t reply = dbus_connection_send_with_reply(nat->conn, + msg, + &call, + 10*1000); + if (reply == TRUE) + { + dbus_pending_call_set_notify(call, + dbus_func_args_async_callback, + pending, + NULL); + + result = JNI_TRUE; + } + else + { + free(pending); + } + } + } +#endif + return result; +} +/** +* Closes an OBEX session. +* @params obex session. +* @returns TRUE on success, FALSE otherwise +*/ +static jboolean closeSessionNative(JNIEnv* env, jobject object, jstring session ) +{ + LOGI(__FUNCTION__); + jboolean result = JNI_FALSE; +#ifdef HAVE_BLUETOOTH + if (nat && session) + { + const char *c_session = env->GetStringUTFChars(session, NULL); + + LOGV("Session = %s\n", c_session); + + + DBusError err; + + dbus_error_init(&err); + + DBusMessage *reply = dbus_func_args_error(env, nat->conn, &err, + OBEXD_DBUS_CLIENT_SVC, + c_session, + OBEXD_DBUS_CLIENT_SESSION_IFC, + OBEXD_DBUS_CLIENT_SESSION_CLOSE, + DBUS_TYPE_INVALID); + + if (reply) + { + dbus_message_unref(reply); + result = JNI_TRUE; + } + else + { + LOG_AND_FREE_DBUS_ERROR(&err); + } + + env->ReleaseStringUTFChars(session, c_session); + } + + if (env->ExceptionCheck()) + { + LOGE("VM Exception occurred in native function %s (%s:%d)", + __FUNCTION__, __FILE__, __LINE__); + } +#endif + return result; +} +/** +* Changes the current folder of the remote device to the specified folder. +* @params OBEX session object path. +* @folder name to be changed to. +* @returns FALSE if send fails, TRUE otherwise +*/ +static jboolean changeFolderNative(JNIEnv* env, jobject object, jstring session, jstring folder) +{ + LOGI(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + if (nat && session) + { + const char *c_session = env->GetStringUTFChars(session, NULL); + LOGV("Session = %s\n", c_session); + + const char *c_folder = env->GetStringUTFChars(folder, NULL); + LOGV("Folder = %s\n", c_folder); + + size_t session_sz = env->GetStringUTFLength(session) + 1; + size_t folder_sz = env->GetStringUTFLength(folder) + 1; + + callback_data_t *callback_userdata = (callback_data_t *)malloc(sizeof(callback_data_t));// callback data + + callback_userdata->session = (char *)malloc(session_sz); // storing session + strncpy(callback_userdata->session, c_session, session_sz); + + callback_userdata->folder = (char *)malloc(folder_sz); // storing folder + strncpy(callback_userdata->folder, c_folder, folder_sz); + + bool reply = dbus_func_args_async(env, nat->conn, -1, + onChangeFolderComplete, (void *)callback_userdata, nat, + OBEXD_DBUS_CLIENT_SVC, c_session, + OBEXD_DBUS_CLIENT_FTP_IFC, OBEXD_DBUS_CLIENT_FTP_CHANGE_FOLDER, + DBUS_TYPE_STRING, &c_folder, + DBUS_TYPE_INVALID); + env->ReleaseStringUTFChars(session, c_session); + env->ReleaseStringUTFChars(session, c_folder); + if (!reply) + { + free( callback_userdata->session ); + free( callback_userdata->folder ); + free( callback_userdata ); + return JNI_FALSE; + } + return JNI_TRUE; + } +#endif + return JNI_FALSE; +} + +/** +* Creates a new folder in the remote device. +* @params OBEX session object path. +* @folder name to be created. +* @returns FALSE if send fails, TRUE otherwise +*/ +static jboolean createFolderNative(JNIEnv* env, jobject object, jstring session, jstring folder) +{ + LOGI(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + if (nat && session) + { + const char *c_session = env->GetStringUTFChars(session, NULL); + LOGV("Session = %s\n", c_session); + + const char *c_folder = env->GetStringUTFChars(folder, NULL); + LOGV("Folder = %s\n", c_folder); + + size_t session_sz = env->GetStringUTFLength(session) + 1; + size_t folder_sz = env->GetStringUTFLength(folder) + 1; + + callback_data_t *callback_userdata = (callback_data_t *)malloc(sizeof(callback_data_t));// callback data + + callback_userdata->session = (char *)malloc(session_sz); // storing session + strncpy(callback_userdata->session, c_session, session_sz); + + callback_userdata->folder = (char *)malloc(folder_sz); // storing folder + strncpy(callback_userdata->folder, c_folder, folder_sz); + + bool reply = dbus_func_args_async(env, nat->conn, -1, + onCreateFolderComplete, (void *)callback_userdata, nat, + OBEXD_DBUS_CLIENT_SVC, c_session, + OBEXD_DBUS_CLIENT_FTP_IFC, OBEXD_DBUS_CLIENT_FTP_CREATE_FOLDER, + DBUS_TYPE_STRING, &c_folder, + DBUS_TYPE_INVALID); + env->ReleaseStringUTFChars(session, c_session); + env->ReleaseStringUTFChars(session, c_folder); + if (!reply) + { + free( callback_userdata->session ); + free( callback_userdata->folder ); + free( callback_userdata ); + return JNI_FALSE; + } + return JNI_TRUE; + } +#endif + return JNI_FALSE; +} + +/** +* Returns a dictionary containing information about the current folder content. + The following keys are defined: + jstring Name : Object name in UTF-8 format + jstring Type : Either "folder" or "file" + ulong Size : Object size or number of items in folder + jstring Permission : Group, owner and other permission + ulong Modified : Last change + ulong Accessed : Last access + ulong Created : Creation date + + This is an asynchronous call and the results will be obtained in + onListFolderComplete method. +* @params OBEX session object path. +* @returns FALSE if send fails, TRUE other wise. +*/ +static jboolean listFolderNative(JNIEnv* env, jobject object, jstring session) +{ + LOGI(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + +if (nat && session) + { + const char *c_session = env->GetStringUTFChars(session, NULL); + LOGV("listFolderNative ..... Session = %s\n", c_session); + + size_t session_sz = env->GetStringUTFLength(session) + 1; + char *c_session_copy = (char *)malloc(session_sz); // callback data + strncpy(c_session_copy, c_session, session_sz); + + bool reply = dbus_func_args_async(env, nat->conn, 10*1000, + onListFolderComplete, (void *)c_session_copy, nat, + OBEXD_DBUS_CLIENT_SVC, c_session, + OBEXD_DBUS_CLIENT_FTP_IFC, OBEXD_DBUS_CLIENT_FTP_LIST_FOLDER, + DBUS_TYPE_INVALID); + env->ReleaseStringUTFChars(session, c_session); + if (!reply) + { + free(c_session_copy); + return JNI_FALSE; + } + return JNI_TRUE; + } +#endif + return JNI_FALSE; +} + +/** +* Copies the source file from the remote device to the target file on +* the local system. +* This will trigger a transfer object to be created and the agent will request +* the file name where to store the file. +* Transfer progress, transfer complete and error if any will be indicated. +* @params OBEX session object path. +* @params sourcefile(remote device) to be copied from +* @params targerfile(local system) to be copied into +* @returns FALSE if send fails, TRUE otherwise. +*/ +static jboolean getFileNative(JNIEnv* env, jobject object, jstring session, + jstring targetfile, jstring sourcefile) +{ + LOGI(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + +if (nat && session) + { + + const char *c_session = env->GetStringUTFChars(session, NULL); + LOGV("Session = %s\n", c_session); + + const char *c_targetfile = env->GetStringUTFChars(targetfile, NULL); + LOGV("Targetfile = %s\n", c_targetfile); + + const char *c_sourcefile = env->GetStringUTFChars(sourcefile, NULL); + LOGV("Sourcefile = %s\n", c_sourcefile); + + size_t session_sz = env->GetStringUTFLength(session) + 1; + size_t targetfile_sz = env->GetStringUTFLength(targetfile) + 1; + size_t sourcefile_sz = env->GetStringUTFLength(sourcefile) + 1; + + callback_data_t *callback_userdata = (callback_data_t *)malloc(sizeof(callback_data_t));// callback data + + callback_userdata->session = (char *)malloc(session_sz); // storing session + strncpy(callback_userdata->session, c_session, session_sz); + + callback_userdata->targetfile= (char *)malloc(targetfile_sz); // storing targetfile + strncpy(callback_userdata->targetfile, c_targetfile, targetfile_sz); + + callback_userdata->sourcefile= (char *)malloc(sourcefile_sz); // storing sourcefile + strncpy(callback_userdata->sourcefile, c_sourcefile, sourcefile_sz); + + bool reply = dbus_func_args_async(env, nat->conn, 10*1000, + onGetFileComplete, (void *)callback_userdata, nat, + OBEXD_DBUS_CLIENT_SVC, c_session, + OBEXD_DBUS_CLIENT_FTP_IFC, OBEXD_DBUS_CLIENT_FTP_GET_FILE, + DBUS_TYPE_STRING, &c_targetfile, + DBUS_TYPE_STRING, &c_sourcefile, + DBUS_TYPE_INVALID); + + env->ReleaseStringUTFChars(session, c_session); + env->ReleaseStringUTFChars(session, c_targetfile); + env->ReleaseStringUTFChars(session, c_sourcefile); + if (!reply) + { + free( callback_userdata->session ); + free( callback_userdata->targetfile ); + free( callback_userdata->sourcefile); + free( callback_userdata); + return JNI_FALSE; + } + return JNI_TRUE; + } +#endif + return JNI_FALSE; +} +/** +* Copies the source file from the local filesystem to the target file on +* the remote device. +* This will trigger a transfer object to be created and it will request +* the file name to show to the remote device. +* Transfer progress, transfer complete and error if any will be indicated. +* @params OBEX session object path. +* @params sourcefile(local system) to be copied from +* @params targerfile(remote device) to be copied into +* @returns FALSE if send fails, TRUE otherwise. +*/ +static jboolean putFileNative(JNIEnv* env, jobject object, jstring session, + jstring sourcefile, jstring targetfile) +{ + LOGI(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + +if (nat && session) + { + const char *c_session = env->GetStringUTFChars(session, NULL); + LOGV("Session = %s\n", c_session); + + const char *c_targetfile = env->GetStringUTFChars(targetfile, NULL); + LOGV("Targetfile = %s\n", c_targetfile); + + const char *c_sourcefile = env->GetStringUTFChars(sourcefile, NULL); + LOGV("Sourcefile = %s\n", c_sourcefile); + + size_t session_sz = env->GetStringUTFLength(session) + 1; + size_t targetfile_sz = env->GetStringUTFLength(targetfile) + 1; + size_t sourcefile_sz = env->GetStringUTFLength(sourcefile) + 1; + + callback_data_t *callback_userdata = (callback_data_t *)malloc(sizeof(callback_data_t));// callback data + + callback_userdata->session = (char *)malloc(session_sz); // storing session + strncpy(callback_userdata->session, c_session, session_sz); + + callback_userdata->targetfile= (char *)malloc(targetfile_sz); // storing targetfile + strncpy(callback_userdata->targetfile, c_targetfile, targetfile_sz); + + callback_userdata->sourcefile= (char *)malloc(sourcefile_sz); // storing sourcefile + strncpy(callback_userdata->sourcefile, c_sourcefile, sourcefile_sz); + bool reply = dbus_func_args_async(env, nat->conn, -1, + onPutFileComplete, (void *)callback_userdata, nat, + OBEXD_DBUS_CLIENT_SVC, c_session, + OBEXD_DBUS_CLIENT_FTP_IFC, OBEXD_DBUS_CLIENT_FTP_PUT_FILE, + DBUS_TYPE_STRING, &c_sourcefile, + DBUS_TYPE_STRING, &c_targetfile, + DBUS_TYPE_INVALID); + env->ReleaseStringUTFChars(session, c_session); + env->ReleaseStringUTFChars(session, c_targetfile); + env->ReleaseStringUTFChars(session, c_sourcefile); + if (!reply) + { + free( callback_userdata->session ); + free( callback_userdata->targetfile ); + free( callback_userdata->sourcefile); + free( callback_userdata); + return JNI_FALSE; + } + return JNI_TRUE; + } +#endif + return JNI_FALSE; +} +/** +* Deletes the specified file or folder. +* @params OBEX session object path. +* @params file or folder name to be deleted +* @returns FALSE if delete fails, TRUE otherwise. +*/ +static jboolean deleteNative(JNIEnv* env, jobject object, jstring session, jstring name) +{ + LOGI(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + +if (nat && session) + { + const char *c_session = env->GetStringUTFChars(session, NULL); + LOGV("Session = %s\n", c_session); + + const char *c_name = env->GetStringUTFChars(name, NULL); + LOGV("Name = %s\n", c_name); + + size_t session_sz = env->GetStringUTFLength(session) + 1; + size_t name_sz = env->GetStringUTFLength(name) + 1; + + callback_data_t *callback_userdata = (callback_data_t *)malloc(sizeof(callback_data_t));// callback data + + callback_userdata->session = (char *)malloc(session_sz); // storing session + strncpy(callback_userdata->session, c_session, session_sz); + + callback_userdata->folder = (char *)malloc(name_sz); // storing folder + strncpy(callback_userdata->folder, c_name, name_sz); + + bool reply = dbus_func_args_async(env, nat->conn, -1, + onDeleteComplete, (void *)callback_userdata, nat, + OBEXD_DBUS_CLIENT_SVC, c_session, + OBEXD_DBUS_CLIENT_FTP_IFC, OBEXD_DBUS_CLIENT_FTP_DELETE, + DBUS_TYPE_STRING, &c_name, + DBUS_TYPE_INVALID); + env->ReleaseStringUTFChars(session, c_session); + env->ReleaseStringUTFChars(session, c_name); + if (!reply) + { + free( callback_userdata->session ); + free( callback_userdata->folder ); + free( callback_userdata ); + return JNI_FALSE; + } + return JNI_TRUE; + } +#endif + return JNI_FALSE; +} +/** +* Cancels the session's transfer. +* @params transfer object path. +* @returns FALSE if transfer does not belongs to the session, TRUE otherwise. +*/ +static jboolean cancelTransferNative(JNIEnv* env, jobject object, jstring transfer) +{ + LOGI(__FUNCTION__); + jboolean result = JNI_FALSE; +#ifdef HAVE_BLUETOOTH + if (nat && transfer) + { + const char *c_transfer = env->GetStringUTFChars(transfer, NULL); + + LOGV("Transfer = %s\n", c_transfer); + + DBusError err; + + dbus_error_init(&err); + + DBusMessage *reply = dbus_func_args_error(env, nat->conn, &err, + OBEXD_DBUS_CLIENT_SVC, + c_transfer, + OBEXD_DBUS_CLIENT_TRANS_IFC, + OBEXD_DBUS_CLIENT_TRANS_CANCEL, + DBUS_TYPE_INVALID); + + if (reply) + { + dbus_message_unref(reply); + result = JNI_TRUE; + } + else + { + LOG_AND_FREE_DBUS_ERROR(&err); + } + + env->ReleaseStringUTFChars(transfer, c_transfer); + } + + if (env->ExceptionCheck()) + { + LOGE("VM Exception occurred in native function %s (%s:%d)", + __FUNCTION__, __FILE__, __LINE__); + } +#endif + return result; + +} + +/** +* Returns all the properties for the transfer. + jstring Name of the transferred object. + jint Size of the transferred object. If the size is unknown, + then this property will not be present. + jstring Filename Complete name of the file being received or sent. +* @params transfer object path. +*/ +static jobject obexTransferGetPropertiesNative(JNIEnv* env, jobject object, jstring transfer) +{ + LOGI(__FUNCTION__); + jobject result = NULL; +#ifdef HAVE_BLUETOOTH + if (nat && transfer ) + { + const char *c_transfer = env->GetStringUTFChars(transfer, NULL); + + LOGV("%s: Transfer = %s", __FUNCTION__, c_transfer); + + DBusMessage *reply; + + reply = dbus_func_args(env, nat->conn, OBEXD_DBUS_CLIENT_SVC, + c_transfer, OBEXD_DBUS_CLIENT_TRANS_IFC, + OBEXD_DBUS_CLIENT_TRANS_GETPROPS, + DBUS_TYPE_INVALID); + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + } + + env->ReleaseStringUTFChars(transfer, c_transfer); + + if (reply == NULL) + { + LOGE("%s:%d Cannot create message to D-Bus", __FUNCTION__, __LINE__); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + + } + return result; + } + + DBusMessageIter iter, array; + const char *name = NULL; + const char *filename = NULL; + uint64_t size = 0; + + dbus_message_iter_init(reply, &iter); + dbus_message_iter_recurse(&iter, &array); + + while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) + { + const char *dict_key; + DBusMessageIter dict_entry, dict_variant; + + dbus_message_iter_recurse(&array, &dict_entry); + + dbus_message_iter_get_basic(&dict_entry, &dict_key); + + dbus_message_iter_next(&dict_entry); + + dbus_message_iter_recurse(&dict_entry, &dict_variant); + + int32_t type; + + type = dbus_message_iter_get_arg_type(&dict_variant); + + if ((type == DBUS_TYPE_STRING) || (type == DBUS_TYPE_INT64)) + { + if (!strncmp(dict_key, "Name", sizeof("Name"))) + { + dbus_message_iter_get_basic(&dict_variant, &name); + } + else if (!strncmp(dict_key, "Size", sizeof("Size"))) + { + dbus_message_iter_get_basic(&dict_variant, &size); + } + else if (!strncmp(dict_key, "Filename", sizeof("Filename"))) + { + dbus_message_iter_get_basic(&dict_variant, & filename); + } + } + + dbus_message_iter_next(&array); + } + + LOGV("Properties: Name = %s Size = %d Filename = %s", name , + (int32_t) size, filename); + + dbus_message_unref(reply); + + jclass clazz = + env->FindClass("android/server/BluetoothFtpService$TransferProperties"); + + if (clazz == NULL) + { + LOGE("%s:%d Cannot FindClass", __FUNCTION__, __LINE__); + return result; + } + + jmethodID constructor = env->GetMethodID(clazz,"","(Landroid/server/BluetoothFtpService;Ljava/lang/String;ILjava/lang/String;)V"); + + if (constructor == NULL) + { + LOGE("%s:%d Cannot Get Constructor", __FUNCTION__, __LINE__); + return result; + } + + result = env->NewObject(clazz, constructor, object, + env->NewStringUTF(name), (jint) size, + env->NewStringUTF(filename)); + } + + if (env->ExceptionCheck()) + { + LOGE("VM Exception occurred in native function %s (%s:%d)", + __FUNCTION__, __FILE__, __LINE__); + } +#endif + return result; +} + + +static JNINativeMethod sMethods[] = { + + {"initNative", "()Z", (void *)initNative}, + {"cleanupNative", "()V", (void *)cleanupNative}, + + /* Obexd 0.8 API */ + {"createSessionNative", "(Ljava/lang/String;)Z", (void *) createSessionNative}, + {"closeSessionNative", "(Ljava/lang/String;)Z", (void *) closeSessionNative}, + {"changeFolderNative", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*) changeFolderNative}, + {"createFolderNative", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*) createFolderNative}, + {"listFolderNative", "(Ljava/lang/String;)Z", (void*) listFolderNative}, + {"getFileNative", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z", (void*) getFileNative}, + {"putFileNative", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z", (void*) putFileNative}, + {"deleteNative", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*) deleteNative}, + {"cancelTransferNative", "(Ljava/lang/String;)Z", (void*) cancelTransferNative}, + {"obexTransferGetPropertiesNative","(Ljava/lang/String;)Landroid/server/BluetoothFtpService$TransferProperties;", (void *) obexTransferGetPropertiesNative}, + +}; + +int register_android_server_BluetoothFtpService(JNIEnv *env) +{ + jclass clazz = env->FindClass("android/server/BluetoothFtpService"); + if (clazz == NULL) + { + LOGE("Can't find android/server/BluetoothFtpService"); + return -1; + } +#ifdef HAVE_BLUETOOTH + method_onCreateSessionComplete = env->GetMethodID( clazz, "onCreateSessionComplete", "(Ljava/lang/String;Ljava/lang/String;Z)V" ); + method_onChangeFolderComplete = env->GetMethodID( clazz, "onChangeFolderComplete", "(Ljava/lang/String;Ljava/lang/String;Z)V" ); + method_onCreateFolderComplete = env->GetMethodID( clazz, "onCreateFolderComplete", "(Ljava/lang/String;Ljava/lang/String;Z)V" ); + method_onListFolderComplete = env->GetMethodID( clazz, "onListFolderComplete", "(Ljava/lang/String;[Landroid/server/BluetoothFtpService$ObjectProperties;Z)V" ); + method_onGetFileComplete = env->GetMethodID( clazz, "onGetFileComplete", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V" ); + method_onPutFileComplete = env->GetMethodID( clazz, "onPutFileComplete", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V" ); + method_onDeleteComplete = env->GetMethodID( clazz, "onDeleteComplete", "(Ljava/lang/String;Ljava/lang/String;Z)V" ); + method_onObexRequest = env->GetMethodID( clazz, "onObexRequest", "(Ljava/lang/String;)Ljava/lang/String;" ); + method_onObexProgress = env->GetMethodID( clazz, "onObexProgress", "(Ljava/lang/String;I)V" ); + method_onObexTransferComplete = env->GetMethodID( clazz, "onObexTransferComplete", "(Ljava/lang/String;ZLjava/lang/String;)V" ); + method_onObexSessionClosed = env->GetMethodID(clazz,"onObexSessionClosed", "(Ljava/lang/String;)V"); +#endif + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + } + + return AndroidRuntime::registerNativeMethods(env, + "android/server/BluetoothFtpService", sMethods, NELEM(sMethods)); +} + +} /* namespace android */ diff --git a/core/jni/android_server_BluetoothOppService.cpp b/core/jni/android_server_BluetoothOppService.cpp new file mode 100755 index 0000000..562a478 --- /dev/null +++ b/core/jni/android_server_BluetoothOppService.cpp @@ -0,0 +1,1201 @@ +/* + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Code Aurora nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_TAG "BluetoothOppService.cpp" + +#include "android_bluetooth_common.h" +#include "android_runtime/AndroidRuntime.h" +#include "JNIHelp.h" +#include "jni.h" +#include "utils/Log.h" +#include "utils/misc.h" + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_BLUETOOTH +#include +#endif + +namespace android { + +#ifdef HAVE_BLUETOOTH +static jmethodID method_onObexAuthorize; +static jmethodID method_onObexAuthorizeCancel; +static jmethodID method_onObexRequest; +static jmethodID method_onObexProgress; +static jmethodID method_onObexTransferComplete; +static jmethodID method_onSendFilesComplete; +static jmethodID method_onPullBusinessCardComplete; + +typedef struct { + DBusConnection *conn; + jobject me; // for callbacks to java + /* our vm and env Version for future env generation */ + JavaVM *vm; + int envVer; +} native_data_t; + +static native_data_t *nat = NULL; // global native data + +extern void dbus_func_args_async_callback(DBusPendingCall *call, void *data); +static void onSendFilesComplete(DBusMessage *msg, void *user, void *nat_cb); +static void onPullBusinessCardComplete(DBusMessage *msg, void *user, void *nat_cb); +static DBusHandlerResult oppclient_agent_release_handler(DBusConnection *conn, + DBusMessage *msg); +static DBusHandlerResult oppclient_agent_request_handler(DBusConnection *conn, + DBusMessage *msg); +static DBusHandlerResult oppclient_agent_progress_handler(DBusConnection *conn, + DBusMessage *msg); +static DBusHandlerResult oppclient_agent_complete_handler(DBusConnection *conn, + DBusMessage *msg); +static DBusHandlerResult oppclient_agent_error_handler(DBusConnection *conn, + DBusMessage *msg); +static DBusHandlerResult oppserver_agent_authorize_handler(DBusConnection *conn, + DBusMessage *msg); +static DBusHandlerResult oppserver_agent_cancel_handler(DBusConnection *conn, + DBusMessage *msg); + +#endif + +/* Returns true on success (even if adapter is present but disabled). + * Return false if dbus is down, or another serious error (out of memory) + */ +static bool initNative(JNIEnv* env, jobject object) { + LOGI(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + nat = (native_data_t *)calloc(1, sizeof(native_data_t)); + if (NULL == nat) { + LOGE("%s: out of memory!", __FUNCTION__); + return false; + } + nat->me = env->NewGlobalRef(object); + + env->GetJavaVM( &(nat->vm) ); + nat->envVer = env->GetVersion(); + + DBusError err; + dbus_error_init(&err); + dbus_threads_init_default(); + nat->conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err); + if (dbus_error_is_set(&err)) { + LOGE("Could not get onto the system bus: %s", err.message); + dbus_error_free(&err); + return false; + } + dbus_connection_set_exit_on_disconnect(nat->conn, FALSE); +#endif /*HAVE_BLUETOOTH*/ + return true; +} + +static void cleanupNative(JNIEnv* env, jobject object) { +#ifdef HAVE_BLUETOOTH + LOGI(__FUNCTION__); + if (nat) { + dbus_connection_close(nat->conn); + env->DeleteGlobalRef(nat->me); + free(nat); + nat = NULL; + } +#endif +} + +static jboolean sendFilesNative(JNIEnv *env, jobject object, + jstring address, + jobjectArray txFilenames) { + LOGI(__FUNCTION__); + jboolean result = JNI_FALSE; +#ifdef HAVE_BLUETOOTH + if (nat) { + DBusMessage *msg = dbus_message_new_method_call(OBEXD_DBUS_CLIENT_SVC, + OBEXD_DBUS_CLIENT_PATH, + OBEXD_DBUS_CLIENT_IFC, + OBEXD_DBUS_CLIENT_SENDFILES); + + if (msg == NULL) { + return JNI_FALSE; + } + + DBusMessageIter iter, array, dict_entry; + + dbus_message_iter_init_append(msg, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &array); + + const char *c_address = env->GetStringUTFChars(address, NULL); + LOGV("%s Destination Address = %s\n", __FUNCTION__, c_address); + + dbus_append_ss_dict_entry(&array, "Destination", c_address); + dbus_message_iter_close_container(&iter, &array); + + jsize i = 0; + jsize length = 0; + + length = env->GetArrayLength((jarray) txFilenames); + + LOGV("Number of files = %d", length); + + jstring java_string[length]; + const char **filename = (const char **) calloc((size_t) length, + sizeof(filename)); + + if (filename != NULL) { + for (i = 0; i < length; i++) { + java_string[i] = (jstring) env->GetObjectArrayElement(txFilenames, i); + + filename[i] = env->GetStringUTFChars(java_string[i], NULL); + + LOGV("Filename[%d] = %s", i, filename[i]); + } + + const char *path = ANDROID_OPPCLIENT_AGENT_PATH; + + dbus_message_append_args(msg, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, + &filename, length, DBUS_TYPE_OBJECT_PATH, + &path, DBUS_TYPE_INVALID); + + dbus_async_call_t *pending = NULL; + + pending = (dbus_async_call_t *) malloc(sizeof(dbus_async_call_t)); + + if (pending) { + DBusPendingCall *call; + + char *context_address = (char *) calloc(BTADDR_SIZE, + sizeof(char)); + + strlcpy(context_address, c_address, BTADDR_SIZE); // for callback + + pending->env = env; + pending->user_cb = onSendFilesComplete; + pending->user = context_address; + pending->nat = nat; + + dbus_bool_t reply = dbus_connection_send_with_reply(nat->conn, + msg, + &call, + 10*1000); + if (reply == TRUE) { + dbus_pending_call_set_notify(call, + dbus_func_args_async_callback, + pending, + NULL); + + result = JNI_TRUE; + } else { + LOGE("Failed to Send D-BUS message"); + free(pending); + } + } + + for (i = 0; i < length; i++) { + env->ReleaseStringUTFChars(java_string[i], filename[i]); + } + + free(filename); + } + + env->ReleaseStringUTFChars(address, c_address); + dbus_message_unref(msg); + } + + if (env->ExceptionCheck()) { + LOGE("VM Exception occurred in native function %s (%s:%d)", + __FUNCTION__, __FILE__, __LINE__); + } +#endif + return result; +} + +static jboolean pullBusinessCardNative(JNIEnv *env, jobject object, + jstring address, jstring rxFilename) { + LOGI(__FUNCTION__); + jboolean result = JNI_FALSE; +#ifdef HAVE_BLUETOOTH + if (nat) { + DBusMessage *msg = dbus_message_new_method_call(OBEXD_DBUS_CLIENT_SVC, + OBEXD_DBUS_CLIENT_PATH, + OBEXD_DBUS_CLIENT_IFC, + OBEXD_DBUS_CLIENT_PULLCARD); + + if (msg == NULL) { + return JNI_FALSE; + } + + DBusMessageIter iter, array, dict_entry; + + dbus_message_iter_init_append(msg, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &array); + + const char *c_address = env->GetStringUTFChars(address, NULL); + LOGV("%s Destination Address = %s\n", __FUNCTION__, c_address); + + dbus_append_ss_dict_entry(&array, "Destination", c_address); + dbus_message_iter_close_container(&iter, &array); + + const char *filename = env->GetStringUTFChars(rxFilename, NULL); + LOGV("%s Filename = %s\n", __FUNCTION__, filename); + + dbus_message_append_args(msg, DBUS_TYPE_STRING, &filename, + DBUS_TYPE_INVALID); + + dbus_async_call_t *pending = NULL; + + pending = (dbus_async_call_t *) malloc(sizeof(dbus_async_call_t)); + + if (pending) { + DBusPendingCall *call; + + char *context_address = (char *) calloc(BTADDR_SIZE, sizeof(char)); + + strlcpy(context_address, c_address, BTADDR_SIZE); // for callback + + pending->env = env; + pending->user_cb = onPullBusinessCardComplete; + pending->user = context_address; + pending->nat = nat; + + dbus_bool_t reply = dbus_connection_send_with_reply(nat->conn, + msg, + &call, + 10*1000); + if (reply == TRUE) { + dbus_pending_call_set_notify(call, + dbus_func_args_async_callback, + pending, + NULL); + + result = JNI_TRUE; + } else { + free(pending); + } + } + + env->ReleaseStringUTFChars(rxFilename, filename); + env->ReleaseStringUTFChars(address, c_address); + dbus_message_unref(msg); + } + + if (env->ExceptionCheck()) { + LOGE("VM Exception occurred in native function %s (%s:%d)", + __FUNCTION__, __FILE__, __LINE__); + } +#endif + return result; +} + +static jboolean cancelTransferNative(JNIEnv *env, jobject object, + jstring transfer, jboolean isServer) { + LOGI(__FUNCTION__); + jboolean result = JNI_FALSE; +#ifdef HAVE_BLUETOOTH + if (nat && transfer) { + const char *c_transfer = env->GetStringUTFChars(transfer, NULL); + DBusMessage *reply = NULL; + DBusError err; + + LOGV("Transfer = %s\n", c_transfer); + + dbus_error_init(&err); + + if (isServer == JNI_TRUE) { + reply = dbus_func_args_error(env, nat->conn, &err, + OBEXD_DBUS_SRV_SVC, + c_transfer, + OBEXD_DBUS_SRV_TRANS_IFC, + OBEXD_DBUS_SRV_TRANS_CANCEL, + DBUS_TYPE_INVALID); + } else { + reply = dbus_func_args_error(env, nat->conn, &err, + OBEXD_DBUS_CLIENT_SVC, + c_transfer, + OBEXD_DBUS_CLIENT_TRANS_IFC, + OBEXD_DBUS_CLIENT_TRANS_CANCEL, + DBUS_TYPE_INVALID); + } + + if (reply) { + dbus_message_unref(reply); + result = JNI_TRUE; + } else { + LOG_AND_FREE_DBUS_ERROR(&err); + } + + env->ReleaseStringUTFChars(transfer, c_transfer); + } + + if (env->ExceptionCheck()) { + LOGE("VM Exception occurred in native function %s (%s:%d)", + __FUNCTION__, __FILE__, __LINE__); + } +#endif + return result; +} + +static jboolean obexAuthorizeCompleteNative(JNIEnv *env, jobject object, + jboolean accept, jstring filename, + jint message) { + LOGI(__FUNCTION__); + jboolean result = JNI_FALSE; +#ifdef HAVE_BLUETOOTH + if (nat) { + DBusMessage *reply; + DBusMessage *msg = (DBusMessage *) message; + bool error; + const char *c_filename; + + LOGV("Authorization: accept = %d", accept); + + if (accept == JNI_TRUE) { + reply = dbus_message_new_method_return(msg); + + error = FALSE; + } else { + reply = dbus_message_new_error(msg, + OBEXD_DBUS_ERROR_CANCELLED, + NULL); + + error = TRUE; + } + + if (reply != NULL) { + if (error == FALSE) { + c_filename = env->GetStringUTFChars(filename, NULL); + + dbus_message_append_args(reply, + DBUS_TYPE_STRING, &c_filename, + DBUS_TYPE_INVALID); + + LOGV("Authorization: filename = %s", c_filename); + } + + dbus_connection_send(nat->conn, reply, NULL); + + if (error == FALSE) { + env->ReleaseStringUTFChars(filename, c_filename); + } + + dbus_message_unref(reply); + dbus_message_unref(msg); + + result = JNI_TRUE; + } else { + LOGE("%s: Cannot create message reply to return filename to " + "D-Bus\n", __FUNCTION__); + + dbus_message_unref(msg); + + result = JNI_FALSE; + } + } + + if (env->ExceptionCheck()) { + LOGE("VM Exception occurred in native function %s (%s:%d)", + __FUNCTION__, __FILE__, __LINE__); + } +#endif + return result; +} + +static jobject obexTransferGetPropertiesNative(JNIEnv *env, jobject object, + jstring transfer) { + LOGI(__FUNCTION__); + jobject result = NULL; +#ifdef HAVE_BLUETOOTH + if (nat) { + const char *c_transfer = env->GetStringUTFChars(transfer, NULL); + + LOGV("%s: Transfer = %s", __FUNCTION__, c_transfer); + + DBusMessage *reply = NULL; + + reply = dbus_func_args(env, nat->conn, OBEXD_DBUS_CLIENT_SVC, + c_transfer, OBEXD_DBUS_CLIENT_TRANS_IFC, + OBEXD_DBUS_CLIENT_TRANS_GETPROPS, + DBUS_TYPE_INVALID); + + env->ReleaseStringUTFChars(transfer, c_transfer); + + if (reply == NULL) { + LOGE("%s:%d Cannot create message to D-Bus", __FUNCTION__, __LINE__); + result = NULL; + + goto Done; + } + + DBusMessageIter iter, array; + const char *name = NULL; + const char *filename = NULL; + uint64_t size = 0; + bool pass; + + pass = dbus_message_iter_init(reply, &iter); + + if (!pass) + { + LOGE("%s: Iter init fails", __FUNCTION__); + return NULL; + } + + dbus_message_iter_recurse(&iter, &array); + + while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) { + const char *dict_key; + DBusMessageIter dict_entry, dict_variant; + + dbus_message_iter_recurse(&array, &dict_entry); + + dbus_message_iter_get_basic(&dict_entry, &dict_key); + + dbus_message_iter_next(&dict_entry); + + dbus_message_iter_recurse(&dict_entry, &dict_variant); + + int32_t type; + + type = dbus_message_iter_get_arg_type(&dict_variant); + + if ((type == DBUS_TYPE_STRING) || (DBUS_TYPE_INT64)) { + if (!strncmp(dict_key, "Name", sizeof("Name"))) { + dbus_message_iter_get_basic(&dict_variant, &name); + LOGV("%s: Name = %s", __FUNCTION__, name); + } else if (!strncmp(dict_key, "Size", sizeof("Size"))) { + dbus_message_iter_get_basic(&dict_variant, &size); + LOGV("%s: Size = %d", __FUNCTION__, size); + } else if (!strncmp(dict_key, "Filename", sizeof("Filename"))) { + dbus_message_iter_get_basic(&dict_variant, &filename); + LOGV("%s: Filename = %s", __FUNCTION__, filename); + } + } + + dbus_message_iter_next(&array); + } + + dbus_message_unref(reply); + + jclass clazz = + env->FindClass("android/server/BluetoothOppService$TransferProperties"); + + if (clazz == NULL) { + LOGE("%s:%d Cannot FindClass", __FUNCTION__, __LINE__); + result = NULL; + + goto Done; + } + + jmethodID constructor = + env->GetMethodID(clazz,"", "(Landroid/server/BluetoothOppService;" + "Ljava/lang/String;ILjava/lang/String;)V"); + + if (constructor == NULL) { + LOGE("%s:%d Cannot Get Constructor", __FUNCTION__, __LINE__); + result = NULL; + + goto Done; + } + + result = env->NewObject(clazz, constructor, object, + env->NewStringUTF(name), (jint) size, + env->NewStringUTF(filename)); + } +#endif +Done: + if (env->ExceptionCheck()) { + LOGE("VM Exception occurred in native function %s (%s:%d)", + __FUNCTION__, __FILE__, __LINE__); + } + + return result; +} + +#ifdef HAVE_BLUETOOTH +static void onSendFilesComplete(DBusMessage *msg, void *user, void *nat_cb) { + LOGI(__FUNCTION__); + + char *c_address = (char *)user; + DBusError err; + jboolean is_error = JNI_FALSE; + JNIEnv *env = NULL; + nat->vm->GetEnv((void**)&env, nat->envVer); + + dbus_error_init(&err); + + LOGV("... address = %s", c_address); + if (dbus_set_error_from_message(&err, msg)) { + LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message); + + dbus_error_free(&err); + + is_error = JNI_TRUE; + } + + jstring address = env->NewStringUTF(c_address); + + env->CallVoidMethod(nat->me, + method_onSendFilesComplete, + address, is_error); + + if (env->ExceptionCheck()) { + LOGE("VM Exception occurred in native function %s (%s:%d)", + __FUNCTION__, __FILE__, __LINE__); + } + + env->DeleteLocalRef(address); + + free(c_address); +} + +static void onPullBusinessCardComplete(DBusMessage *msg, void *user, void *nat_cb) { + LOGI(__FUNCTION__); + + char *c_address = (char *)user; + DBusError err; + jboolean is_error = JNI_FALSE; + JNIEnv *env = NULL; + nat->vm->GetEnv((void**)&env, nat->envVer); + + dbus_error_init(&err); + + LOGV("... address = %s", c_address); + if (dbus_set_error_from_message(&err, msg)) { + LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message); + + dbus_error_free(&err); + + is_error = JNI_TRUE; + } + + jstring address = env->NewStringUTF(c_address); + + env->CallVoidMethod(nat->me, + method_onPullBusinessCardComplete, + address, is_error); + + if (env->ExceptionCheck()) { + LOGE("VM Exception occurred in native function %s (%s:%d)", + __FUNCTION__, __FILE__, __LINE__); + } + + env->DeleteLocalRef(address); + + free(c_address); +} + +DBusHandlerResult opp_event_filter(DBusMessage *msg, JNIEnv *env) { + LOGI(__FUNCTION__); + + DBusError err; + + if (!nat) { + LOGE("... skipping %s\n", __FUNCTION__); + LOGE("... ignored\n"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + dbus_error_init(&err); + + if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + DBusHandlerResult result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (dbus_message_is_signal(msg, OBEXD_DBUS_SRV_MGR_IFC, + OBEXD_DBUS_SRV_MGR_SGNL_OPP_SESS_CREATED)) { + char *c_session; + if (dbus_message_get_args(msg, &err, + DBUS_TYPE_OBJECT_PATH, &c_session, + DBUS_TYPE_INVALID)) { + LOGV("... Session Created = %s", c_session); + } else { + LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); + } + + result = DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_signal(msg, OBEXD_DBUS_SRV_MGR_IFC, + OBEXD_DBUS_SRV_MGR_SGNL_OPP_SESS_REMOVED)) { + char *c_session; + if (dbus_message_get_args(msg, &err, + DBUS_TYPE_OBJECT_PATH, &c_session, + DBUS_TYPE_INVALID)) { + LOGV("... Session Removed = %s", c_session); + } else { + LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); + } + + result = DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_signal(msg, OBEXD_DBUS_SRV_MGR_IFC, + OBEXD_DBUS_SRV_MGR_SGNL_OPP_TRANS_STARTED)) { + + char *c_transfer; + + if (dbus_message_get_args(msg, &err, + DBUS_TYPE_OBJECT_PATH, &c_transfer, + DBUS_TYPE_INVALID)) { + LOGV("... Transfer Started = %s", c_transfer); + } else { + LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); + } + + result = DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_signal(msg, OBEXD_DBUS_SRV_MGR_IFC, + OBEXD_DBUS_SRV_MGR_SGNL_OPP_TRANS_COMPLETED)) { + const char *c_transfer; + dbus_bool_t c_success; + + if (dbus_message_get_args(msg, &err, + DBUS_TYPE_OBJECT_PATH, &c_transfer, + DBUS_TYPE_BOOLEAN, &c_success, + DBUS_TYPE_INVALID)) { + LOGV("... Transfer Completed = %s Success = %d", c_transfer, + c_success); + + jstring transfer = env->NewStringUTF(c_transfer); + jboolean success = c_success ? JNI_TRUE : JNI_FALSE; + + env->CallVoidMethod(nat->me, method_onObexTransferComplete, + transfer, success, NULL); + + env->DeleteLocalRef(transfer); + } else { + LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); + } + + result = DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_signal(msg, OBEXD_DBUS_SRV_TRANS_IFC, + OBEXD_DBUS_SRV_TRANS_SGNL_PROGRESS)) { + int32_t total; + int32_t transferred; + + if (dbus_message_get_args(msg, &err, + DBUS_TYPE_INT32, &total, + DBUS_TYPE_INT32, &transferred, + DBUS_TYPE_INVALID)) { + const char *c_path = NULL; + + c_path = dbus_message_get_path(msg); + + if (c_path == NULL){ + LOGE("Function %s:%d Unable to get path", + __FUNCTION__, __LINE__); + } else { + LOGV("... Transfer: %s Total = %d Transferred = %d", c_path, + total, transferred); + + jstring path = env->NewStringUTF(c_path); + + env->CallVoidMethod(nat->me, method_onObexProgress, + path, (jint) transferred); + + env->DeleteLocalRef(path); + } + } else { + LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); + } + + result = DBUS_HANDLER_RESULT_HANDLED; + } + + if (result == DBUS_HANDLER_RESULT_NOT_YET_HANDLED) { + LOGV("... ignored"); + } + + if (env->ExceptionCheck()) { + LOGE("VM Exception occurred while handling %s.%s (%s) in %s," + " leaving for VM", + dbus_message_get_interface(msg), dbus_message_get_member(msg), + dbus_message_get_path(msg), __FUNCTION__); + } + + return result; +} + +// Called by dbus during WaitForAndDispatchEventNative() +DBusHandlerResult oppclient_agent(DBusConnection *conn, + DBusMessage *msg, + void *data) { + LOGI(__FUNCTION__); + + if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_METHOD_CALL) { + LOGE("%s: not interested (not a method call).", __FUNCTION__); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + LOGV("%s: Received method %s:%s", __FUNCTION__, + dbus_message_get_interface(msg), dbus_message_get_member(msg)); + + if (dbus_message_is_method_call(msg, OBEXD_DBUS_CLIENT_AGENT_IFC, + OBEXD_DBUS_CLIENT_AGENT_RELEASE)) { + + return oppclient_agent_release_handler(conn, msg); + + } else if (dbus_message_is_method_call(msg, OBEXD_DBUS_CLIENT_AGENT_IFC, + OBEXD_DBUS_CLIENT_AGENT_REQUEST)) { + + return oppclient_agent_request_handler(conn, msg); + + } else if (dbus_message_is_method_call(msg, OBEXD_DBUS_CLIENT_AGENT_IFC, + OBEXD_DBUS_CLIENT_AGENT_PROGRESS)) { + + return oppclient_agent_progress_handler(conn, msg); + + } else if (dbus_message_is_method_call(msg, OBEXD_DBUS_CLIENT_AGENT_IFC, + OBEXD_DBUS_CLIENT_AGENT_COMPLETE)) { + + return oppclient_agent_complete_handler(conn, msg); + + } else if (dbus_message_is_method_call(msg, OBEXD_DBUS_CLIENT_AGENT_IFC, + OBEXD_DBUS_CLIENT_AGENT_ERROR)) { + + return oppclient_agent_error_handler(conn, msg); + + } else { + LOGV("... ignored"); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +// Called by dbus during WaitForAndDispatchEventNative() +DBusHandlerResult oppserver_agent(DBusConnection *conn, + DBusMessage *msg, + void *data) { + LOGI(__FUNCTION__); + + if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_METHOD_CALL) { + LOGE("%s: not interested (not a method call).", __FUNCTION__); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + LOGV("%s: Received method %s:%s", __FUNCTION__, + dbus_message_get_interface(msg), dbus_message_get_member(msg)); + + if (dbus_message_is_method_call(msg, OBEXD_DBUS_SRV_AGENT_IFC, + OBEXD_DBUS_SRV_AGENT_AUTHORIZE)) { + + return oppserver_agent_authorize_handler(conn, msg); + + } else if (dbus_message_is_method_call(msg, OBEXD_DBUS_SRV_AGENT_IFC, + OBEXD_DBUS_SRV_AGENT_CANCEL)) { + + return oppserver_agent_cancel_handler(conn, msg); + + } else { + LOGV("... ignored"); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + + +static DBusHandlerResult oppclient_agent_release_handler(DBusConnection *conn, + DBusMessage *msg) +{ + LOGI(__FUNCTION__); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_INVALID)) { + LOGE("%s: Invalid arguments for %s() method", __FUNCTION__, + OBEXD_DBUS_CLIENT_AGENT_RELEASE); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + DBusMessage *reply = dbus_message_new_method_return(msg); + + if (reply) { + dbus_message_append_args(reply, DBUS_TYPE_INVALID); + + dbus_connection_send(nat->conn, reply, NULL); + + LOGV("No longer the obexd Client agent!"); + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult oppclient_agent_request_handler(DBusConnection *conn, + DBusMessage *msg) { + LOGI(__FUNCTION__); + + JNIEnv *env = NULL; + nat->vm->GetEnv((void**)&env, nat->envVer); + + const char *c_transfer; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &c_transfer, + DBUS_TYPE_INVALID)) { + LOGE("%s: Invalid arguments for %s() method", __FUNCTION__, + OBEXD_DBUS_CLIENT_AGENT_REQUEST); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + LOGV("... transfer = %s", c_transfer); + + jstring transfer = env->NewStringUTF(c_transfer); + + jstring filename = (jstring) env->CallObjectMethod(nat->me, + method_onObexRequest, + transfer); + + env->DeleteLocalRef(transfer); + + DBusMessage *reply; + const char *c_filename; + + if (filename != NULL) { + reply = dbus_message_new_method_return(msg); + } else { + reply = dbus_message_new_error(msg, + OBEXD_DBUS_ERROR_CANCELLED, + NULL); + } + + if (reply != NULL) { + if (filename != NULL) { + c_filename = env->GetStringUTFChars(filename, NULL); + + dbus_message_append_args(reply, + DBUS_TYPE_STRING, &c_filename, + DBUS_TYPE_INVALID); + } + + dbus_connection_send(nat->conn, reply, NULL); + + if (filename != NULL) { + env->ReleaseStringUTFChars(filename, c_filename); + env->DeleteLocalRef(filename); + } + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; + } else if (filename != NULL) { + env->DeleteLocalRef(filename); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult oppclient_agent_progress_handler(DBusConnection *conn, + DBusMessage *msg) { + LOGI(__FUNCTION__); + + const char *c_transfer; + uint64_t transferred; + + JNIEnv *env = NULL; + nat->vm->GetEnv((void**)&env, nat->envVer); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &c_transfer, + DBUS_TYPE_UINT64, &transferred, + DBUS_TYPE_INVALID)) { + LOGE("%s: Invalid arguments for %s() method", __FUNCTION__, + OBEXD_DBUS_CLIENT_AGENT_PROGRESS); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + LOGV("... transfer = %s\n... bytes transferred = %d", c_transfer, + (int32_t) transferred); + + jstring transfer = env->NewStringUTF(c_transfer); + + env->CallVoidMethod(nat->me, method_onObexProgress, + transfer, (jint) transferred); + + env->DeleteLocalRef(transfer); + + DBusMessage *reply = dbus_message_new_method_return(msg); + + if (reply) { + dbus_message_append_args(reply, DBUS_TYPE_INVALID); + + dbus_connection_send(nat->conn, reply, NULL); + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult oppclient_agent_complete_handler(DBusConnection *conn, + DBusMessage *msg) { + LOGI(__FUNCTION__); + + const char *c_transfer; + + JNIEnv *env = NULL; + nat->vm->GetEnv((void**)&env, nat->envVer); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &c_transfer, + DBUS_TYPE_INVALID)) { + LOGE("%s: Invalid arguments for %s() method", __FUNCTION__, + OBEXD_DBUS_CLIENT_AGENT_COMPLETE); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + LOGV("... transfer = %s", c_transfer); + + jstring transfer = env->NewStringUTF(c_transfer); + + env->CallVoidMethod(nat->me, method_onObexTransferComplete, + transfer, JNI_TRUE, NULL); + + env->DeleteLocalRef(transfer); + + DBusMessage *reply = dbus_message_new_method_return(msg); + + if (reply) { + dbus_message_append_args(reply, DBUS_TYPE_INVALID); + + dbus_connection_send(nat->conn, reply, NULL); + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult oppclient_agent_error_handler(DBusConnection *conn, + DBusMessage *msg) { + LOGI(__FUNCTION__); + + const char *c_transfer, *c_message; + + JNIEnv *env = NULL; + nat->vm->GetEnv((void**)&env, nat->envVer); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &c_transfer, + DBUS_TYPE_STRING, &c_message, + DBUS_TYPE_INVALID)) { + LOGE("%s: Invalid arguments for %s() method", __FUNCTION__, + OBEXD_DBUS_CLIENT_AGENT_ERROR); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + LOGV("... transfer = %s\n... message = %s", c_transfer, c_message); + + jstring transfer = env->NewStringUTF(c_transfer); + jstring message = env->NewStringUTF(c_message); + + env->CallVoidMethod(nat->me, method_onObexTransferComplete, + transfer, JNI_FALSE, message); + + env->DeleteLocalRef(transfer); + env->DeleteLocalRef(message); + + DBusMessage *reply = dbus_message_new_method_return(msg); + + if (reply) { + dbus_message_append_args(reply, DBUS_TYPE_INVALID); + + dbus_connection_send(nat->conn, reply, NULL); + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult oppserver_agent_authorize_handler(DBusConnection *conn, + DBusMessage *msg) { + LOGI(__FUNCTION__); + + const char *c_transfer, *c_address, *c_filename, *c_type; + int32_t length, time; + + JNIEnv *env = NULL; + nat->vm->GetEnv((void**)&env, nat->envVer); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &c_transfer, + DBUS_TYPE_STRING, &c_address, + DBUS_TYPE_STRING, &c_filename, + DBUS_TYPE_STRING, &c_type, + DBUS_TYPE_INT32, &length, + DBUS_TYPE_INT32, &time, + DBUS_TYPE_INVALID)) { + LOGE("%s: Invalid arguments for %s() method", __FUNCTION__, + OBEXD_DBUS_SRV_AGENT_AUTHORIZE); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + LOGV("... transfer = %s\n... address = %s\n... filename = %s\n... type =" + "%s\n... length = %d\n... time = %d\n", c_transfer, c_address, c_filename, + c_type, length, time); + + dbus_message_ref(msg); + + jstring transfer = env->NewStringUTF(c_transfer); + jstring address = env->NewStringUTF(c_address); + jstring filename = env->NewStringUTF(c_filename); + jstring type = env->NewStringUTF(c_type); + + env->CallBooleanMethod(nat->me, method_onObexAuthorize, + transfer, address, filename, type, + (jint) length, + (jint) msg); + + env->DeleteLocalRef(transfer); + env->DeleteLocalRef(address); + env->DeleteLocalRef(filename); + env->DeleteLocalRef(type); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult oppserver_agent_cancel_handler(DBusConnection *conn, + DBusMessage *msg) { + LOGI(__FUNCTION__); + + JNIEnv *env = NULL; + nat->vm->GetEnv((void**)&env, nat->envVer); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_INVALID)) { + LOGE("%s: Invalid arguments for %s() method", __FUNCTION__, + OBEXD_DBUS_SRV_AGENT_CANCEL); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + const char *c_path; + + c_path = dbus_message_get_path(msg); + + if (!c_path){ + LOGE("Function %s:%d Unable to get path", + __FUNCTION__, __LINE__); + } else { + LOGV("... Authorize for transfer %s cancelled by OBEX", c_path); + + jstring path = env->NewStringUTF(c_path); + + env->CallBooleanMethod(nat->me, method_onObexAuthorizeCancel, + path); + + env->DeleteLocalRef(path); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + + +#endif + +static JNINativeMethod sMethods[] = { + {"initNative", "()Z", (void *)initNative}, + {"cleanupNative", "()V", (void *)cleanupNative}, + + /* Obexd 0.8 API */ + { + "sendFilesNative", + "(Ljava/lang/String;[Ljava/lang/String;)Z", + (void *) sendFilesNative + }, + { + "pullBusinessCardNative", + "(Ljava/lang/String;Ljava/lang/String;)Z", + (void *) pullBusinessCardNative + }, + { + "cancelTransferNative", + "(Ljava/lang/String;Z)Z", + (void*) cancelTransferNative + }, + { + "obexAuthorizeCompleteNative", + "(ZLjava/lang/String;I)Z", + (void *) obexAuthorizeCompleteNative + }, + { + "obexTransferGetPropertiesNative", + "(Ljava/lang/String;)Landroid/server/BluetoothOppService$TransferProperties;", + (void *) obexTransferGetPropertiesNative + }, +}; + +int register_android_server_BluetoothOppService(JNIEnv *env) { + LOGI(__FUNCTION__); + + jclass clazz = env->FindClass("android/server/BluetoothOppService"); + if (clazz == NULL) { + LOGE("Can't find android/server/BluetoothOppService"); + return -1; + } + +#ifdef HAVE_BLUETOOTH + method_onSendFilesComplete = env->GetMethodID(clazz, "onSendFilesComplete", + "(Ljava/lang/String;Z)V"); + + method_onPullBusinessCardComplete = env->GetMethodID(clazz, "onPullBusinessCardComplete", + "(Ljava/lang/String;Z)V"); + + method_onObexRequest = env->GetMethodID(clazz, "onObexRequest", + "(Ljava/lang/String;)Ljava/lang/String;"); + + method_onObexProgress = env->GetMethodID(clazz, "onObexProgress", + "(Ljava/lang/String;I)V"); + + method_onObexTransferComplete = env->GetMethodID(clazz, "onObexTransferComplete", + "(Ljava/lang/String;ZLjava/lang/String;)V"); + + method_onObexAuthorize = env->GetMethodID(clazz, "onObexAuthorize", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;II)Z"); + + method_onObexAuthorizeCancel = env->GetMethodID(clazz, "onObexAuthorizeCancel", + "(Ljava/lang/String;)Z"); +#endif + + return AndroidRuntime::registerNativeMethods(env, + "android/server/BluetoothOppService", sMethods, NELEM(sMethods)); +} + +} /* namespace android */ diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index b2848ac..2534007 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2006 The Android Open Source Project + * Copyright (c) 2009, Code Aurora Forum, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +40,8 @@ import android.os.SystemProperties; import android.provider.Contacts.People; import android.provider.Settings; import android.server.BluetoothA2dpService; +import android.server.BluetoothFtpService; +import android.server.BluetoothOppService; import android.server.BluetoothDeviceService; import android.server.search.SearchManagerService; import android.util.EventLog; @@ -89,6 +92,8 @@ class ServerThread extends Thread { WindowManagerService wm = null; BluetoothDeviceService bluetooth = null; BluetoothA2dpService bluetoothA2dp = null; + BluetoothFtpService bluetoothFtp = null; + BluetoothOppService bluetoothOpp = null; HeadsetObserver headset = null; // Critical services... @@ -172,6 +177,15 @@ class ServerThread extends Thread { ServiceManager.addService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE, bluetoothA2dp); + if (SystemProperties.getBoolean("ro.qualcomm.proprietary_obex", false)) { + bluetoothFtp = new BluetoothFtpService(context); + ServiceManager.addService(BluetoothFtpService.BLUETOOTH_FTP_SERVICE, + bluetoothFtp); + bluetoothOpp = new BluetoothOppService(context); + ServiceManager.addService(BluetoothOppService.BLUETOOTH_OPP_SERVICE, + bluetoothOpp); + } + int bluetoothOn = Settings.Secure.getInt(mContentResolver, Settings.Secure.BLUETOOTH_ON, 0); if (bluetoothOn > 0) { diff --git a/tools/aidl/Type.cpp b/tools/aidl/Type.cpp index a44072d..6fe6aa7 100755 --- a/tools/aidl/Type.cpp +++ b/tools/aidl/Type.cpp @@ -564,6 +564,12 @@ IBinderType::IBinderType() { } +string +IBinderType::CreatorName() const +{ + return "android.os.Parcel.iBinderTypeCreator"; +} + void IBinderType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) { @@ -703,6 +709,12 @@ MapType::MapType() { } +string +MapType::CreatorName() const +{ + return "android.os.Parcel.mapTypeCreator"; +} + void MapType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags) { @@ -1000,6 +1012,8 @@ GenericListType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* par { if (m_creator == STRING_TYPE->CreatorName()) { addTo->Add(new MethodCall(parcel, "writeStringList", 1, v)); + } else if (m_creator == MAP_TYPE->CreatorName()) { + addTo->Add(new MethodCall(parcel, "writeMapList", 1, v)); } else if (m_creator == IBINDER_TYPE->CreatorName()) { addTo->Add(new MethodCall(parcel, "writeBinderList", 1, v)); } else { @@ -1014,6 +1028,9 @@ GenericListType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* if (m_creator == STRING_TYPE->CreatorName()) { addTo->Add(new Assignment(v, new MethodCall(parcel, "createStringArrayList", 0))); + } else if (m_creator == MAP_TYPE->CreatorName()) { + addTo->Add(new Assignment(v, + new MethodCall(parcel, "createMapArrayList", 0))); } else if (m_creator == IBINDER_TYPE->CreatorName()) { addTo->Add(new Assignment(v, new MethodCall(parcel, "createBinderArrayList", 0))); @@ -1031,6 +1048,8 @@ GenericListType::ReadFromParcel(StatementBlock* addTo, Variable* v, { if (m_creator == STRING_TYPE->CreatorName()) { addTo->Add(new MethodCall(parcel, "readStringList", 1, v)); + } else if (m_creator == MAP_TYPE->CreatorName()) { + addTo->Add(new MethodCall(parcel, "readMapList", 1, v)); } else if (m_creator == IBINDER_TYPE->CreatorName()) { addTo->Add(new MethodCall(parcel, "readBinderList", 1, v)); } else { diff --git a/tools/aidl/Type.h b/tools/aidl/Type.h index 2ea3ac9..27186a4 100755 --- a/tools/aidl/Type.h +++ b/tools/aidl/Type.h @@ -211,6 +211,8 @@ class IBinderType : public Type public: IBinderType(); + virtual string CreatorName() const; + virtual void WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags); virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, @@ -284,6 +286,8 @@ class MapType : public Type public: MapType(); + virtual string CreatorName() const; + virtual void WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags); virtual void CreateFromParcel(StatementBlock* addTo, Variable* v, -- 1.6.0.4