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