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