BluetoothMapMasInstance.java revision e6564029f132077c8a4877431a95899db201e506
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*/
15package com.android.bluetooth.map;
16
17import java.io.IOException;
18import java.util.Calendar;
19import java.util.HashMap;
20import java.util.Map;
21import java.util.concurrent.atomic.AtomicLong;
22
23import javax.obex.ServerSession;
24
25import com.android.bluetooth.BluetoothObexTransport;
26import com.android.bluetooth.IObexConnectionHandler;
27import com.android.bluetooth.ObexServerSockets;
28import com.android.bluetooth.map.BluetoothMapContentObserver.Msg;
29import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
30import com.android.bluetooth.sdp.SdpManager;
31
32import android.bluetooth.BluetoothAdapter;
33import android.bluetooth.BluetoothDevice;
34import android.bluetooth.BluetoothServerSocket;
35import android.bluetooth.BluetoothSocket;
36import android.bluetooth.BluetoothUuid;
37import android.content.Context;
38import android.content.Intent;
39import android.os.Handler;
40import android.os.RemoteException;
41import android.util.Log;
42
43public 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     */
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 */
134    protected BluetoothMapMasInstance() {
135        TAG = "BluetoothMapMasInstance" + sInstanceCounter++;
136    }
137
138    @Override
139    public String toString() {
140        return "MasId: " + mMasInstanceId + " Uri:" + mBaseUri + " SMS/MMS:" + mEnableSmsMms;
141    }
142
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     */
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     */
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     */
175    /* package */ void updateSmsMmsConvoListVersionCounter() {
176        mSmsMmsConvoListVersionCounter.incrementAndGet();
177    }
178
179    /* package */ void updateImEmailConvoListVersionCounter() {
180        mImEmailConvoListVersionCounter.incrementAndGet();
181    }
182
183    /* package */ Map<Long, Msg> getMsgListSms() {
184        return mMsgListSms;
185    }
186
187    /* package */ void setMsgListSms(Map<Long, Msg> msgListSms) {
188        mMsgListSms = msgListSms;
189    }
190
191    /* package */ Map<Long, Msg> getMsgListMms() {
192        return mMsgListMms;
193    }
194
195    /* package */ void setMsgListMms(Map<Long, Msg> msgListMms) {
196        mMsgListMms = msgListMms;
197    }
198
199    /* package */ Map<Long, Msg> getMsgListMsg() {
200        return mMsgListMsg;
201    }
202
203    /* package */ void setMsgListMsg(Map<Long, Msg> msgListMsg) {
204        mMsgListMsg = msgListMsg;
205    }
206
207    /* package */ Map<String, BluetoothMapConvoContactElement> getContactList() {
208        return mContactList;
209    }
210
211    /* package */ void setContactList(Map<String, BluetoothMapConvoContactElement> contactList) {
212        mContactList = contactList;
213    }
214
215    HashMap<Long,BluetoothMapConvoListingElement> getSmsMmsConvoList() {
216        return mSmsMmsConvoList;
217    }
218
219    void setSmsMmsConvoList(HashMap<Long,BluetoothMapConvoListingElement> smsMmsConvoList) {
220        mSmsMmsConvoList = smsMmsConvoList;
221    }
222
223    HashMap<Long,BluetoothMapConvoListingElement> getImEmailConvoList() {
224        return mImEmailConvoList;
225    }
226
227    void setImEmailConvoList(HashMap<Long,BluetoothMapConvoListingElement> imEmailConvoList) {
228        mImEmailConvoList = imEmailConvoList;
229    }
230
231    /* package*/
232    int getMasId() {
233        return mMasInstanceId;
234    }
235
236    /* package*/
237    long getDbIdentifier() {
238        return mDbIndetifier.get();
239    }
240
241    /* package*/
242    long getFolderVersionCounter() {
243        return mFolderVersionCounter.get();
244    }
245
246    /* package */
247    long getCombinedConvoListVersionCounter() {
248        long combinedVersionCounter = mSmsMmsConvoListVersionCounter.get();
249        combinedVersionCounter += mImEmailConvoListVersionCounter.get();
250        return combinedVersionCounter;
251    }
252
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     */
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. */
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
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     */
386    public boolean isStarted() {
387        return (mConnSocket != null);
388    }
389
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     */
410    public void restartObexServerSession() {
411        if (D) Log.d(TAG, "MAP Service restartObexServerSession()");
412        startRfcommSocketListener();
413    }
414
415
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
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
437    public void setRemoteFeatureMask(int supportedFeatures) {
438       if(V) Log.v(TAG, "setRemoteFeatureMask : Curr: "+ mRemoteFeatureMask);
439       mRemoteFeatureMask  = supportedFeatures;
440       if (mObserver != null) {
441           mObserver.setObserverRemoteFeatureMask(mRemoteFeatureMask);
442           if(V) Log.v(TAG, "setRemoteFeatureMask : set: " + mRemoteFeatureMask);
443       }
444    }
445
446    public int getRemoteFeatureMask(){
447        return this.mRemoteFeatureMask;
448    }
449
450    @Override
451    public synchronized boolean onConnect(BluetoothDevice device, BluetoothSocket socket) {
452        /* Signal to the service that we have received an incoming connection.
453         */
454        boolean isValid = mMapService.onConnect(device, BluetoothMapMasInstance.this);
455
456        if(isValid == true) {
457            mRemoteDevice = device;
458            mConnSocket = socket;
459        }
460
461        return isValid;
462    }
463
464    /**
465     * Called when an unrecoverable error occurred in an accept thread.
466     * Close down the server socket, and restart.
467     * TODO: Change to message, to call start in correct context.
468     */
469    @Override
470    public synchronized void onAcceptFailed() {
471        mServerSockets = null; // Will cause a new to be created when calling start.
472        if(mShutdown) {
473            Log.e(TAG,"Failed to accept incomming connection - " + "shutdown");
474        } else {
475            Log.e(TAG,"Failed to accept incomming connection - " + "restarting");
476            startRfcommSocketListener();
477        }
478    }
479
480}
481