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