• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2 * Copyright (C) 2014 Samsung System LSI
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *      http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 
16 package com.android.bluetooth.map;
17 
18 import android.app.AlarmManager;
19 import android.app.PendingIntent;
20 import android.bluetooth.BluetoothAdapter;
21 import android.bluetooth.BluetoothDevice;
22 import android.bluetooth.BluetoothMap;
23 import android.bluetooth.BluetoothProfile;
24 import android.bluetooth.BluetoothUuid;
25 import android.bluetooth.IBluetoothMap;
26 import android.bluetooth.SdpMnsRecord;
27 import android.content.BroadcastReceiver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.IntentFilter.MalformedMimeTypeException;
32 import android.os.Handler;
33 import android.os.HandlerThread;
34 import android.os.Looper;
35 import android.os.Message;
36 import android.os.ParcelUuid;
37 import android.os.PowerManager;
38 import android.os.RemoteException;
39 import android.provider.Settings;
40 import android.text.TextUtils;
41 import android.util.Log;
42 import android.util.SparseArray;
43 
44 import com.android.bluetooth.R;
45 import com.android.bluetooth.Utils;
46 import com.android.bluetooth.btservice.AdapterService;
47 import com.android.bluetooth.btservice.ProfileService;
48 import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder;
49 
50 import java.io.IOException;
51 import java.util.ArrayList;
52 import java.util.HashMap;
53 import java.util.List;
54 import java.util.Set;
55 
56 public class BluetoothMapService extends ProfileService {
57     private static final String TAG = "BluetoothMapService";
58 
59     /**
60      * To enable MAP DEBUG/VERBOSE logging - run below cmd in adb shell, and
61      * restart com.android.bluetooth process. only enable DEBUG log:
62      * "setprop log.tag.BluetoothMapService DEBUG"; enable both VERBOSE and
63      * DEBUG log: "setprop log.tag.BluetoothMapService VERBOSE"
64      */
65 
66     public static final boolean DEBUG = true; //FIXME set to false;
67 
68     public static final boolean VERBOSE = false;
69 
70     /**
71      * Intent indicating timeout for user confirmation, which is sent to
72      * BluetoothMapActivity
73      */
74     public static final String USER_CONFIRM_TIMEOUT_ACTION =
75             "com.android.bluetooth.map.USER_CONFIRM_TIMEOUT";
76     private static final int USER_CONFIRM_TIMEOUT_VALUE = 25000;
77 
78     /** Intent indicating that the email settings activity should be opened*/
79     public static final String ACTION_SHOW_MAPS_SETTINGS =
80             "android.btmap.intent.action.SHOW_MAPS_SETTINGS";
81 
82     public static final int MSG_SERVERSESSION_CLOSE = 5000;
83 
84     public static final int MSG_SESSION_ESTABLISHED = 5001;
85 
86     public static final int MSG_SESSION_DISCONNECTED = 5002;
87 
88     public static final int MSG_MAS_CONNECT = 5003; // Send at MAS connect, including the MAS_ID
89     public static final int MSG_MAS_CONNECT_CANCEL = 5004; // Send at auth. declined
90 
91     public static final int MSG_ACQUIRE_WAKE_LOCK = 5005;
92 
93     public static final int MSG_RELEASE_WAKE_LOCK = 5006;
94 
95     public static final int MSG_MNS_SDP_SEARCH = 5007;
96 
97     public static final int MSG_OBSERVER_REGISTRATION = 5008;
98 
99     private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
100 
101     private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
102 
103     private static final int START_LISTENER = 1;
104 
105     private static final int USER_TIMEOUT = 2;
106 
107     private static final int DISCONNECT_MAP = 3;
108 
109     private static final int SHUTDOWN = 4;
110 
111     private static final int RELEASE_WAKE_LOCK_DELAY = 10000;
112 
113     private PowerManager.WakeLock mWakeLock = null;
114 
115     private static final int UPDATE_MAS_INSTANCES = 5;
116 
117     public static final int UPDATE_MAS_INSTANCES_ACCOUNT_ADDED = 0;
118     public static final int UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED = 1;
119     public static final int UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED = 2;
120     public static final int UPDATE_MAS_INSTANCES_ACCOUNT_DISCONNECT = 3;
121 
122     private static final int MAS_ID_SMS_MMS = 0;
123 
124     private BluetoothAdapter mAdapter;
125 
126     private BluetoothMnsObexClient mBluetoothMnsObexClient = null;
127 
128     /* mMasInstances: A list of the active MasInstances with the key being the MasId */
129     private SparseArray<BluetoothMapMasInstance> mMasInstances =
130             new SparseArray<BluetoothMapMasInstance>(1);
131     /* mMasInstanceMap: A list of the active MasInstances with the key being the account */
132     private HashMap<BluetoothMapAccountItem, BluetoothMapMasInstance> mMasInstanceMap =
133             new HashMap<BluetoothMapAccountItem, BluetoothMapMasInstance>(1);
134 
135     private static BluetoothDevice mRemoteDevice = null; // The remote connected device - protect access
136 
137     private ArrayList<BluetoothMapAccountItem> mEnabledAccounts = null;
138     private static String sRemoteDeviceName = null;
139 
140     private int mState;
141     private BluetoothMapAppObserver mAppObserver = null;
142     private AlarmManager mAlarmManager = null;
143 
144     private boolean mIsWaitingAuthorization = false;
145     private boolean mRemoveTimeoutMsg = false;
146     private boolean mRegisteredMapReceiver = false;
147     private int mPermission = BluetoothDevice.ACCESS_UNKNOWN;
148     private boolean mAccountChanged = false;
149     private boolean mSdpSearchInitiated = false;
150     SdpMnsRecord mMnsRecord = null;
151     private MapServiceMessageHandler mSessionStatusHandler;
152     private boolean mStartError = true;
153 
154     private boolean mSmsCapable = true;
155 
156     private static final ParcelUuid[] MAP_UUIDS = {
157         BluetoothUuid.MAP,
158         BluetoothUuid.MNS,
159     };
160 
BluetoothMapService()161     public BluetoothMapService() {
162         mState = BluetoothMap.STATE_DISCONNECTED;
163     }
164 
165 
closeService()166     private synchronized void closeService() {
167         if (DEBUG) Log.d(TAG, "MAP Service closeService in");
168 
169         if (mBluetoothMnsObexClient != null) {
170             mBluetoothMnsObexClient.shutdown();
171             mBluetoothMnsObexClient = null;
172         }
173         if (mMasInstances.size() > 0) {
174             for (int i=0, c=mMasInstances.size(); i < c; i++) {
175                 mMasInstances.valueAt(i).shutdown();
176             }
177             mMasInstances.clear();
178         }
179 
180         mIsWaitingAuthorization = false;
181         mPermission = BluetoothDevice.ACCESS_UNKNOWN;
182         setState(BluetoothMap.STATE_DISCONNECTED);
183 
184         if (mWakeLock != null) {
185             mWakeLock.release();
186             if (VERBOSE) Log.v(TAG, "CloseService(): Release Wake Lock");
187             mWakeLock = null;
188         }
189         /* Only one SHUTDOWN message expected to closeService.
190          * Hence, quit looper and Handler on first SHUTDOWN message*/
191         if (mSessionStatusHandler != null) {
192             //Perform cleanup in Handler running on worker Thread
193             mSessionStatusHandler.removeCallbacksAndMessages(null);
194             Looper looper = mSessionStatusHandler.getLooper();
195             if (looper != null) {
196                 looper.quit();
197                 if(VERBOSE) Log.i(TAG, "Quit looper");
198             }
199             mSessionStatusHandler = null;
200             if(VERBOSE) Log.i(TAG, "Remove Handler");
201         }
202         mRemoteDevice = null;
203 
204         if (VERBOSE) Log.v(TAG, "MAP Service closeService out");
205     }
206 
207     /**
208      * Starts the RFComm listener threads for each MAS
209      * @throws IOException
210      */
startRfcommSocketListeners(int masId)211     private final void startRfcommSocketListeners(int masId) {
212         if(masId == -1) {
213             for(int i=0, c=mMasInstances.size(); i < c; i++) {
214                 mMasInstances.valueAt(i).startRfcommSocketListener();
215             }
216         } else {
217             BluetoothMapMasInstance masInst = mMasInstances.get(masId); // returns null for -1
218             if(masInst != null) {
219                 masInst.startRfcommSocketListener();
220             } else {
221                 Log.w(TAG, "startRfcommSocketListeners(): Invalid MasId: " + masId);
222             }
223         }
224     }
225 
226     /**
227      * Start a MAS instance for SMS/MMS and each e-mail account.
228      */
startObexServerSessions()229     private final void startObexServerSessions() {
230         if (DEBUG) Log.d(TAG, "Map Service START ObexServerSessions()");
231 
232         // acquire the wakeLock before start Obex transaction thread
233         if (mWakeLock == null) {
234             PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
235             mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
236                     "StartingObexMapTransaction");
237             mWakeLock.setReferenceCounted(false);
238             mWakeLock.acquire();
239             if (VERBOSE) Log.v(TAG, "startObexSessions(): Acquire Wake Lock");
240         }
241 
242         if(mBluetoothMnsObexClient == null) {
243             mBluetoothMnsObexClient =
244                     new BluetoothMnsObexClient(mRemoteDevice, mMnsRecord, mSessionStatusHandler);
245         }
246 
247         boolean connected = false;
248         for(int i=0, c=mMasInstances.size(); i < c; i++) {
249             try {
250                 if(mMasInstances.valueAt(i)
251                         .startObexServerSession(mBluetoothMnsObexClient) == true) {
252                     connected = true;
253                 }
254             } catch (IOException e) {
255                 Log.w(TAG,"IOException occured while starting an obexServerSession restarting" +
256                         " the listener",e);
257                 mMasInstances.valueAt(i).restartObexServerSession();
258             } catch (RemoteException e) {
259                 Log.w(TAG,"RemoteException occured while starting an obexServerSession restarting" +
260                         " the listener",e);
261                 mMasInstances.valueAt(i).restartObexServerSession();
262             }
263         }
264         if(connected) {
265             setState(BluetoothMap.STATE_CONNECTED);
266         }
267 
268         mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
269         mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
270                 .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY);
271 
272         if (VERBOSE) Log.v(TAG, "startObexServerSessions() success!");
273     }
274 
getHandler()275     public Handler getHandler() {
276         return mSessionStatusHandler;
277     }
278 
279     /**
280      * Restart a MAS instances.
281      * @param masId use -1 to stop all instances
282      */
stopObexServerSessions(int masId)283     private void stopObexServerSessions(int masId) {
284         if (DEBUG) Log.d(TAG, "MAP Service STOP ObexServerSessions()");
285 
286         boolean lastMasInst = true;
287 
288         if(masId != -1) {
289             for(int i=0, c=mMasInstances.size(); i < c; i++) {
290                 BluetoothMapMasInstance masInst = mMasInstances.valueAt(i);
291                 if(masInst.getMasId() != masId && masInst.isStarted()) {
292                     lastMasInst = false;
293                 }
294             }
295         } // Else just close down it all
296 
297         /* Shutdown the MNS client - currently must happen before MAS close */
298         if(mBluetoothMnsObexClient != null && lastMasInst) {
299             mBluetoothMnsObexClient.shutdown();
300             mBluetoothMnsObexClient = null;
301         }
302 
303         BluetoothMapMasInstance masInst = mMasInstances.get(masId); // returns null for -1
304         if(masInst != null) {
305             masInst.restartObexServerSession();
306         } else  if(masId == -1) {
307             for(int i=0, c=mMasInstances.size(); i < c; i++) {
308                 mMasInstances.valueAt(i).restartObexServerSession();
309             }
310         }
311 
312         if(lastMasInst) {
313             setState(BluetoothMap.STATE_DISCONNECTED);
314             mPermission = BluetoothDevice.ACCESS_UNKNOWN;
315             mRemoteDevice = null;
316             if(mAccountChanged) {
317                 updateMasInstances(UPDATE_MAS_INSTANCES_ACCOUNT_DISCONNECT);
318             }
319         }
320 
321         // Release the wake lock at disconnect
322         if (mWakeLock != null && lastMasInst) {
323             mSessionStatusHandler.removeMessages(MSG_ACQUIRE_WAKE_LOCK);
324             mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
325             mWakeLock.release();
326             if (VERBOSE) Log.v(TAG, "stopObexServerSessions(): Release Wake Lock");
327         }
328     }
329 
330     private final class MapServiceMessageHandler extends Handler {
MapServiceMessageHandler(Looper looper)331         private MapServiceMessageHandler(Looper looper) {
332             super(looper);
333         }
334         @Override
handleMessage(Message msg)335         public void handleMessage(Message msg) {
336             if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what);
337 
338             switch (msg.what) {
339                 case UPDATE_MAS_INSTANCES:
340                     updateMasInstancesHandler();
341                     break;
342                 case START_LISTENER:
343                     if (mAdapter.isEnabled()) {
344                         startRfcommSocketListeners(msg.arg1);
345                     }
346                     break;
347                 case MSG_MAS_CONNECT:
348                     onConnectHandler(msg.arg1);
349                     break;
350                 case MSG_MAS_CONNECT_CANCEL:
351                     /* TODO: We need to handle this by accepting the connection and reject at
352                      * OBEX level, by using ObexRejectServer - add timeout to handle clients not
353                      * closing the transport channel.
354                      */
355                     stopObexServerSessions(-1);
356                     break;
357                 case USER_TIMEOUT:
358                     if (mIsWaitingAuthorization) {
359                         Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
360                         intent.setPackage(getString(R.string.pairing_ui_package));
361                         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
362                         intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
363                                 BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS);
364                         sendBroadcast(intent);
365                         cancelUserTimeoutAlarm();
366                         mIsWaitingAuthorization = false;
367                         stopObexServerSessions(-1);
368                     }
369                     break;
370                 case MSG_SERVERSESSION_CLOSE:
371                     stopObexServerSessions(msg.arg1);
372                     break;
373                 case MSG_SESSION_ESTABLISHED:
374                     break;
375                 case MSG_SESSION_DISCONNECTED:
376                     // handled elsewhere
377                     break;
378                 case DISCONNECT_MAP:
379                     disconnectMap((BluetoothDevice)msg.obj);
380                     break;
381                 case SHUTDOWN:
382                     /* Ensure to call close from this handler to avoid starting new stuff
383                        because of pending messages */
384                     closeService();
385                     break;
386                 case MSG_ACQUIRE_WAKE_LOCK:
387                     if (VERBOSE) Log.v(TAG, "Acquire Wake Lock request message");
388                     if (mWakeLock == null) {
389                         PowerManager pm = (PowerManager)getSystemService(
390                                           Context.POWER_SERVICE);
391                         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
392                                     "StartingObexMapTransaction");
393                         mWakeLock.setReferenceCounted(false);
394                     }
395                     if(!mWakeLock.isHeld()) {
396                         mWakeLock.acquire();
397                         if (DEBUG) Log.d(TAG, "  Acquired Wake Lock by message");
398                     }
399                     mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
400                     mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
401                       .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY);
402                     break;
403                 case MSG_RELEASE_WAKE_LOCK:
404                     if (VERBOSE) Log.v(TAG, "Release Wake Lock request message");
405                     if (mWakeLock != null) {
406                         mWakeLock.release();
407                         if (DEBUG) Log.d(TAG, "  Released Wake Lock by message");
408                     }
409                     break;
410                 case MSG_MNS_SDP_SEARCH:
411                     if (mRemoteDevice != null) {
412                         if (DEBUG) Log.d(TAG,"MNS SDP Initiate Search ..");
413                         mRemoteDevice.sdpSearch(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS);
414                     } else {
415                         Log.w(TAG, "remoteDevice info not available");
416                     }
417                     break;
418                 case MSG_OBSERVER_REGISTRATION:
419                     if (DEBUG) Log.d(TAG,"ContentObserver Registration MASID: " + msg.arg1
420                         + " Enable: " + msg.arg2);
421                     BluetoothMapMasInstance masInst = mMasInstances.get(msg.arg1);
422                     if (masInst != null && masInst.mObserver != null) {
423                         try {
424                             if (msg.arg2 == BluetoothMapAppParams.NOTIFICATION_STATUS_YES) {
425                                 masInst.mObserver.registerObserver();
426                             } else {
427                                 masInst.mObserver.unregisterObserver();
428                             }
429                         } catch (RemoteException e) {
430                             Log.e(TAG,"ContentObserverRegistarion Failed: "+ e);
431                         }
432                     }
433                     break;
434                 default:
435                     break;
436             }
437         }
438     };
439 
onConnectHandler(int masId)440     private void onConnectHandler(int masId) {
441         if (mIsWaitingAuthorization == true || mRemoteDevice == null
442                 || mSdpSearchInitiated == true) {
443             return;
444         }
445         BluetoothMapMasInstance masInst = mMasInstances.get(masId);
446         // Need to ensure we are still allowed.
447         if (DEBUG) Log.d(TAG, "mPermission = " + mPermission);
448         if (mPermission == BluetoothDevice.ACCESS_ALLOWED) {
449             try {
450                 if (VERBOSE) Log.v(TAG, "incoming connection accepted from: "
451                         + sRemoteDeviceName + " automatically as trusted device");
452                 if (mBluetoothMnsObexClient != null && masInst != null) {
453                     masInst.startObexServerSession(mBluetoothMnsObexClient);
454                 } else {
455                     startObexServerSessions();
456                 }
457             } catch (IOException ex) {
458                 Log.e(TAG, "catch IOException starting obex server session", ex);
459             } catch (RemoteException ex) {
460                 Log.e(TAG, "catch RemoteException starting obex server session", ex);
461             }
462         }
463     }
464 
getState()465     public int getState() {
466         return mState;
467     }
468 
isMapStarted()469     protected boolean isMapStarted() {
470         return !mStartError;
471     }
getRemoteDevice()472     public static BluetoothDevice getRemoteDevice() {
473         return mRemoteDevice;
474     }
setState(int state)475     private void setState(int state) {
476         setState(state, BluetoothMap.RESULT_SUCCESS);
477     }
478 
setState(int state, int result)479     private synchronized void setState(int state, int result) {
480         if (state != mState) {
481             if (DEBUG) Log.d(TAG, "Map state " + mState + " -> " + state + ", result = "
482                     + result);
483             int prevState = mState;
484             mState = state;
485             Intent intent = new Intent(BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
486             intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
487             intent.putExtra(BluetoothProfile.EXTRA_STATE, mState);
488             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
489             sendBroadcast(intent, BLUETOOTH_PERM);
490         }
491     }
492 
getRemoteDeviceName()493     public static String getRemoteDeviceName() {
494         return sRemoteDeviceName;
495     }
496 
disconnect(BluetoothDevice device)497     public boolean disconnect(BluetoothDevice device) {
498         mSessionStatusHandler.sendMessage(mSessionStatusHandler
499                 .obtainMessage(DISCONNECT_MAP, 0, 0, device));
500         return true;
501     }
502 
disconnectMap(BluetoothDevice device)503     public boolean disconnectMap(BluetoothDevice device) {
504         boolean result = false;
505         if (DEBUG) Log.d(TAG, "disconnectMap");
506         if (getRemoteDevice()!= null && getRemoteDevice().equals(device)) {
507             switch (mState) {
508                 case BluetoothMap.STATE_CONNECTED:
509                     /* Disconnect all connections and restart all MAS instances */
510                     stopObexServerSessions(-1);
511                     result = true;
512                     break;
513                 default:
514                     break;
515                 }
516         }
517         return result;
518     }
519 
getConnectedDevices()520     public List<BluetoothDevice> getConnectedDevices() {
521         List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
522         synchronized(this) {
523             if (mState == BluetoothMap.STATE_CONNECTED && mRemoteDevice != null) {
524                 devices.add(mRemoteDevice);
525             }
526         }
527         return devices;
528     }
529 
getDevicesMatchingConnectionStates(int[] states)530     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
531         List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
532         Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
533         int connectionState;
534         synchronized (this) {
535             for (BluetoothDevice device : bondedDevices) {
536                 ParcelUuid[] featureUuids = device.getUuids();
537                 if (!BluetoothUuid.containsAnyUuid(featureUuids, MAP_UUIDS)) {
538                     continue;
539                 }
540                 connectionState = getConnectionState(device);
541                 for(int i = 0; i < states.length; i++) {
542                     if (connectionState == states[i]) {
543                         deviceList.add(device);
544                     }
545                 }
546             }
547         }
548         return deviceList;
549     }
550 
getConnectionState(BluetoothDevice device)551     public int getConnectionState(BluetoothDevice device) {
552         synchronized(this) {
553             if (getState() == BluetoothMap.STATE_CONNECTED && getRemoteDevice().equals(device)) {
554                 return BluetoothProfile.STATE_CONNECTED;
555             } else {
556                 return BluetoothProfile.STATE_DISCONNECTED;
557             }
558         }
559     }
560 
setPriority(BluetoothDevice device, int priority)561     public boolean setPriority(BluetoothDevice device, int priority) {
562         Settings.Global.putInt(getContentResolver(),
563             Settings.Global.getBluetoothMapPriorityKey(device.getAddress()),
564             priority);
565         if (VERBOSE) Log.v(TAG, "Saved priority " + device + " = " + priority);
566         return true;
567     }
568 
getPriority(BluetoothDevice device)569     public int getPriority(BluetoothDevice device) {
570         int priority = Settings.Global.getInt(getContentResolver(),
571             Settings.Global.getBluetoothMapPriorityKey(device.getAddress()),
572             BluetoothProfile.PRIORITY_UNDEFINED);
573         return priority;
574     }
575 
576     @Override
initBinder()577     protected IProfileServiceBinder initBinder() {
578         return new BluetoothMapBinder(this);
579     }
580 
581     @Override
start()582     protected boolean start() {
583         if (DEBUG) Log.d(TAG, "start()");
584         if (isMapStarted()) {
585             Log.w(TAG, "start received for already started, ignoring");
586             return false;
587         }
588         HandlerThread thread = new HandlerThread("BluetoothMapHandler");
589         thread.start();
590         Looper looper = thread.getLooper();
591         mSessionStatusHandler = new MapServiceMessageHandler(looper);
592 
593         IntentFilter filter = new IntentFilter();
594         filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
595         filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
596         filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
597         filter.addAction(BluetoothDevice.ACTION_SDP_RECORD);
598         filter.addAction(ACTION_SHOW_MAPS_SETTINGS);
599         filter.addAction(USER_CONFIRM_TIMEOUT_ACTION);
600 
601         // We need two filters, since Type only applies to the ACTION_MESSAGE_SENT
602         IntentFilter filterMessageSent = new IntentFilter();
603         filterMessageSent.addAction(BluetoothMapContentObserver.ACTION_MESSAGE_SENT);
604         try{
605             filterMessageSent.addDataType("message/*");
606         } catch (MalformedMimeTypeException e) {
607             Log.e(TAG, "Wrong mime type!!!", e);
608         }
609         if (!mRegisteredMapReceiver) {
610             try {
611                 registerReceiver(mMapReceiver, filter);
612                 registerReceiver(mMapReceiver, filterMessageSent);
613                 mRegisteredMapReceiver = true;
614             } catch (Exception e) {
615                 Log.e(TAG,"Unable to register map receiver",e);
616             }
617         }
618         mAdapter = BluetoothAdapter.getDefaultAdapter();
619         mAppObserver = new BluetoothMapAppObserver(this, this);
620 
621         mEnabledAccounts = mAppObserver.getEnabledAccountItems();
622 
623         mSmsCapable = getResources().getBoolean(
624                 com.android.internal.R.bool.config_sms_capable);
625         // Uses mEnabledAccounts, hence getEnabledAccountItems() must be called before this.
626         createMasInstances();
627 
628         // start RFCOMM listener
629         sendStartListenerMessage(-1);
630         mStartError = false;
631         return !mStartError;
632     }
633 
634     /**
635      * Call this to trigger an update of the MAS instance list.
636      * No changes will be applied unless in disconnected state
637      */
updateMasInstances(int action)638     public void updateMasInstances(int action) {
639             mSessionStatusHandler.obtainMessage (UPDATE_MAS_INSTANCES,
640                     action, 0).sendToTarget();
641     }
642 
643     /**
644      * Update the active MAS Instances according the difference between mEnabledDevices
645      * and the current list of accounts.
646      * Will only make changes if state is disconnected.
647      *
648      * How it works:
649      * 1) Build lists of account changes from last update of mEnabledAccounts.
650      *      newAccounts - accounts that have been enabled since mEnabledAccounts
651      *                    was last updated.
652      *      removedAccounts - Accounts that is on mEnabledAccounts, but no longer
653      *                        enabled.
654      *      enabledAccounts - A new list of all enabled accounts.
655      * 2) Stop and remove all MasInstances on the remove list
656      * 3) Add and start MAS instances for accounts on the new list.
657      * Called at:
658      *  - Each change in accounts
659      *  - Each disconnect - before MasInstances restart.
660      *
661      * @return true is any changes are made, false otherwise.
662      */
updateMasInstancesHandler()663     private boolean updateMasInstancesHandler(){
664         if (DEBUG) Log.d(TAG,"updateMasInstancesHandler() state = " + getState());
665         boolean changed = false;
666 
667         if(getState() == BluetoothMap.STATE_DISCONNECTED) {
668             ArrayList<BluetoothMapAccountItem> newAccountList =
669                     mAppObserver.getEnabledAccountItems();
670             ArrayList<BluetoothMapAccountItem> newAccounts = null;
671             ArrayList<BluetoothMapAccountItem> removedAccounts = null;
672             newAccounts = new ArrayList<BluetoothMapAccountItem>();
673             removedAccounts = mEnabledAccounts; // reuse the current enabled list, to track removed
674                                                 // accounts
675             for(BluetoothMapAccountItem account: newAccountList) {
676                 if(!removedAccounts.remove(account)) {
677                     newAccounts.add(account);
678                 }
679             }
680 
681             if(removedAccounts != null) {
682                 /* Remove all disabled/removed accounts */
683                 for(BluetoothMapAccountItem account : removedAccounts) {
684                     BluetoothMapMasInstance masInst = mMasInstanceMap.remove(account);
685                     if (VERBOSE) Log.v(TAG,"  Removing account: " + account + " masInst = " + masInst);
686                     if(masInst != null) {
687                         masInst.shutdown();
688                         mMasInstances.remove(masInst.getMasId());
689                         changed = true;
690                     }
691                 }
692             }
693 
694             if(newAccounts != null) {
695                 /* Add any newly created accounts */
696                 for(BluetoothMapAccountItem account : newAccounts) {
697                     if (VERBOSE) Log.v(TAG,"  Adding account: " + account);
698                     int masId = getNextMasId();
699                     BluetoothMapMasInstance newInst =
700                             new BluetoothMapMasInstance(this,
701                                     this,
702                                     account,
703                                     masId,
704                                     false);
705                     mMasInstances.append(masId, newInst);
706                     mMasInstanceMap.put(account, newInst);
707                     changed = true;
708                     /* Start the new instance */
709                     if (mAdapter.isEnabled()) {
710                         newInst.startRfcommSocketListener();
711                     }
712                 }
713             }
714             mEnabledAccounts = newAccountList;
715             if (VERBOSE) {
716                 Log.v(TAG,"  Enabled accounts:");
717                 for(BluetoothMapAccountItem account : mEnabledAccounts) {
718                     Log.v(TAG, "   " + account);
719                 }
720                 Log.v(TAG,"  Active MAS instances:");
721                 for(int i=0, c=mMasInstances.size(); i < c; i++) {
722                     BluetoothMapMasInstance masInst = mMasInstances.valueAt(i);
723                     Log.v(TAG, "   " + masInst);
724                 }
725             }
726             mAccountChanged = false;
727         } else {
728             mAccountChanged = true;
729         }
730         return changed;
731     }
732 
733     /**
734      * Will return the next MasId to use.
735      * Will ensure the key returned is greater than the largest key in use.
736      * Unless the key 255 is in use, in which case the first free masId
737      * will be returned.
738      * @return
739      */
getNextMasId()740     private int getNextMasId() {
741         /* Find the largest masId in use */
742         int largestMasId = 0;
743         for(int i=0, c=mMasInstances.size(); i < c; i++) {
744             int masId = mMasInstances.keyAt(i);
745             if(masId > largestMasId) {
746                 largestMasId = masId;
747             }
748         }
749         if(largestMasId < 0xff) {
750             return largestMasId + 1;
751         }
752         /* If 0xff is already in use, wrap and choose the first free
753          * MasId. */
754         for(int i = 1; i <= 0xff; i++) {
755             if(mMasInstances.get(i) == null) {
756                 return i;
757             }
758         }
759         return 0xff; // This will never happen, as we only allow 10 e-mail accounts to be enabled
760     }
761 
createMasInstances()762     private void createMasInstances() {
763         int masId = mSmsCapable ? MAS_ID_SMS_MMS : -1;
764 
765         if (mSmsCapable) {
766             // Add the SMS/MMS instance
767             BluetoothMapMasInstance smsMmsInst =
768                     new BluetoothMapMasInstance(this,
769                             this,
770                             null,
771                             masId,
772                             true);
773             mMasInstances.append(masId, smsMmsInst);
774             mMasInstanceMap.put(null, smsMmsInst);
775         }
776 
777         // get list of accounts already set to be visible through MAP
778         for(BluetoothMapAccountItem account : mEnabledAccounts) {
779             masId++;  // SMS/MMS is masId=0, increment before adding next
780             BluetoothMapMasInstance newInst =
781                     new BluetoothMapMasInstance(this,
782                             this,
783                             account,
784                             masId,
785                             false);
786             mMasInstances.append(masId, newInst);
787             mMasInstanceMap.put(account, newInst);
788         }
789     }
790 
791     @Override
stop()792     protected boolean stop() {
793         if (DEBUG) Log.d(TAG, "stop()");
794         if (mRegisteredMapReceiver) {
795             try {
796                 mRegisteredMapReceiver = false;
797                 unregisterReceiver(mMapReceiver);
798                 mAppObserver.shutdown();
799             } catch (Exception e) {
800                 Log.e(TAG,"Unable to unregister map receiver",e);
801             }
802         }
803         //Stop MapProfile if already started.
804         //TODO: Check if the profile state can be retreived from ProfileService or AdapterService.
805         if (!isMapStarted()) {
806             if (DEBUG) Log.d(TAG, "Service Not Available to STOP, ignoring");
807             return true;
808         } else {
809             if (VERBOSE) Log.d(TAG, "Service Stoping()");
810         }
811         if (mSessionStatusHandler != null) {
812             sendShutdownMessage();
813         }
814         mStartError = true;
815         setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED);
816         return true;
817     }
818 
cleanup()819     public boolean cleanup()  {
820         if (DEBUG) Log.d(TAG, "cleanup()");
821         setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED);
822         //Cleanup already handled in Stop().
823         //Move this  extra check to Handler.
824         sendShutdownMessage();
825         return true;
826     }
827 
828     /**
829      * Called from each MAS instance when a connection is received.
830      * @param remoteDevice The device connecting
831      * @param masInst a reference to the calling MAS instance.
832      * @return
833      */
onConnect(BluetoothDevice remoteDevice, BluetoothMapMasInstance masInst)834     public boolean onConnect(BluetoothDevice remoteDevice, BluetoothMapMasInstance masInst) {
835         boolean sendIntent = false;
836         boolean cancelConnection = false;
837 
838         // As this can be called from each MasInstance, we need to lock access to member variables
839         synchronized(this) {
840             if (mRemoteDevice == null) {
841                 mRemoteDevice = remoteDevice;
842                 sRemoteDeviceName = mRemoteDevice.getName();
843                 // In case getRemoteName failed and return null
844                 if (TextUtils.isEmpty(sRemoteDeviceName)) {
845                     sRemoteDeviceName = getString(R.string.defaultname);
846                 }
847 
848                 mPermission = mRemoteDevice.getMessageAccessPermission();
849                 if (mPermission == BluetoothDevice.ACCESS_UNKNOWN) {
850                     sendIntent = true;
851                     mIsWaitingAuthorization = true;
852                     setUserTimeoutAlarm();
853                 } else if (mPermission == BluetoothDevice.ACCESS_REJECTED) {
854                     cancelConnection = true;
855                 } else if(mPermission == BluetoothDevice.ACCESS_ALLOWED) {
856                     mRemoteDevice.sdpSearch(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS);
857                     mSdpSearchInitiated = true;
858                 }
859             } else if (!mRemoteDevice.equals(remoteDevice)) {
860                 Log.w(TAG, "Unexpected connection from a second Remote Device received. name: " +
861                             ((remoteDevice==null)?"unknown":remoteDevice.getName()));
862                 return false; /* The connecting device is different from what is already
863                                  connected, reject the connection. */
864             } // Else second connection to same device, just continue
865         }
866 
867         if (sendIntent) {
868             // This will trigger Settings app's dialog.
869             Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
870             intent.setPackage(getString(R.string.pairing_ui_package));
871             intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
872                             BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS);
873             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
874             sendOrderedBroadcast(intent, BLUETOOTH_ADMIN_PERM);
875 
876             if (VERBOSE) Log.v(TAG, "waiting for authorization for connection from: "
877                     + sRemoteDeviceName);
878             //Queue USER_TIMEOUT to disconnect MAP OBEX session. If user doesn't
879             //accept or reject authorization request
880         } else if (cancelConnection) {
881             sendConnectCancelMessage();
882         } else if (mPermission == BluetoothDevice.ACCESS_ALLOWED) {
883             /* Signal to the service that we have a incoming connection. */
884             sendConnectMessage(masInst.getMasId());
885         }
886         return true;
887     };
888 
889 
setUserTimeoutAlarm()890     private void setUserTimeoutAlarm(){
891         if (DEBUG) Log.d(TAG,"SetUserTimeOutAlarm()");
892         if(mAlarmManager == null){
893             mAlarmManager =(AlarmManager) this.getSystemService (Context.ALARM_SERVICE);
894         }
895         mRemoveTimeoutMsg = true;
896         Intent timeoutIntent =
897                 new Intent(USER_CONFIRM_TIMEOUT_ACTION);
898         PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0);
899         mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() +
900                 USER_CONFIRM_TIMEOUT_VALUE,pIntent);
901     }
902 
cancelUserTimeoutAlarm()903     private void cancelUserTimeoutAlarm(){
904         if (DEBUG) Log.d(TAG,"cancelUserTimeOutAlarm()");
905         Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
906         PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0);
907         pIntent.cancel();
908 
909         AlarmManager alarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
910         alarmManager.cancel(pIntent);
911         mRemoveTimeoutMsg = false;
912     }
913 
914     /**
915      * Start the incoming connection listeners for a MAS ID
916      * @param masId the MasID to start. Use -1 to start all listeners.
917      */
sendStartListenerMessage(int masId)918     public void sendStartListenerMessage(int masId) {
919         if (mSessionStatusHandler != null && ! mSessionStatusHandler.hasMessages(START_LISTENER)) {
920             Message msg = mSessionStatusHandler.obtainMessage(START_LISTENER, masId, 0);
921             /* We add a small delay here to ensure the call returns true before this message is
922              * handled. It seems wrong to add a delay, but the alternative is to build a lock
923              * system to handle synchronization, which isn't nice either... */
924             mSessionStatusHandler.sendMessageDelayed(msg, 20);
925         } else if (mSessionStatusHandler != null) {
926             if(DEBUG)
927                 Log.w(TAG, "mSessionStatusHandler START_LISTENER message already in Queue");
928         }
929     }
930 
sendConnectMessage(int masId)931     private void sendConnectMessage(int masId) {
932         if(mSessionStatusHandler != null) {
933             Message msg = mSessionStatusHandler.obtainMessage(MSG_MAS_CONNECT, masId, 0);
934             /* We add a small delay here to ensure onConnect returns true before this message is
935              * handled. It seems wrong, but the alternative is to store a reference to the
936              * connection in this message, which isn't nice either... */
937             mSessionStatusHandler.sendMessageDelayed(msg, 20);
938         } // Can only be null during shutdown
939     }
sendConnectTimeoutMessage()940     private void sendConnectTimeoutMessage() {
941         if (DEBUG) Log.d(TAG, "sendConnectTimeoutMessage()");
942         if(mSessionStatusHandler != null) {
943             Message msg = mSessionStatusHandler.obtainMessage(USER_TIMEOUT);
944             msg.sendToTarget();
945         } // Can only be null during shutdown
946     }
sendConnectCancelMessage()947     private void sendConnectCancelMessage() {
948         if(mSessionStatusHandler != null) {
949             Message msg = mSessionStatusHandler.obtainMessage(MSG_MAS_CONNECT_CANCEL);
950             msg.sendToTarget();
951         } // Can only be null during shutdown
952     }
953 
sendShutdownMessage()954     private void sendShutdownMessage() {
955         /* Any pending messages are no longer valid.
956         To speed up things, simply delete them. */
957         if (mRemoveTimeoutMsg) {
958             Intent timeoutIntent =
959                     new Intent(USER_CONFIRM_TIMEOUT_ACTION);
960             sendBroadcast(timeoutIntent, BLUETOOTH_PERM);
961             mIsWaitingAuthorization = false;
962             cancelUserTimeoutAlarm();
963         }
964         if (mSessionStatusHandler != null && !mSessionStatusHandler.hasMessages(SHUTDOWN)) {
965             mSessionStatusHandler.removeCallbacksAndMessages(null);
966             // Request release of all resources
967             Message msg = mSessionStatusHandler.obtainMessage(SHUTDOWN);
968             if( mSessionStatusHandler.sendMessage(msg) == false) {
969                 /* most likely caused by shutdown being called from multiple sources - e.g.BT off
970                  * signaled through intent and a service shutdown simultaneously.
971                  * Intended behavior not documented, hence we need to be able to handle all cases*/
972             } else {
973                 if(DEBUG)
974                     Log.e(TAG, "mSessionStatusHandler.sendMessage() dispatched shutdown message");
975             }
976         } else if (mSessionStatusHandler != null) {
977                 if(DEBUG)
978                     Log.w(TAG, "mSessionStatusHandler shutdown message already in Queue");
979         }
980         if (VERBOSE) Log.d(TAG, "sendShutdownMessage() Out");
981     }
982 
983     private MapBroadcastReceiver mMapReceiver = new MapBroadcastReceiver();
984 
985     private class MapBroadcastReceiver extends BroadcastReceiver {
986         @Override
onReceive(Context context, Intent intent)987         public void onReceive(Context context, Intent intent) {
988             if (DEBUG) Log.d(TAG, "onReceive");
989             String action = intent.getAction();
990             if (DEBUG) Log.d(TAG, "onReceive: " + action);
991             if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
992                 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
993                                                BluetoothAdapter.ERROR);
994                 if (state == BluetoothAdapter.STATE_TURNING_OFF) {
995                     if (DEBUG) Log.d(TAG, "STATE_TURNING_OFF");
996                     sendShutdownMessage();
997                 } else if (state == BluetoothAdapter.STATE_ON) {
998                     if (DEBUG) Log.d(TAG, "STATE_ON");
999                     // start ServerSocket listener threads
1000                     sendStartListenerMessage(-1);
1001                 }
1002 
1003             }else if (action.equals(USER_CONFIRM_TIMEOUT_ACTION)){
1004                 if (DEBUG) Log.d(TAG, "USER_CONFIRM_TIMEOUT ACTION Received.");
1005                 // send us self a message about the timeout.
1006                 sendConnectTimeoutMessage();
1007 
1008             } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
1009 
1010                 int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
1011                                                BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
1012 
1013                 if (DEBUG) Log.d(TAG, "Received ACTION_CONNECTION_ACCESS_REPLY:" +
1014                            requestType + "isWaitingAuthorization:" + mIsWaitingAuthorization);
1015                 if ((!mIsWaitingAuthorization)
1016                         || (requestType != BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS)) {
1017                     // this reply is not for us
1018                     return;
1019                 }
1020 
1021                 mIsWaitingAuthorization = false;
1022                 if (mRemoveTimeoutMsg) {
1023                     mSessionStatusHandler.removeMessages(USER_TIMEOUT);
1024                     cancelUserTimeoutAlarm();
1025                     setState(BluetoothMap.STATE_DISCONNECTED);
1026                 }
1027 
1028                 if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
1029                                        BluetoothDevice.CONNECTION_ACCESS_NO)
1030                         == BluetoothDevice.CONNECTION_ACCESS_YES) {
1031                     // Bluetooth connection accepted by user
1032                     mPermission = BluetoothDevice.ACCESS_ALLOWED;
1033                     if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
1034                         boolean result = mRemoteDevice.setMessageAccessPermission(
1035                                 BluetoothDevice.ACCESS_ALLOWED);
1036                         if (DEBUG) {
1037                             Log.d(TAG, "setMessageAccessPermission(ACCESS_ALLOWED) result="
1038                                     + result);
1039                         }
1040                     }
1041 
1042                     mRemoteDevice.sdpSearch(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS);
1043                     mSdpSearchInitiated = true;
1044                 } else {
1045                     // Auth. declined by user, serverSession should not be running, but
1046                     // call stop anyway to restart listener.
1047                     mPermission = BluetoothDevice.ACCESS_REJECTED;
1048                     if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
1049                         boolean result = mRemoteDevice.setMessageAccessPermission(
1050                                 BluetoothDevice.ACCESS_REJECTED);
1051                         if (DEBUG) {
1052                             Log.d(TAG, "setMessageAccessPermission(ACCESS_REJECTED) result="
1053                                     + result);
1054                         }
1055                     }
1056                     sendConnectCancelMessage();
1057                 }
1058             } else if (action.equals(BluetoothDevice.ACTION_SDP_RECORD)) {
1059                 if (DEBUG) Log.d(TAG, "Received ACTION_SDP_RECORD.");
1060                 ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID);
1061                 if (VERBOSE) {
1062                     Log.v(TAG, "Received UUID: " + uuid.toString());
1063                     Log.v(TAG, "expected UUID: " +
1064                           BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS.toString());
1065                 }
1066                 if (uuid.equals(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS)) {
1067                     mMnsRecord = intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD);
1068                     int status = intent.getIntExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, -1);
1069                     if (VERBOSE) {
1070                         Log.v(TAG, " -> MNS Record:" + mMnsRecord);
1071                         Log.v(TAG, " -> status: " + status);
1072                     }
1073                     if (mBluetoothMnsObexClient != null && !mSdpSearchInitiated) {
1074                         mBluetoothMnsObexClient.setMnsRecord(mMnsRecord);
1075                     }
1076                     if (status != -1 && mMnsRecord != null) {
1077                         for (int i = 0, c = mMasInstances.size(); i < c; i++) {
1078                                 mMasInstances.valueAt(i).setRemoteFeatureMask(
1079                                         mMnsRecord.getSupportedFeatures());
1080                         }
1081                     }
1082                     if (mSdpSearchInitiated) {
1083                         mSdpSearchInitiated = false; // done searching
1084                         sendConnectMessage(-1); // -1 indicates all MAS instances
1085                     }
1086                 }
1087             } else if (action.equals(ACTION_SHOW_MAPS_SETTINGS)) {
1088                 if (VERBOSE) Log.v(TAG, "Received ACTION_SHOW_MAPS_SETTINGS.");
1089 
1090                 Intent in = new Intent(context, BluetoothMapSettings.class);
1091                 in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
1092                 context.startActivity(in);
1093             } else if (action.equals(BluetoothMapContentObserver.ACTION_MESSAGE_SENT)) {
1094                 BluetoothMapMasInstance masInst = null;
1095                 int result = getResultCode();
1096                 boolean handled = false;
1097                 if(mSmsCapable && mMasInstances != null &&
1098                         (masInst = mMasInstances.get(MAS_ID_SMS_MMS)) != null) {
1099                     intent.putExtra(BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_RESULT, result);
1100                     if(masInst.handleSmsSendIntent(context, intent)) {
1101                         // The intent was handled by the mas instance it self
1102                         handled = true;
1103                     }
1104                 }
1105                 if(handled == false)
1106                 {
1107                     /* We do not have a connection to a device, hence we need to move
1108                        the SMS to the correct folder. */
1109                     BluetoothMapContentObserver
1110                             .actionMessageSentDisconnected(context, intent, result);
1111                 }
1112             } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) &&
1113                     mIsWaitingAuthorization) {
1114                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
1115 
1116                 if (mRemoteDevice == null || device == null) {
1117                     Log.e(TAG, "Unexpected error!");
1118                     return;
1119                 }
1120 
1121                 if (VERBOSE) Log.v(TAG,"ACL disconnected for " + device);
1122 
1123                 if (mRemoteDevice.equals(device)) {
1124                     // Send any pending timeout now, since ACL got disconnected
1125                     mSessionStatusHandler.removeMessages(USER_TIMEOUT);
1126                     mSessionStatusHandler.obtainMessage(USER_TIMEOUT).sendToTarget();
1127                 }
1128             }
1129         }
1130     };
1131 
1132     //Binder object: Must be static class or memory leak may occur
1133     /**
1134      * This class implements the IBluetoothMap interface - or actually it validates the
1135      * preconditions for calling the actual functionality in the MapService, and calls it.
1136      */
1137     private static class BluetoothMapBinder extends IBluetoothMap.Stub
1138         implements IProfileServiceBinder {
1139         private BluetoothMapService mService;
1140 
getService()1141         private BluetoothMapService getService() {
1142             if (!Utils.checkCaller()) {
1143                 Log.w(TAG,"MAP call not allowed for non-active user");
1144                 return null;
1145             }
1146 
1147             if (mService != null && mService.isAvailable()) {
1148                 mService.enforceCallingOrSelfPermission(BLUETOOTH_PERM,"Need BLUETOOTH permission");
1149                 return mService;
1150             }
1151             return null;
1152         }
1153 
BluetoothMapBinder(BluetoothMapService service)1154         BluetoothMapBinder(BluetoothMapService service) {
1155             if (VERBOSE) Log.v(TAG, "BluetoothMapBinder()");
1156             mService = service;
1157         }
1158 
cleanup()1159         public boolean cleanup()  {
1160             mService = null;
1161             return true;
1162         }
1163 
getState()1164         public int getState() {
1165             if (VERBOSE) Log.v(TAG, "getState()");
1166             BluetoothMapService service = getService();
1167             if (service == null) return BluetoothMap.STATE_DISCONNECTED;
1168             return getService().getState();
1169         }
1170 
getClient()1171         public BluetoothDevice getClient() {
1172             if (VERBOSE) Log.v(TAG, "getClient()");
1173             BluetoothMapService service = getService();
1174             if (service == null) return null;
1175             if (VERBOSE) Log.v(TAG, "getClient() - returning " + service.getRemoteDevice());
1176             return service.getRemoteDevice();
1177         }
1178 
isConnected(BluetoothDevice device)1179         public boolean isConnected(BluetoothDevice device) {
1180             if (VERBOSE) Log.v(TAG, "isConnected()");
1181             BluetoothMapService service = getService();
1182             if (service == null) return false;
1183             return service.getState() == BluetoothMap.STATE_CONNECTED
1184                     && service.getRemoteDevice().equals(device);
1185         }
1186 
connect(BluetoothDevice device)1187         public boolean connect(BluetoothDevice device) {
1188             if (VERBOSE) Log.v(TAG, "connect()");
1189             BluetoothMapService service = getService();
1190             if (service == null) return false;
1191             return false;
1192         }
1193 
disconnect(BluetoothDevice device)1194         public boolean disconnect(BluetoothDevice device) {
1195             if (VERBOSE) Log.v(TAG, "disconnect()");
1196             BluetoothMapService service = getService();
1197             if (service == null) return false;
1198             return service.disconnect(device);
1199         }
1200 
getConnectedDevices()1201         public List<BluetoothDevice> getConnectedDevices() {
1202             if (VERBOSE) Log.v(TAG, "getConnectedDevices()");
1203             BluetoothMapService service = getService();
1204             if (service == null) return new ArrayList<BluetoothDevice>(0);
1205             return service.getConnectedDevices();
1206         }
1207 
getDevicesMatchingConnectionStates(int[] states)1208         public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
1209             if (VERBOSE) Log.v(TAG, "getDevicesMatchingConnectionStates()");
1210             BluetoothMapService service = getService();
1211             if (service == null) return new ArrayList<BluetoothDevice>(0);
1212             return service.getDevicesMatchingConnectionStates(states);
1213         }
1214 
getConnectionState(BluetoothDevice device)1215         public int getConnectionState(BluetoothDevice device) {
1216             if (VERBOSE) Log.v(TAG, "getConnectionState()");
1217             BluetoothMapService service = getService();
1218             if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
1219             return service.getConnectionState(device);
1220         }
1221 
setPriority(BluetoothDevice device, int priority)1222         public boolean setPriority(BluetoothDevice device, int priority) {
1223             BluetoothMapService service = getService();
1224             if (service == null) return false;
1225             return service.setPriority(device, priority);
1226         }
1227 
getPriority(BluetoothDevice device)1228         public int getPriority(BluetoothDevice device) {
1229             BluetoothMapService service = getService();
1230             if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
1231             return service.getPriority(device);
1232         }
1233     }
1234 
1235     @Override
dump(StringBuilder sb)1236     public void dump(StringBuilder sb) {
1237         super.dump(sb);
1238         println(sb, "mRemoteDevice: " + mRemoteDevice);
1239         println(sb, "sRemoteDeviceName: " + sRemoteDeviceName);
1240         println(sb, "mState: " + mState);
1241         println(sb, "mAppObserver: " + mAppObserver);
1242         println(sb, "mIsWaitingAuthorization: " + mIsWaitingAuthorization);
1243         println(sb, "mRemoveTimeoutMsg: " + mRemoveTimeoutMsg);
1244         println(sb, "mPermission: " + mPermission);
1245         println(sb, "mAccountChanged: " + mAccountChanged);
1246         println(sb, "mBluetoothMnsObexClient: " + mBluetoothMnsObexClient);
1247         println(sb, "mMasInstanceMap:");
1248         for (BluetoothMapAccountItem key : mMasInstanceMap.keySet()) {
1249             println(sb, "  " + key + " : " + mMasInstanceMap.get(key));
1250         }
1251         println(sb, "mEnabledAccounts:");
1252         for (BluetoothMapAccountItem account : mEnabledAccounts) {
1253             println(sb, "  " + account);
1254         }
1255     }
1256 }
1257