1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.stk;
18
19import android.app.AlertDialog;
20import android.app.Notification;
21import android.app.NotificationManager;
22import android.app.PendingIntent;
23import android.app.Service;
24import android.content.Context;
25import android.content.DialogInterface;
26import android.content.Intent;
27import android.graphics.Bitmap;
28import android.graphics.BitmapFactory;
29import android.net.Uri;
30import android.os.Bundle;
31import android.os.Handler;
32import android.os.IBinder;
33import android.os.Looper;
34import android.os.Message;
35import android.telephony.TelephonyManager;
36import android.view.Gravity;
37import android.view.LayoutInflater;
38import android.view.View;
39import android.view.Window;
40import android.view.WindowManager;
41import android.widget.ImageView;
42import android.widget.RemoteViews;
43import android.widget.TextView;
44import android.widget.Toast;
45
46import com.android.internal.telephony.cat.AppInterface;
47import com.android.internal.telephony.cat.Menu;
48import com.android.internal.telephony.cat.Item;
49import com.android.internal.telephony.cat.Input;
50import com.android.internal.telephony.cat.ResultCode;
51import com.android.internal.telephony.cat.CatCmdMessage;
52import com.android.internal.telephony.cat.CatCmdMessage.BrowserSettings;
53import com.android.internal.telephony.cat.CatLog;
54import com.android.internal.telephony.cat.CatResponseMessage;
55import com.android.internal.telephony.cat.TextMessage;
56import com.android.internal.telephony.uicc.IccRefreshResponse;
57import com.android.internal.telephony.uicc.IccCardStatus.CardState;
58
59import java.util.LinkedList;
60
61/**
62 * SIM toolkit application level service. Interacts with Telephopny messages,
63 * application's launch and user input from STK UI elements.
64 *
65 */
66public class StkAppService extends Service implements Runnable {
67
68    // members
69    private volatile Looper mServiceLooper;
70    private volatile ServiceHandler mServiceHandler;
71    private AppInterface mStkService;
72    private Context mContext = null;
73    private CatCmdMessage mMainCmd = null;
74    private CatCmdMessage mCurrentCmd = null;
75    private Menu mCurrentMenu = null;
76    private String lastSelectedItem = null;
77    private boolean mMenuIsVisibile = false;
78    private boolean responseNeeded = true;
79    private boolean mCmdInProgress = false;
80    private NotificationManager mNotificationManager = null;
81    private LinkedList<DelayedCmd> mCmdsQ = null;
82    private boolean launchBrowser = false;
83    private BrowserSettings mBrowserSettings = null;
84    static StkAppService sInstance = null;
85
86    // Used for setting FLAG_ACTIVITY_NO_USER_ACTION when
87    // creating an intent.
88    private enum InitiatedByUserAction {
89        yes,            // The action was started via a user initiated action
90        unknown,        // Not known for sure if user initated the action
91    }
92
93    // constants
94    static final String OPCODE = "op";
95    static final String CMD_MSG = "cmd message";
96    static final String RES_ID = "response id";
97    static final String MENU_SELECTION = "menu selection";
98    static final String INPUT = "input";
99    static final String HELP = "help";
100    static final String CONFIRMATION = "confirm";
101    static final String CHOICE = "choice";
102
103    // operations ids for different service functionality.
104    static final int OP_CMD = 1;
105    static final int OP_RESPONSE = 2;
106    static final int OP_LAUNCH_APP = 3;
107    static final int OP_END_SESSION = 4;
108    static final int OP_BOOT_COMPLETED = 5;
109    private static final int OP_DELAYED_MSG = 6;
110    static final int OP_CARD_STATUS_CHANGED = 7;
111
112    // Response ids
113    static final int RES_ID_MENU_SELECTION = 11;
114    static final int RES_ID_INPUT = 12;
115    static final int RES_ID_CONFIRM = 13;
116    static final int RES_ID_DONE = 14;
117    static final int RES_ID_CHOICE = 15;
118
119    static final int RES_ID_TIMEOUT = 20;
120    static final int RES_ID_BACKWARD = 21;
121    static final int RES_ID_END_SESSION = 22;
122    static final int RES_ID_EXIT = 23;
123
124    static final int YES = 1;
125    static final int NO = 0;
126
127    private static final String PACKAGE_NAME = "com.android.stk";
128    private static final String MENU_ACTIVITY_NAME =
129                                        PACKAGE_NAME + ".StkMenuActivity";
130    private static final String INPUT_ACTIVITY_NAME =
131                                        PACKAGE_NAME + ".StkInputActivity";
132
133    // Notification id used to display Idle Mode text in NotificationManager.
134    private static final int STK_NOTIFICATION_ID = 333;
135
136    // Inner class used for queuing telephony messages (proactive commands,
137    // session end) while the service is busy processing a previous message.
138    private class DelayedCmd {
139        // members
140        int id;
141        CatCmdMessage msg;
142
143        DelayedCmd(int id, CatCmdMessage msg) {
144            this.id = id;
145            this.msg = msg;
146        }
147    }
148
149    @Override
150    public void onCreate() {
151        // Initialize members
152        mCmdsQ = new LinkedList<DelayedCmd>();
153        Thread serviceThread = new Thread(null, this, "Stk App Service");
154        serviceThread.start();
155        mContext = getBaseContext();
156        mNotificationManager = (NotificationManager) mContext
157                .getSystemService(Context.NOTIFICATION_SERVICE);
158        sInstance = this;
159    }
160
161    @Override
162    public void onStart(Intent intent, int startId) {
163
164        mStkService = com.android.internal.telephony.cat.CatService
165                .getInstance();
166
167        if (mStkService == null) {
168            stopSelf();
169            CatLog.d(this, " Unable to get Service handle");
170            StkAppInstaller.unInstall(mContext);
171            return;
172        }
173
174        waitForLooper();
175        // onStart() method can be passed a null intent
176        // TODO: replace onStart() with onStartCommand()
177        if (intent == null) {
178            return;
179        }
180
181        Bundle args = intent.getExtras();
182
183        if (args == null) {
184            return;
185        }
186
187        Message msg = mServiceHandler.obtainMessage();
188        msg.arg1 = args.getInt(OPCODE);
189        switch(msg.arg1) {
190        case OP_CMD:
191            msg.obj = args.getParcelable(CMD_MSG);
192            break;
193        case OP_RESPONSE:
194        case OP_CARD_STATUS_CHANGED:
195            msg.obj = args;
196            /* falls through */
197        case OP_LAUNCH_APP:
198        case OP_END_SESSION:
199        case OP_BOOT_COMPLETED:
200            break;
201        default:
202            return;
203        }
204        mServiceHandler.sendMessage(msg);
205    }
206
207    @Override
208    public void onDestroy() {
209        waitForLooper();
210        mServiceLooper.quit();
211    }
212
213    @Override
214    public IBinder onBind(Intent intent) {
215        return null;
216    }
217
218    public void run() {
219        Looper.prepare();
220
221        mServiceLooper = Looper.myLooper();
222        mServiceHandler = new ServiceHandler();
223
224        Looper.loop();
225    }
226
227    /*
228     * Package api used by StkMenuActivity to indicate if its on the foreground.
229     */
230    void indicateMenuVisibility(boolean visibility) {
231        mMenuIsVisibile = visibility;
232    }
233
234    /*
235     * Package api used by StkMenuActivity to get its Menu parameter.
236     */
237    Menu getMenu() {
238        return mCurrentMenu;
239    }
240
241    /*
242     * Package api used by UI Activities and Dialogs to communicate directly
243     * with the service to deliver state information and parameters.
244     */
245    static StkAppService getInstance() {
246        return sInstance;
247    }
248
249    private void waitForLooper() {
250        while (mServiceHandler == null) {
251            synchronized (this) {
252                try {
253                    wait(100);
254                } catch (InterruptedException e) {
255                }
256            }
257        }
258    }
259
260    private final class ServiceHandler extends Handler {
261        @Override
262        public void handleMessage(Message msg) {
263            int opcode = msg.arg1;
264
265            switch (opcode) {
266            case OP_LAUNCH_APP:
267                if (mMainCmd == null) {
268                    // nothing todo when no SET UP MENU command didn't arrive.
269                    return;
270                }
271                launchMenuActivity(null);
272                break;
273            case OP_CMD:
274                CatCmdMessage cmdMsg = (CatCmdMessage) msg.obj;
275                // There are two types of commands:
276                // 1. Interactive - user's response is required.
277                // 2. Informative - display a message, no interaction with the user.
278                //
279                // Informative commands can be handled immediately without any delay.
280                // Interactive commands can't override each other. So if a command
281                // is already in progress, we need to queue the next command until
282                // the user has responded or a timeout expired.
283                if (!isCmdInteractive(cmdMsg)) {
284                    handleCmd(cmdMsg);
285                } else {
286                    if (!mCmdInProgress) {
287                        mCmdInProgress = true;
288                        handleCmd((CatCmdMessage) msg.obj);
289                    } else {
290                        mCmdsQ.addLast(new DelayedCmd(OP_CMD,
291                                (CatCmdMessage) msg.obj));
292                    }
293                }
294                break;
295            case OP_RESPONSE:
296                if (responseNeeded) {
297                    handleCmdResponse((Bundle) msg.obj);
298                }
299                // call delayed commands if needed.
300                if (mCmdsQ.size() != 0) {
301                    callDelayedMsg();
302                } else {
303                    mCmdInProgress = false;
304                }
305                // reset response needed state var to its original value.
306                responseNeeded = true;
307                break;
308            case OP_END_SESSION:
309                if (!mCmdInProgress) {
310                    mCmdInProgress = true;
311                    handleSessionEnd();
312                } else {
313                    mCmdsQ.addLast(new DelayedCmd(OP_END_SESSION, null));
314                }
315                break;
316            case OP_BOOT_COMPLETED:
317                CatLog.d(this, "OP_BOOT_COMPLETED");
318                if (mMainCmd == null) {
319                    StkAppInstaller.unInstall(mContext);
320                }
321                break;
322            case OP_DELAYED_MSG:
323                handleDelayedCmd();
324                break;
325            case OP_CARD_STATUS_CHANGED:
326                CatLog.d(this, "Card/Icc Status change received");
327                handleCardStatusChangeAndIccRefresh((Bundle) msg.obj);
328                break;
329            }
330        }
331
332        private void handleCardStatusChangeAndIccRefresh(Bundle args) {
333            boolean cardStatus = args.getBoolean(AppInterface.CARD_STATUS);
334
335            CatLog.d(this, "CardStatus: " + cardStatus);
336            if (cardStatus == false) {
337                CatLog.d(this, "CARD is ABSENT");
338                // Uninstall STKAPP, Clear Idle text, Stop StkAppService
339                StkAppInstaller.unInstall(mContext);
340                mNotificationManager.cancel(STK_NOTIFICATION_ID);
341                stopSelf();
342            } else {
343                IccRefreshResponse state = new IccRefreshResponse();
344                state.refreshResult = args.getInt(AppInterface.REFRESH_RESULT);
345
346                CatLog.d(this, "Icc Refresh Result: "+ state.refreshResult);
347                if ((state.refreshResult == IccRefreshResponse.REFRESH_RESULT_INIT) ||
348                    (state.refreshResult == IccRefreshResponse.REFRESH_RESULT_RESET)) {
349                    // Clear Idle Text
350                    mNotificationManager.cancel(STK_NOTIFICATION_ID);
351                }
352
353                if (state.refreshResult == IccRefreshResponse.REFRESH_RESULT_RESET) {
354                    // Uninstall STkmenu
355                    StkAppInstaller.unInstall(mContext);
356                    mCurrentMenu = null;
357                    mMainCmd = null;
358                }
359            }
360        }
361    }
362
363    private boolean isCmdInteractive(CatCmdMessage cmd) {
364        switch (cmd.getCmdType()) {
365        case SEND_DTMF:
366        case SEND_SMS:
367        case SEND_SS:
368        case SEND_USSD:
369        case SET_UP_IDLE_MODE_TEXT:
370        case SET_UP_MENU:
371        case CLOSE_CHANNEL:
372        case RECEIVE_DATA:
373        case SEND_DATA:
374            return false;
375        }
376
377        return true;
378    }
379
380    private void handleDelayedCmd() {
381        if (mCmdsQ.size() != 0) {
382            DelayedCmd cmd = mCmdsQ.poll();
383            switch (cmd.id) {
384            case OP_CMD:
385                handleCmd(cmd.msg);
386                break;
387            case OP_END_SESSION:
388                handleSessionEnd();
389                break;
390            }
391        }
392    }
393
394    private void callDelayedMsg() {
395        Message msg = mServiceHandler.obtainMessage();
396        msg.arg1 = OP_DELAYED_MSG;
397        mServiceHandler.sendMessage(msg);
398    }
399
400    private void handleSessionEnd() {
401        mCurrentCmd = mMainCmd;
402        lastSelectedItem = null;
403        // In case of SET UP MENU command which removed the app, don't
404        // update the current menu member.
405        if (mCurrentMenu != null && mMainCmd != null) {
406            mCurrentMenu = mMainCmd.getMenu();
407        }
408        if (mMenuIsVisibile) {
409            launchMenuActivity(null);
410        }
411        if (mCmdsQ.size() != 0) {
412            callDelayedMsg();
413        } else {
414            mCmdInProgress = false;
415        }
416        // In case a launch browser command was just confirmed, launch that url.
417        if (launchBrowser) {
418            launchBrowser = false;
419            launchBrowser(mBrowserSettings);
420        }
421    }
422
423    private void handleCmd(CatCmdMessage cmdMsg) {
424        if (cmdMsg == null) {
425            return;
426        }
427        // save local reference for state tracking.
428        mCurrentCmd = cmdMsg;
429        boolean waitForUsersResponse = true;
430
431        CatLog.d(this, cmdMsg.getCmdType().name());
432        switch (cmdMsg.getCmdType()) {
433        case DISPLAY_TEXT:
434            TextMessage msg = cmdMsg.geTextMessage();
435            responseNeeded = msg.responseNeeded;
436            waitForUsersResponse = msg.responseNeeded;
437            if (lastSelectedItem != null) {
438                msg.title = lastSelectedItem;
439            } else if (mMainCmd != null){
440                msg.title = mMainCmd.getMenu().title;
441            } else {
442                // TODO: get the carrier name from the SIM
443                msg.title = "";
444            }
445            launchTextDialog();
446            break;
447        case SELECT_ITEM:
448            mCurrentMenu = cmdMsg.getMenu();
449            launchMenuActivity(cmdMsg.getMenu());
450            break;
451        case SET_UP_MENU:
452            mMainCmd = mCurrentCmd;
453            mCurrentMenu = cmdMsg.getMenu();
454            if (removeMenu()) {
455                CatLog.d(this, "Uninstall App");
456                mCurrentMenu = null;
457                StkAppInstaller.unInstall(mContext);
458            } else {
459                CatLog.d(this, "Install App");
460                StkAppInstaller.install(mContext);
461            }
462            if (mMenuIsVisibile) {
463                launchMenuActivity(null);
464            }
465            break;
466        case GET_INPUT:
467        case GET_INKEY:
468            launchInputActivity();
469            break;
470        case SET_UP_IDLE_MODE_TEXT:
471            waitForUsersResponse = false;
472            launchIdleText();
473            break;
474        case SEND_DTMF:
475        case SEND_SMS:
476        case SEND_SS:
477        case SEND_USSD:
478            waitForUsersResponse = false;
479            launchEventMessage();
480            break;
481        case LAUNCH_BROWSER:
482            launchConfirmationDialog(mCurrentCmd.geTextMessage());
483            break;
484        case SET_UP_CALL:
485            launchConfirmationDialog(mCurrentCmd.getCallSettings().confirmMsg);
486            break;
487        case PLAY_TONE:
488            launchToneDialog();
489            break;
490        case OPEN_CHANNEL:
491            launchOpenChannelDialog();
492            break;
493        case CLOSE_CHANNEL:
494        case RECEIVE_DATA:
495        case SEND_DATA:
496            TextMessage m = mCurrentCmd.geTextMessage();
497
498            if ((m != null) && (m.text == null)) {
499                switch(cmdMsg.getCmdType()) {
500                case CLOSE_CHANNEL:
501                    m.text = getResources().getString(R.string.default_close_channel_msg);
502                    break;
503                case RECEIVE_DATA:
504                    m.text = getResources().getString(R.string.default_receive_data_msg);
505                    break;
506                case SEND_DATA:
507                    m.text = getResources().getString(R.string.default_send_data_msg);
508                    break;
509                }
510            }
511            /*
512             * Display indication in the form of a toast to the user if required.
513             */
514            launchEventMessage();
515            break;
516        }
517
518        if (!waitForUsersResponse) {
519            if (mCmdsQ.size() != 0) {
520                callDelayedMsg();
521            } else {
522                mCmdInProgress = false;
523            }
524        }
525    }
526
527    private void handleCmdResponse(Bundle args) {
528        if (mCurrentCmd == null) {
529            return;
530        }
531        if (mStkService == null) {
532            mStkService = com.android.internal.telephony.cat.CatService.getInstance();
533            if (mStkService == null) {
534                // This should never happen (we should be responding only to a message
535                // that arrived from StkService). It has to exist by this time
536                throw new RuntimeException("mStkService is null when we need to send response");
537            }
538        }
539
540        CatResponseMessage resMsg = new CatResponseMessage(mCurrentCmd);
541
542        // set result code
543        boolean helpRequired = args.getBoolean(HELP, false);
544        boolean confirmed    = false;
545
546        switch(args.getInt(RES_ID)) {
547        case RES_ID_MENU_SELECTION:
548            CatLog.d(this, "RES_ID_MENU_SELECTION");
549            int menuSelection = args.getInt(MENU_SELECTION);
550            switch(mCurrentCmd.getCmdType()) {
551            case SET_UP_MENU:
552            case SELECT_ITEM:
553                lastSelectedItem = getItemName(menuSelection);
554                if (helpRequired) {
555                    resMsg.setResultCode(ResultCode.HELP_INFO_REQUIRED);
556                } else {
557                    resMsg.setResultCode(ResultCode.OK);
558                }
559                resMsg.setMenuSelection(menuSelection);
560                break;
561            }
562            break;
563        case RES_ID_INPUT:
564            CatLog.d(this, "RES_ID_INPUT");
565            String input = args.getString(INPUT);
566            Input cmdInput = mCurrentCmd.geInput();
567            if (cmdInput != null && cmdInput.yesNo) {
568                boolean yesNoSelection = input
569                        .equals(StkInputActivity.YES_STR_RESPONSE);
570                resMsg.setYesNo(yesNoSelection);
571            } else {
572                if (helpRequired) {
573                    resMsg.setResultCode(ResultCode.HELP_INFO_REQUIRED);
574                } else {
575                    resMsg.setResultCode(ResultCode.OK);
576                    resMsg.setInput(input);
577                }
578            }
579            break;
580        case RES_ID_CONFIRM:
581            CatLog.d(this, "RES_ID_CONFIRM");
582            confirmed = args.getBoolean(CONFIRMATION);
583            switch (mCurrentCmd.getCmdType()) {
584            case DISPLAY_TEXT:
585                resMsg.setResultCode(confirmed ? ResultCode.OK
586                        : ResultCode.UICC_SESSION_TERM_BY_USER);
587                break;
588            case LAUNCH_BROWSER:
589                resMsg.setResultCode(confirmed ? ResultCode.OK
590                        : ResultCode.UICC_SESSION_TERM_BY_USER);
591                if (confirmed) {
592                    launchBrowser = true;
593                    mBrowserSettings = mCurrentCmd.getBrowserSettings();
594                }
595                break;
596            case SET_UP_CALL:
597                resMsg.setResultCode(ResultCode.OK);
598                resMsg.setConfirmation(confirmed);
599                if (confirmed) {
600                    launchEventMessage(mCurrentCmd.getCallSettings().callMsg);
601                }
602                break;
603            }
604            break;
605        case RES_ID_DONE:
606            resMsg.setResultCode(ResultCode.OK);
607            break;
608        case RES_ID_BACKWARD:
609            CatLog.d(this, "RES_ID_BACKWARD");
610            resMsg.setResultCode(ResultCode.BACKWARD_MOVE_BY_USER);
611            break;
612        case RES_ID_END_SESSION:
613            CatLog.d(this, "RES_ID_END_SESSION");
614            resMsg.setResultCode(ResultCode.UICC_SESSION_TERM_BY_USER);
615            break;
616        case RES_ID_TIMEOUT:
617            CatLog.d(this, "RES_ID_TIMEOUT");
618            // GCF test-case 27.22.4.1.1 Expected Sequence 1.5 (DISPLAY TEXT,
619            // Clear message after delay, successful) expects result code OK.
620            // If the command qualifier specifies no user response is required
621            // then send OK instead of NO_RESPONSE_FROM_USER
622            if ((mCurrentCmd.getCmdType().value() == AppInterface.CommandType.DISPLAY_TEXT
623                    .value())
624                    && (mCurrentCmd.geTextMessage().userClear == false)) {
625                resMsg.setResultCode(ResultCode.OK);
626            } else {
627                resMsg.setResultCode(ResultCode.NO_RESPONSE_FROM_USER);
628            }
629            break;
630        case RES_ID_CHOICE:
631            int choice = args.getInt(CHOICE);
632            CatLog.d(this, "User Choice=" + choice);
633            switch (choice) {
634                case YES:
635                    resMsg.setResultCode(ResultCode.OK);
636                    confirmed = true;
637                    break;
638                case NO:
639                    resMsg.setResultCode(ResultCode.USER_NOT_ACCEPT);
640                    break;
641            }
642
643            if (mCurrentCmd.getCmdType().value() == AppInterface.CommandType.OPEN_CHANNEL
644                    .value()) {
645                resMsg.setConfirmation(confirmed);
646            }
647            break;
648
649        default:
650            CatLog.d(this, "Unknown result id");
651            return;
652        }
653        mStkService.onCmdResponse(resMsg);
654    }
655
656    /**
657     * Returns 0 or FLAG_ACTIVITY_NO_USER_ACTION, 0 means the user initiated the action.
658     *
659     * @param userAction If the userAction is yes then we always return 0 otherwise
660     * mMenuIsVisible is used to determine what to return. If mMenuIsVisible is true
661     * then we are the foreground app and we'll return 0 as from our perspective a
662     * user action did cause. If it's false than we aren't the foreground app and
663     * FLAG_ACTIVITY_NO_USER_ACTION is returned.
664     *
665     * @return 0 or FLAG_ACTIVITY_NO_USER_ACTION
666     */
667    private int getFlagActivityNoUserAction(InitiatedByUserAction userAction) {
668        return ((userAction == InitiatedByUserAction.yes) | mMenuIsVisibile) ?
669                                                    0 : Intent.FLAG_ACTIVITY_NO_USER_ACTION;
670    }
671
672    private void launchMenuActivity(Menu menu) {
673        Intent newIntent = new Intent(Intent.ACTION_VIEW);
674        newIntent.setClassName(PACKAGE_NAME, MENU_ACTIVITY_NAME);
675        int intentFlags = Intent.FLAG_ACTIVITY_NEW_TASK
676                | Intent.FLAG_ACTIVITY_CLEAR_TOP;
677        if (menu == null) {
678            // We assume this was initiated by the user pressing the tool kit icon
679            intentFlags |= getFlagActivityNoUserAction(InitiatedByUserAction.yes);
680
681            newIntent.putExtra("STATE", StkMenuActivity.STATE_MAIN);
682        } else {
683            // We don't know and we'll let getFlagActivityNoUserAction decide.
684            intentFlags |= getFlagActivityNoUserAction(InitiatedByUserAction.unknown);
685
686            newIntent.putExtra("STATE", StkMenuActivity.STATE_SECONDARY);
687        }
688        newIntent.setFlags(intentFlags);
689        mContext.startActivity(newIntent);
690    }
691
692    private void launchInputActivity() {
693        Intent newIntent = new Intent(Intent.ACTION_VIEW);
694        newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
695                            | getFlagActivityNoUserAction(InitiatedByUserAction.unknown));
696        newIntent.setClassName(PACKAGE_NAME, INPUT_ACTIVITY_NAME);
697        newIntent.putExtra("INPUT", mCurrentCmd.geInput());
698        mContext.startActivity(newIntent);
699    }
700
701    private void launchTextDialog() {
702        Intent newIntent = new Intent(this, StkDialogActivity.class);
703        newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
704                | Intent.FLAG_ACTIVITY_CLEAR_TOP
705                | Intent.FLAG_ACTIVITY_NO_HISTORY
706                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
707                | getFlagActivityNoUserAction(InitiatedByUserAction.unknown));
708        newIntent.putExtra("TEXT", mCurrentCmd.geTextMessage());
709        startActivity(newIntent);
710    }
711
712    private void launchEventMessage() {
713        launchEventMessage(mCurrentCmd.geTextMessage());
714    }
715
716    private void launchEventMessage(TextMessage msg) {
717        if (msg == null || msg.text == null) {
718            return;
719        }
720        Toast toast = new Toast(mContext.getApplicationContext());
721        LayoutInflater inflate = (LayoutInflater) mContext
722                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
723        View v = inflate.inflate(R.layout.stk_event_msg, null);
724        TextView tv = (TextView) v
725                .findViewById(com.android.internal.R.id.message);
726        ImageView iv = (ImageView) v
727                .findViewById(com.android.internal.R.id.icon);
728        if (msg.icon != null) {
729            iv.setImageBitmap(msg.icon);
730        } else {
731            iv.setVisibility(View.GONE);
732        }
733        if (!msg.iconSelfExplanatory) {
734            tv.setText(msg.text);
735        }
736
737        toast.setView(v);
738        toast.setDuration(Toast.LENGTH_LONG);
739        toast.setGravity(Gravity.BOTTOM, 0, 0);
740        toast.show();
741    }
742
743    private void launchConfirmationDialog(TextMessage msg) {
744        msg.title = lastSelectedItem;
745        Intent newIntent = new Intent(this, StkDialogActivity.class);
746        newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
747                | Intent.FLAG_ACTIVITY_NO_HISTORY
748                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
749                | getFlagActivityNoUserAction(InitiatedByUserAction.unknown));
750        newIntent.putExtra("TEXT", msg);
751        startActivity(newIntent);
752    }
753
754    private void launchBrowser(BrowserSettings settings) {
755        if (settings == null) {
756            return;
757        }
758
759        Intent intent = null;
760        Uri data = null;
761
762        if (settings.url != null) {
763            CatLog.d(this, "settings.url = " + settings.url);
764            if ((settings.url.startsWith("http://") || (settings.url.startsWith("https://")))) {
765                data = Uri.parse(settings.url);
766            } else {
767                String modifiedUrl = "http://" + settings.url;
768                CatLog.d(this, "modifiedUrl = " + modifiedUrl);
769                data = Uri.parse(modifiedUrl);
770            }
771        }
772        if (data != null) {
773            intent = new Intent(Intent.ACTION_VIEW);
774            intent.setData(data);
775        } else {
776            // if the command did not contain a URL,
777            // launch the browser to the default homepage.
778            CatLog.d(this, "launch browser with default URL ");
779            intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN,
780                    Intent.CATEGORY_APP_BROWSER);
781        }
782
783        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
784        switch (settings.mode) {
785        case USE_EXISTING_BROWSER:
786            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
787            break;
788        case LAUNCH_NEW_BROWSER:
789            intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
790            break;
791        case LAUNCH_IF_NOT_ALREADY_LAUNCHED:
792            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
793            break;
794        }
795        // start browser activity
796        startActivity(intent);
797        // a small delay, let the browser start, before processing the next command.
798        // this is good for scenarios where a related DISPLAY TEXT command is
799        // followed immediately.
800        try {
801            Thread.sleep(10000);
802        } catch (InterruptedException e) {}
803    }
804
805    private void launchIdleText() {
806        TextMessage msg = mCurrentCmd.geTextMessage();
807
808        if (msg == null) {
809            CatLog.d(this, "mCurrent.getTextMessage is NULL");
810            mNotificationManager.cancel(STK_NOTIFICATION_ID);
811            return;
812        }
813        if (msg.text == null) {
814            mNotificationManager.cancel(STK_NOTIFICATION_ID);
815        } else {
816            PendingIntent pendingIntent = PendingIntent.getService(mContext, 0,
817                    new Intent(mContext, StkAppService.class), 0);
818
819            final Notification.Builder notificationBuilder = new Notification.Builder(
820                    StkAppService.this);
821            if (mMainCmd != null && mMainCmd.getMenu() != null) {
822                notificationBuilder.setContentTitle(mMainCmd.getMenu().title);
823            } else {
824                notificationBuilder.setContentTitle("");
825            }
826            notificationBuilder
827                    .setSmallIcon(com.android.internal.R.drawable.stat_notify_sim_toolkit);
828            notificationBuilder.setContentIntent(pendingIntent);
829            notificationBuilder.setOngoing(true);
830            // Set text and icon for the status bar and notification body.
831            if (!msg.iconSelfExplanatory) {
832                notificationBuilder.setContentText(msg.text);
833                notificationBuilder.setTicker(msg.text);
834            }
835            if (msg.icon != null) {
836                notificationBuilder.setLargeIcon(msg.icon);
837            } else {
838                Bitmap bitmapIcon = BitmapFactory.decodeResource(StkAppService.this
839                    .getResources().getSystem(),
840                    com.android.internal.R.drawable.stat_notify_sim_toolkit);
841                notificationBuilder.setLargeIcon(bitmapIcon);
842            }
843            notificationBuilder.setColor(mContext.getResources().getColor(
844                    com.android.internal.R.color.system_notification_accent_color));
845            mNotificationManager.notify(STK_NOTIFICATION_ID, notificationBuilder.build());
846        }
847    }
848
849    private void launchToneDialog() {
850        Intent newIntent = new Intent(this, ToneDialog.class);
851        newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
852                | Intent.FLAG_ACTIVITY_NO_HISTORY
853                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
854                | getFlagActivityNoUserAction(InitiatedByUserAction.unknown));
855        newIntent.putExtra("TEXT", mCurrentCmd.geTextMessage());
856        newIntent.putExtra("TONE", mCurrentCmd.getToneSettings());
857        startActivity(newIntent);
858    }
859
860    private void launchOpenChannelDialog() {
861        TextMessage msg = mCurrentCmd.geTextMessage();
862        if (msg == null) {
863            CatLog.d(this, "msg is null, return here");
864            return;
865        }
866
867        msg.title = getResources().getString(R.string.stk_dialog_title);
868        if (msg.text == null) {
869            msg.text = getResources().getString(R.string.default_open_channel_msg);
870        }
871
872        final AlertDialog dialog = new AlertDialog.Builder(mContext)
873                    .setIconAttribute(android.R.attr.alertDialogIcon)
874                    .setTitle(msg.title)
875                    .setMessage(msg.text)
876                    .setCancelable(false)
877                    .setPositiveButton(getResources().getString(R.string.stk_dialog_accept),
878                                       new DialogInterface.OnClickListener() {
879                        public void onClick(DialogInterface dialog, int which) {
880                            Bundle args = new Bundle();
881                            args.putInt(RES_ID, RES_ID_CHOICE);
882                            args.putInt(CHOICE, YES);
883                            Message message = mServiceHandler.obtainMessage();
884                            message.arg1 = OP_RESPONSE;
885                            message.obj = args;
886                            mServiceHandler.sendMessage(message);
887                        }
888                    })
889                    .setNegativeButton(getResources().getString(R.string.stk_dialog_reject),
890                                       new DialogInterface.OnClickListener() {
891                        public void onClick(DialogInterface dialog, int which) {
892                            Bundle args = new Bundle();
893                            args.putInt(RES_ID, RES_ID_CHOICE);
894                            args.putInt(CHOICE, NO);
895                            Message message = mServiceHandler.obtainMessage();
896                            message.arg1 = OP_RESPONSE;
897                            message.obj = args;
898                            mServiceHandler.sendMessage(message);
899                        }
900                    })
901                    .create();
902
903        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
904        if (!mContext.getResources().getBoolean(
905                com.android.internal.R.bool.config_sf_slowBlur)) {
906            dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
907        }
908
909        dialog.show();
910    }
911
912    private void launchTransientEventMessage() {
913        TextMessage msg = mCurrentCmd.geTextMessage();
914        if (msg == null) {
915            CatLog.d(this, "msg is null, return here");
916            return;
917        }
918
919        msg.title = getResources().getString(R.string.stk_dialog_title);
920
921        final AlertDialog dialog = new AlertDialog.Builder(mContext)
922                    .setIconAttribute(android.R.attr.alertDialogIcon)
923                    .setTitle(msg.title)
924                    .setMessage(msg.text)
925                    .setCancelable(false)
926                    .setPositiveButton(getResources().getString(android.R.string.ok),
927                                       new DialogInterface.OnClickListener() {
928                        public void onClick(DialogInterface dialog, int which) {
929                        }
930                    })
931                    .create();
932
933        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
934        if (!mContext.getResources().getBoolean(
935                com.android.internal.R.bool.config_sf_slowBlur)) {
936            dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
937        }
938
939        dialog.show();
940    }
941
942    private String getItemName(int itemId) {
943        Menu menu = mCurrentCmd.getMenu();
944        if (menu == null) {
945            return null;
946        }
947        for (Item item : menu.items) {
948            if (item.id == itemId) {
949                return item.text;
950            }
951        }
952        return null;
953    }
954
955    private boolean removeMenu() {
956        try {
957            if (mCurrentMenu.items.size() == 1 &&
958                mCurrentMenu.items.get(0) == null) {
959                return true;
960            }
961        } catch (NullPointerException e) {
962            CatLog.d(this, "Unable to get Menu's items size");
963            return true;
964        }
965        return false;
966    }
967}
968