SapServer.java revision a169df6d977b08b6ad20f38f111665abac44199c
1package com.android.bluetooth.sap;
2
3import android.app.AlarmManager;
4import android.app.Notification;
5import android.app.NotificationChannel;
6import android.app.NotificationManager;
7import android.app.PendingIntent;
8import android.bluetooth.BluetoothSap;
9import android.content.BroadcastReceiver;
10import android.content.Context;
11import android.content.Intent;
12import android.content.IntentFilter;
13import android.hardware.radio.V1_0.ISap;
14import android.os.Handler;
15import android.os.Handler.Callback;
16import android.os.HandlerThread;
17import android.os.Looper;
18import android.os.Message;
19import android.os.RemoteException;
20import android.os.SystemClock;
21import android.os.SystemProperties;
22import android.telephony.TelephonyManager;
23import android.util.Log;
24
25import com.android.bluetooth.R;
26
27import java.io.BufferedInputStream;
28import java.io.BufferedOutputStream;
29import java.io.IOException;
30import java.io.InputStream;
31import java.io.OutputStream;
32import java.util.concurrent.CountDownLatch;
33
34
35/**
36 * The SapServer uses two threads, one for reading messages from the RFCOMM socket and
37 * one for writing the responses.
38 * Incoming requests are decoded in the "main" SapServer, by the decoder build into SapMEssage.
39 * The relevant RIL calls are made from the message handler thread through the rild-bt socket.
40 * The RIL replies are read in the SapRilReceiver, and passed to the SapServer message handler
41 * to be written to the RFCOMM socket.
42 * All writes to the RFCOMM and rild-bt socket must be synchronized, hence to write e.g. an error
43 * response, send a message to the Sap Handler thread. (There are helper functions to do this)
44 * Communication to the RIL is through an intent, and a BroadcastReceiver.
45 */
46public class SapServer extends Thread implements Callback {
47    private static final String TAG = "SapServer";
48    private static final String TAG_HANDLER = "SapServerHandler";
49    public static final boolean DEBUG = SapService.DEBUG;
50    public static final boolean VERBOSE = SapService.VERBOSE;
51
52    private enum SAP_STATE    {
53        DISCONNECTED, CONNECTING, CONNECTING_CALL_ONGOING, CONNECTED,
54        CONNECTED_BUSY, DISCONNECTING;
55    }
56
57    private SAP_STATE mState = SAP_STATE.DISCONNECTED;
58
59    private Context mContext = null;
60    /* RFCOMM socket I/O streams */
61    private BufferedOutputStream mRfcommOut = null;
62    private BufferedInputStream mRfcommIn = null;
63    /* References to the SapRilReceiver object */
64    private SapRilReceiver mRilBtReceiver = null;
65    /* The message handler members */
66    private Handler mSapHandler = null;
67    private HandlerThread mHandlerThread = null;
68    /* Reference to the SAP service - which created this instance of the SAP server */
69    private Handler mSapServiceHandler = null;
70
71    /* flag for when user forces disconnect of rfcomm */
72    private boolean mIsLocalInitDisconnect = false;
73    private CountDownLatch mDeinitSignal = new CountDownLatch(1);
74
75    /* Message ID's handled by the message handler */
76    public static final int SAP_MSG_RFC_REPLY =   0x00;
77    public static final int SAP_MSG_RIL_CONNECT = 0x01;
78    public static final int SAP_MSG_RIL_REQ =     0x02;
79    public static final int SAP_MSG_RIL_IND =     0x03;
80    public static final int SAP_RIL_SOCK_CLOSED = 0x04;
81    public static final int SAP_PROXY_DEAD = 0x05;
82
83    public static final String SAP_DISCONNECT_ACTION =
84            "com.android.bluetooth.sap.action.DISCONNECT_ACTION";
85    public static final String SAP_DISCONNECT_TYPE_EXTRA =
86            "com.android.bluetooth.sap.extra.DISCONNECT_TYPE";
87    public static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth;
88    private static final String SAP_NOTIFICATION_CHANNEL = "sap_notification_channel";
89    public static final int ISAP_GET_SERVICE_DELAY_MILLIS = 3 * 1000;
90    private static final int DISCONNECT_TIMEOUT_IMMEDIATE = 5000; /* ms */
91    private static final int DISCONNECT_TIMEOUT_RFCOMM = 2000; /* ms */
92    private PendingIntent pDiscIntent = null; // Holds a reference to disconnect timeout intents
93
94    /* We store the mMaxMessageSize, as we need a copy of it when the init. sequence completes */
95    private int mMaxMsgSize = 0;
96    /* keep track of the current RIL test mode */
97    private int mTestMode = SapMessage.INVALID_VALUE; // used to set the RIL in test mode
98
99    /**
100     * SapServer constructor
101     * @param serviceHandler The handler to send a SapService.MSG_SERVERSESSION_CLOSE when closing
102     * @param inStream The socket input stream
103     * @param outStream The socket output stream
104     */
105    public SapServer(Handler serviceHandler, Context context, InputStream inStream,
106            OutputStream outStream) {
107        mContext = context;
108        mSapServiceHandler = serviceHandler;
109
110        /* Open in- and output streams */
111        mRfcommIn = new BufferedInputStream(inStream);
112        mRfcommOut = new BufferedOutputStream(outStream);
113
114        /* Register for phone state change and the RIL cfm message */
115        IntentFilter filter = new IntentFilter();
116        filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
117        filter.addAction(SAP_DISCONNECT_ACTION);
118        mIntentReceiver = new SapServerBroadcastReceiver();
119        mContext.registerReceiver(mIntentReceiver, filter);
120    }
121
122    /**
123     * This handles the response from RIL.
124     */
125    private BroadcastReceiver mIntentReceiver;
126    private class SapServerBroadcastReceiver extends BroadcastReceiver {
127        @Override
128        public void onReceive(Context context, Intent intent) {
129            if(intent.getAction().equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
130                if(VERBOSE) Log.i(TAG, "ACTION_PHONE_STATE_CHANGED intent received in state "
131                                        + mState.name()
132                                        + "PhoneState: "
133                                        + intent.getStringExtra(TelephonyManager.EXTRA_STATE));
134                if(mState == SAP_STATE.CONNECTING_CALL_ONGOING) {
135                    String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
136                    if(state != null) {
137                        if(state.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
138                            if(DEBUG) Log.i(TAG, "sending RIL.ACTION_RIL_RECONNECT_OFF_REQ intent");
139                            SapMessage fakeConReq = new SapMessage(SapMessage.ID_CONNECT_REQ);
140                            fakeConReq.setMaxMsgSize(mMaxMsgSize);
141                            onConnectRequest(fakeConReq);
142                        }
143                    }
144                }
145            } else if (intent.getAction().equals(SAP_DISCONNECT_ACTION)) {
146                int disconnectType = intent.getIntExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA,
147                        SapMessage.DISC_GRACEFULL);
148                Log.v(TAG, " - Received SAP_DISCONNECT_ACTION type: " + disconnectType);
149
150                if(disconnectType == SapMessage.DISC_RFCOMM) {
151                    // At timeout we need to close the RFCOMM socket to complete shutdown
152                    shutdown();
153                } else if (mState != SAP_STATE.DISCONNECTED && mState != SAP_STATE.DISCONNECTING) {
154                    // The user pressed disconnect - initiate disconnect sequence.
155                    sendDisconnectInd(disconnectType);
156                }
157            } else {
158                Log.w(TAG, "RIL-BT received unexpected Intent: " + intent.getAction());
159            }
160        }
161    }
162
163    /**
164     * Set RIL driver in test mode - only possible if SapMessage is build with TEST == true
165     * The value set by this function will take effect at the next connect request received
166     * in DISCONNECTED state.
167     * @param testMode Use SapMessage.TEST_MODE_XXX
168     */
169    public void setTestMode(int testMode) {
170        if(SapMessage.TEST) {
171            mTestMode = testMode;
172        }
173    }
174
175    private void sendDisconnectInd(int discType) {
176        if(VERBOSE) Log.v(TAG, "in sendDisconnectInd()");
177
178        if(discType != SapMessage.DISC_FORCED){
179            if(VERBOSE) Log.d(TAG, "Sending  disconnect ("+discType+") indication to client");
180            /* Send disconnect to client */
181            SapMessage discInd = new SapMessage(SapMessage.ID_DISCONNECT_IND);
182            discInd.setDisconnectionType(discType);
183            sendClientMessage(discInd);
184
185            /* Handle local disconnect procedures */
186            if (discType == SapMessage.DISC_GRACEFULL)
187            {
188                /* Update the notification to allow the user to initiate a force disconnect */
189                setNotification(SapMessage.DISC_IMMEDIATE, PendingIntent.FLAG_CANCEL_CURRENT);
190
191            } else if (discType == SapMessage.DISC_IMMEDIATE){
192                /* Request an immediate disconnect, but start a timer to force disconnect if the
193                 * client do not obey our request. */
194                startDisconnectTimer(SapMessage.DISC_FORCED, DISCONNECT_TIMEOUT_IMMEDIATE);
195            }
196
197        } else {
198            SapMessage msg = new SapMessage(SapMessage.ID_DISCONNECT_REQ);
199            /* Force disconnect of RFCOMM - but first we need to clean up. */
200            clearPendingRilResponses(msg);
201
202            /* We simply need to forward to RIL, but not change state to busy - hence send and set
203               message to null. */
204            changeState(SAP_STATE.DISCONNECTING);
205            sendRilThreadMessage(msg);
206            mIsLocalInitDisconnect = true;
207        }
208    }
209
210    void setNotification(int type, int flags)
211    {
212        String title, text, button, ticker;
213        Notification notification;
214        NotificationManager notificationManager =
215                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
216        NotificationChannel notificationChannel = new NotificationChannel(SAP_NOTIFICATION_CHANNEL,
217                mContext.getString(R.string.bluetooth_sap_notif_title),
218                NotificationManager.IMPORTANCE_HIGH);
219        notificationManager.createNotificationChannel(notificationChannel);
220        if(VERBOSE) Log.i(TAG, "setNotification type: " + type);
221        /* For PTS TC_SERVER_DCN_BV_03_I we need to expose the option to send immediate disconnect
222         * without first sending a graceful disconnect.
223         * To enable this option set
224         * bt.sap.pts="true" */
225        String pts_enabled = SystemProperties.get("bt.sap.pts");
226        Boolean pts_test = Boolean.parseBoolean(pts_enabled);
227
228        /* put notification up for the user to be able to disconnect from the client*/
229        Intent sapDisconnectIntent = new Intent(SapServer.SAP_DISCONNECT_ACTION);
230        if (type == SapMessage.DISC_GRACEFULL) {
231            title = mContext.getString(R.string.bluetooth_sap_notif_title);
232            button = mContext.getString(R.string.bluetooth_sap_notif_disconnect_button);
233            text = mContext.getString(R.string.bluetooth_sap_notif_message);
234            ticker = mContext.getString(R.string.bluetooth_sap_notif_ticker);
235        } else {
236            title = mContext.getString(R.string.bluetooth_sap_notif_title);
237            button = mContext.getString(R.string.bluetooth_sap_notif_force_disconnect_button);
238            text = mContext.getString(R.string.bluetooth_sap_notif_disconnecting);
239            ticker = mContext.getString(R.string.bluetooth_sap_notif_ticker);
240        }
241        if (!pts_test) {
242            sapDisconnectIntent.putExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA, type);
243            PendingIntent pIntentDisconnect = PendingIntent.getBroadcast(mContext, type,
244                    sapDisconnectIntent,flags);
245            notification = new Notification.Builder(mContext, SAP_NOTIFICATION_CHANNEL)
246                                   .setOngoing(true)
247                                   .addAction(android.R.drawable.stat_sys_data_bluetooth, button,
248                                           pIntentDisconnect)
249                                   .setContentTitle(title)
250                                   .setTicker(ticker)
251                                   .setContentText(text)
252                                   .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
253                                   .setAutoCancel(false)
254                                   .setPriority(Notification.PRIORITY_MAX)
255                                   .setOnlyAlertOnce(true)
256                                   .build();
257        } else {
258            sapDisconnectIntent.putExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA,
259                    SapMessage.DISC_GRACEFULL);
260            Intent sapForceDisconnectIntent = new Intent(SapServer.SAP_DISCONNECT_ACTION);
261            sapForceDisconnectIntent.putExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA,
262                    SapMessage.DISC_IMMEDIATE);
263            PendingIntent pIntentDisconnect = PendingIntent.getBroadcast(mContext,
264                    SapMessage.DISC_GRACEFULL, sapDisconnectIntent,flags);
265            PendingIntent pIntentForceDisconnect = PendingIntent.getBroadcast(mContext,
266                    SapMessage.DISC_IMMEDIATE, sapForceDisconnectIntent,flags);
267            notification =
268                    new Notification.Builder(mContext, SAP_NOTIFICATION_CHANNEL)
269                            .setOngoing(true)
270                            .addAction(android.R.drawable.stat_sys_data_bluetooth,
271                                    mContext.getString(
272                                            R.string.bluetooth_sap_notif_disconnect_button),
273                                    pIntentDisconnect)
274                            .addAction(android.R.drawable.stat_sys_data_bluetooth,
275                                    mContext.getString(
276                                            R.string.bluetooth_sap_notif_force_disconnect_button),
277                                    pIntentForceDisconnect)
278                            .setContentTitle(title)
279                            .setTicker(ticker)
280                            .setContentText(text)
281                            .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
282                            .setAutoCancel(false)
283                            .setPriority(Notification.PRIORITY_MAX)
284                            .setOnlyAlertOnce(true)
285                            .build();
286        }
287
288        // cannot be set with the builder
289        notification.flags |= Notification.FLAG_NO_CLEAR |Notification.FLAG_ONLY_ALERT_ONCE;
290
291        notificationManager.notify(NOTIFICATION_ID, notification);
292    }
293
294    void clearNotification() {
295        NotificationManager notificationManager =
296                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
297        notificationManager.cancel(SapServer.NOTIFICATION_ID);
298    }
299
300    /**
301     * The SapServer RFCOMM reader thread. Sets up the handler thread and handle
302     * all read from the RFCOMM in-socket. This thread also handle writes to the RIL socket.
303     */
304    @Override
305    public void run() {
306        try {
307            /* SAP is not time critical, hence lowering priority to ensure critical tasks are
308             * executed in a timely manner. */
309            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
310
311            /* Start the SAP message handler thread */
312            mHandlerThread = new HandlerThread("SapServerHandler",
313                    android.os.Process.THREAD_PRIORITY_BACKGROUND);
314            mHandlerThread.start();
315
316            // This will return when the looper is ready
317            Looper sapLooper = mHandlerThread.getLooper();
318            mSapHandler = new Handler(sapLooper, this);
319
320            mRilBtReceiver = new SapRilReceiver(mSapHandler, mSapServiceHandler);
321            boolean done = false;
322            while (!done) {
323                if(VERBOSE) Log.i(TAG, "Waiting for incomming RFCOMM message...");
324                int requestType = mRfcommIn.read();
325                if (VERBOSE) Log.i(TAG, "RFCOMM message read...");
326                if(requestType == -1) {
327                    if (VERBOSE) Log.i(TAG, "requestType == -1");
328                    done = true; // EOF reached
329                } else {
330                    if (VERBOSE) Log.i(TAG, "requestType != -1");
331                    SapMessage msg = SapMessage.readMessage(requestType, mRfcommIn);
332                    /* notify about an incoming message from the BT Client */
333                    SapService.notifyUpdateWakeLock(mSapServiceHandler);
334                    if(msg != null && mState != SAP_STATE.DISCONNECTING)
335                    {
336                        switch (requestType) {
337                        case SapMessage.ID_CONNECT_REQ:
338                            if(VERBOSE) Log.d(TAG, "CONNECT_REQ - MaxMsgSize: "
339                                    + msg.getMaxMsgSize());
340                            onConnectRequest(msg);
341                            msg = null; /* don't send ril connect yet */
342                            break;
343                        case SapMessage.ID_DISCONNECT_REQ: /* No params */
344                            /*
345                             * 1) send RIL_REQUEST_SIM_SAP_DISCONNECT
346                             *      (block for all incoming requests, as they are not
347                             *       allowed, don't even send an error_resp)
348                             * 2) on response disconnect ril socket.
349                             * 3) when disconnected send RIL.ACTION_RIL_RECONNECT_OFF_REQ
350                             * 4) on RIL.ACTION_RIL_RECONNECT_CFM
351                             *       send SAP_DISCONNECT_RESP to client.
352                             * 5) Start RFCOMM disconnect timer
353                             * 6.a) on rfcomm disconnect:
354                             *       cancel timer and initiate cleanup
355                             * 6.b) on rfcomm disc. timeout:
356                             *       close socket-streams and initiate cleanup */
357                            if(VERBOSE) Log.d(TAG, "DISCONNECT_REQ");
358
359                            if (mState ==  SAP_STATE.CONNECTING_CALL_ONGOING) {
360                                Log.d(TAG, "disconnect received when call was ongoing, " +
361                                     "send disconnect response");
362                                changeState(SAP_STATE.DISCONNECTING);
363                                SapMessage reply = new SapMessage(SapMessage.ID_DISCONNECT_RESP);
364                                sendClientMessage(reply);
365                            } else {
366                                clearPendingRilResponses(msg);
367                                changeState(SAP_STATE.DISCONNECTING);
368                                sendRilThreadMessage(msg);
369                                /*cancel the timer for the hard-disconnect intent*/
370                                stopDisconnectTimer();
371                            }
372                            msg = null; // No message needs to be sent to RIL
373                            break;
374                        case SapMessage.ID_POWER_SIM_OFF_REQ: // Fall through
375                        case SapMessage.ID_RESET_SIM_REQ:
376                            /* Forward these to the RIL regardless of the state, and clear any
377                             * pending resp */
378                            clearPendingRilResponses(msg);
379                            break;
380                        case SapMessage.ID_SET_TRANSPORT_PROTOCOL_REQ:
381                            /* The RIL might support more protocols that specified in the SAP,
382                             * allow only the valid values. */
383                            if(mState == SAP_STATE.CONNECTED
384                                    && msg.getTransportProtocol() != 0
385                                    && msg.getTransportProtocol() != 1) {
386                                Log.w(TAG, "Invalid TransportProtocol received:"
387                                        + msg.getTransportProtocol());
388                                // We shall only handle one request at the time, hence return error
389                                SapMessage errorReply = new SapMessage(SapMessage.ID_ERROR_RESP);
390                                sendClientMessage(errorReply);
391                                msg = null;
392                            }
393                            // Fall through
394                        default:
395                            /* Remaining cases just needs to be forwarded to the RIL unless we are
396                             * in busy state. */
397                            if(mState != SAP_STATE.CONNECTED) {
398                                Log.w(TAG, "Message received in STATE != CONNECTED - state = "
399                                        + mState.name());
400                                // We shall only handle one request at the time, hence return error
401                                SapMessage errorReply = new SapMessage(SapMessage.ID_ERROR_RESP);
402                                sendClientMessage(errorReply);
403                                msg = null;
404                            }
405                        }
406
407                        if(msg != null && msg.getSendToRil() == true) {
408                            changeState(SAP_STATE.CONNECTED_BUSY);
409                            sendRilThreadMessage(msg);
410                        }
411
412                    } else {
413                        //An unknown message or in disconnecting state - send error indication
414                        Log.e(TAG, "Unable to parse message.");
415                        SapMessage atrReply = new SapMessage(SapMessage.ID_ERROR_RESP);
416                        sendClientMessage(atrReply);
417                    }
418                }
419            } // end while
420        } catch (NullPointerException e) {
421            Log.w(TAG, e);
422        } catch (IOException e) {
423            /* This is expected during shutdown */
424            Log.i(TAG, "IOException received, this is probably a shutdown signal, cleaning up...");
425        } catch (Exception e) {
426            /* TODO: Change to the needed Exception types when done testing */
427            Log.w(TAG, e);
428        } finally {
429            // Do cleanup even if an exception occurs
430            stopDisconnectTimer();
431            /* In case of e.g. a RFCOMM close while connected:
432             *        - Initiate a FORCED shutdown
433             *        - Wait for RIL deinit to complete
434             */
435            if (mState == SAP_STATE.CONNECTING_CALL_ONGOING) {
436                /* Most likely remote device closed rfcomm, update state */
437                changeState(SAP_STATE.DISCONNECTED);
438            } else if (mState != SAP_STATE.DISCONNECTED) {
439                if(mState != SAP_STATE.DISCONNECTING &&
440                        mIsLocalInitDisconnect != true) {
441                    sendDisconnectInd(SapMessage.DISC_FORCED);
442                }
443                if(DEBUG) Log.i(TAG, "Waiting for deinit to complete");
444                try {
445                    mDeinitSignal.await();
446                } catch (InterruptedException e) {
447                    Log.e(TAG, "Interrupt received while waitinf for de-init to complete", e);
448                }
449            }
450
451            if(mIntentReceiver != null) {
452                mContext.unregisterReceiver(mIntentReceiver);
453                mIntentReceiver = null;
454            }
455            stopDisconnectTimer();
456            clearNotification();
457
458            if(mHandlerThread != null) try {
459                mHandlerThread.quit();
460                mHandlerThread.join();
461                mHandlerThread = null;
462            } catch (InterruptedException e) {}
463            if (mRilBtReceiver != null) {
464                mRilBtReceiver.resetSapProxy();
465                mRilBtReceiver = null;
466            }
467
468            if(mRfcommIn != null) try {
469                if(VERBOSE) Log.i(TAG, "Closing mRfcommIn...");
470                mRfcommIn.close();
471                mRfcommIn = null;
472            } catch (IOException e) {}
473
474            if(mRfcommOut != null) try {
475                if(VERBOSE) Log.i(TAG, "Closing mRfcommOut...");
476                mRfcommOut.close();
477                mRfcommOut = null;
478            } catch (IOException e) {}
479
480            if (mSapServiceHandler != null) {
481                Message msg = Message.obtain(mSapServiceHandler);
482                msg.what = SapService.MSG_SERVERSESSION_CLOSE;
483                msg.sendToTarget();
484                if (DEBUG) Log.d(TAG, "MSG_SERVERSESSION_CLOSE sent out.");
485            }
486            Log.i(TAG, "All done exiting thread...");
487        }
488    }
489
490
491    /**
492     * This function needs to determine:
493     *  - if the maxMsgSize is acceptable - else reply CON_STATUS_ERROR_MAX_MSG_SIZE_UNSUPPORTED
494     *      + new maxMsgSize if too big
495     *  - connect to the RIL-BT socket
496     *  - if a call is ongoing reply CON_STATUS_OK_ONGOING_CALL.
497     *  - if all ok, just respond CON_STATUS_OK.
498     *
499     * @param msg the incoming SapMessage
500     */
501    private void onConnectRequest(SapMessage msg) {
502        SapMessage reply = new SapMessage(SapMessage.ID_CONNECT_RESP);
503
504        if(mState == SAP_STATE.CONNECTING) {
505            /* A connect request might have been rejected because of maxMessageSize negotiation, and
506             * this is a new connect request. Simply forward to RIL, and stay in connecting state.
507             * */
508            reply = null;
509            sendRilMessage(msg);
510            stopDisconnectTimer();
511
512        } else if(mState != SAP_STATE.DISCONNECTED && mState != SAP_STATE.CONNECTING_CALL_ONGOING) {
513            reply.setConnectionStatus(SapMessage.CON_STATUS_ERROR_CONNECTION);
514        } else {
515            // Store the MaxMsgSize for future use
516            mMaxMsgSize = msg.getMaxMsgSize();
517            // All parameters OK, examine if a call is ongoing and start the RIL-BT listener thread
518            if (isCallOngoing() == true) {
519                /* If a call is ongoing we set the state, inform the SAP client and wait for a state
520                 * change intent from the TelephonyManager with state IDLE. */
521                reply.setConnectionStatus(SapMessage.CON_STATUS_OK_ONGOING_CALL);
522            } else {
523                /* no call is ongoing, initiate the connect sequence:
524                 *  1) Start the SapRilReceiver thread (open the rild-bt socket)
525                 *  2) Send a RIL_SIM_SAP_CONNECT request to RILD
526                 *  3) Send a RIL_SIM_RESET request and a connect confirm to the SAP client */
527                changeState(SAP_STATE.CONNECTING);
528                if (mRilBtReceiver != null) {
529                    // Notify the SapServer that we have connected to the SAP service
530                    mRilBtReceiver.sendRilConnectMessage();
531                    // Don't send reply yet
532                    reply = null;
533                } else {
534                    reply = new SapMessage(SapMessage.ID_CONNECT_RESP);
535                    reply.setConnectionStatus(SapMessage.CON_STATUS_ERROR_CONNECTION);
536                    sendClientMessage(reply);
537                }
538            }
539        }
540        if(reply != null)
541            sendClientMessage(reply);
542    }
543
544    private void clearPendingRilResponses(SapMessage msg) {
545        if(mState == SAP_STATE.CONNECTED_BUSY) {
546            msg.setClearRilQueue(true);
547        }
548    }
549    /**
550     * Send RFCOMM message to the Sap Server Handler Thread
551     * @param sapMsg The message to send
552     */
553    private void sendClientMessage(SapMessage sapMsg) {
554        Message newMsg = mSapHandler.obtainMessage(SAP_MSG_RFC_REPLY, sapMsg);
555        mSapHandler.sendMessage(newMsg);
556    }
557
558    /**
559     * Send a RIL message to the SapServer message handler thread
560     * @param sapMsg
561     */
562    private void sendRilThreadMessage(SapMessage sapMsg) {
563        Message newMsg = mSapHandler.obtainMessage(SAP_MSG_RIL_REQ, sapMsg);
564        mSapHandler.sendMessage(newMsg);
565    }
566
567    /**
568     * Examine if a call is ongoing, by asking the telephony manager
569     * @return false if the phone is IDLE (can be used for SAP), true otherwise.
570     */
571    private boolean isCallOngoing() {
572        TelephonyManager tManager =
573                (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
574        if(tManager.getCallState() == TelephonyManager.CALL_STATE_IDLE) {
575            return false;
576        }
577        return true;
578    }
579
580    /**
581     * Change the SAP Server state.
582     * We add thread protection, as we access the state from two threads.
583     * @param newState
584     */
585    private void changeState(SAP_STATE newState) {
586        if(DEBUG) Log.i(TAG_HANDLER,"Changing state from " + mState.name() +
587                                        " to " + newState.name());
588        synchronized (this) {
589            mState = newState;
590        }
591    }
592
593    /*************************************************************************
594     * SAP Server Message Handler Thread Functions
595     *************************************************************************/
596
597    /**
598     * The SapServer message handler thread implements the SAP state machine.
599     *  - Handle all outgoing communication to the out-socket. Either replies from the RIL or direct
600     *    messages send from the SapServe (e.g. connect_resp).
601     *  - Handle all outgoing communication to the RIL-BT socket.
602     *  - Handle all replies from the RIL
603     */
604    @Override
605    public boolean handleMessage(Message msg) {
606        if(VERBOSE) Log.i(TAG_HANDLER,"Handling message (ID: " + msg.what + "): "
607                + getMessageName(msg.what));
608
609        SapMessage sapMsg = null;
610
611        switch(msg.what) {
612        case SAP_MSG_RFC_REPLY:
613            sapMsg = (SapMessage) msg.obj;
614            handleRfcommReply(sapMsg);
615            break;
616        case SAP_MSG_RIL_CONNECT:
617            /* The connection to rild-bt have been established. Store the outStream handle
618             * and send the connect request. */
619            if(mTestMode != SapMessage.INVALID_VALUE) {
620                SapMessage rilTestModeReq = new SapMessage(SapMessage.ID_RIL_SIM_ACCESS_TEST_REQ);
621                rilTestModeReq.setTestMode(mTestMode);
622                sendRilMessage(rilTestModeReq);
623                mTestMode = SapMessage.INVALID_VALUE;
624            }
625            SapMessage rilSapConnect = new SapMessage(SapMessage.ID_CONNECT_REQ);
626            rilSapConnect.setMaxMsgSize(mMaxMsgSize);
627            sendRilMessage(rilSapConnect);
628            break;
629        case SAP_MSG_RIL_REQ:
630            sapMsg = (SapMessage) msg.obj;
631            if(sapMsg != null) {
632                sendRilMessage(sapMsg);
633            }
634            break;
635        case SAP_MSG_RIL_IND:
636            sapMsg = (SapMessage) msg.obj;
637            handleRilInd(sapMsg);
638            break;
639        case SAP_RIL_SOCK_CLOSED:
640            /* The RIL socket was closed unexpectedly, send immediate disconnect indication
641               - close RFCOMM after timeout if no response. */
642            sendDisconnectInd(SapMessage.DISC_IMMEDIATE);
643            startDisconnectTimer(SapMessage.DISC_RFCOMM, DISCONNECT_TIMEOUT_RFCOMM);
644            break;
645        case SAP_PROXY_DEAD:
646            if ((long) msg.obj == mRilBtReceiver.mSapProxyCookie.get()) {
647                mRilBtReceiver.notifyShutdown(); /* Only needed in case of a connection error */
648                mRilBtReceiver.resetSapProxy();
649
650                // todo: rild should be back up since message was sent with a delay. this is a hack.
651                mRilBtReceiver.getSapProxy();
652            }
653        default:
654            /* Message not handled */
655            return false;
656        }
657        return true; // Message handles
658    }
659
660    /**
661     * Close the in/out rfcomm streams, to trigger a shutdown of the SapServer main thread.
662     * Use this after completing the deinit sequence.
663     */
664    private void shutdown() {
665
666        if(DEBUG) Log.i(TAG_HANDLER, "in Shutdown()");
667        try {
668            if (mRfcommOut != null)
669                mRfcommOut.close();
670        } catch (IOException e) {}
671        try {
672            if (mRfcommIn != null)
673                mRfcommIn.close();
674        } catch (IOException e) {}
675        mRfcommIn = null;
676        mRfcommOut = null;
677        stopDisconnectTimer();
678        clearNotification();
679    }
680
681    private void startDisconnectTimer(int discType, int timeMs) {
682
683        stopDisconnectTimer();
684        synchronized (this) {
685            Intent sapDisconnectIntent = new Intent(SapServer.SAP_DISCONNECT_ACTION);
686            sapDisconnectIntent.putExtra(SAP_DISCONNECT_TYPE_EXTRA, discType);
687            AlarmManager alarmManager =
688                    (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
689            pDiscIntent = PendingIntent.getBroadcast(mContext,
690                                                    discType,
691                                                    sapDisconnectIntent,
692                                                    PendingIntent.FLAG_CANCEL_CURRENT);
693            alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
694                    SystemClock.elapsedRealtime() + timeMs, pDiscIntent);
695
696            if(VERBOSE) Log.d(TAG_HANDLER, "Setting alarm for " + timeMs +
697                    " ms to activate disconnect type " + discType);
698        }
699    }
700
701    private void stopDisconnectTimer() {
702        synchronized (this) {
703            if(pDiscIntent != null)
704            {
705                AlarmManager alarmManager =
706                        (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
707                alarmManager.cancel(pDiscIntent);
708                pDiscIntent.cancel();
709                if(VERBOSE) {
710                    Log.d(TAG_HANDLER, "Canceling disconnect alarm");
711                }
712                pDiscIntent = null;
713            }
714        }
715    }
716
717    /**
718     * Here we handle the replies to the SAP client, normally forwarded directly from the RIL.
719     * We do need to handle some of the messages in the SAP profile, hence we look at the messages
720     * here before they go to the client
721     * @param sapMsg the message to send to the SAP client
722     */
723    private void handleRfcommReply(SapMessage sapMsg) {
724        if(sapMsg != null) {
725
726            if(DEBUG) Log.i(TAG_HANDLER, "handleRfcommReply() handling "
727                    + SapMessage.getMsgTypeName(sapMsg.getMsgType()));
728
729            switch(sapMsg.getMsgType()) {
730
731                case SapMessage.ID_CONNECT_RESP:
732                    if(mState == SAP_STATE.CONNECTING_CALL_ONGOING) {
733                        /* Hold back the connect resp if a call was ongoing when the connect req
734                         * was received.
735                         * A response with status call-ongoing was sent, and the connect response
736                         * received from the RIL when call ends must be discarded.
737                         */
738                        if (sapMsg.getConnectionStatus() == SapMessage.CON_STATUS_OK) {
739                            // This is successful connect response from RIL/modem.
740                            changeState(SAP_STATE.CONNECTED);
741                        }
742                        if(VERBOSE) Log.i(TAG, "Hold back the connect resp, as a call was ongoing" +
743                                " when the initial response were sent.");
744                        sapMsg = null;
745                    } else if (sapMsg.getConnectionStatus() == SapMessage.CON_STATUS_OK) {
746                        // This is successful connect response from RIL/modem.
747                        changeState(SAP_STATE.CONNECTED);
748                    } else if(sapMsg.getConnectionStatus() ==
749                            SapMessage.CON_STATUS_OK_ONGOING_CALL) {
750                        changeState(SAP_STATE.CONNECTING_CALL_ONGOING);
751                    } else if(sapMsg.getConnectionStatus() != SapMessage.CON_STATUS_OK) {
752                        /* Most likely the peer will try to connect again, hence we keep the
753                         * connection to RIL open and stay in connecting state.
754                         *
755                         * Start timer to do shutdown if a new connect request is not received in
756                         * time. */
757                        startDisconnectTimer(SapMessage.DISC_FORCED, DISCONNECT_TIMEOUT_RFCOMM);
758                    }
759                    break;
760                case SapMessage.ID_DISCONNECT_RESP:
761                    if(mState == SAP_STATE.DISCONNECTING) {
762                        /* Close the RIL-BT output Stream and signal to SapRilReceiver to close
763                         * down the input stream. */
764                        if(DEBUG) Log.i(TAG, "ID_DISCONNECT_RESP received in SAP_STATE." +
765                                "DISCONNECTING.");
766
767                        /* Send the disconnect resp, and wait for the client to close the Rfcomm,
768                         * but start a timeout timer, just to be sure. Use alarm, to ensure we wake
769                         * the host to close the connection to minimize power consumption. */
770                        SapMessage disconnectResp = new SapMessage(SapMessage.ID_DISCONNECT_RESP);
771                        changeState(SAP_STATE.DISCONNECTED);
772                        sapMsg = disconnectResp;
773                        startDisconnectTimer(SapMessage.DISC_RFCOMM, DISCONNECT_TIMEOUT_RFCOMM);
774                        mDeinitSignal.countDown(); /* Signal deinit complete */
775                    } else { /* DISCONNECTED */
776                        mDeinitSignal.countDown(); /* Signal deinit complete */
777                        if(mIsLocalInitDisconnect == true) {
778                            if(VERBOSE) Log.i(TAG_HANDLER, "This is a FORCED disconnect.");
779                            /* We needed to force the disconnect, hence no hope for the client to
780                             * close the RFCOMM connection, hence we do it here. */
781                            shutdown();
782                            sapMsg = null;
783                        } else {
784                            /* The client must disconnect the RFCOMM, but in case it does not, we
785                             * need to do it.
786                             * We start an alarm, and if it triggers, we must send the
787                             * MSG_SERVERSESSION_CLOSE */
788                            if(VERBOSE) Log.i(TAG_HANDLER, "This is a NORMAL disconnect.");
789                            startDisconnectTimer(SapMessage.DISC_RFCOMM, DISCONNECT_TIMEOUT_RFCOMM);
790                        }
791                    }
792                    break;
793                case SapMessage.ID_STATUS_IND:
794                    /* Some car-kits only "likes" status indication when connected, hence discard
795                     * any arriving outside this state */
796                    if(mState == SAP_STATE.DISCONNECTED ||
797                            mState == SAP_STATE.CONNECTING ||
798                            mState == SAP_STATE.DISCONNECTING) {
799                        sapMsg = null;
800                    }
801                    if (mSapServiceHandler != null && mState == SAP_STATE.CONNECTED) {
802                        Message msg = Message.obtain(mSapServiceHandler);
803                        msg.what = SapService.MSG_CHANGE_STATE;
804                        msg.arg1 = BluetoothSap.STATE_CONNECTED;
805                        msg.sendToTarget();
806                        setNotification(SapMessage.DISC_GRACEFULL, 0);
807                        if (DEBUG) Log.d(TAG, "MSG_CHANGE_STATE sent out.");
808                    }
809                    break;
810                default:
811                // Nothing special, just send the message
812            }
813        }
814
815        /* Update state variable based on the number of pending commands. We are only able to
816         * handle one request at the time, except from disconnect, sim off and sim reset.
817         * Hence if one of these are received while in busy state, we might have a crossing
818         * response, hence we must stay in BUSY state if we have an ongoing RIL request. */
819        if(mState == SAP_STATE.CONNECTED_BUSY) {
820            if(SapMessage.getNumPendingRilMessages() == 0) {
821                changeState(SAP_STATE.CONNECTED);
822            }
823        }
824
825        // This is the default case - just send the message to the SAP client.
826        if(sapMsg != null)
827            sendReply(sapMsg);
828    }
829
830    private void handleRilInd(SapMessage sapMsg) {
831        if(sapMsg == null)
832            return;
833
834        switch(sapMsg.getMsgType()) {
835        case SapMessage.ID_DISCONNECT_IND:
836        {
837            if(mState != SAP_STATE.DISCONNECTED && mState != SAP_STATE.DISCONNECTING){
838                /* we only send disconnect indication to the client if we are actually connected*/
839                SapMessage reply = new SapMessage(SapMessage.ID_DISCONNECT_IND);
840                reply.setDisconnectionType(sapMsg.getDisconnectionType()) ;
841                sendClientMessage(reply);
842            } else {
843                /* TODO: This was introduced to handle disconnect indication from RIL */
844                sendDisconnectInd(sapMsg.getDisconnectionType());
845            }
846            break;
847        }
848
849        default:
850            if(DEBUG) Log.w(TAG_HANDLER,"Unhandled message - type: "
851                    + SapMessage.getMsgTypeName(sapMsg.getMsgType()));
852        }
853    }
854
855    /**
856     * This is only to be called from the handlerThread, else use sendRilThreadMessage();
857     * @param sapMsg
858     */
859    private void sendRilMessage(SapMessage sapMsg) {
860        if(VERBOSE) Log.i(TAG_HANDLER, "sendRilMessage() - "
861                + SapMessage.getMsgTypeName(sapMsg.getMsgType()));
862
863        Log.d(TAG_HANDLER, "sendRilMessage: calling getSapProxy");
864        synchronized (mRilBtReceiver.getSapProxyLock()) {
865            ISap sapProxy = mRilBtReceiver.getSapProxy();
866            if (sapProxy == null) {
867                Log.e(TAG_HANDLER,
868                        "sendRilMessage: Unable to send message to RIL; sapProxy is null");
869                sendClientMessage(new SapMessage(SapMessage.ID_ERROR_RESP));
870                return;
871            }
872
873            try {
874                sapMsg.send(sapProxy);
875                if (VERBOSE) {
876                    Log.d(TAG_HANDLER, "sendRilMessage: sapMsg.callISapReq called successfully");
877                }
878            } catch (IllegalArgumentException e) {
879                Log.e(TAG_HANDLER, "sendRilMessage: IllegalArgumentException", e);
880                sendClientMessage(new SapMessage(SapMessage.ID_ERROR_RESP));
881            } catch (RemoteException | RuntimeException e) {
882                Log.e(TAG_HANDLER, "sendRilMessage: Unable to send message to RIL: " + e);
883                sendClientMessage(new SapMessage(SapMessage.ID_ERROR_RESP));
884                mRilBtReceiver.notifyShutdown(); /* Only needed in case of a connection error */
885                mRilBtReceiver.resetSapProxy();
886            }
887        }
888    }
889
890    /**
891     * Only call this from the sapHandler thread.
892     */
893    private void sendReply(SapMessage msg) {
894        if(VERBOSE) Log.i(TAG_HANDLER, "sendReply() RFCOMM - "
895                + SapMessage.getMsgTypeName(msg.getMsgType()));
896        if(mRfcommOut != null) { // Needed to handle brutal shutdown from car-kit and out of range
897            try {
898                msg.write(mRfcommOut);
899                mRfcommOut.flush();
900            } catch (IOException e) {
901                Log.w(TAG_HANDLER, e);
902                /* As we cannot write to the rfcomm channel we are disconnected.
903                   Shutdown and prepare for a new connect. */
904            }
905        }
906    }
907
908    private static String getMessageName(int messageId) {
909        switch (messageId) {
910        case SAP_MSG_RFC_REPLY:
911            return "SAP_MSG_REPLY";
912        case SAP_MSG_RIL_CONNECT:
913            return "SAP_MSG_RIL_CONNECT";
914        case SAP_MSG_RIL_REQ:
915            return "SAP_MSG_RIL_REQ";
916        case SAP_MSG_RIL_IND:
917            return "SAP_MSG_RIL_IND";
918        default:
919            return "Unknown message ID";
920        }
921    }
922
923}
924