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