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