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