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