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 package com.android.bluetooth.map;
     16 
     17 import java.io.IOException;
     18 import java.util.Calendar;
     19 import java.util.HashMap;
     20 import java.util.Map;
     21 import java.util.concurrent.atomic.AtomicLong;
     22 
     23 import javax.obex.ServerSession;
     24 
     25 import com.android.bluetooth.BluetoothObexTransport;
     26 import com.android.bluetooth.IObexConnectionHandler;
     27 import com.android.bluetooth.ObexServerSockets;
     28 import com.android.bluetooth.map.BluetoothMapContentObserver.Msg;
     29 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
     30 import com.android.bluetooth.sdp.SdpManager;
     31 
     32 import android.bluetooth.BluetoothAdapter;
     33 import android.bluetooth.BluetoothDevice;
     34 import android.bluetooth.BluetoothServerSocket;
     35 import android.bluetooth.BluetoothSocket;
     36 import android.bluetooth.BluetoothUuid;
     37 import android.content.Context;
     38 import android.content.Intent;
     39 import android.os.Handler;
     40 import android.os.RemoteException;
     41 import android.util.Log;
     42 
     43 public class BluetoothMapMasInstance implements IObexConnectionHandler {
     44     private final String TAG;
     45     private static volatile int sInstanceCounter = 0;
     46 
     47     private static final boolean D = BluetoothMapService.DEBUG;
     48     private static final boolean V = BluetoothMapService.VERBOSE;
     49 
     50     private static final int SDP_MAP_MSG_TYPE_EMAIL    = 0x01;
     51     private static final int SDP_MAP_MSG_TYPE_SMS_GSM  = 0x02;
     52     private static final int SDP_MAP_MSG_TYPE_SMS_CDMA = 0x04;
     53     private static final int SDP_MAP_MSG_TYPE_MMS      = 0x08;
     54     private static final int SDP_MAP_MSG_TYPE_IM       = 0x10;
     55 
     56     private static final int SDP_MAP_MAS_VERSION       = 0x0102;
     57 
     58     /* TODO: Should these be adaptive for each MAS? - e.g. read from app? */
     59     private static final int SDP_MAP_MAS_FEATURES      = 0x0000007F;
     60 
     61     private ServerSession mServerSession = null;
     62     // The handle to the socket registration with SDP
     63     private ObexServerSockets mServerSockets = null;
     64     private int mSdpHandle = -1;
     65 
     66     // The actual incoming connection handle
     67     private BluetoothSocket mConnSocket = null;
     68     // The remote connected device
     69     private BluetoothDevice mRemoteDevice = null;
     70     private BluetoothAdapter mAdapter;
     71 
     72     private volatile boolean mInterrupted;              // Used to interrupt socket accept thread
     73     private volatile boolean mShutdown = false;         // Used to interrupt socket accept thread
     74 
     75     private Handler mServiceHandler = null;             // MAP service message handler
     76     private BluetoothMapService mMapService = null;     // Handle to the outer MAP service
     77     private Context mContext = null;                    // MAP service context
     78     private BluetoothMnsObexClient mMnsClient = null;   // Shared MAP MNS client
     79     private BluetoothMapAccountItem mAccount = null;    //
     80     private String mBaseUri = null;                     // Client base URI for this instance
     81     private int mMasInstanceId = -1;
     82     private boolean mEnableSmsMms = false;
     83     BluetoothMapContentObserver mObserver;
     84 
     85     private AtomicLong mDbIndetifier = new AtomicLong();
     86     private AtomicLong mFolderVersionCounter = new AtomicLong(0);
     87     private AtomicLong mSmsMmsConvoListVersionCounter = new AtomicLong(0);
     88     private AtomicLong mImEmailConvoListVersionCounter = new AtomicLong(0);
     89 
     90     private Map<Long, Msg> mMsgListSms=null;
     91     private Map<Long, Msg> mMsgListMms=null;
     92     private Map<Long, Msg> mMsgListMsg=null;
     93 
     94     private Map<String, BluetoothMapConvoContactElement> mContactList;
     95 
     96     private HashMap<Long,BluetoothMapConvoListingElement> mSmsMmsConvoList =
     97             new HashMap<Long, BluetoothMapConvoListingElement>();
     98 
     99     private HashMap<Long,BluetoothMapConvoListingElement> mImEmailConvoList =
    100             new HashMap<Long, BluetoothMapConvoListingElement>();
    101 
    102     private int mRemoteFeatureMask = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
    103 
    104     public static final String TYPE_SMS_MMS_STR = "SMS/MMS";
    105     public static final String TYPE_EMAIL_STR = "EMAIL";
    106     public static final String TYPE_IM_STR = "IM";
    107 
    108     /**
    109      * Create a e-mail MAS instance
    110      * @param callback
    111      * @param context
    112      * @param mns
    113      * @param emailBaseUri - use null to create a SMS/MMS MAS instance
    114      */
    115     public BluetoothMapMasInstance (BluetoothMapService mapService,
    116             Context context,
    117             BluetoothMapAccountItem account,
    118             int masId,
    119             boolean enableSmsMms) {
    120         TAG = "BluetoothMapMasInstance" + sInstanceCounter++;
    121         mMapService = mapService;
    122         mServiceHandler = mapService.getHandler();
    123         mContext = context;
    124         mAccount = account;
    125         if(account != null) {
    126             mBaseUri = account.mBase_uri;
    127         }
    128         mMasInstanceId = masId;
    129         mEnableSmsMms = enableSmsMms;
    130         init();
    131     }
    132 
    133     private void removeSdpRecord() {
    134         if (mAdapter != null && mSdpHandle >= 0 &&
    135                 SdpManager.getDefaultManager() != null) {
    136             if (V) Log.d(TAG, "Removing SDP record for MAS instance: " + mMasInstanceId +
    137                     " Object reference: " + this + "SDP handle: " + mSdpHandle);
    138             boolean status = SdpManager.getDefaultManager().removeSdpRecord(mSdpHandle);
    139             Log.d(TAG, "RemoveSDPrecord returns " + status);
    140             mSdpHandle = -1;
    141         }
    142     }
    143 
    144     /* Needed only for test */
    145     protected BluetoothMapMasInstance() {
    146         TAG = "BluetoothMapMasInstance" + sInstanceCounter++;
    147     }
    148 
    149     @Override
    150     public String toString() {
    151         return "MasId: " + mMasInstanceId + " Uri:" + mBaseUri + " SMS/MMS:" + mEnableSmsMms;
    152     }
    153 
    154     private void init() {
    155         mAdapter = BluetoothAdapter.getDefaultAdapter();
    156     }
    157 
    158     /**
    159      * The data base identifier is used by connecting MCE devices to evaluate if cached data
    160      * is still valid, hence only update this value when something actually invalidates the data.
    161      * Situations where this must be called:
    162      * - MAS ID's vs. server channels are scrambled (As neither MAS ID, name or server channels)
    163      *   can be used by a client to uniquely identify a specific message database - except MAS id 0
    164      *   we should change this value if the server channel is changed.
    165      * - If a MAS instance folderVersionCounter roles over - will not happen before a long
    166      *   is too small to hold a unix time-stamp, hence is not handled.
    167      */
    168     private void updateDbIdentifier(){
    169         mDbIndetifier.set(Calendar.getInstance().getTime().getTime());
    170     }
    171 
    172     /**
    173      * update the time stamp used for FOLDER version counter.
    174      * Call once when a content provider notification caused applicable changes to the
    175      * list of messages.
    176      */
    177     /* package */ void updateFolderVersionCounter() {
    178         mFolderVersionCounter.incrementAndGet();
    179     }
    180 
    181     /**
    182      * update the CONVO LIST version counter.
    183      * Call once when a content provider notification caused applicable changes to the
    184      * list of contacts, or when an update is manually triggered.
    185      */
    186     /* package */ void updateSmsMmsConvoListVersionCounter() {
    187         mSmsMmsConvoListVersionCounter.incrementAndGet();
    188     }
    189 
    190     /* package */ void updateImEmailConvoListVersionCounter() {
    191         mImEmailConvoListVersionCounter.incrementAndGet();
    192     }
    193 
    194     /* package */ Map<Long, Msg> getMsgListSms() {
    195         return mMsgListSms;
    196     }
    197 
    198     /* package */ void setMsgListSms(Map<Long, Msg> msgListSms) {
    199         mMsgListSms = msgListSms;
    200     }
    201 
    202     /* package */ Map<Long, Msg> getMsgListMms() {
    203         return mMsgListMms;
    204     }
    205 
    206     /* package */ void setMsgListMms(Map<Long, Msg> msgListMms) {
    207         mMsgListMms = msgListMms;
    208     }
    209 
    210     /* package */ Map<Long, Msg> getMsgListMsg() {
    211         return mMsgListMsg;
    212     }
    213 
    214     /* package */ void setMsgListMsg(Map<Long, Msg> msgListMsg) {
    215         mMsgListMsg = msgListMsg;
    216     }
    217 
    218     /* package */ Map<String, BluetoothMapConvoContactElement> getContactList() {
    219         return mContactList;
    220     }
    221 
    222     /* package */ void setContactList(Map<String, BluetoothMapConvoContactElement> contactList) {
    223         mContactList = contactList;
    224     }
    225 
    226     HashMap<Long,BluetoothMapConvoListingElement> getSmsMmsConvoList() {
    227         return mSmsMmsConvoList;
    228     }
    229 
    230     void setSmsMmsConvoList(HashMap<Long,BluetoothMapConvoListingElement> smsMmsConvoList) {
    231         mSmsMmsConvoList = smsMmsConvoList;
    232     }
    233 
    234     HashMap<Long,BluetoothMapConvoListingElement> getImEmailConvoList() {
    235         return mImEmailConvoList;
    236     }
    237 
    238     void setImEmailConvoList(HashMap<Long,BluetoothMapConvoListingElement> imEmailConvoList) {
    239         mImEmailConvoList = imEmailConvoList;
    240     }
    241 
    242     /* package*/
    243     int getMasId() {
    244         return mMasInstanceId;
    245     }
    246 
    247     /* package*/
    248     long getDbIdentifier() {
    249         return mDbIndetifier.get();
    250     }
    251 
    252     /* package*/
    253     long getFolderVersionCounter() {
    254         return mFolderVersionCounter.get();
    255     }
    256 
    257     /* package */
    258     long getCombinedConvoListVersionCounter() {
    259         long combinedVersionCounter = mSmsMmsConvoListVersionCounter.get();
    260         combinedVersionCounter += mImEmailConvoListVersionCounter.get();
    261         return combinedVersionCounter;
    262     }
    263 
    264     synchronized public void startRfcommSocketListener() {
    265         if (D) Log.d(TAG, "Map Service startRfcommSocketListener");
    266 
    267         if (mServerSession != null) {
    268             if (D) Log.d(TAG, "mServerSession exists - shutting it down...");
    269             mServerSession.close();
    270             mServerSession = null;
    271         }
    272         if (mObserver != null) {
    273             if (D) Log.d(TAG, "mObserver exists - shutting it down...");
    274             mObserver.deinit();
    275             mObserver = null;
    276         }
    277 
    278         closeConnectionSocket();
    279 
    280         if(mServerSockets != null) {
    281             mServerSockets.prepareForNewConnect();
    282         } else {
    283 
    284             mServerSockets = ObexServerSockets.create(this);
    285 
    286             if(mServerSockets == null) {
    287                 // TODO: Handle - was not handled before
    288                 Log.e(TAG, "Failed to start the listeners");
    289                 return;
    290             }
    291             removeSdpRecord();
    292             mSdpHandle = createMasSdpRecord(mServerSockets.getRfcommChannel(),
    293                     mServerSockets.getL2capPsm());
    294             // Here we might have changed crucial data, hence reset DB identifier
    295             if(V) Log.d(TAG, "Creating new SDP record for MAS instance: " + mMasInstanceId +
    296                     " Object reference: " + this + "SDP handle: " + mSdpHandle);
    297             updateDbIdentifier();
    298         }
    299     }
    300 
    301     /**
    302      * Create the MAS SDP record with the information stored in the instance.
    303      * @param rfcommChannel the rfcomm channel ID
    304      * @param l2capPsm the l2capPsm - set to -1 to exclude
    305      */
    306     private int createMasSdpRecord(int rfcommChannel, int l2capPsm) {
    307         String masName = "";
    308         int messageTypeFlags = 0;
    309         if(mEnableSmsMms) {
    310             masName = TYPE_SMS_MMS_STR;
    311             messageTypeFlags |= SDP_MAP_MSG_TYPE_SMS_GSM |
    312                            SDP_MAP_MSG_TYPE_SMS_CDMA|
    313                            SDP_MAP_MSG_TYPE_MMS;
    314         }
    315 
    316         if(mBaseUri != null) {
    317             if(mEnableSmsMms) {
    318                 if(mAccount.getType() == TYPE.EMAIL) {
    319                     masName += "/" + TYPE_EMAIL_STR;
    320                 } else if(mAccount.getType() == TYPE.IM) {
    321                     masName += "/" + TYPE_IM_STR;
    322                 }
    323             } else {
    324                 masName = mAccount.getName();
    325             }
    326 
    327             if(mAccount.getType() == TYPE.EMAIL) {
    328                 messageTypeFlags |= SDP_MAP_MSG_TYPE_EMAIL;
    329             } else if(mAccount.getType() == TYPE.IM) {
    330                 messageTypeFlags |= SDP_MAP_MSG_TYPE_IM;
    331             }
    332         }
    333 
    334         return SdpManager.getDefaultManager().createMapMasRecord(masName,
    335                 mMasInstanceId,
    336                 rfcommChannel,
    337                 l2capPsm,
    338                 SDP_MAP_MAS_VERSION,
    339                 messageTypeFlags,
    340                 SDP_MAP_MAS_FEATURES);
    341     }
    342 
    343     /* Called for all MAS instances for each instance when auth. is completed, hence
    344      * must check if it has a valid connection before creating a session.
    345      * Returns true at success. */
    346     public boolean startObexServerSession(BluetoothMnsObexClient mnsClient)
    347             throws IOException, RemoteException {
    348         if (D) Log.d(TAG, "Map Service startObexServerSession masid = " + mMasInstanceId);
    349 
    350         if (mConnSocket != null) {
    351             if(mServerSession != null) {
    352                 // Already connected, just return true
    353                 return true;
    354             }
    355 
    356             mMnsClient = mnsClient;
    357             BluetoothMapObexServer mapServer;
    358             mObserver = new  BluetoothMapContentObserver(mContext,
    359                                                          mMnsClient,
    360                                                          this,
    361                                                          mAccount,
    362                                                          mEnableSmsMms);
    363             mObserver.init();
    364             mapServer = new BluetoothMapObexServer(mServiceHandler,
    365                                                     mContext,
    366                                                     mObserver,
    367                                                     this,
    368                                                     mAccount,
    369                                                     mEnableSmsMms);
    370 
    371             // setup transport
    372             BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
    373             mServerSession = new ServerSession(transport, mapServer, null);
    374             if (D) Log.d(TAG, "    ServerSession started.");
    375 
    376             return true;
    377         }
    378         if (D) Log.d(TAG, "    No connection for this instance");
    379         return false;
    380     }
    381 
    382     public boolean handleSmsSendIntent(Context context, Intent intent){
    383         if(mObserver != null) {
    384             return mObserver.handleSmsSendIntent(context, intent);
    385         }
    386         return false;
    387     }
    388 
    389     /**
    390      * Check if this instance is started.
    391      * @return true if started
    392      */
    393     public boolean isStarted() {
    394         return (mConnSocket != null);
    395     }
    396 
    397     public void shutdown() {
    398         if (D) Log.d(TAG, "MAP Service shutdown");
    399 
    400         if (mServerSession != null) {
    401             mServerSession.close();
    402             mServerSession = null;
    403         }
    404         if (mObserver != null) {
    405             mObserver.deinit();
    406             mObserver = null;
    407         }
    408 
    409         removeSdpRecord();
    410 
    411         closeConnectionSocket();
    412 
    413         closeServerSockets(true);
    414     }
    415 
    416     /**
    417      * Signal to the ServerSockets handler that a new connection may be accepted.
    418      */
    419     public void restartObexServerSession() {
    420         if (D) Log.d(TAG, "MAP Service restartObexServerSession()");
    421         startRfcommSocketListener();
    422     }
    423 
    424 
    425     private final synchronized void closeServerSockets(boolean block) {
    426         // exit SocketAcceptThread early
    427         ObexServerSockets sockets = mServerSockets;
    428         if (sockets != null) {
    429             sockets.shutdown(block);
    430             mServerSockets = null;
    431         }
    432     }
    433 
    434     private final synchronized void closeConnectionSocket() {
    435         if (mConnSocket != null) {
    436             try {
    437                 mConnSocket.close();
    438             } catch (IOException e) {
    439                 Log.e(TAG, "Close Connection Socket error: ", e);
    440             } finally {
    441                 mConnSocket = null;
    442             }
    443         }
    444     }
    445 
    446     public void setRemoteFeatureMask(int supportedFeatures) {
    447        if(V) Log.v(TAG, "setRemoteFeatureMask : Curr: "+ mRemoteFeatureMask);
    448        mRemoteFeatureMask  = supportedFeatures;
    449        if (mObserver != null) {
    450            mObserver.setObserverRemoteFeatureMask(mRemoteFeatureMask);
    451            if(V) Log.v(TAG, "setRemoteFeatureMask : set: " + mRemoteFeatureMask);
    452        }
    453     }
    454 
    455     public int getRemoteFeatureMask(){
    456         return this.mRemoteFeatureMask;
    457     }
    458 
    459     @Override
    460     public synchronized boolean onConnect(BluetoothDevice device, BluetoothSocket socket) {
    461         /* Signal to the service that we have received an incoming connection.
    462          */
    463         boolean isValid = mMapService.onConnect(device, BluetoothMapMasInstance.this);
    464 
    465         if(isValid == true) {
    466             mRemoteDevice = device;
    467             mConnSocket = socket;
    468         }
    469 
    470         return isValid;
    471     }
    472 
    473     /**
    474      * Called when an unrecoverable error occurred in an accept thread.
    475      * Close down the server socket, and restart.
    476      * TODO: Change to message, to call start in correct context.
    477      */
    478     @Override
    479     public synchronized void onAcceptFailed() {
    480         mServerSockets = null; // Will cause a new to be created when calling start.
    481         if(mShutdown) {
    482             Log.e(TAG,"Failed to accept incomming connection - " + "shutdown");
    483         } else {
    484             Log.e(TAG,"Failed to accept incomming connection - " + "restarting");
    485             startRfcommSocketListener();
    486         }
    487     }
    488 
    489 }
    490