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