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