• 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.BluetoothProfile;
20 import android.bluetooth.BluetoothProtoEnums;
21 import android.bluetooth.BluetoothSocket;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.os.Handler;
25 import android.os.RemoteException;
26 import android.os.SystemProperties;
27 import android.util.Log;
28 
29 import com.android.bluetooth.BluetoothObexTransport;
30 import com.android.bluetooth.BluetoothStatsLog;
31 import com.android.bluetooth.IObexConnectionHandler;
32 import com.android.bluetooth.ObexServerSockets;
33 import com.android.bluetooth.content_profiles.ContentProfileErrorReportUtils;
34 import com.android.bluetooth.map.BluetoothMapContentObserver.Msg;
35 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
36 import com.android.bluetooth.sdp.SdpManagerNativeInterface;
37 import com.android.obex.ServerSession;
38 
39 import java.io.IOException;
40 import java.util.Calendar;
41 import java.util.HashMap;
42 import java.util.Map;
43 import java.util.concurrent.atomic.AtomicLong;
44 
45 // Next tag value for ContentProfileErrorReportUtils.report(): 4
46 public class BluetoothMapMasInstance implements IObexConnectionHandler {
47     private static final String TAG = BluetoothMapMasInstance.class.getSimpleName();
48 
49     private static int sInstanceCounter = 0;
50 
51     private final int mObjectInstanceId;
52 
53     private static final int SDP_MAP_MSG_TYPE_EMAIL = 0x01;
54     private static final int SDP_MAP_MSG_TYPE_SMS_GSM = 0x02;
55     private static final int SDP_MAP_MSG_TYPE_SMS_CDMA = 0x04;
56     private static final int SDP_MAP_MSG_TYPE_MMS = 0x08;
57     private static final int SDP_MAP_MSG_TYPE_IM = 0x10;
58 
59     private static final String BLUETOOTH_MAP_VERSION_PROPERTY = "persist.bluetooth.mapversion";
60 
61     private static final int SDP_MAP_MAS_VERSION_1_2 = 0x0102;
62     private static final int SDP_MAP_MAS_VERSION_1_3 = 0x0103;
63     private static final int SDP_MAP_MAS_VERSION_1_4 = 0x0104;
64 
65     /* TODO: Should these be adaptive for each MAS? - e.g. read from app? */
66     static final int SDP_MAP_MAS_FEATURES_1_2 = 0x0000007F;
67     static final int SDP_MAP_MAS_FEATURES_1_3 = 0x000603FF;
68     static final int SDP_MAP_MAS_FEATURES_1_4 = 0x000603FF;
69 
70     private ServerSession mServerSession = null;
71     // The handle to the socket registration with SDP
72     private ObexServerSockets mServerSockets = null;
73     private int mSdpHandle = -1;
74 
75     // The actual incoming connection handle
76     private BluetoothSocket mConnSocket = null;
77     // The remote connected device
78     private BluetoothAdapter mAdapter;
79 
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 final AtomicLong mDbIdentifier = new AtomicLong();
94     private final AtomicLong mFolderVersionCounter = new AtomicLong(0);
95     private final AtomicLong mSmsMmsConvoListVersionCounter = new AtomicLong(0);
96     private final 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 Map<Long, BluetoothMapConvoListingElement> mSmsMmsConvoList = new HashMap<>();
105 
106     private Map<Long, BluetoothMapConvoListingElement> mImEmailConvoList = new HashMap<>();
107 
108     private int mRemoteFeatureMask = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
109     private static int sFeatureMask = SDP_MAP_MAS_FEATURES_1_4;
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     /** Create a e-mail MAS instance */
BluetoothMapMasInstance( BluetoothMapService mapService, Context context, BluetoothMapAccountItem account, int masId, boolean enableSmsMms)116     public BluetoothMapMasInstance(
117             BluetoothMapService mapService,
118             Context context,
119             BluetoothMapAccountItem account,
120             int masId,
121             boolean enableSmsMms) {
122         mObjectInstanceId = sInstanceCounter++;
123         mMapService = mapService;
124         mServiceHandler = mapService.getHandler();
125         mContext = context;
126         mAccount = account;
127         if (account != null) {
128             mBaseUri = account.mBase_uri;
129         }
130         mMasInstanceId = masId;
131         mEnableSmsMms = enableSmsMms;
132         init();
133     }
134 
removeSdpRecord()135     private void removeSdpRecord() {
136         SdpManagerNativeInterface nativeInterface = SdpManagerNativeInterface.getInstance();
137         if (mAdapter != null && mSdpHandle >= 0 && nativeInterface.isAvailable()) {
138             verbose(
139                     "Removing SDP record for MAS instance: "
140                             + mMasInstanceId
141                             + " Object reference: "
142                             + this
143                             + ", SDP handle: "
144                             + mSdpHandle);
145             boolean status = nativeInterface.removeSdpRecord(mSdpHandle);
146             debug("RemoveSDPRecord returns " + status);
147             mSdpHandle = -1;
148         }
149     }
150 
151     @Override
toString()152     public String toString() {
153         return "MasId: " + mMasInstanceId + " Uri:" + mBaseUri + " SMS/MMS:" + mEnableSmsMms;
154     }
155 
init()156     private void init() {
157         mAdapter = BluetoothAdapter.getDefaultAdapter();
158     }
159 
160     /**
161      * The data base identifier is used by connecting MCE devices to evaluate if cached data is
162      * still valid, hence only update this value when something actually invalidates the data.
163      * Situations where this must be called: - MAS ID's vs. server channels are scrambled (As
164      * neither MAS ID, name or server channels) can be used by a client to uniquely identify a
165      * specific message database - except MAS id 0 we should change this value if the server channel
166      * is changed. - If a MAS instance folderVersionCounter roles over - will not happen before a
167      * long is too small to hold a unix time-stamp, hence is not handled.
168      */
169     @SuppressWarnings("JavaUtilDate") // TODO: b/365629730 -- prefer Instant or LocalDate
updateDbIdentifier()170     private void updateDbIdentifier() {
171         mDbIdentifier.set(Calendar.getInstance().getTime().getTime());
172     }
173 
174     /**
175      * update the time stamp used for FOLDER version counter. Call once when a content provider
176      * notification caused applicable changes to the list of messages.
177      */
updateFolderVersionCounter()178     /* package */ void updateFolderVersionCounter() {
179         mFolderVersionCounter.incrementAndGet();
180     }
181 
182     /**
183      * update the CONVO LIST version counter. Call once when a content provider notification caused
184      * applicable changes to the 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     Map<Long, BluetoothMapConvoListingElement> getSmsMmsConvoList() {
227         return mSmsMmsConvoList;
228     }
229 
setSmsMmsConvoList(Map<Long, BluetoothMapConvoListingElement> smsMmsConvoList)230     void setSmsMmsConvoList(Map<Long, BluetoothMapConvoListingElement> smsMmsConvoList) {
231         mSmsMmsConvoList = smsMmsConvoList;
232     }
233 
getImEmailConvoList()234     Map<Long, BluetoothMapConvoListingElement> getImEmailConvoList() {
235         return mImEmailConvoList;
236     }
237 
setImEmailConvoList(Map<Long, BluetoothMapConvoListingElement> imEmailConvoList)238     void setImEmailConvoList(Map<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 mDbIdentifier.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 
264     /** Start Obex Server Sockets and create the SDP record. */
startSocketListeners()265     public synchronized void startSocketListeners() {
266         debug("Map Service startSocketListeners");
267 
268         if (mServerSession != null) {
269             debug("mServerSession exists - shutting it down...");
270             mServerSession.close();
271             mServerSession = null;
272         }
273         if (mObserver != null) {
274             debug("mObserver exists - shutting it down...");
275             mObserver.deinit();
276             mObserver = null;
277         }
278 
279         closeConnectionSocket();
280 
281         if (mServerSockets != null) {
282             mAcceptNewConnections = true;
283         } else {
284 
285             mServerSockets = ObexServerSockets.create(this);
286             mAcceptNewConnections = true;
287 
288             if (mServerSockets == null) {
289                 // TODO: Handle - was not handled before
290                 error("Failed to start the listeners");
291                 ContentProfileErrorReportUtils.report(
292                         BluetoothProfile.MAP,
293                         BluetoothProtoEnums.BLUETOOTH_MAP_MAS_INSTANCE,
294                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR,
295                         0);
296                 return;
297             }
298             removeSdpRecord();
299             mSdpHandle =
300                     createMasSdpRecord(
301                             mServerSockets.getRfcommChannel(), mServerSockets.getL2capPsm());
302             // Here we might have changed crucial data, hence reset DB identifier
303             verbose(
304                     "Creating new SDP record for MAS instance: "
305                             + mMasInstanceId
306                             + " Object reference: "
307                             + this
308                             + ", SDP handle: "
309                             + mSdpHandle);
310             updateDbIdentifier();
311         }
312     }
313 
314     /**
315      * Create the MAS SDP record with the information stored in the instance.
316      *
317      * @param rfcommChannel the rfcomm channel ID
318      * @param l2capPsm the l2capPsm - set to -1 to exclude
319      */
createMasSdpRecord(int rfcommChannel, int l2capPsm)320     private int createMasSdpRecord(int rfcommChannel, int l2capPsm) {
321         String masName = "";
322         int messageTypeFlags = 0;
323         if (mEnableSmsMms) {
324             masName = TYPE_SMS_MMS_STR;
325             messageTypeFlags |=
326                     SDP_MAP_MSG_TYPE_SMS_GSM | SDP_MAP_MSG_TYPE_SMS_CDMA | SDP_MAP_MSG_TYPE_MMS;
327         }
328 
329         if (mBaseUri != null) {
330             if (mEnableSmsMms) {
331                 if (mAccount.getType() == TYPE.EMAIL) {
332                     masName = masName + "/" + TYPE_EMAIL_STR;
333                 } else if (mAccount.getType() == TYPE.IM) {
334                     masName = masName + "/" + TYPE_IM_STR;
335                 }
336             } else {
337                 masName = mAccount.getName();
338             }
339 
340             if (mAccount.getType() == TYPE.EMAIL) {
341                 messageTypeFlags |= SDP_MAP_MSG_TYPE_EMAIL;
342             } else if (mAccount.getType() == TYPE.IM) {
343                 messageTypeFlags |= SDP_MAP_MSG_TYPE_IM;
344             }
345         }
346 
347         final String currentValue = SystemProperties.get(BLUETOOTH_MAP_VERSION_PROPERTY, "map14");
348         int masVersion;
349 
350         switch (currentValue) {
351             case "map12":
352                 masVersion = SDP_MAP_MAS_VERSION_1_2;
353                 sFeatureMask = SDP_MAP_MAS_FEATURES_1_2;
354                 break;
355             case "map13":
356                 masVersion = SDP_MAP_MAS_VERSION_1_3;
357                 sFeatureMask = SDP_MAP_MAS_FEATURES_1_3;
358                 break;
359             case "map14":
360                 masVersion = SDP_MAP_MAS_VERSION_1_4;
361                 sFeatureMask = SDP_MAP_MAS_FEATURES_1_4;
362                 break;
363             default:
364                 masVersion = SDP_MAP_MAS_VERSION_1_4;
365                 sFeatureMask = SDP_MAP_MAS_FEATURES_1_4;
366         }
367 
368         return SdpManagerNativeInterface.getInstance()
369                 .createMapMasRecord(
370                         masName,
371                         mMasInstanceId,
372                         rfcommChannel,
373                         l2capPsm,
374                         masVersion,
375                         messageTypeFlags,
376                         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         debug("Map Service startObexServerSession masid = " + mMasInstanceId);
385 
386         if (mConnSocket != null) {
387             if (mServerSession != null) {
388                 // Already connected, just return true
389                 return true;
390             }
391 
392             mMnsClient = mnsClient;
393             mObserver =
394                     new BluetoothMapContentObserver(
395                             mContext, mMnsClient, this, mAccount, mEnableSmsMms);
396             mObserver.init();
397             mMapServer =
398                     new BluetoothMapObexServer(
399                             mServiceHandler, mContext, mObserver, this, mAccount, mEnableSmsMms);
400             mMapServer.setRemoteFeatureMask(mRemoteFeatureMask);
401             // setup transport
402             BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
403             mServerSession = new ServerSession(transport, mMapServer, null);
404             debug("    ServerSession started.");
405 
406             return true;
407         }
408         debug("    No connection for this instance");
409         return false;
410     }
411 
handleSmsSendIntent(Context context, Intent intent)412     public boolean handleSmsSendIntent(Context context, Intent intent) {
413         if (mObserver != null) {
414             return mObserver.handleSmsSendIntent(context, intent);
415         }
416         return false;
417     }
418 
419     /**
420      * Check if this instance is started.
421      *
422      * @return true if started
423      */
isStarted()424     public boolean isStarted() {
425         return (mConnSocket != null);
426     }
427 
shutdown()428     public void shutdown() {
429         debug("MAP Service shutdown");
430 
431         if (mServerSession != null) {
432             mServerSession.close();
433             mServerSession = null;
434         }
435         if (mObserver != null) {
436             mObserver.deinit();
437             mObserver = null;
438         }
439 
440         removeSdpRecord();
441 
442         closeConnectionSocket();
443         // Do not block for Accept thread cleanup.
444         // Fix Handler Thread block during BT Turn OFF.
445         closeServerSockets(false);
446     }
447 
448     /** Signal to the ServerSockets handler that a new connection may be accepted. */
restartObexServerSession()449     public void restartObexServerSession() {
450         debug("MAP Service restartObexServerSession()");
451         startSocketListeners();
452     }
453 
closeServerSockets(boolean block)454     private synchronized void closeServerSockets(boolean block) {
455         // exit SocketAcceptThread early
456         ObexServerSockets sockets = mServerSockets;
457         if (sockets != null) {
458             sockets.shutdown(block);
459             mServerSockets = null;
460         }
461     }
462 
closeConnectionSocket()463     private synchronized void closeConnectionSocket() {
464         if (mConnSocket != null) {
465             try {
466                 mConnSocket.close();
467             } catch (IOException e) {
468                 ContentProfileErrorReportUtils.report(
469                         BluetoothProfile.MAP,
470                         BluetoothProtoEnums.BLUETOOTH_MAP_MAS_INSTANCE,
471                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
472                         1);
473                 error("Close Connection Socket error: ", e);
474             } finally {
475                 mConnSocket = null;
476             }
477         }
478     }
479 
setRemoteFeatureMask(int supportedFeatures)480     public void setRemoteFeatureMask(int supportedFeatures) {
481         verbose("setRemoteFeatureMask : Curr: " + mRemoteFeatureMask);
482         mRemoteFeatureMask = supportedFeatures & sFeatureMask;
483         BluetoothMapUtils.savePeerSupportUtcTimeStamp(mRemoteFeatureMask);
484         if (mObserver != null) {
485             mObserver.setObserverRemoteFeatureMask(mRemoteFeatureMask);
486             mMapServer.setRemoteFeatureMask(mRemoteFeatureMask);
487             verbose("setRemoteFeatureMask : set: " + mRemoteFeatureMask);
488         }
489     }
490 
getRemoteFeatureMask()491     public int getRemoteFeatureMask() {
492         return this.mRemoteFeatureMask;
493     }
494 
getFeatureMask()495     public static int getFeatureMask() {
496         return sFeatureMask;
497     }
498 
499     @Override
onConnect(BluetoothDevice device, BluetoothSocket socket)500     public synchronized boolean onConnect(BluetoothDevice device, BluetoothSocket socket) {
501         if (!mAcceptNewConnections) {
502             return false;
503         }
504         /* Signal to the service that we have received an incoming connection.
505          */
506         boolean isValid = mMapService.onConnect(device, BluetoothMapMasInstance.this);
507 
508         if (isValid) {
509             mConnSocket = socket;
510             mAcceptNewConnections = false;
511         }
512 
513         return isValid;
514     }
515 
516     /**
517      * Called when an unrecoverable error occurred in an accept thread. Close down the server
518      * socket, and restart. TODO: Change to message, to call start in correct context.
519      */
520     @Override
onAcceptFailed()521     public synchronized void onAcceptFailed() {
522         mServerSockets = null; // Will cause a new to be created when calling start.
523         if (mShutdown) {
524             error("Failed to accept incoming connection - shutdown");
525             ContentProfileErrorReportUtils.report(
526                     BluetoothProfile.MAP,
527                     BluetoothProtoEnums.BLUETOOTH_MAP_MAS_INSTANCE,
528                     BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR,
529                     2);
530         } else {
531             error("Failed to accept incoming connection - restarting");
532             ContentProfileErrorReportUtils.report(
533                     BluetoothProfile.MAP,
534                     BluetoothProtoEnums.BLUETOOTH_MAP_MAS_INSTANCE,
535                     BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR,
536                     3);
537             startSocketListeners();
538         }
539     }
540 
541     // Per-Instance logging
verbose(String message)542     public void verbose(String message) {
543         Log.v(TAG, "[instance=" + mObjectInstanceId + "] " + message);
544     }
545 
debug(String message)546     public void debug(String message) {
547         Log.d(TAG, "[instance=" + mObjectInstanceId + "] " + message);
548     }
549 
error(String message)550     public void error(String message) {
551         Log.e(TAG, "[instance=" + mObjectInstanceId + "] " + message);
552     }
553 
error(String message, Exception e)554     public void error(String message, Exception e) {
555         Log.e(TAG, "[instance=" + mObjectInstanceId + "] " + message, e);
556     }
557 }
558