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