BluetoothMapService.java revision 824929471ee80476e6d6774eedac9f30c5623eb2
15e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux/*
25e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux* Copyright (C) 2013 Samsung System LSI
35e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux* Licensed under the Apache License, Version 2.0 (the "License");
45e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux* you may not use this file except in compliance with the License.
55e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux* You may obtain a copy of the License at
65e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux*
75e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux*      http://www.apache.org/licenses/LICENSE-2.0
85e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux*
95e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux* Unless required by applicable law or agreed to in writing, software
105e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux* distributed under the License is distributed on an "AS IS" BASIS,
115e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
125e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux* See the License for the specific language governing permissions and
135e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux* limitations under the License.
145e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux*/
155e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux
165e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuxpackage com.android.bluetooth.map;
175e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux
185e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport java.io.IOException;
195e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport java.util.ArrayList;
205e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport java.util.List;
215e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport java.util.Set;
225e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux
235e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport javax.obex.ServerSession;
245e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport android.app.Notification;
255e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport android.app.NotificationManager;
265e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport android.app.PendingIntent;
275e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport android.app.Service;
285e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport android.bluetooth.BluetoothAdapter;
295e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport android.bluetooth.BluetoothDevice;
305e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport android.bluetooth.BluetoothProfile;
315e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport 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.shutdown();
267            mBluetoothMnsObexClient = null;
268        }
269
270        closeConnectionSocket();
271
272        if (mSessionStatusHandler != null) {
273            mSessionStatusHandler.removeCallbacksAndMessages(null);
274        }
275        isWaitingAuthorization = false;
276
277        if (VERBOSE) Log.v(TAG, "MAP Service closeService out");
278    }
279
280    private final void startObexServerSession() throws IOException {
281        if (DEBUG) Log.d(TAG, "Map Service startObexServerSession");
282
283        // acquire the wakeLock before start Obex transaction thread
284        if (mWakeLock == null) {
285            PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
286            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
287                    "StartingObexMapTransaction");
288            mWakeLock.setReferenceCounted(false);
289            mWakeLock.acquire();
290        }
291
292        mBluetoothMnsObexClient = new BluetoothMnsObexClient(this, mRemoteDevice);
293        mMapServer = new BluetoothMapObexServer(mSessionStatusHandler, this,
294                                                mBluetoothMnsObexClient);
295        synchronized (this) {
296            // We need to get authentication now that obex server is up
297            mAuth = new BluetoothMapAuthenticator(mSessionStatusHandler);
298            mAuth.setChallenged(false);
299            mAuth.setCancelled(false);
300        }
301        // setup RFCOMM transport
302        BluetoothMapRfcommTransport transport = new BluetoothMapRfcommTransport(mConnSocket);
303        mServerSession = new ServerSession(transport, mMapServer, mAuth);
304        setState(BluetoothMap.STATE_CONNECTED);
305        if (VERBOSE) {
306            Log.v(TAG, "startObexServerSession() success!");
307        }
308    }
309
310    private void stopObexServerSession() {
311        if (DEBUG) Log.d(TAG, "MAP Service stopObexServerSession");
312
313        // Release the wake lock if obex transaction is over
314        if (mWakeLock != null) {
315            mWakeLock.release();
316            mWakeLock = null;
317        }
318
319        if (mServerSession != null) {
320            mServerSession.close();
321            mServerSession = null;
322        }
323
324        mAcceptThread = null;
325
326        if(mBluetoothMnsObexClient != null) {
327            mBluetoothMnsObexClient.shutdown();
328            mBluetoothMnsObexClient = null;
329        }
330        closeConnectionSocket();
331
332        // Last obex transaction is finished, we start to listen for incoming
333        // connection again
334        if (mAdapter.isEnabled()) {
335            startRfcommSocketListener();
336        }
337        setState(BluetoothMap.STATE_DISCONNECTED);
338    }
339
340
341
342    /**
343     * A thread that runs in the background waiting for remote rfcomm
344     * connect.Once a remote socket connected, this thread shall be
345     * shutdown.When the remote disconnect,this thread shall run again waiting
346     * for next request.
347     */
348    private class SocketAcceptThread extends Thread {
349
350        private boolean stopped = false;
351
352        @Override
353        public void run() {
354            BluetoothServerSocket serverSocket;
355            if (mServerSocket == null) {
356                if (!initSocket()) {
357                    return;
358                }
359            }
360
361            while (!stopped) {
362                try {
363                    if (DEBUG) Log.d(TAG, "Accepting socket connection...");
364                    serverSocket = mServerSocket;
365                    if(serverSocket == null) {
366                        Log.w(TAG, "mServerSocket is null");
367                        break;
368                    }
369                    mConnSocket = serverSocket.accept();
370                    if (DEBUG) Log.d(TAG, "Accepted socket connection...");
371                    synchronized (BluetoothMapService.this) {
372                        if (mConnSocket == null) {
373                            Log.w(TAG, "mConnSocket is null");
374                            break;
375                        }
376                        mRemoteDevice = mConnSocket.getRemoteDevice();
377                    }
378                    if (mRemoteDevice == null) {
379                        Log.i(TAG, "getRemoteDevice() = null");
380                        break;
381                    }
382
383                    sRemoteDeviceName = mRemoteDevice.getName();
384                    // In case getRemoteName failed and return null
385                    if (TextUtils.isEmpty(sRemoteDeviceName)) {
386                        sRemoteDeviceName = getString(R.string.defaultname);
387                    }
388                    boolean trust = mRemoteDevice.getTrustState();
389                    if (DEBUG) Log.d(TAG, "GetTrustState() = " + trust);
390
391
392                    if (trust) {
393                        try {
394                            if (DEBUG) Log.d(TAG, "incoming connection accepted from: "
395                                + sRemoteDeviceName + " automatically as trusted device");
396                            startObexServerSession();
397                        } catch (IOException ex) {
398                            Log.e(TAG, "catch exception starting obex server session"
399                                    + ex.toString());
400                        }
401                    } else {
402                        Intent intent = new
403                            Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
404                        intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
405                        intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
406                                        BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS);
407                        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
408
409                        isWaitingAuthorization = true;
410                        sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
411
412                        if (DEBUG) Log.d(TAG, "waiting for authorization for connection from: "
413                                + sRemoteDeviceName);
414
415                    }
416                    stopped = true; // job done ,close this thread;
417                } catch (IOException ex) {
418                    stopped=true;
419                    if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString());
420                }
421            }
422        }
423
424        void shutdown() {
425            stopped = true;
426            interrupt();
427        }
428    }
429
430    private final Handler mSessionStatusHandler = new Handler() {
431        @Override
432        public void handleMessage(Message msg) {
433            if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what);
434
435            switch (msg.what) {
436                case START_LISTENER:
437                    if (mAdapter.isEnabled()) {
438                        startRfcommSocketListener();
439                    }
440                    break;
441                case USER_TIMEOUT:
442                    Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
443                    intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
444                    intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
445                                    BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS);
446                    sendBroadcast(intent);
447                    isWaitingAuthorization = false;
448                    stopObexServerSession();
449                    break;
450                case MSG_SERVERSESSION_CLOSE:
451                    stopObexServerSession();
452                    break;
453                case MSG_SESSION_ESTABLISHED:
454                    break;
455                case MSG_SESSION_DISCONNECTED:
456                    // handled elsewhere
457                    break;
458                case DISCONNECT_MAP:
459                    disconnectMap((BluetoothDevice)msg.obj);
460                    break;
461                default:
462                    break;
463            }
464        }
465    };
466
467
468   public int getState() {
469        return mState;
470    }
471
472    public 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            AdapterService s = AdapterService.getAdapterService();
491            if (s != null) {
492                s.onProfileConnectionStateChanged(mRemoteDevice, BluetoothProfile.MAP,
493                        mState, prevState);
494            }
495        }
496    }
497
498    public static String getRemoteDeviceName() {
499        return sRemoteDeviceName;
500    }
501
502    public boolean disconnect(BluetoothDevice device) {
503        mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(DISCONNECT_MAP, 0, 0, device));
504        return true;
505    }
506
507    public boolean disconnectMap(BluetoothDevice device) {
508        boolean result = false;
509        if (DEBUG) Log.d(TAG, "disconnectMap");
510        if (getRemoteDevice().equals(device)) {
511            switch (mState) {
512                case BluetoothMap.STATE_CONNECTED:
513                    if (mServerSession != null) {
514                        mServerSession.close();
515                        mServerSession = null;
516                    }
517                    if(mBluetoothMnsObexClient != null) {
518                        mBluetoothMnsObexClient.shutdown();
519                        mBluetoothMnsObexClient = null;
520                    }
521                    closeConnectionSocket();
522
523                    setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED);
524                    result = true;
525                    break;
526                default:
527                    break;
528                }
529        }
530        return result;
531    }
532
533    public List<BluetoothDevice> getConnectedDevices() {
534        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
535        synchronized(this) {
536            if (mState == BluetoothMap.STATE_CONNECTED && mRemoteDevice != null) {
537                devices.add(mRemoteDevice);
538            }
539        }
540        return devices;
541    }
542
543    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
544        List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
545        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
546        int connectionState;
547        synchronized (this) {
548            for (BluetoothDevice device : bondedDevices) {
549                ParcelUuid[] featureUuids = device.getUuids();
550                if (!BluetoothUuid.containsAnyUuid(featureUuids, MAP_UUIDS)) {
551                    continue;
552                }
553                connectionState = getConnectionState(device);
554                for(int i = 0; i < states.length; i++) {
555                    if (connectionState == states[i]) {
556                        deviceList.add(device);
557                    }
558                }
559            }
560        }
561        return deviceList;
562    }
563
564    public int getConnectionState(BluetoothDevice device) {
565        synchronized(this) {
566            if (getState() == BluetoothMap.STATE_CONNECTED && getRemoteDevice().equals(device)) {
567                return BluetoothProfile.STATE_CONNECTED;
568            } else {
569                return BluetoothProfile.STATE_DISCONNECTED;
570            }
571        }
572    }
573
574    public boolean setPriority(BluetoothDevice device, int priority) {
575        Settings.Global.putInt(getContentResolver(),
576            Settings.Global.getBluetoothMapPriorityKey(device.getAddress()),
577            priority);
578        if (DEBUG) Log.d(TAG, "Saved priority " + device + " = " + priority);
579        return true;
580    }
581
582    public int getPriority(BluetoothDevice device) {
583        int priority = Settings.Global.getInt(getContentResolver(),
584            Settings.Global.getBluetoothMapPriorityKey(device.getAddress()),
585            BluetoothProfile.PRIORITY_UNDEFINED);
586        return priority;
587    }
588
589    @Override
590    protected IProfileServiceBinder initBinder() {
591        return new BluetoothMapBinder(this);
592    }
593
594    @Override
595    protected boolean start() {
596        if (DEBUG) Log.d(TAG, "start()");
597        IntentFilter filter = new IntentFilter();
598        filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
599        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
600        try {
601            registerReceiver(mMapReceiver, filter);
602        } catch (Exception e) {
603            Log.w(TAG,"Unable to register map receiver",e);
604        }
605        mInterrupted = false;
606        mAdapter = BluetoothAdapter.getDefaultAdapter();
607        // start RFCOMM listener
608        mSessionStatusHandler.sendMessage(mSessionStatusHandler
609                .obtainMessage(START_LISTENER));
610        return true;
611    }
612
613    @Override
614    protected boolean stop() {
615        if (DEBUG) Log.d(TAG, "stop()");
616        try {
617            unregisterReceiver(mMapReceiver);
618        } catch (Exception e) {
619            Log.w(TAG,"Unable to unregister map receiver",e);
620        }
621
622        setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED);
623        closeService();
624        return true;
625    }
626
627    public boolean cleanup()  {
628        if (DEBUG) Log.d(TAG, "cleanup()");
629        setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED);
630        closeService();
631        return true;
632    }
633
634    private MapBroadcastReceiver mMapReceiver = new MapBroadcastReceiver();
635
636    private class MapBroadcastReceiver extends BroadcastReceiver {
637        @Override
638        public void onReceive(Context context, Intent intent) {
639            if (DEBUG) Log.d(TAG, "onReceive");
640            String action = intent.getAction();
641            if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
642                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
643                                               BluetoothAdapter.ERROR);
644                if (state == BluetoothAdapter.STATE_TURNING_OFF) {
645                    if (DEBUG) Log.d(TAG, "STATE_TURNING_OFF");
646                    // Release all resources
647                    closeService();
648                } else if (state == BluetoothAdapter.STATE_ON) {
649                    if (DEBUG) Log.d(TAG, "STATE_ON");
650                    mInterrupted = false;
651                    // start RFCOMM listener
652                    mSessionStatusHandler.sendMessage(mSessionStatusHandler
653                                  .obtainMessage(START_LISTENER));
654                }
655            } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
656                int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
657                                               BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
658                if (DEBUG) Log.d(TAG, "Received ACTION_CONNECTION_ACCESS_REPLY:" +
659                           requestType + ":" + isWaitingAuthorization);
660                if ((!isWaitingAuthorization) ||
661                    (requestType != BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS)) {
662                    // this reply is not for us
663                    return;
664                }
665
666                isWaitingAuthorization = false;
667
668                if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
669                                       BluetoothDevice.CONNECTION_ACCESS_NO) ==
670                    BluetoothDevice.CONNECTION_ACCESS_YES) {
671                    //bluetooth connection accepted by user
672                    if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
673                        boolean result = mRemoteDevice.setTrust(true);
674                        if (DEBUG) Log.d(TAG, "setTrust() result=" + result);
675                    }
676                    try {
677                        if (mConnSocket != null) {
678                            // start obex server and rfcomm connection
679                            startObexServerSession();
680                        } else {
681                            stopObexServerSession();
682                        }
683                    } catch (IOException ex) {
684                        Log.e(TAG, "Caught the error: " + ex.toString());
685                    }
686                } else {
687                    stopObexServerSession();
688                }
689            }
690        }
691    };
692
693    //Binder object: Must be static class or memory leak may occur
694    /**
695     * This class implements the IBluetoothMap interface - or actually it validates the
696     * preconditions for calling the actual functionality in the MapService, and calls it.
697     */
698    private static class BluetoothMapBinder extends IBluetoothMap.Stub
699        implements IProfileServiceBinder {
700        private BluetoothMapService mService;
701
702        private BluetoothMapService getService() {
703            if (!Utils.checkCaller()) {
704                Log.w(TAG,"MAP call not allowed for non-active user");
705                return null;
706            }
707
708            if (mService != null && mService.isAvailable()) {
709                mService.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
710                return mService;
711            }
712            return null;
713        }
714
715        BluetoothMapBinder(BluetoothMapService service) {
716            if (VERBOSE) Log.v(TAG, "BluetoothMapBinder()");
717            mService = service;
718        }
719
720        public boolean cleanup()  {
721            mService = null;
722            return true;
723        }
724
725        public int getState() {
726            if (VERBOSE) Log.v(TAG, "getState()");
727            BluetoothMapService service = getService();
728            if (service == null) return BluetoothMap.STATE_DISCONNECTED;
729            return getService().getState();
730        }
731
732        public BluetoothDevice getClient() {
733            if (VERBOSE) Log.v(TAG, "getClient()");
734            BluetoothMapService service = getService();
735            if (service == null) return null;
736            Log.v(TAG, "getClient() - returning " + service.getRemoteDevice());
737            return service.getRemoteDevice();
738        }
739
740        public boolean isConnected(BluetoothDevice device) {
741            if (VERBOSE) Log.v(TAG, "isConnected()");
742            BluetoothMapService service = getService();
743            if (service == null) return false;
744            return service.getState() == BluetoothMap.STATE_CONNECTED && service.getRemoteDevice().equals(device);
745        }
746
747        public boolean connect(BluetoothDevice device) {
748            if (VERBOSE) Log.v(TAG, "connect()");
749            BluetoothMapService service = getService();
750            if (service == null) return false;
751            return false;
752        }
753
754        public boolean disconnect(BluetoothDevice device) {
755            if (VERBOSE) Log.v(TAG, "disconnect()");
756            BluetoothMapService service = getService();
757            if (service == null) return false;
758            return service.disconnect(device);
759        }
760
761        public List<BluetoothDevice> getConnectedDevices() {
762            if (VERBOSE) Log.v(TAG, "getConnectedDevices()");
763            BluetoothMapService service = getService();
764            if (service == null) return new ArrayList<BluetoothDevice>(0);
765            return service.getConnectedDevices();
766        }
767
768        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
769            if (VERBOSE) Log.v(TAG, "getDevicesMatchingConnectionStates()");
770            BluetoothMapService service = getService();
771            if (service == null) return new ArrayList<BluetoothDevice>(0);
772            return service.getDevicesMatchingConnectionStates(states);
773        }
774
775        public int getConnectionState(BluetoothDevice device) {
776            if (VERBOSE) Log.v(TAG, "getConnectionState()");
777            BluetoothMapService service = getService();
778            if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
779            return service.getConnectionState(device);
780        }
781
782        public boolean setPriority(BluetoothDevice device, int priority) {
783            BluetoothMapService service = getService();
784            if (service == null) return false;
785            return service.setPriority(device, priority);
786        }
787
788        public int getPriority(BluetoothDevice device) {
789            BluetoothMapService service = getService();
790            if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
791            return service.getPriority(device);
792        }
793    };
794}
795