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