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