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