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