Home | History | Annotate | Download | only in map
      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 
     16 package com.android.bluetooth.map;
     17 
     18 import java.io.IOException;
     19 import java.util.ArrayList;
     20 import java.util.List;
     21 import java.util.Set;
     22 
     23 import javax.obex.ServerSession;
     24 import android.app.Notification;
     25 import android.app.NotificationManager;
     26 import android.app.PendingIntent;
     27 import android.app.Service;
     28 import android.bluetooth.BluetoothAdapter;
     29 import android.bluetooth.BluetoothDevice;
     30 import android.bluetooth.BluetoothProfile;
     31 import android.bluetooth.BluetoothServerSocket;
     32 import android.bluetooth.IBluetooth;
     33 import android.bluetooth.IBluetoothMap;
     34 import android.bluetooth.BluetoothUuid;
     35 import android.bluetooth.BluetoothMap;
     36 import android.bluetooth.BluetoothSocket;
     37 import android.content.Context;
     38 import android.content.Intent;
     39 import android.os.Handler;
     40 import android.os.IBinder;
     41 import android.os.Message;
     42 import android.os.PowerManager;
     43 import android.os.ParcelUuid;
     44 import android.text.TextUtils;
     45 import android.util.Log;
     46 import android.provider.Settings;
     47 import android.content.IntentFilter;
     48 import android.content.BroadcastReceiver;
     49 
     50 import com.android.bluetooth.R;
     51 import com.android.bluetooth.Utils;
     52 import com.android.bluetooth.btservice.AdapterService;
     53 import com.android.bluetooth.btservice.ProfileService;
     54 import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder;
     55 
     56 
     57 public 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