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