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