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