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