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