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