BluetoothMapService.java revision 70be005a18a35ec5fcb46152f0dfbe82156efa3a
1/*
2* Copyright (C) 2013 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 java.io.IOException;
19import java.util.ArrayList;
20import java.util.List;
21import java.util.Set;
22
23import javax.obex.ServerSession;
24import android.app.Notification;
25import android.app.NotificationManager;
26import android.app.PendingIntent;
27import android.app.Service;
28import android.bluetooth.BluetoothAdapter;
29import android.bluetooth.BluetoothDevice;
30import android.bluetooth.BluetoothProfile;
31import android.bluetooth.BluetoothServerSocket;
32import android.bluetooth.IBluetooth;
33import android.bluetooth.IBluetoothMap;
34import android.bluetooth.BluetoothUuid;
35import android.bluetooth.BluetoothMap;
36import android.bluetooth.BluetoothSocket;
37import android.content.Context;
38import android.content.Intent;
39import android.os.Handler;
40import android.os.IBinder;
41import android.os.Message;
42import android.os.PowerManager;
43import android.os.ParcelUuid;
44import android.text.TextUtils;
45import android.util.Log;
46import android.provider.Settings;
47import android.content.IntentFilter;
48import android.content.BroadcastReceiver;
49
50import com.android.bluetooth.R;
51import com.android.bluetooth.Utils;
52import com.android.bluetooth.btservice.AdapterService;
53import com.android.bluetooth.btservice.ProfileService;
54import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder;
55
56
57public class BluetoothMapService extends ProfileService {
58    private static final String TAG = "BluetoothMapService";
59
60    /**
61     * To enable MAP DEBUG/VERBOSE logging - run below cmd in adb shell, and
62     * restart com.android.bluetooth process. only enable DEBUG log:
63     * "setprop log.tag.BluetoothMapService DEBUG"; enable both VERBOSE and
64     * DEBUG log: "setprop log.tag.BluetoothMapService VERBOSE"
65     */
66
67    public static final boolean DEBUG = true;
68
69    public static final boolean VERBOSE = false;
70
71    /**
72     * Intent indicating incoming obex authentication request which is from
73     * PCE(Carkit)
74     */
75    public static final String AUTH_CHALL_ACTION = "com.android.bluetooth.map.authchall";
76
77    /**
78     * Intent indicating timeout for user confirmation, which is sent to
79     * BluetoothMapActivity
80     */
81    public static final String USER_CONFIRM_TIMEOUT_ACTION =
82            "com.android.bluetooth.map.userconfirmtimeout";
83
84    /**
85     * Intent Extra name indicating session key which is sent from
86     * BluetoothMapActivity
87     */
88    public static final String EXTRA_SESSION_KEY = "com.android.bluetooth.map.sessionkey";
89
90    public static final String THIS_PACKAGE_NAME = "com.android.bluetooth";
91
92    public static final int MSG_SERVERSESSION_CLOSE = 5000;
93
94    public static final int MSG_SESSION_ESTABLISHED = 5001;
95
96    public static final int MSG_SESSION_DISCONNECTED = 5002;
97
98    public static final int MSG_OBEX_AUTH_CHALL = 5003;
99
100    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
101
102    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
103
104    private static final int START_LISTENER = 1;
105
106    private static final int USER_TIMEOUT = 2;
107
108    private static final int DISCONNECT_MAP = 3;
109
110    private PowerManager.WakeLock mWakeLock = null;
111
112    private BluetoothAdapter mAdapter;
113
114    private SocketAcceptThread mAcceptThread = null;
115
116    private BluetoothMapAuthenticator mAuth = null;
117
118    private BluetoothMapObexServer mMapServer;
119
120    private ServerSession mServerSession = null;
121
122    private BluetoothMnsObexClient mBluetoothMnsObexClient = null;
123
124    private BluetoothServerSocket mServerSocket = null;
125
126    private BluetoothSocket mConnSocket = null;
127
128    private BluetoothDevice mRemoteDevice = null;
129
130    private static String sRemoteDeviceName = null;
131
132    private volatile boolean mInterrupted;
133
134    private int mState;
135
136    private boolean isWaitingAuthorization = false;
137
138    // package and class name to which we send intent to check message access access permission
139    private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings";
140    private static final String ACCESS_AUTHORITY_CLASS =
141        "com.android.settings.bluetooth.BluetoothPermissionRequest";
142
143    private static final ParcelUuid[] MAP_UUIDS = {
144        BluetoothUuid.MAP,
145        BluetoothUuid.MNS,
146    };
147
148    public BluetoothMapService() {
149        mState = BluetoothMap.STATE_DISCONNECTED;
150    }
151
152    private void startRfcommSocketListener() {
153        if (DEBUG) Log.d(TAG, "Map Service startRfcommSocketListener");
154
155        if (mAcceptThread == null) {
156            mAcceptThread = new SocketAcceptThread();
157            mAcceptThread.setName("BluetoothMapAcceptThread");
158            mAcceptThread.start();
159        }
160    }
161
162    private final boolean initSocket() {
163        if (DEBUG) Log.d(TAG, "Map Service initSocket");
164
165        boolean initSocketOK = false;
166        final int CREATE_RETRY_TIME = 10;
167
168        // It's possible that create will fail in some cases. retry for 10 times
169        for (int i = 0; (i < CREATE_RETRY_TIME) && !mInterrupted; i++) {
170            initSocketOK = true;
171            try {
172                // It is mandatory for MSE to support initiation of bonding and
173                // encryption.
174                mServerSocket = mAdapter.listenUsingEncryptedRfcommWithServiceRecord
175                    ("MAP SMS/MMS", BluetoothUuid.MAS.getUuid());
176
177            } catch (IOException e) {
178                Log.e(TAG, "Error create RfcommServerSocket " + e.toString());
179                initSocketOK = false;
180            }
181            if (!initSocketOK) {
182                // Need to break out of this loop if BT is being turned off.
183                if (mAdapter == null) break;
184                int state = mAdapter.getState();
185                if ((state != BluetoothAdapter.STATE_TURNING_ON) &&
186                    (state != BluetoothAdapter.STATE_ON)) {
187                    Log.w(TAG, "initServerSocket failed as BT is (being) turned off");
188                    break;
189                }
190                try {
191                    if (VERBOSE) Log.v(TAG, "wait 300 ms");
192                    Thread.sleep(300);
193                } catch (InterruptedException e) {
194                    Log.e(TAG, "socketAcceptThread thread was interrupted (3)");
195                }
196            } else {
197                break;
198            }
199        }
200        if (mInterrupted) {
201            initSocketOK = false;
202            // close server socket to avoid resource leakage
203            closeServerSocket();
204        }
205
206        if (initSocketOK) {
207            if (VERBOSE) Log.v(TAG, "Succeed to create listening socket ");
208
209        } else {
210            Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try");
211        }
212        return initSocketOK;
213    }
214
215    private final synchronized void closeServerSocket() {
216        // exit SocketAcceptThread early
217        if (mServerSocket != null) {
218            try {
219                // this will cause mServerSocket.accept() return early with IOException
220                mServerSocket.close();
221                mServerSocket = null;
222            } catch (IOException ex) {
223                Log.e(TAG, "Close Server Socket error: " + ex);
224            }
225        }
226    }
227    private final synchronized void closeConnectionSocket() {
228        if (mConnSocket != null) {
229            try {
230                mConnSocket.close();
231                mConnSocket = null;
232            } catch (IOException e) {
233                Log.e(TAG, "Close Connection Socket error: " + e.toString());
234            }
235        }
236    }
237
238    private final void closeService() {
239        if (DEBUG) Log.d(TAG, "MAP Service closeService in");
240
241         // exit initSocket early
242         mInterrupted = true;
243         closeServerSocket();
244
245         if (mAcceptThread != null) {
246             try {
247                 mAcceptThread.shutdown();
248                 mAcceptThread.join();
249                 mAcceptThread = null;
250             } catch (InterruptedException ex) {
251                 Log.w(TAG, "mAcceptThread close error" + ex);
252             }
253         }
254
255         if (mWakeLock != null) {
256             mWakeLock.release();
257             mWakeLock = null;
258         }
259
260         if (mServerSession != null) {
261             mServerSession.close();
262             mServerSession = null;
263         }
264
265         if (mBluetoothMnsObexClient != null) {
266             mBluetoothMnsObexClient.disconnect();
267             mBluetoothMnsObexClient = null;
268         }
269
270         closeConnectionSocket();
271         if (VERBOSE) Log.v(TAG, "MAP Service closeService out");
272    }
273
274    private final void startObexServerSession() throws IOException {
275        if (DEBUG) Log.d(TAG, "Map Service startObexServerSession");
276
277        // acquire the wakeLock before start Obex transaction thread
278        if (mWakeLock == null) {
279            PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
280            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
281                    "StartingObexMapTransaction");
282            mWakeLock.setReferenceCounted(false);
283            mWakeLock.acquire();
284        }
285
286
287        mMapServer = new BluetoothMapObexServer(mSessionStatusHandler, this);
288        synchronized (this) {
289            // We need to get authentication now that obex server is up
290            mAuth = new BluetoothMapAuthenticator(mSessionStatusHandler);
291            mAuth.setChallenged(false);
292            mAuth.setCancelled(false);
293        }
294        // setup RFCOMM transport
295        BluetoothMapRfcommTransport transport = new BluetoothMapRfcommTransport(mConnSocket);
296        mServerSession = new ServerSession(transport, mMapServer, mAuth);
297        mBluetoothMnsObexClient = new BluetoothMnsObexClient(this, mRemoteDevice);
298        mBluetoothMnsObexClient.start(); // Initiate the MNS message loop.
299        setState(BluetoothMap.STATE_CONNECTED);
300        if (VERBOSE) {
301            Log.v(TAG, "startObexServerSession() success!");
302        }
303    }
304
305    private void stopObexServerSession() {
306        if (DEBUG) Log.d(TAG, "MAP Service stopObexServerSession");
307
308        // Release the wake lock if obex transaction is over
309        if (mWakeLock != null) {
310            mWakeLock.release();
311            mWakeLock = null;
312        }
313
314        if (mServerSession != null) {
315            mServerSession.close();
316            mServerSession = null;
317        }
318
319        mAcceptThread = null;
320
321        if(mBluetoothMnsObexClient != null) {
322            mBluetoothMnsObexClient.disconnect();
323            mBluetoothMnsObexClient = null;
324        }
325        closeConnectionSocket();
326
327        // Last obex transaction is finished, we start to listen for incoming
328        // connection again
329        if (mAdapter.isEnabled()) {
330            startRfcommSocketListener();
331        }
332        setState(BluetoothMap.STATE_DISCONNECTED);
333    }
334
335
336
337    /**
338     * A thread that runs in the background waiting for remote rfcomm
339     * connect.Once a remote socket connected, this thread shall be
340     * shutdown.When the remote disconnect,this thread shall run again waiting
341     * for next request.
342     */
343    private class SocketAcceptThread extends Thread {
344
345        private boolean stopped = false;
346
347        @Override
348        public void run() {
349            BluetoothServerSocket serverSocket;
350            if (mServerSocket == null) {
351                if (!initSocket()) {
352                    return;
353                }
354            }
355
356            while (!stopped) {
357                try {
358                    if (DEBUG) Log.d(TAG, "Accepting socket connection...");
359                    serverSocket = mServerSocket;
360                    if(serverSocket == null) {
361                        Log.w(TAG, "mServerSocket is null");
362                        break;
363                    }
364                    mConnSocket = serverSocket.accept();
365                    if (DEBUG) Log.d(TAG, "Accepted socket connection...");
366                    synchronized (BluetoothMapService.this) {
367                        if (mConnSocket == null) {
368                            Log.w(TAG, "mConnSocket is null");
369                            break;
370                        }
371                        mRemoteDevice = mConnSocket.getRemoteDevice();
372                    }
373                    if (mRemoteDevice == null) {
374                        Log.i(TAG, "getRemoteDevice() = null");
375                        break;
376                    }
377
378                    sRemoteDeviceName = mRemoteDevice.getName();
379                    // In case getRemoteName failed and return null
380                    if (TextUtils.isEmpty(sRemoteDeviceName)) {
381                        sRemoteDeviceName = getString(R.string.defaultname);
382                    }
383                    boolean trust = mRemoteDevice.getTrustState();
384                    if (DEBUG) Log.d(TAG, "GetTrustState() = " + trust);
385
386
387                    if (trust) {
388                        try {
389                            if (DEBUG) Log.d(TAG, "incoming connection accepted from: "
390                                + sRemoteDeviceName + " automatically as trusted device");
391                            startObexServerSession();
392                        } catch (IOException ex) {
393                            Log.e(TAG, "catch exception starting obex server session"
394                                    + ex.toString());
395                        }
396                    } else {
397                        Intent intent = new
398                            Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
399                        intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
400                        intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
401                                        BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS);
402                        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
403                        sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
404                        isWaitingAuthorization = true;
405
406                        if (DEBUG) Log.d(TAG, "waiting for authorization for connection from: "
407                                + sRemoteDeviceName);
408
409                    }
410                    stopped = true; // job done ,close this thread;
411                } catch (IOException ex) {
412                    stopped=true;
413                    if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString());
414                }
415            }
416        }
417
418        void shutdown() {
419            stopped = true;
420            interrupt();
421        }
422    }
423
424    private final Handler mSessionStatusHandler = new Handler() {
425        @Override
426        public void handleMessage(Message msg) {
427            if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what);
428
429            switch (msg.what) {
430                case START_LISTENER:
431                    if (mAdapter.isEnabled()) {
432                        startRfcommSocketListener();
433                    }
434                    break;
435                case USER_TIMEOUT:
436                    Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
437                    intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
438                    intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
439                                    BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS);
440                    sendBroadcast(intent);
441                    isWaitingAuthorization = false;
442                    stopObexServerSession();
443                    break;
444                case MSG_SERVERSESSION_CLOSE:
445                    stopObexServerSession();
446                    break;
447                case MSG_SESSION_ESTABLISHED:
448                    break;
449                case MSG_SESSION_DISCONNECTED:
450                    // handled elsewhere
451                    break;
452                case DISCONNECT_MAP:
453                    disconnectMap((BluetoothDevice)msg.obj);
454                    break;
455                default:
456                    break;
457            }
458        }
459    };
460
461
462   public int getState() {
463        return mState;
464    }
465
466    public BluetoothDevice getRemoteDevice() {
467        return mRemoteDevice;
468    }
469    private void setState(int state) {
470        setState(state, BluetoothMap.RESULT_SUCCESS);
471    }
472
473    private synchronized void setState(int state, int result) {
474        if (state != mState) {
475            if (DEBUG) Log.d(TAG, "Map state " + mState + " -> " + state + ", result = "
476                    + result);
477            int prevState = mState;
478            mState = state;
479            Intent intent = new Intent(BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
480            intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
481            intent.putExtra(BluetoothProfile.EXTRA_STATE, mState);
482            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
483            sendBroadcast(intent, BLUETOOTH_PERM);
484            AdapterService s = AdapterService.getAdapterService();
485            if (s != null) {
486                s.onProfileConnectionStateChanged(mRemoteDevice, BluetoothProfile.MAP,
487                        mState, prevState);
488            }
489        }
490    }
491
492    public static String getRemoteDeviceName() {
493        return sRemoteDeviceName;
494    }
495
496    public boolean disconnect(BluetoothDevice device) {
497        mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(DISCONNECT_MAP, 0, 0, device));
498        return true;
499    }
500
501    public boolean disconnectMap(BluetoothDevice device) {
502        boolean result = false;
503        if (DEBUG) Log.d(TAG, "disconnectMap");
504        if (getRemoteDevice().equals(device)) {
505            switch (mState) {
506                case BluetoothMap.STATE_CONNECTED:
507                    if (mServerSession != null) {
508                        mServerSession.close();
509                        mServerSession = null;
510                    }
511                    if(mBluetoothMnsObexClient != null) {
512                        mBluetoothMnsObexClient.disconnect(); //FIXME should use shutdown when implemented
513                        mBluetoothMnsObexClient = null;
514                    }
515                    closeConnectionSocket();
516
517                    setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED);
518                    result = true;
519                    break;
520                default:
521                    break;
522                }
523        }
524        return result;
525    }
526
527    public List<BluetoothDevice> getConnectedDevices() {
528        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
529        synchronized(this) {
530            if (mState == BluetoothMap.STATE_CONNECTED && mRemoteDevice != null) {
531                devices.add(mRemoteDevice);
532            }
533        }
534        return devices;
535    }
536
537    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
538        List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
539        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
540        int connectionState;
541        synchronized (this) {
542            for (BluetoothDevice device : bondedDevices) {
543                ParcelUuid[] featureUuids = device.getUuids();
544                if (!BluetoothUuid.containsAnyUuid(featureUuids, MAP_UUIDS)) {
545                    continue;
546                }
547                connectionState = getConnectionState(device);
548                for(int i = 0; i < states.length; i++) {
549                    if (connectionState == states[i]) {
550                        deviceList.add(device);
551                    }
552                }
553            }
554        }
555        return deviceList;
556    }
557
558    public int getConnectionState(BluetoothDevice device) {
559        synchronized(this) {
560            if (getState() == BluetoothMap.STATE_CONNECTED && getRemoteDevice().equals(device)) {
561                return BluetoothProfile.STATE_CONNECTED;
562            } else {
563                return BluetoothProfile.STATE_DISCONNECTED;
564            }
565        }
566    }
567
568    public boolean setPriority(BluetoothDevice device, int priority) {
569        Settings.Global.putInt(getContentResolver(),
570            Settings.Global.getBluetoothMapPriorityKey(device.getAddress()),
571            priority);
572        if (DEBUG) Log.d(TAG, "Saved priority " + device + " = " + priority);
573        return true;
574    }
575
576    public int getPriority(BluetoothDevice device) {
577        int priority = Settings.Global.getInt(getContentResolver(),
578            Settings.Global.getBluetoothMapPriorityKey(device.getAddress()),
579            BluetoothProfile.PRIORITY_UNDEFINED);
580        return priority;
581    }
582
583    @Override
584    protected IProfileServiceBinder initBinder() {
585        return new BluetoothMapBinder(this);
586    }
587
588    @Override
589    protected boolean start() {
590        if (DEBUG) Log.d(TAG, "start()");
591        IntentFilter filter = new IntentFilter();
592        filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
593        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
594        try {
595            registerReceiver(mMapReceiver, filter);
596        } catch (Exception e) {
597            Log.w(TAG,"Unable to register map receiver",e);
598        }
599        mInterrupted = false;
600        mAdapter = BluetoothAdapter.getDefaultAdapter();
601        // start RFCOMM listener
602        mSessionStatusHandler.sendMessage(mSessionStatusHandler
603                .obtainMessage(START_LISTENER));
604        return true;
605    }
606
607    @Override
608    protected boolean stop() {
609        if (DEBUG) Log.d(TAG, "stop()");
610        try {
611            unregisterReceiver(mMapReceiver);
612        } catch (Exception e) {
613            Log.w(TAG,"Unable to unregister map receiver",e);
614        }
615
616        setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED);
617        closeService();
618        if(mSessionStatusHandler != null) {
619            mSessionStatusHandler.removeCallbacksAndMessages(null);
620        }
621        isWaitingAuthorization = false;
622        return true;
623    }
624
625    public boolean cleanup()  {
626        if (DEBUG) Log.d(TAG, "cleanup()");
627        setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED);
628        closeService();
629        if(mSessionStatusHandler != null) {
630            mSessionStatusHandler.removeCallbacksAndMessages(null);
631        }
632        isWaitingAuthorization = false;
633        return true;
634    }
635
636    private MapBroadcastReceiver mMapReceiver = new MapBroadcastReceiver();
637
638    private class MapBroadcastReceiver extends BroadcastReceiver {
639        @Override
640        public void onReceive(Context context, Intent intent) {
641            if (DEBUG) Log.d(TAG, "onReceive");
642            String action = intent.getAction();
643            if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
644                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
645                                               BluetoothAdapter.ERROR);
646                if (state == BluetoothAdapter.STATE_TURNING_OFF) {
647                    if (DEBUG) Log.d(TAG, "STATE_TURNING_OFF");
648                    // Release all resources
649                    closeService();
650                    isWaitingAuthorization = false;
651                } else if (state == BluetoothAdapter.STATE_ON) {
652                    if (DEBUG) Log.d(TAG, "STATE_ON");
653                    mInterrupted = false;
654                    // start RFCOMM listener
655                    mSessionStatusHandler.sendMessage(mSessionStatusHandler
656                                  .obtainMessage(START_LISTENER));
657                }
658            } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
659                int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
660                                               BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
661                if (DEBUG) Log.d(TAG, "Received ACTION_CONNECTION_ACCESS_REPLY:" +
662                           requestType + ":" + isWaitingAuthorization);
663                if ((!isWaitingAuthorization) ||
664                    (requestType != BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS)) {
665                    // this reply is not for us
666                    return;
667                }
668
669                isWaitingAuthorization = false;
670
671                if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
672                                       BluetoothDevice.CONNECTION_ACCESS_NO) ==
673                    BluetoothDevice.CONNECTION_ACCESS_YES) {
674                    //bluetooth connection accepted by user
675                    if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
676                        boolean result = mRemoteDevice.setTrust(true);
677                        if (DEBUG) Log.d(TAG, "setTrust() result=" + result);
678                    }
679                    try {
680                        if (mConnSocket != null) {
681                            // start obex server and rfcomm connection
682                            startObexServerSession();
683                        } else {
684                            stopObexServerSession();
685                        }
686                    } catch (IOException ex) {
687                        Log.e(TAG, "Caught the error: " + ex.toString());
688                    }
689                } else {
690                    stopObexServerSession();
691                }
692            }
693        }
694    };
695
696    //Binder object: Must be static class or memory leak may occur
697    /**
698     * This class implements the IBluetoothMap interface - or actually it validates the
699     * preconditions for calling the actual functionality in the MapService, and calls it.
700     */
701    private static class BluetoothMapBinder extends IBluetoothMap.Stub
702        implements IProfileServiceBinder {
703        private BluetoothMapService mService;
704
705        private BluetoothMapService getService() {
706            if (!Utils.checkCaller()) {
707                Log.w(TAG,"MAP call not allowed for non-active user");
708                return null;
709            }
710
711            if (mService != null && mService.isAvailable()) {
712                mService.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
713                return mService;
714            }
715            return null;
716        }
717
718        BluetoothMapBinder(BluetoothMapService service) {
719            if (VERBOSE) Log.v(TAG, "BluetoothMapBinder()");
720            mService = service;
721        }
722
723        public boolean cleanup()  {
724            mService = null;
725            return true;
726        }
727
728        public int getState() {
729            if (VERBOSE) Log.v(TAG, "getState()");
730            BluetoothMapService service = getService();
731            if (service == null) return BluetoothMap.STATE_DISCONNECTED;
732            return getService().getState();
733        }
734
735        public BluetoothDevice getClient() {
736            if (VERBOSE) Log.v(TAG, "getClient()");
737            BluetoothMapService service = getService();
738            if (service == null) return null;
739            Log.v(TAG, "getClient() - returning " + service.getRemoteDevice());
740            return service.getRemoteDevice();
741        }
742
743        public boolean isConnected(BluetoothDevice device) {
744            if (VERBOSE) Log.v(TAG, "isConnected()");
745            BluetoothMapService service = getService();
746            if (service == null) return false;
747            return service.getState() == BluetoothMap.STATE_CONNECTED && service.getRemoteDevice().equals(device);
748        }
749
750        public boolean connect(BluetoothDevice device) {
751            if (VERBOSE) Log.v(TAG, "connect()");
752            BluetoothMapService service = getService();
753            if (service == null) return false;
754            return false;
755        }
756
757        public boolean disconnect(BluetoothDevice device) {
758            if (VERBOSE) Log.v(TAG, "disconnect()");
759            BluetoothMapService service = getService();
760            if (service == null) return false;
761            return service.disconnect(device);
762        }
763
764        public List<BluetoothDevice> getConnectedDevices() {
765            if (VERBOSE) Log.v(TAG, "getConnectedDevices()");
766            BluetoothMapService service = getService();
767            if (service == null) return new ArrayList<BluetoothDevice>(0);
768            return service.getConnectedDevices();
769        }
770
771        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
772            if (VERBOSE) Log.v(TAG, "getDevicesMatchingConnectionStates()");
773            BluetoothMapService service = getService();
774            if (service == null) return new ArrayList<BluetoothDevice>(0);
775            return service.getDevicesMatchingConnectionStates(states);
776        }
777
778        public int getConnectionState(BluetoothDevice device) {
779            if (VERBOSE) Log.v(TAG, "getConnectionState()");
780            BluetoothMapService service = getService();
781            if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
782            return service.getConnectionState(device);
783        }
784
785        public boolean setPriority(BluetoothDevice device, int priority) {
786            BluetoothMapService service = getService();
787            if (service == null) return false;
788            return service.setPriority(device, priority);
789        }
790
791        public int getPriority(BluetoothDevice device) {
792            BluetoothMapService service = getService();
793            if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
794            return service.getPriority(device);
795        }
796    };
797}
798