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