Home | History | Annotate | Download | only in map
      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 
    151     public BluetoothMapService() {
    152         mState = BluetoothMap.STATE_DISCONNECTED;
    153     }
    154 
    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      */
    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      */
    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 
    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      */
    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 {
    336         private MapServiceMessageHandler(Looper looper) {
    337             super(looper);
    338         }
    339 
    340         @Override
    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 
    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 
    487     public int getState() {
    488         return mState;
    489     }
    490 
    491     public static BluetoothDevice getRemoteDevice() {
    492         return sRemoteDevice;
    493     }
    494 
    495     private void setState(int state) {
    496         setState(state, BluetoothMap.RESULT_SUCCESS);
    497     }
    498 
    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 
    514     void disconnect(BluetoothDevice device) {
    515         mSessionStatusHandler.sendMessage(
    516                 mSessionStatusHandler.obtainMessage(DISCONNECT_MAP, 0, 0, device));
    517     }
    518 
    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 
    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 
    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 
    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 
    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 
    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
    593     protected IProfileServiceBinder initBinder() {
    594         return new BluetoothMapBinder(this);
    595     }
    596 
    597     @Override
    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
    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 
    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      */
    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      */
    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      */
    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 
    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
    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      */
    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 
    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 
    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      */
    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 
    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 
    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 
    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 
    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
    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 
   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 
   1171         BluetoothMapBinder(BluetoothMapService service) {
   1172             if (VERBOSE) {
   1173                 Log.v(TAG, "BluetoothMapBinder()");
   1174             }
   1175             mService = service;
   1176         }
   1177 
   1178         @Override
   1179         public synchronized void cleanup() {
   1180             mService = null;
   1181         }
   1182 
   1183         @Override
   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
   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
   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
   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
   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
   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
   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
   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
   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
   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
   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