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