diff options
author | Casper Bonde <c.bonde@samsung.com> | 2014-07-24 13:47:23 +0200 |
---|---|---|
committer | Matthew Xie <mattx@google.com> | 2014-08-06 00:06:12 -0700 |
commit | 326b5e610063ac24c0ba467ac585bd4c7f618a67 (patch) | |
tree | 863e6fa83714e668d7fc9eeab1c693942763b219 /src/com/android/bluetooth/map/BluetoothMapService.java | |
parent | f021c4ee6ba53c8512807c1469b2432278cf6cca (diff) | |
download | android_packages_apps_Bluetooth-326b5e610063ac24c0ba467ac585bd4c7f618a67.tar.gz android_packages_apps_Bluetooth-326b5e610063ac24c0ba467ac585bd4c7f618a67.tar.bz2 android_packages_apps_Bluetooth-326b5e610063ac24c0ba467ac585bd4c7f618a67.zip |
BT MAP: added support for email sharing over BT
- added support for Emails
- added activity to do setup of the email accounts to share
- added improved handling of MMS, SMS and Email
- Many optimizations to speed (especially getMessageListing)
- fixed wakelock problem
- fixed user timeout problem when user do not react to msg access request
- added missing privileges
- support for Quoted Printable format
- added accountId in test case URIs
- fixed problem with service numbers being strings
- fixed problem with read flag in getMessage
- added support for transparent flag in Email Push
- added missing send-event for non-imap accounts
- set attachment size to 0 if text-only message
- fixed double send for sms messages with retry
- removed secondary phone numbers from recipient/originator
- removed insert-address-token in MMS messages
- fixed null-pointer exception in settings (missing extra in intent)
- send text-only mms as sms (workaround to make it cheaper)
- fixed rejection of native and fraction requests
- better handling of unknown message types in push
- fixed problem with possible illigal xml chars in message listing
- added missing WRITE_APN_SETTINGS permission to manifest
- fixed problem with notifications when pushing to folders other than OUTBOX
- removed debugging code
- added support for threadId
- fixed permission problems
- changed to use ContentProviderClients for Email app access
- fixed names for member vars
UPDATE: Moved the MAP E-mail API to the bluetooth package.
UPDATE: Added check for the presense of MMS parts.
This is needed due to a change in the MMS app/subsystem,
where deleted messages gets corrupted.
Signed-off-by: Casper Bonde <c.bonde@samsung.com>
Change-Id: Ib5dbe7c2d5c0ba8d978ae843d840028592e3cab4
Diffstat (limited to 'src/com/android/bluetooth/map/BluetoothMapService.java')
-rwxr-xr-x | src/com/android/bluetooth/map/BluetoothMapService.java | 894 |
1 files changed, 550 insertions, 344 deletions
diff --git a/src/com/android/bluetooth/map/BluetoothMapService.java b/src/com/android/bluetooth/map/BluetoothMapService.java index 7d42b29fc..077e2a3b6 100755 --- a/src/com/android/bluetooth/map/BluetoothMapService.java +++ b/src/com/android/bluetooth/map/BluetoothMapService.java @@ -1,5 +1,5 @@ /* -* Copyright (C) 2013 Samsung System LSI +* Copyright (C) 2014 Samsung System LSI * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -17,10 +17,19 @@ package com.android.bluetooth.map; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Set; import javax.obex.ServerSession; + +import android.app.AlarmManager; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; @@ -31,21 +40,29 @@ import android.bluetooth.BluetoothMap; import android.bluetooth.BluetoothSocket; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter.MalformedMimeTypeException; import android.os.Handler; import android.os.Message; import android.os.PowerManager; import android.os.ParcelUuid; +import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; +import android.util.SparseArray; import android.provider.Settings; +import android.provider.Telephony.Sms; import android.content.IntentFilter; import android.content.BroadcastReceiver; +import android.database.ContentObserver; import com.android.bluetooth.R; import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ProfileService; - +import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder; +import com.android.bluetooth.opp.BluetoothOppTransferHistory; +import com.android.bluetooth.opp.BluetoothShare; +import com.android.bluetooth.opp.Constants; public class BluetoothMapService extends ProfileService { private static final String TAG = "BluetoothMapService"; @@ -57,31 +74,20 @@ public class BluetoothMapService extends ProfileService { * DEBUG log: "setprop log.tag.BluetoothMapService VERBOSE" */ - public static final boolean DEBUG = true; + public static final boolean DEBUG = true; //TODO: set to false - public static final boolean VERBOSE = false; - - /** - * Intent indicating incoming obex authentication request which is from - * PCE(Carkit) - */ - public static final String AUTH_CHALL_ACTION = "com.android.bluetooth.map.authchall"; + public static final boolean VERBOSE = true; //TODO: set to false /** * Intent indicating timeout for user confirmation, which is sent to * BluetoothMapActivity */ public static final String USER_CONFIRM_TIMEOUT_ACTION = - "com.android.bluetooth.map.userconfirmtimeout"; - private static final int USER_CONFIRM_TIMEOUT_VALUE = 30000; + "com.android.bluetooth.map.USER_CONFIRM_TIMEOUT"; + private static final int USER_CONFIRM_TIMEOUT_VALUE = 25000; - /** - * Intent Extra name indicating session key which is sent from - * BluetoothMapActivity - */ - public static final String EXTRA_SESSION_KEY = "com.android.bluetooth.map.sessionkey"; - - public static final String THIS_PACKAGE_NAME = "com.android.bluetooth"; + /** Intent indicating that the email settings activity should be opened*/ + public static final String ACTION_SHOW_MAPS_EMAIL_SETTINGS = "android.btmap.intent.action.SHOW_MAPS_EMAIL_SETTINGS"; public static final int MSG_SERVERSESSION_CLOSE = 5000; @@ -89,11 +95,12 @@ public class BluetoothMapService extends ProfileService { public static final int MSG_SESSION_DISCONNECTED = 5002; - public static final int MSG_OBEX_AUTH_CHALL = 5003; + public static final int MSG_MAS_CONNECT = 5003; // Send at MAS connect, including the MAS_ID + public static final int MSG_MAS_CONNECT_CANCEL = 5004; // Send at auth. declined - public static final int MSG_ACQUIRE_WAKE_LOCK = 5004; + public static final int MSG_ACQUIRE_WAKE_LOCK = 5005; - public static final int MSG_RELEASE_WAKE_LOCK = 5005; + public static final int MSG_RELEASE_WAKE_LOCK = 5006; private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; @@ -105,36 +112,50 @@ public class BluetoothMapService extends ProfileService { private static final int DISCONNECT_MAP = 3; + private static final int SHUTDOWN = 4; + private static final int RELEASE_WAKE_LOCK_DELAY = 10000; private PowerManager.WakeLock mWakeLock = null; - private BluetoothAdapter mAdapter; - - private SocketAcceptThread mAcceptThread = null; + private static final int UPDATE_MAS_INSTANCES = 5; - private BluetoothMapAuthenticator mAuth = null; + public static final int UPDATE_MAS_INSTANCES_ACCOUNT_ADDED = 0; + public static final int UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED = 1; + public static final int UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED = 2; + public static final int UPDATE_MAS_INSTANCES_ACCOUNT_DISCONNECT = 3; - private BluetoothMapObexServer mMapServer; + private static final int MAS_ID_SMS_MMS = 0; - private ServerSession mServerSession = null; + private BluetoothAdapter mAdapter; private BluetoothMnsObexClient mBluetoothMnsObexClient = null; - private BluetoothServerSocket mServerSocket = null; - - private BluetoothSocket mConnSocket = null; + /* mMasInstances: A list of the active MasInstances with the key being the MasId */ + private SparseArray<BluetoothMapMasInstance> mMasInstances = + new SparseArray<BluetoothMapMasInstance>(1); + /* mMasInstanceMap: A list of the active MasInstances with the key being the account */ + private HashMap<BluetoothMapEmailSettingsItem, BluetoothMapMasInstance> mMasInstanceMap = + new HashMap<BluetoothMapEmailSettingsItem, BluetoothMapMasInstance>(1); - private BluetoothDevice mRemoteDevice = null; + private BluetoothDevice mRemoteDevice = null; // The remote connected device - protect access + private ArrayList<BluetoothMapEmailSettingsItem> mEnabledAccounts = null; private static String sRemoteDeviceName = null; - private volatile boolean mInterrupted; - private int mState; + private BluetoothMapEmailAppObserver mAppObserver = null; + private AlarmManager mAlarmManager = null; - private boolean isWaitingAuthorization = false; - private boolean removeTimeoutMsg = false; + private boolean mIsWaitingAuthorization = false; + private boolean mRemoveTimeoutMsg = false; + private boolean mTrust = false; // Temp. fix for missing BluetoothDevice.getTrustState() + private boolean mAccountChanged = false; + + // package and class name to which we send intent to check phone book access permission + private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings"; + private static final String ACCESS_AUTHORITY_CLASS = + "com.android.settings.bluetooth.BluetoothPermissionRequest"; private static final ParcelUuid[] MAP_UUIDS = { BluetoothUuid.MAP, @@ -143,138 +164,56 @@ public class BluetoothMapService extends ProfileService { public BluetoothMapService() { mState = BluetoothMap.STATE_DISCONNECTED; - } - private void startRfcommSocketListener() { - if (DEBUG) Log.d(TAG, "Map Service startRfcommSocketListener"); - - if (mAcceptThread == null) { - mAcceptThread = new SocketAcceptThread(); - mAcceptThread.setName("BluetoothMapAcceptThread"); - mAcceptThread.start(); - } } - private final boolean initSocket() { - if (DEBUG) Log.d(TAG, "Map Service initSocket"); - boolean initSocketOK = false; - final int CREATE_RETRY_TIME = 10; - - // It's possible that create will fail in some cases. retry for 10 times - for (int i = 0; (i < CREATE_RETRY_TIME) && !mInterrupted; i++) { - initSocketOK = true; - try { - // It is mandatory for MSE to support initiation of bonding and - // encryption. - mServerSocket = mAdapter.listenUsingEncryptedRfcommWithServiceRecord - ("MAP SMS/MMS", BluetoothUuid.MAS.getUuid()); + private final void closeService() { + if (DEBUG) Log.d(TAG, "MAP Service closeService in"); - } catch (IOException e) { - Log.e(TAG, "Error create RfcommServerSocket " + e.toString()); - initSocketOK = false; - } - if (!initSocketOK) { - // Need to break out of this loop if BT is being turned off. - if (mAdapter == null) break; - int state = mAdapter.getState(); - if ((state != BluetoothAdapter.STATE_TURNING_ON) && - (state != BluetoothAdapter.STATE_ON)) { - Log.w(TAG, "initServerSocket failed as BT is (being) turned off"); - break; - } - try { - if (VERBOSE) Log.v(TAG, "wait 300 ms"); - Thread.sleep(300); - } catch (InterruptedException e) { - Log.e(TAG, "socketAcceptThread thread was interrupted (3)"); - } - } else { - break; - } - } - if (mInterrupted) { - initSocketOK = false; - // close server socket to avoid resource leakage - closeServerSocket(); + if (mBluetoothMnsObexClient != null) { + mBluetoothMnsObexClient.shutdown(); + mBluetoothMnsObexClient = null; } - if (initSocketOK) { - if (VERBOSE) Log.v(TAG, "Succeed to create listening socket "); - - } else { - Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try"); + for(int i=0, c=mMasInstances.size(); i < c; i++) { + mMasInstances.valueAt(i).shutdown(); } - return initSocketOK; - } + mMasInstances.clear(); - private final synchronized void closeServerSocket() { - // exit SocketAcceptThread early - if (mServerSocket != null) { - try { - // this will cause mServerSocket.accept() return early with IOException - mServerSocket.close(); - mServerSocket = null; - } catch (IOException ex) { - Log.e(TAG, "Close Server Socket error: " + ex); - } - } - } - private final synchronized void closeConnectionSocket() { - if (mConnSocket != null) { - try { - mConnSocket.close(); - mConnSocket = null; - } catch (IOException e) { - Log.e(TAG, "Close Connection Socket error: " + e.toString()); - } + if (mSessionStatusHandler != null) { + mSessionStatusHandler.removeCallbacksAndMessages(null); } - } - - private final void closeService() { - if (DEBUG) Log.d(TAG, "MAP Service closeService in"); - - // exit initSocket early - mInterrupted = true; - closeServerSocket(); - if (mAcceptThread != null) { - try { - mAcceptThread.shutdown(); - mAcceptThread.join(); - mAcceptThread = null; - } catch (InterruptedException ex) { - Log.w(TAG, "mAcceptThread close error" + ex); - } - } + mIsWaitingAuthorization = false; + mTrust = false; + setState(BluetoothMap.STATE_DISCONNECTED); if (mWakeLock != null) { mWakeLock.release(); + if(VERBOSE)Log.i(TAG, "CloseService(): Release Wake Lock"); mWakeLock = null; } + mRemoteDevice = null; - if (mServerSession != null) { - mServerSession.close(); - mServerSession = null; - } - - if (mBluetoothMnsObexClient != null) { - mBluetoothMnsObexClient.shutdown(); - mBluetoothMnsObexClient = null; - } - - closeConnectionSocket(); + if (VERBOSE) Log.v(TAG, "MAP Service closeService out"); + } - if (mSessionStatusHandler != null) { - mSessionStatusHandler.removeCallbacksAndMessages(null); + /** + * Starts the RFComm listerner threads for each MAS + * @throws IOException + */ + private final void startRfcommSocketListeners() { + for(int i=0, c=mMasInstances.size(); i < c; i++) { + mMasInstances.valueAt(i).startRfcommSocketListener(); } - isWaitingAuthorization = false; - - if (VERBOSE) Log.v(TAG, "MAP Service closeService out"); } - private final void startObexServerSession() throws IOException { - if (DEBUG) Log.d(TAG, "Map Service startObexServerSession"); + /** + * Start a MAS instance for SMS/MMS and each e-mail account. + */ + private final void startObexServerSessions() { + if (DEBUG) Log.d(TAG, "Map Service START ObexServerSessions()"); // acquire the wakeLock before start Obex transaction thread if (mWakeLock == null) { @@ -283,157 +222,93 @@ public class BluetoothMapService extends ProfileService { "StartingObexMapTransaction"); mWakeLock.setReferenceCounted(false); mWakeLock.acquire(); + if(VERBOSE)Log.i(TAG, "startObexSessions(): Acquire Wake Lock"); } - mBluetoothMnsObexClient = new BluetoothMnsObexClient(this, mRemoteDevice, - mSessionStatusHandler); - mMapServer = new BluetoothMapObexServer(mSessionStatusHandler, this, - mBluetoothMnsObexClient); - synchronized (this) { - // We need to get authentication now that obex server is up - mAuth = new BluetoothMapAuthenticator(mSessionStatusHandler); - mAuth.setChallenged(false); - mAuth.setCancelled(false); + if(mBluetoothMnsObexClient == null) { + mBluetoothMnsObexClient = new BluetoothMnsObexClient(mRemoteDevice, mSessionStatusHandler); + } + + boolean connected = false; + for(int i=0, c=mMasInstances.size(); i < c; i++) { + try { + if(mMasInstances.valueAt(i) + .startObexServerSession(mBluetoothMnsObexClient) == true) { + connected = true; + } + } catch (IOException e) { + Log.w(TAG,"IOException occured while starting an obexServerSession restarting the listener",e); + mMasInstances.valueAt(i).restartObexServerSession(); + } catch (RemoteException e) { + Log.w(TAG,"RemoteException occured while starting an obexServerSession restarting the listener",e); + mMasInstances.valueAt(i).restartObexServerSession(); + } + } + if(connected) { + setState(BluetoothMap.STATE_CONNECTED); } - // setup RFCOMM transport - BluetoothMapRfcommTransport transport = new BluetoothMapRfcommTransport(mConnSocket); - mServerSession = new ServerSession(transport, mMapServer, mAuth); - setState(BluetoothMap.STATE_CONNECTED); mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK); mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler - .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY); + .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY); if (VERBOSE) { - Log.v(TAG, "startObexServerSession() success!"); + Log.v(TAG, "startObexServerSessions() success!"); } } - private void stopObexServerSession() { - if (DEBUG) Log.d(TAG, "MAP Service stopObexServerSession"); - - mSessionStatusHandler.removeMessages(MSG_ACQUIRE_WAKE_LOCK); - mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK); - - // Release the wake lock if obex transaction is over - if (mWakeLock != null) { - mWakeLock.release(); - mWakeLock = null; - } - - if (mServerSession != null) { - mServerSession.close(); - mServerSession = null; - } - - mAcceptThread = null; - - if(mBluetoothMnsObexClient != null) { - mBluetoothMnsObexClient.shutdown(); - mBluetoothMnsObexClient = null; - } - closeConnectionSocket(); - - // Last obex transaction is finished, we start to listen for incoming - // connection again - if (mAdapter.isEnabled()) { - startRfcommSocketListener(); - } - setState(BluetoothMap.STATE_DISCONNECTED); + public Handler getHandler() { + return mSessionStatusHandler; } - - /** - * A thread that runs in the background waiting for remote rfcomm - * connect.Once a remote socket connected, this thread shall be - * shutdown.When the remote disconnect,this thread shall run again waiting - * for next request. + * Restart a MAS instances. + * @param masId use -1 to stop all instances */ - private class SocketAcceptThread extends Thread { + private void stopObexServerSessions(int masId) { + if (DEBUG) Log.d(TAG, "MAP Service STOP ObexServerSessions()"); - private boolean stopped = false; + boolean lastMasInst = true; - @Override - public void run() { - BluetoothServerSocket serverSocket; - if (mServerSocket == null) { - if (!initSocket()) { - return; + if(masId != -1) { + for(int i=0, c=mMasInstances.size(); i < c; i++) { + BluetoothMapMasInstance masInst = mMasInstances.valueAt(i); + if(masInst.getMasId() != masId && masInst.isStarted()) { + lastMasInst = false; } } + } // Else just close down it all - while (!stopped) { - try { - if (DEBUG) Log.d(TAG, "Accepting socket connection..."); - serverSocket = mServerSocket; - if(serverSocket == null) { - Log.w(TAG, "mServerSocket is null"); - break; - } - mConnSocket = serverSocket.accept(); - if (DEBUG) Log.d(TAG, "Accepted socket connection..."); - synchronized (BluetoothMapService.this) { - if (mConnSocket == null) { - Log.w(TAG, "mConnSocket is null"); - break; - } - mRemoteDevice = mConnSocket.getRemoteDevice(); - } - if (mRemoteDevice == null) { - Log.i(TAG, "getRemoteDevice() = null"); - break; - } - - sRemoteDeviceName = mRemoteDevice.getName(); - // In case getRemoteName failed and return null - if (TextUtils.isEmpty(sRemoteDeviceName)) { - sRemoteDeviceName = getString(R.string.defaultname); - } - boolean trust = mRemoteDevice.getTrustState(); - if (DEBUG) Log.d(TAG, "GetTrustState() = " + trust); - - - if (trust) { - try { - if (DEBUG) Log.d(TAG, "incoming connection accepted from: " - + sRemoteDeviceName + " automatically as trusted device"); - startObexServerSession(); - } catch (IOException ex) { - Log.e(TAG, "catch exception starting obex server session" - + ex.toString()); - } - } else { - Intent intent = new - Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST); - intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, - BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); - - isWaitingAuthorization = true; - sendOrderedBroadcast(intent, BLUETOOTH_ADMIN_PERM); - - if (DEBUG) Log.d(TAG, "waiting for authorization for connection from: " - + sRemoteDeviceName); - //Queue USER_TIMEOUT to disconnect MAP OBEX session. If user doesn't - //accept or reject authorization request - removeTimeoutMsg = true; - mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler - .obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE); + /* Shutdown the MNS client - currently must happen before MAS close */ + if(mBluetoothMnsObexClient != null && lastMasInst) { + mBluetoothMnsObexClient.shutdown(); + mBluetoothMnsObexClient = null; + } + BluetoothMapMasInstance masInst = mMasInstances.get(masId); // returns null for -1 + if(masInst != null) { + masInst.restartObexServerSession(); + } else { + for(int i=0, c=mMasInstances.size(); i < c; i++) { + mMasInstances.valueAt(i).restartObexServerSession(); + } + } - } - stopped = true; // job done ,close this thread; - } catch (IOException ex) { - stopped=true; - if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString()); - } + if(lastMasInst) { + setState(BluetoothMap.STATE_DISCONNECTED); + mTrust = false; + mRemoteDevice = null; + if(mAccountChanged) { + updateMasInstances(UPDATE_MAS_INSTANCES_ACCOUNT_DISCONNECT); } } - void shutdown() { - stopped = true; - interrupt(); + // Release the wake lock at disconnect + if (mWakeLock != null && lastMasInst) { + mSessionStatusHandler.removeMessages(MSG_ACQUIRE_WAKE_LOCK); + mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK); + mWakeLock.release(); + if(VERBOSE)Log.i(TAG, "stopObexServerSessions(): Release Wake Lock"); } } @@ -443,23 +318,35 @@ public class BluetoothMapService extends ProfileService { if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what); switch (msg.what) { + case UPDATE_MAS_INSTANCES: + updateMasInstancesHandler(); + break; case START_LISTENER: if (mAdapter.isEnabled()) { - startRfcommSocketListener(); + startRfcommSocketListeners(); } break; + case MSG_MAS_CONNECT: + onConnectHandler(msg.arg1); + break; + case MSG_MAS_CONNECT_CANCEL: + stopObexServerSessions(-1); + break; case USER_TIMEOUT: - Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); - intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, - BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS); - sendBroadcast(intent, BLUETOOTH_PERM); - isWaitingAuthorization = false; - removeTimeoutMsg = false; - stopObexServerSession(); + if(mIsWaitingAuthorization){ + Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); + intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); + intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, + BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS); + sendBroadcast(intent); + cancelUserTimeoutAlarm(); + mIsWaitingAuthorization = false; + stopObexServerSessions(-1); + } break; case MSG_SERVERSESSION_CLOSE: - stopObexServerSession(); + stopObexServerSessions(msg.arg1); break; case MSG_SESSION_ESTABLISHED: break; @@ -469,25 +356,33 @@ public class BluetoothMapService extends ProfileService { case DISCONNECT_MAP: disconnectMap((BluetoothDevice)msg.obj); break; + case SHUTDOWN: + /* Ensure to call close from this handler to avoid starting new stuff + because of pending messages */ + closeService(); + break; case MSG_ACQUIRE_WAKE_LOCK: + if(VERBOSE)Log.i(TAG, "Acquire Wake Lock request message"); if (mWakeLock == null) { PowerManager pm = (PowerManager)getSystemService( Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "StartingObexMapTransaction"); mWakeLock.setReferenceCounted(false); + } + if(!mWakeLock.isHeld()) { mWakeLock.acquire(); - Log.w(TAG, "Acquire Wake Lock"); + if(DEBUG)Log.i(TAG, " Acquired Wake Lock by message"); } mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK); mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY); break; case MSG_RELEASE_WAKE_LOCK: + if(VERBOSE)Log.i(TAG, "Release Wake Lock request message"); if (mWakeLock != null) { mWakeLock.release(); - mWakeLock = null; - Log.w(TAG, "Release Wake Lock"); + if(DEBUG) Log.i(TAG, " Released Wake Lock by message"); } break; default: @@ -496,8 +391,34 @@ public class BluetoothMapService extends ProfileService { } }; + private void onConnectHandler(int masId) { + if(mIsWaitingAuthorization == true || mRemoteDevice == null) { + return; + } + BluetoothMapMasInstance masInst = mMasInstances.get(masId); + // getTrustState() is not implemented, use local cache + // boolean trust = mRemoteDevice.getTrustState(); // Need to ensure we are still trusted + boolean trust = mTrust; + if (DEBUG) Log.d(TAG, "GetTrustState() = " + trust); - public int getState() { + if (trust) { + try { + if (DEBUG) Log.d(TAG, "incoming connection accepted from: " + + sRemoteDeviceName + " automatically as trusted device"); + if(mBluetoothMnsObexClient != null + && masInst != null) { + masInst.startObexServerSession(mBluetoothMnsObexClient); + } else { + startObexServerSessions(); + } + } catch (IOException ex) { + Log.e(TAG, "catch IOException starting obex server session", ex); + } catch (RemoteException ex) { + Log.e(TAG, "catch RemoteException starting obex server session", ex); + } + } + } + public int getState() { return mState; } @@ -542,17 +463,7 @@ public class BluetoothMapService extends ProfileService { if (getRemoteDevice().equals(device)) { switch (mState) { case BluetoothMap.STATE_CONNECTED: - if (mServerSession != null) { - mServerSession.close(); - mServerSession = null; - } - if(mBluetoothMnsObexClient != null) { - mBluetoothMnsObexClient.shutdown(); - mBluetoothMnsObexClient = null; - } - closeConnectionSocket(); - - setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED); + sendShutdownMessage(); result = true; break; default: @@ -630,40 +541,328 @@ public class BluetoothMapService extends ProfileService { filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); + filter.addAction(ACTION_SHOW_MAPS_EMAIL_SETTINGS); + filter.addAction(USER_CONFIRM_TIMEOUT_ACTION); + + // We need two filters, since Type only applies to the ACTION_MESSAGE_SENT + IntentFilter filterMessageSent = new IntentFilter(); + filterMessageSent.addAction(BluetoothMapContentObserver.ACTION_MESSAGE_SENT); + try{ + filterMessageSent.addDataType("message/*"); + } catch (MalformedMimeTypeException e) { + Log.e(TAG, "Wrong mime type!!!", e); + } + try { registerReceiver(mMapReceiver, filter); + registerReceiver(mMapReceiver, filterMessageSent); } catch (Exception e) { Log.w(TAG,"Unable to register map receiver",e); } - mInterrupted = false; mAdapter = BluetoothAdapter.getDefaultAdapter(); + mAppObserver = new BluetoothMapEmailAppObserver(this, this); + + mEnabledAccounts = mAppObserver.getEnabledAccountItems(); + // Uses mEnabledAccounts, hence getEnabledAccountItems() must be called before this. + createMasInstances(); + // start RFCOMM listener mSessionStatusHandler.sendMessage(mSessionStatusHandler .obtainMessage(START_LISTENER)); + return true; } + /** + * Call this to trigger an update of the MAS instance list. + * No changes will be applied unless in disconnected state + */ + public void updateMasInstances(int action) { + mSessionStatusHandler.obtainMessage (UPDATE_MAS_INSTANCES, + action, 0).sendToTarget(); + } + + /** + * Update the active MAS Instances according the difference between mEnabledDevices + * and the current list of accounts. + * Will only make changes if state is disconnected. + * + * How it works: + * 1) Build lists of account changes from last update of mEnabledAccounts. + * newAccounts - accounts that have been enabled since mEnabledAccounts + * was last updated. + * removedAccounts - Accounts that is on mEnabledAccounts, but no longer + * enabled. + * enabledAccounts - A new list of all enabled accounts. + * 2) Stop and remove all MasInstances on the remove list + * 3) Add and start MAS instances for accounts on the new list. + * Called at: + * - Each change in accounts + * - Each disconnect - before MasInstances restart. + * + * @return true is any changes are made, false otherwise. + */ + private boolean updateMasInstancesHandler(){ + if(DEBUG)Log.d(TAG,"updateMasInstancesHandler() state = " + getState()); + boolean changed = false; + + if(getState() == BluetoothMap.STATE_DISCONNECTED) { + ArrayList<BluetoothMapEmailSettingsItem> newAccountList = mAppObserver.getEnabledAccountItems(); + ArrayList<BluetoothMapEmailSettingsItem> newAccounts = null; + ArrayList<BluetoothMapEmailSettingsItem> removedAccounts = null; + newAccounts = new ArrayList<BluetoothMapEmailSettingsItem>(); + removedAccounts = mEnabledAccounts; // reuse the current enabled list, to track removed accounts + for(BluetoothMapEmailSettingsItem account: newAccountList) { + if(!removedAccounts.remove(account)) { + newAccounts.add(account); + } + } + + if(removedAccounts != null) { + /* Remove all disabled/removed accounts */ + for(BluetoothMapEmailSettingsItem account : removedAccounts) { + BluetoothMapMasInstance masInst = mMasInstanceMap.remove(account); + if(DEBUG)Log.d(TAG," Removing account: " + account + " masInst = " + masInst); + if(masInst != null) { + masInst.shutdown(); + mMasInstances.remove(masInst.getMasId()); + changed = true; + } + } + } + + if(newAccounts != null) { + /* Add any newly created accounts */ + for(BluetoothMapEmailSettingsItem account : newAccounts) { + if(DEBUG)Log.d(TAG," Adding account: " + account); + int masId = getNextMasId(); + BluetoothMapMasInstance newInst = + new BluetoothMapMasInstance(this, + this, + account, + masId, + false); + mMasInstances.append(masId, newInst); + mMasInstanceMap.put(account, newInst); + changed = true; + /* Start the new instance */ + if (mAdapter.isEnabled()) { + newInst.startRfcommSocketListener(); + } + } + } + mEnabledAccounts = newAccountList; + if(VERBOSE) { + Log.d(TAG," Enabled accounts:"); + for(BluetoothMapEmailSettingsItem account : mEnabledAccounts) { + Log.d(TAG, " " + account); + } + Log.d(TAG," Active MAS instances:"); + for(int i=0, c=mMasInstances.size(); i < c; i++) { + BluetoothMapMasInstance masInst = mMasInstances.valueAt(i); + Log.d(TAG, " " + masInst); + } + } + mAccountChanged = false; + } else { + mAccountChanged = true; + } + return changed; + } + + /** + * Will return the next MasId to use. + * Will ensure the key returned is greater than the largest key in use. + * Unless the key 255 is in use, in which case the first free masId + * will be returned. + * @return + */ + private int getNextMasId() { + /* Find the largest masId in use */ + int largestMasId = 0; + for(int i=0, c=mMasInstances.size(); i < c; i++) { + int masId = mMasInstances.keyAt(i); + if(masId > largestMasId) { + largestMasId = masId; + } + } + if(largestMasId < 0xff) { + return largestMasId + 1; + } + /* If 0xff is already in use, wrap and choose the first free + * MasId. */ + for(int i = 1; i <= 0xff; i++) { + if(mMasInstances.get(i) == null) { + return i; + } + } + return 0xff; // This will never happen, as we only allow 10 e-mail accounts to be enabled + } + + private void createMasInstances() { + int masId = MAS_ID_SMS_MMS; + + // Add the SMS/MMS instance + BluetoothMapMasInstance smsMmsInst = + new BluetoothMapMasInstance(this, + this, + null, + masId, + true); + mMasInstances.append(masId, smsMmsInst); + mMasInstanceMap.put(null, smsMmsInst); + + // get list of accounts already set to be visible through MAP + for(BluetoothMapEmailSettingsItem account : mEnabledAccounts) { + masId++; // SMS/MMS is masId=0, increment before adding next + BluetoothMapMasInstance newInst = + new BluetoothMapMasInstance(this, + this, + account, + masId, + false); + mMasInstances.append(masId, newInst); + mMasInstanceMap.put(account, newInst); + } + } + @Override protected boolean stop() { if (DEBUG) Log.d(TAG, "stop()"); try { unregisterReceiver(mMapReceiver); + mAppObserver.shutdown(); } catch (Exception e) { Log.w(TAG,"Unable to unregister map receiver",e); } setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED); - closeService(); + sendShutdownMessage(); return true; } public boolean cleanup() { if (DEBUG) Log.d(TAG, "cleanup()"); setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED); + // TODO: Change to use message? - do we need to wait for completion? closeService(); return true; } + /** + * Called from each MAS instance when a connection is received. + * @param remoteDevice The device connecting + * @param masInst a reference to the calling MAS instance. + * @return + */ + public boolean onConnect(BluetoothDevice remoteDevice, BluetoothMapMasInstance masInst) { + + boolean sendIntent=false; + // As this can be called from each MasInstance, we need to lock access to member variables + synchronized(this) { + if(mRemoteDevice == null) { + mRemoteDevice = remoteDevice; + sRemoteDeviceName = mRemoteDevice.getName(); + // In case getRemoteName failed and return null + if (TextUtils.isEmpty(sRemoteDeviceName)) { + sRemoteDeviceName = getString(R.string.defaultname); + } + + if(mTrust == false) { + sendIntent = true; + mIsWaitingAuthorization = true; + setUserTimeoutAlarm(); + } + } else if (!mRemoteDevice.equals(remoteDevice)) { + Log.w(TAG, "Unexpected connection from a second Remote Device received. name: " + + ((remoteDevice==null)?"unknown":remoteDevice.getName())); + return false; /* The connecting device is different from what is already + connected, reject the connection. */ + } // Else second connection to same device, just continue + } + + + if(sendIntent == true) { + /* This will trigger */ + Intent intent = new + Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST); + intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); + intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, + BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); + sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); + + if (DEBUG) Log.d(TAG, "waiting for authorization for connection from: " + + sRemoteDeviceName); + //Queue USER_TIMEOUT to disconnect MAP OBEX session. If user doesn't + //accept or reject authorization request + + + + + } else { + /* Signal to the service that we have a incoming connection. */ + sendConnectMessage(masInst.getMasId()); + } + return true; + }; + + + private void setUserTimeoutAlarm(){ + if(DEBUG)Log.d(TAG,"SetUserTimeOutAlarm()"); + if(mAlarmManager == null){ + mAlarmManager =(AlarmManager) this.getSystemService (Context.ALARM_SERVICE); + } + mRemoveTimeoutMsg = true; + Intent timeoutIntent = + new Intent(USER_CONFIRM_TIMEOUT_ACTION); + PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0); + mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+USER_CONFIRM_TIMEOUT_VALUE,pIntent); + } + + private void cancelUserTimeoutAlarm(){ + if(DEBUG)Log.d(TAG,"cancelUserTimeOutAlarm()"); + Intent intent = new Intent(this, BluetoothMapService.class); + PendingIntent sender = PendingIntent.getBroadcast(this, 0, intent, 0); + AlarmManager alarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); + alarmManager.cancel(sender); + mRemoveTimeoutMsg = false; + } + + private void sendConnectMessage(int masId) { + if(mSessionStatusHandler != null) { + Message msg = mSessionStatusHandler.obtainMessage(MSG_MAS_CONNECT, masId, 0); + msg.sendToTarget(); + } // Can only be null during shutdown + } + private void sendConnectTimeoutMessage() { + if (DEBUG) Log.d(TAG, "sendConnectTimeoutMessage()"); + if(mSessionStatusHandler != null) { + Message msg = mSessionStatusHandler.obtainMessage(USER_TIMEOUT); + msg.sendToTarget(); + } // Can only be null during shutdown + } + private void sendConnectCancelMessage() { + if(mSessionStatusHandler != null) { + Message msg = mSessionStatusHandler.obtainMessage(MSG_MAS_CONNECT_CANCEL); + msg.sendToTarget(); + } // Can only be null during shutdown + } + + private void sendShutdownMessage() { + /* Any pending messages are no longer valid. + To speed up things, simply delete them. */ + if (mRemoveTimeoutMsg) { + Intent timeoutIntent = + new Intent(USER_CONFIRM_TIMEOUT_ACTION); + sendBroadcast(timeoutIntent, BLUETOOTH_PERM); + mIsWaitingAuthorization = false; + cancelUserTimeoutAlarm(); + } + mSessionStatusHandler.removeCallbacksAndMessages(null); + // Request release of all resources + mSessionStatusHandler.obtainMessage(SHUTDOWN).sendToTarget(); + } private MapBroadcastReceiver mMapReceiver = new MapBroadcastReceiver(); @@ -676,71 +875,78 @@ public class BluetoothMapService extends ProfileService { int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); if (state == BluetoothAdapter.STATE_TURNING_OFF) { - if (DEBUG) Log.d(TAG, "STATE_TURNING_OFF removeTimeoutMsg:" + removeTimeoutMsg); - // Send any pending timeout now, as this service will be destroyed. - if (removeTimeoutMsg) { - mSessionStatusHandler.removeMessages(USER_TIMEOUT); - - Intent timeoutIntent = - new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); - timeoutIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); - timeoutIntent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, - BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS); - sendBroadcast(timeoutIntent, BLUETOOTH_PERM); - isWaitingAuthorization = false; - removeTimeoutMsg = false; - stopObexServerSession(); - } - - // Release all resources - closeService(); + if (DEBUG) Log.d(TAG, "STATE_TURNING_OFF"); + sendShutdownMessage(); } else if (state == BluetoothAdapter.STATE_ON) { if (DEBUG) Log.d(TAG, "STATE_ON"); - mInterrupted = false; // start RFCOMM listener mSessionStatusHandler.sendMessage(mSessionStatusHandler .obtainMessage(START_LISTENER)); } + }else if (action.equals(USER_CONFIRM_TIMEOUT_ACTION)){ + if (DEBUG) Log.d(TAG, "USER_CONFIRM_TIMEOUT ACTION Received."); + // send us self a message about the timeout. + sendConnectTimeoutMessage(); } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); if (DEBUG) Log.d(TAG, "Received ACTION_CONNECTION_ACCESS_REPLY:" + - requestType + "isWaitingAuthorization:" + isWaitingAuthorization); - if ((!isWaitingAuthorization) || + requestType + "isWaitingAuthorization:" + mIsWaitingAuthorization); + if ((!mIsWaitingAuthorization) || (requestType != BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS)) { // this reply is not for us return; } - isWaitingAuthorization = false; - if (removeTimeoutMsg) { + mIsWaitingAuthorization = false; + if (mRemoveTimeoutMsg) { mSessionStatusHandler.removeMessages(USER_TIMEOUT); - removeTimeoutMsg = false; + cancelUserTimeoutAlarm(); + setState(BluetoothMap.STATE_DISCONNECTED); } if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, BluetoothDevice.CONNECTION_ACCESS_NO) == BluetoothDevice.CONNECTION_ACCESS_YES) { - //bluetooth connection accepted by user + // Bluetooth connection accepted by user if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) { - boolean result = mRemoteDevice.setTrust(true); - if (DEBUG) Log.d(TAG, "setTrust() result=" + result); - } - try { - if (mConnSocket != null) { - // start obex server and rfcomm connection - startObexServerSession(); - } else { - stopObexServerSession(); - } - } catch (IOException ex) { - Log.e(TAG, "Caught the error: " + ex.toString()); + // Not implemented in BluetoothDevice + //boolean result = mRemoteDevice.setTrust(true); + //if (DEBUG) Log.d(TAG, "setTrust() result=" + result); } + mTrust = true; + sendConnectMessage(-1); // -1 indicates all MAS instances } else { - stopObexServerSession(); + // Auth. declined by user, serverSession should not be running, but + // call stop anyway to restart listener. + mTrust = false; + sendConnectCancelMessage(); + } + } else if (action.equals(ACTION_SHOW_MAPS_EMAIL_SETTINGS)) { + Log.v(TAG, "Received ACTION_SHOW_MAPS_EMAIL_SETTINGS."); + + Intent in = new Intent(context, BluetoothMapEmailSettings.class); + in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + context.startActivity(in); + } else if (action.equals(BluetoothMapContentObserver.ACTION_MESSAGE_SENT)) { + BluetoothMapMasInstance masInst = null; + int result = getResultCode(); + boolean handled = false; + if(mMasInstances != null && (masInst = mMasInstances.get(MAS_ID_SMS_MMS)) != null) { + intent.putExtra(BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_RESULT, result); + if(masInst.handleSmsSendIntent(context, intent)) { + // The intent was handled by the mas instance it self + handled = true; + } + } + if(handled == false) + { + /* We do not have a connection to a device, hence we need to move + the SMS to the correct folder. */ + BluetoothMapContentObserver.actionMessageSentDisconnected(context, intent, result); } } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) && - isWaitingAuthorization) { + mIsWaitingAuthorization) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (mRemoteDevice == null || device == null) { @@ -750,7 +956,7 @@ public class BluetoothMapService extends ProfileService { if (DEBUG) Log.d(TAG,"ACL disconnected for "+ device); - if (mRemoteDevice.equals(device) && removeTimeoutMsg) { + if (mRemoteDevice.equals(device) && mRemoveTimeoutMsg) { // Send any pending timeout now, as ACL got disconnected. mSessionStatusHandler.removeMessages(USER_TIMEOUT); @@ -760,9 +966,9 @@ public class BluetoothMapService extends ProfileService { timeoutIntent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS); sendBroadcast(timeoutIntent, BLUETOOTH_PERM); - isWaitingAuthorization = false; - removeTimeoutMsg = false; - stopObexServerSession(); + mIsWaitingAuthorization = false; + mRemoveTimeoutMsg = false; + } } } @@ -868,5 +1074,5 @@ public class BluetoothMapService extends ProfileService { if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED; return service.getPriority(device); } - }; + } } |