Home | History | Annotate | Download | only in map
      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; //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 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 
    165     public BluetoothMapService() {
    166         mState = BluetoothMap.STATE_DISCONNECTED;
    167 
    168     }
    169 
    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      */
    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      */
    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 
    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      */
    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 
    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 
    417     public int getState() {
    418         return mState;
    419     }
    420 
    421     public BluetoothDevice getRemoteDevice() {
    422         return mRemoteDevice;
    423     }
    424     private void setState(int state) {
    425         setState(state, BluetoothMap.RESULT_SUCCESS);
    426     }
    427 
    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 
    447     public static String getRemoteDeviceName() {
    448         return sRemoteDeviceName;
    449     }
    450 
    451     public boolean disconnect(BluetoothDevice device) {
    452         mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(DISCONNECT_MAP, 0, 0, device));
    453         return true;
    454     }
    455 
    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 
    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 
    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 
    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 
    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 
    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
    531     protected IProfileServiceBinder initBinder() {
    532         return new BluetoothMapBinder(this);
    533     }
    534 
    535     @Override
    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      */
    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      */
    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      */
    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 
    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
    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 
    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      */
    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 
    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 
    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 
    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     }
    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     }
    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 
    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
    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 
    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 
   1008         BluetoothMapBinder(BluetoothMapService service) {
   1009             if (VERBOSE) Log.v(TAG, "BluetoothMapBinder()");
   1010             mService = service;
   1011         }
   1012 
   1013         public boolean cleanup()  {
   1014             mService = null;
   1015             return true;
   1016         }
   1017 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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