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