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      */
BluetoothMapMasInstance(BluetoothMapService mapService, Context context, BluetoothMapAccountItem account, int masId, boolean enableSmsMms)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 
removeSdpRecord()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 */
BluetoothMapMasInstance()145     protected BluetoothMapMasInstance() {
146         TAG = "BluetoothMapMasInstance" + sInstanceCounter++;
147     }
148 
149     @Override
toString()150     public String toString() {
151         return "MasId: " + mMasInstanceId + " Uri:" + mBaseUri + " SMS/MMS:" + mEnableSmsMms;
152     }
153 
init()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      */
updateDbIdentifier()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      */
updateFolderVersionCounter()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      */
updateSmsMmsConvoListVersionCounter()186     /* package */ void updateSmsMmsConvoListVersionCounter() {
187         mSmsMmsConvoListVersionCounter.incrementAndGet();
188     }
189 
updateImEmailConvoListVersionCounter()190     /* package */ void updateImEmailConvoListVersionCounter() {
191         mImEmailConvoListVersionCounter.incrementAndGet();
192     }
193 
getMsgListSms()194     /* package */ Map<Long, Msg> getMsgListSms() {
195         return mMsgListSms;
196     }
197 
setMsgListSms(Map<Long, Msg> msgListSms)198     /* package */ void setMsgListSms(Map<Long, Msg> msgListSms) {
199         mMsgListSms = msgListSms;
200     }
201 
getMsgListMms()202     /* package */ Map<Long, Msg> getMsgListMms() {
203         return mMsgListMms;
204     }
205 
setMsgListMms(Map<Long, Msg> msgListMms)206     /* package */ void setMsgListMms(Map<Long, Msg> msgListMms) {
207         mMsgListMms = msgListMms;
208     }
209 
getMsgListMsg()210     /* package */ Map<Long, Msg> getMsgListMsg() {
211         return mMsgListMsg;
212     }
213 
setMsgListMsg(Map<Long, Msg> msgListMsg)214     /* package */ void setMsgListMsg(Map<Long, Msg> msgListMsg) {
215         mMsgListMsg = msgListMsg;
216     }
217 
getContactList()218     /* package */ Map<String, BluetoothMapConvoContactElement> getContactList() {
219         return mContactList;
220     }
221 
setContactList(Map<String, BluetoothMapConvoContactElement> contactList)222     /* package */ void setContactList(Map<String, BluetoothMapConvoContactElement> contactList) {
223         mContactList = contactList;
224     }
225 
getSmsMmsConvoList()226     HashMap<Long,BluetoothMapConvoListingElement> getSmsMmsConvoList() {
227         return mSmsMmsConvoList;
228     }
229 
setSmsMmsConvoList(HashMap<Long,BluetoothMapConvoListingElement> smsMmsConvoList)230     void setSmsMmsConvoList(HashMap<Long,BluetoothMapConvoListingElement> smsMmsConvoList) {
231         mSmsMmsConvoList = smsMmsConvoList;
232     }
233 
getImEmailConvoList()234     HashMap<Long,BluetoothMapConvoListingElement> getImEmailConvoList() {
235         return mImEmailConvoList;
236     }
237 
setImEmailConvoList(HashMap<Long,BluetoothMapConvoListingElement> imEmailConvoList)238     void setImEmailConvoList(HashMap<Long,BluetoothMapConvoListingElement> imEmailConvoList) {
239         mImEmailConvoList = imEmailConvoList;
240     }
241 
242     /* package*/
getMasId()243     int getMasId() {
244         return mMasInstanceId;
245     }
246 
247     /* package*/
getDbIdentifier()248     long getDbIdentifier() {
249         return mDbIndetifier.get();
250     }
251 
252     /* package*/
getFolderVersionCounter()253     long getFolderVersionCounter() {
254         return mFolderVersionCounter.get();
255     }
256 
257     /* package */
getCombinedConvoListVersionCounter()258     long getCombinedConvoListVersionCounter() {
259         long combinedVersionCounter = mSmsMmsConvoListVersionCounter.get();
260         combinedVersionCounter += mImEmailConvoListVersionCounter.get();
261         return combinedVersionCounter;
262     }
263 
startRfcommSocketListener()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      */
createMasSdpRecord(int rfcommChannel, int l2capPsm)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. */
startObexServerSession(BluetoothMnsObexClient mnsClient)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 
handleSmsSendIntent(Context context, Intent intent)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      */
isStarted()393     public boolean isStarted() {
394         return (mConnSocket != null);
395     }
396 
shutdown()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      */
restartObexServerSession()419     public void restartObexServerSession() {
420         if (D) Log.d(TAG, "MAP Service restartObexServerSession()");
421         startRfcommSocketListener();
422     }
423 
424 
closeServerSockets(boolean block)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 
closeConnectionSocket()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 
setRemoteFeatureMask(int supportedFeatures)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 
getRemoteFeatureMask()455     public int getRemoteFeatureMask(){
456         return this.mRemoteFeatureMask;
457     }
458 
459     @Override
onConnect(BluetoothDevice device, BluetoothSocket socket)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
onAcceptFailed()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