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