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
16package com.android.bluetooth.map;
17
18import android.app.AlarmManager;
19import android.app.PendingIntent;
20import android.bluetooth.BluetoothAdapter;
21import android.bluetooth.BluetoothDevice;
22import android.bluetooth.BluetoothMap;
23import android.bluetooth.BluetoothProfile;
24import android.bluetooth.BluetoothUuid;
25import android.bluetooth.IBluetoothMap;
26import android.bluetooth.SdpMnsRecord;
27import android.content.BroadcastReceiver;
28import android.content.Context;
29import android.content.Intent;
30import android.content.IntentFilter;
31import android.content.IntentFilter.MalformedMimeTypeException;
32import android.os.Handler;
33import android.os.Message;
34import android.os.ParcelUuid;
35import android.os.PowerManager;
36import android.os.RemoteException;
37import android.provider.Settings;
38import android.text.TextUtils;
39import android.util.Log;
40import android.util.SparseArray;
41
42import com.android.bluetooth.Utils;
43import com.android.bluetooth.btservice.AdapterService;
44import com.android.bluetooth.btservice.ProfileService;
45import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder;
46import com.android.bluetooth.R;
47
48import java.io.IOException;
49import java.util.ArrayList;
50import java.util.HashMap;
51import java.util.List;
52import java.util.Set;
53
54public 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