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