StkAppService.java revision 046db4bee9c4be21c61cc0d1b46ef46f21e315d8
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.Notification;
20import android.app.NotificationManager;
21import android.app.PendingIntent;
22import android.app.Service;
23import android.content.Context;
24import android.content.Intent;
25import android.net.Uri;
26import android.os.Bundle;
27import android.os.Handler;
28import android.os.IBinder;
29import android.os.Looper;
30import android.os.Message;
31import android.telephony.TelephonyManager;
32import android.view.Gravity;
33import android.view.LayoutInflater;
34import android.view.View;
35import android.widget.ImageView;
36import android.widget.RemoteViews;
37import android.widget.TextView;
38import android.widget.Toast;
39
40import com.android.internal.telephony.cat.AppInterface;
41import com.android.internal.telephony.cat.Menu;
42import com.android.internal.telephony.cat.Item;
43import com.android.internal.telephony.cat.Input;
44import com.android.internal.telephony.cat.ResultCode;
45import com.android.internal.telephony.cat.CatCmdMessage;
46import com.android.internal.telephony.cat.CatCmdMessage.BrowserSettings;
47import com.android.internal.telephony.cat.CatLog;
48import com.android.internal.telephony.cat.CatResponseMessage;
49import com.android.internal.telephony.cat.TextMessage;
50
51import java.util.LinkedList;
52
53/**
54 * SIM toolkit application level service. Interacts with Telephopny messages,
55 * application's launch and user input from STK UI elements.
56 *
57 */
58public class StkAppService extends Service implements Runnable {
59
60    // members
61    private volatile Looper mServiceLooper;
62    private volatile ServiceHandler mServiceHandler;
63    private AppInterface mStkService;
64    private Context mContext = null;
65    private CatCmdMessage mMainCmd = null;
66    private CatCmdMessage mCurrentCmd = null;
67    private Menu mCurrentMenu = null;
68    private String lastSelectedItem = null;
69    private boolean mMenuIsVisibile = false;
70    private boolean responseNeeded = true;
71    private boolean mCmdInProgress = false;
72    private NotificationManager mNotificationManager = null;
73    private LinkedList<DelayedCmd> mCmdsQ = null;
74    private boolean launchBrowser = false;
75    private BrowserSettings mBrowserSettings = null;
76    static StkAppService sInstance = null;
77
78    // Used for setting FLAG_ACTIVITY_NO_USER_ACTION when
79    // creating an intent.
80    private enum InitiatedByUserAction {
81        yes,            // The action was started via a user initiated action
82        unknown,        // Not known for sure if user initated the action
83    }
84
85    // constants
86    static final String OPCODE = "op";
87    static final String CMD_MSG = "cmd message";
88    static final String RES_ID = "response id";
89    static final String MENU_SELECTION = "menu selection";
90    static final String INPUT = "input";
91    static final String HELP = "help";
92    static final String CONFIRMATION = "confirm";
93
94    // operations ids for different service functionality.
95    static final int OP_CMD = 1;
96    static final int OP_RESPONSE = 2;
97    static final int OP_LAUNCH_APP = 3;
98    static final int OP_END_SESSION = 4;
99    static final int OP_BOOT_COMPLETED = 5;
100    private static final int OP_DELAYED_MSG = 6;
101
102    // Response ids
103    static final int RES_ID_MENU_SELECTION = 11;
104    static final int RES_ID_INPUT = 12;
105    static final int RES_ID_CONFIRM = 13;
106    static final int RES_ID_DONE = 14;
107
108    static final int RES_ID_TIMEOUT = 20;
109    static final int RES_ID_BACKWARD = 21;
110    static final int RES_ID_END_SESSION = 22;
111    static final int RES_ID_EXIT = 23;
112
113    private static final String PACKAGE_NAME = "com.android.stk";
114    private static final String MENU_ACTIVITY_NAME =
115                                        PACKAGE_NAME + ".StkMenuActivity";
116    private static final String INPUT_ACTIVITY_NAME =
117                                        PACKAGE_NAME + ".StkInputActivity";
118
119    // Notification id used to display Idle Mode text in NotificationManager.
120    private static final int STK_NOTIFICATION_ID = 333;
121
122    // Inner class used for queuing telephony messages (proactive commands,
123    // session end) while the service is busy processing a previous message.
124    private class DelayedCmd {
125        // members
126        int id;
127        CatCmdMessage msg;
128
129        DelayedCmd(int id, CatCmdMessage msg) {
130            this.id = id;
131            this.msg = msg;
132        }
133    }
134
135    @Override
136    public void onCreate() {
137        // Initialize members
138        mStkService = com.android.internal.telephony.cat.CatService
139                .getInstance();
140
141        // NOTE mStkService is a singleton and continues to exist even if the GSMPhone is disposed
142        //   after the radio technology change from GSM to CDMA so the PHONE_TYPE_CDMA check is
143        //   needed. In case of switching back from CDMA to GSM the GSMPhone constructor updates
144        //   the instance. (TODO: test).
145        if ((mStkService == null)
146                && (TelephonyManager.getDefault().getPhoneType()
147                                != TelephonyManager.PHONE_TYPE_CDMA)) {
148            CatLog.d(this, " Unable to get Service handle");
149            return;
150        }
151
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        waitForLooper();
164
165        // onStart() method can be passed a null intent
166        // TODO: replace onStart() with onStartCommand()
167        if (intent == null) {
168            return;
169        }
170
171        Bundle args = intent.getExtras();
172
173        if (args == null) {
174            return;
175        }
176
177        Message msg = mServiceHandler.obtainMessage();
178        msg.arg1 = args.getInt(OPCODE);
179        switch(msg.arg1) {
180        case OP_CMD:
181            msg.obj = args.getParcelable(CMD_MSG);
182            break;
183        case OP_RESPONSE:
184            msg.obj = args;
185            /* falls through */
186        case OP_LAUNCH_APP:
187        case OP_END_SESSION:
188        case OP_BOOT_COMPLETED:
189            break;
190        default:
191            return;
192        }
193        mServiceHandler.sendMessage(msg);
194    }
195
196    @Override
197    public void onDestroy() {
198        waitForLooper();
199        mServiceLooper.quit();
200    }
201
202    @Override
203    public IBinder onBind(Intent intent) {
204        return null;
205    }
206
207    public void run() {
208        Looper.prepare();
209
210        mServiceLooper = Looper.myLooper();
211        mServiceHandler = new ServiceHandler();
212
213        Looper.loop();
214    }
215
216    /*
217     * Package api used by StkMenuActivity to indicate if its on the foreground.
218     */
219    void indicateMenuVisibility(boolean visibility) {
220        mMenuIsVisibile = visibility;
221    }
222
223    /*
224     * Package api used by StkMenuActivity to get its Menu parameter.
225     */
226    Menu getMenu() {
227        return mCurrentMenu;
228    }
229
230    /*
231     * Package api used by UI Activities and Dialogs to communicate directly
232     * with the service to deliver state information and parameters.
233     */
234    static StkAppService getInstance() {
235        return sInstance;
236    }
237
238    private void waitForLooper() {
239        while (mServiceHandler == null) {
240            synchronized (this) {
241                try {
242                    wait(100);
243                } catch (InterruptedException e) {
244                }
245            }
246        }
247    }
248
249    private final class ServiceHandler extends Handler {
250        @Override
251        public void handleMessage(Message msg) {
252            int opcode = msg.arg1;
253
254            switch (opcode) {
255            case OP_LAUNCH_APP:
256                if (mMainCmd == null) {
257                    // nothing todo when no SET UP MENU command didn't arrive.
258                    return;
259                }
260                launchMenuActivity(null);
261                break;
262            case OP_CMD:
263                CatCmdMessage cmdMsg = (CatCmdMessage) msg.obj;
264                // There are two types of commands:
265                // 1. Interactive - user's response is required.
266                // 2. Informative - display a message, no interaction with the user.
267                //
268                // Informative commands can be handled immediately without any delay.
269                // Interactive commands can't override each other. So if a command
270                // is already in progress, we need to queue the next command until
271                // the user has responded or a timeout expired.
272                if (!isCmdInteractive(cmdMsg)) {
273                    handleCmd(cmdMsg);
274                } else {
275                    if (!mCmdInProgress) {
276                        mCmdInProgress = true;
277                        handleCmd((CatCmdMessage) msg.obj);
278                    } else {
279                        mCmdsQ.addLast(new DelayedCmd(OP_CMD,
280                                (CatCmdMessage) msg.obj));
281                    }
282                }
283                break;
284            case OP_RESPONSE:
285                if (responseNeeded) {
286                    handleCmdResponse((Bundle) msg.obj);
287                }
288                // call delayed commands if needed.
289                if (mCmdsQ.size() != 0) {
290                    callDelayedMsg();
291                } else {
292                    mCmdInProgress = false;
293                }
294                // reset response needed state var to its original value.
295                responseNeeded = true;
296                break;
297            case OP_END_SESSION:
298                if (!mCmdInProgress) {
299                    mCmdInProgress = true;
300                    handleSessionEnd();
301                } else {
302                    mCmdsQ.addLast(new DelayedCmd(OP_END_SESSION, null));
303                }
304                break;
305            case OP_BOOT_COMPLETED:
306                CatLog.d(this, "OP_BOOT_COMPLETED");
307                if (mMainCmd == null) {
308                    StkAppInstaller.unInstall(mContext);
309                }
310                break;
311            case OP_DELAYED_MSG:
312                handleDelayedCmd();
313                break;
314            }
315        }
316    }
317
318    private boolean isCmdInteractive(CatCmdMessage cmd) {
319        switch (cmd.getCmdType()) {
320        case SEND_DTMF:
321        case SEND_SMS:
322        case SEND_SS:
323        case SEND_USSD:
324        case SET_UP_IDLE_MODE_TEXT:
325        case SET_UP_MENU:
326            return false;
327        }
328
329        return true;
330    }
331
332    private void handleDelayedCmd() {
333        if (mCmdsQ.size() != 0) {
334            DelayedCmd cmd = mCmdsQ.poll();
335            switch (cmd.id) {
336            case OP_CMD:
337                handleCmd(cmd.msg);
338                break;
339            case OP_END_SESSION:
340                handleSessionEnd();
341                break;
342            }
343        }
344    }
345
346    private void callDelayedMsg() {
347        Message msg = mServiceHandler.obtainMessage();
348        msg.arg1 = OP_DELAYED_MSG;
349        mServiceHandler.sendMessage(msg);
350    }
351
352    private void handleSessionEnd() {
353        mCurrentCmd = mMainCmd;
354        lastSelectedItem = null;
355        // In case of SET UP MENU command which removed the app, don't
356        // update the current menu member.
357        if (mCurrentMenu != null && mMainCmd != null) {
358            mCurrentMenu = mMainCmd.getMenu();
359        }
360        if (mMenuIsVisibile) {
361            launchMenuActivity(null);
362        }
363        if (mCmdsQ.size() != 0) {
364            callDelayedMsg();
365        } else {
366            mCmdInProgress = false;
367        }
368        // In case a launch browser command was just confirmed, launch that url.
369        if (launchBrowser) {
370            launchBrowser = false;
371            launchBrowser(mBrowserSettings);
372        }
373    }
374
375    private void handleCmd(CatCmdMessage cmdMsg) {
376        if (cmdMsg == null) {
377            return;
378        }
379        // save local reference for state tracking.
380        mCurrentCmd = cmdMsg;
381        boolean waitForUsersResponse = true;
382
383        CatLog.d(this, cmdMsg.getCmdType().name());
384        switch (cmdMsg.getCmdType()) {
385        case DISPLAY_TEXT:
386            TextMessage msg = cmdMsg.geTextMessage();
387            responseNeeded = msg.responseNeeded;
388            if (lastSelectedItem != null) {
389                msg.title = lastSelectedItem;
390            } else if (mMainCmd != null){
391                msg.title = mMainCmd.getMenu().title;
392            } else {
393                // TODO: get the carrier name from the SIM
394                msg.title = "";
395            }
396            launchTextDialog();
397            break;
398        case SELECT_ITEM:
399            mCurrentMenu = cmdMsg.getMenu();
400            launchMenuActivity(cmdMsg.getMenu());
401            break;
402        case SET_UP_MENU:
403            mMainCmd = mCurrentCmd;
404            mCurrentMenu = cmdMsg.getMenu();
405            if (removeMenu()) {
406                CatLog.d(this, "Uninstall App");
407                mCurrentMenu = null;
408                StkAppInstaller.unInstall(mContext);
409            } else {
410                CatLog.d(this, "Install App");
411                StkAppInstaller.install(mContext);
412            }
413            if (mMenuIsVisibile) {
414                launchMenuActivity(null);
415            }
416            break;
417        case GET_INPUT:
418        case GET_INKEY:
419            launchInputActivity();
420            break;
421        case SET_UP_IDLE_MODE_TEXT:
422            waitForUsersResponse = false;
423            launchIdleText();
424            break;
425        case SEND_DTMF:
426        case SEND_SMS:
427        case SEND_SS:
428        case SEND_USSD:
429            waitForUsersResponse = false;
430            launchEventMessage();
431            break;
432        case LAUNCH_BROWSER:
433            launchConfirmationDialog(mCurrentCmd.geTextMessage());
434            break;
435        case SET_UP_CALL:
436            launchConfirmationDialog(mCurrentCmd.getCallSettings().confirmMsg);
437            break;
438        case PLAY_TONE:
439            launchToneDialog();
440            break;
441        }
442
443        if (!waitForUsersResponse) {
444            if (mCmdsQ.size() != 0) {
445                callDelayedMsg();
446            } else {
447                mCmdInProgress = false;
448            }
449        }
450    }
451
452    private void handleCmdResponse(Bundle args) {
453        if (mCurrentCmd == null) {
454            return;
455        }
456        CatResponseMessage resMsg = new CatResponseMessage(mCurrentCmd);
457
458        // set result code
459        boolean helpRequired = args.getBoolean(HELP, false);
460
461        switch(args.getInt(RES_ID)) {
462        case RES_ID_MENU_SELECTION:
463            CatLog.d(this, "RES_ID_MENU_SELECTION");
464            int menuSelection = args.getInt(MENU_SELECTION);
465            switch(mCurrentCmd.getCmdType()) {
466            case SET_UP_MENU:
467            case SELECT_ITEM:
468                lastSelectedItem = getItemName(menuSelection);
469                if (helpRequired) {
470                    resMsg.setResultCode(ResultCode.HELP_INFO_REQUIRED);
471                } else {
472                    resMsg.setResultCode(ResultCode.OK);
473                }
474                resMsg.setMenuSelection(menuSelection);
475                break;
476            }
477            break;
478        case RES_ID_INPUT:
479            CatLog.d(this, "RES_ID_INPUT");
480            String input = args.getString(INPUT);
481            Input cmdInput = mCurrentCmd.geInput();
482            if (cmdInput != null && cmdInput.yesNo) {
483                boolean yesNoSelection = input
484                        .equals(StkInputActivity.YES_STR_RESPONSE);
485                resMsg.setYesNo(yesNoSelection);
486            } else {
487                if (helpRequired) {
488                    resMsg.setResultCode(ResultCode.HELP_INFO_REQUIRED);
489                } else {
490                    resMsg.setResultCode(ResultCode.OK);
491                    resMsg.setInput(input);
492                }
493            }
494            break;
495        case RES_ID_CONFIRM:
496            CatLog.d(this, "RES_ID_CONFIRM");
497            boolean confirmed = args.getBoolean(CONFIRMATION);
498            switch (mCurrentCmd.getCmdType()) {
499            case DISPLAY_TEXT:
500                resMsg.setResultCode(confirmed ? ResultCode.OK
501                        : ResultCode.UICC_SESSION_TERM_BY_USER);
502                break;
503            case LAUNCH_BROWSER:
504                resMsg.setResultCode(confirmed ? ResultCode.OK
505                        : ResultCode.UICC_SESSION_TERM_BY_USER);
506                if (confirmed) {
507                    launchBrowser = true;
508                    mBrowserSettings = mCurrentCmd.getBrowserSettings();
509                }
510                break;
511            case SET_UP_CALL:
512                resMsg.setResultCode(ResultCode.OK);
513                resMsg.setConfirmation(confirmed);
514                if (confirmed) {
515                    launchCallMsg();
516                }
517                break;
518            }
519            break;
520        case RES_ID_DONE:
521            resMsg.setResultCode(ResultCode.OK);
522            break;
523        case RES_ID_BACKWARD:
524            CatLog.d(this, "RES_ID_BACKWARD");
525            resMsg.setResultCode(ResultCode.BACKWARD_MOVE_BY_USER);
526            break;
527        case RES_ID_END_SESSION:
528            CatLog.d(this, "RES_ID_END_SESSION");
529            resMsg.setResultCode(ResultCode.UICC_SESSION_TERM_BY_USER);
530            break;
531        case RES_ID_TIMEOUT:
532            CatLog.d(this, "RES_ID_TIMEOUT");
533            // GCF test-case 27.22.4.1.1 Expected Sequence 1.5 (DISPLAY TEXT,
534            // Clear message after delay, successful) expects result code OK.
535            // If the command qualifier specifies no user response is required
536            // then send OK instead of NO_RESPONSE_FROM_USER
537            if ((mCurrentCmd.getCmdType().value() == AppInterface.CommandType.DISPLAY_TEXT
538                    .value())
539                    && (mCurrentCmd.geTextMessage().userClear == false)) {
540                resMsg.setResultCode(ResultCode.OK);
541            } else {
542                resMsg.setResultCode(ResultCode.NO_RESPONSE_FROM_USER);
543            }
544            break;
545        default:
546            CatLog.d(this, "Unknown result id");
547            return;
548        }
549        mStkService.onCmdResponse(resMsg);
550    }
551
552    /**
553     * Returns 0 or FLAG_ACTIVITY_NO_USER_ACTION, 0 means the user initiated the action.
554     *
555     * @param userAction If the userAction is yes then we always return 0 otherwise
556     * mMenuIsVisible is used to determine what to return. If mMenuIsVisible is true
557     * then we are the foreground app and we'll return 0 as from our perspective a
558     * user action did cause. If it's false than we aren't the foreground app and
559     * FLAG_ACTIVITY_NO_USER_ACTION is returned.
560     *
561     * @return 0 or FLAG_ACTIVITY_NO_USER_ACTION
562     */
563    private int getFlagActivityNoUserAction(InitiatedByUserAction userAction) {
564        return ((userAction == InitiatedByUserAction.yes) | mMenuIsVisibile) ?
565                                                    0 : Intent.FLAG_ACTIVITY_NO_USER_ACTION;
566    }
567
568    private void launchMenuActivity(Menu menu) {
569        Intent newIntent = new Intent(Intent.ACTION_VIEW);
570        newIntent.setClassName(PACKAGE_NAME, MENU_ACTIVITY_NAME);
571        int intentFlags = Intent.FLAG_ACTIVITY_NEW_TASK
572                | Intent.FLAG_ACTIVITY_CLEAR_TOP;
573        if (menu == null) {
574            // We assume this was initiated by the user pressing the tool kit icon
575            intentFlags |= getFlagActivityNoUserAction(InitiatedByUserAction.yes);
576
577            newIntent.putExtra("STATE", StkMenuActivity.STATE_MAIN);
578        } else {
579            // We don't know and we'll let getFlagActivityNoUserAction decide.
580            intentFlags |= getFlagActivityNoUserAction(InitiatedByUserAction.unknown);
581
582            newIntent.putExtra("STATE", StkMenuActivity.STATE_SECONDARY);
583        }
584        newIntent.setFlags(intentFlags);
585        mContext.startActivity(newIntent);
586    }
587
588    private void launchInputActivity() {
589        Intent newIntent = new Intent(Intent.ACTION_VIEW);
590        newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
591                            | getFlagActivityNoUserAction(InitiatedByUserAction.unknown));
592        newIntent.setClassName(PACKAGE_NAME, INPUT_ACTIVITY_NAME);
593        newIntent.putExtra("INPUT", mCurrentCmd.geInput());
594        mContext.startActivity(newIntent);
595    }
596
597    private void launchTextDialog() {
598        Intent newIntent = new Intent(this, StkDialogActivity.class);
599        newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
600                | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
601                | Intent.FLAG_ACTIVITY_NO_HISTORY
602                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
603                | getFlagActivityNoUserAction(InitiatedByUserAction.unknown));
604        newIntent.putExtra("TEXT", mCurrentCmd.geTextMessage());
605        startActivity(newIntent);
606    }
607
608    private void launchEventMessage() {
609        TextMessage msg = mCurrentCmd.geTextMessage();
610        if (msg == null || msg.text == null) {
611            return;
612        }
613        Toast toast = new Toast(mContext.getApplicationContext());
614        LayoutInflater inflate = (LayoutInflater) mContext
615                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
616        View v = inflate.inflate(R.layout.stk_event_msg, null);
617        TextView tv = (TextView) v
618                .findViewById(com.android.internal.R.id.message);
619        ImageView iv = (ImageView) v
620                .findViewById(com.android.internal.R.id.icon);
621        if (msg.icon != null) {
622            iv.setImageBitmap(msg.icon);
623        } else {
624            iv.setVisibility(View.GONE);
625        }
626        if (!msg.iconSelfExplanatory) {
627            tv.setText(msg.text);
628        }
629
630        toast.setView(v);
631        toast.setDuration(Toast.LENGTH_LONG);
632        toast.setGravity(Gravity.BOTTOM, 0, 0);
633        toast.show();
634    }
635
636    private void launchConfirmationDialog(TextMessage msg) {
637        msg.title = lastSelectedItem;
638        Intent newIntent = new Intent(this, StkDialogActivity.class);
639        newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
640                | Intent.FLAG_ACTIVITY_NO_HISTORY
641                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
642                | getFlagActivityNoUserAction(InitiatedByUserAction.unknown));
643        newIntent.putExtra("TEXT", msg);
644        startActivity(newIntent);
645    }
646
647    private void launchBrowser(BrowserSettings settings) {
648        if (settings == null) {
649            return;
650        }
651
652        Intent intent = new Intent(Intent.ACTION_VIEW);
653
654        Uri data;
655        if (settings.url != null) {
656            data = Uri.parse(settings.url);
657        } else {
658            // If no URL specified, just bring up the "home page".
659            //
660            // (Note we need to specify *something* in the intent's data field
661            // here, since if you fire off a VIEW intent with no data at all
662            // you'll get an activity chooser rather than the browser.  There's
663            // no specific URI that means "use the default home page", so
664            // instead let's just explicitly bring up http://google.com.)
665            data = Uri.parse("http://google.com/");
666        }
667        intent.setData(data);
668
669        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
670        switch (settings.mode) {
671        case USE_EXISTING_BROWSER:
672            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
673            break;
674        case LAUNCH_NEW_BROWSER:
675            intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
676            break;
677        case LAUNCH_IF_NOT_ALREADY_LAUNCHED:
678            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
679            break;
680        }
681        // start browser activity
682        startActivity(intent);
683        // a small delay, let the browser start, before processing the next command.
684        // this is good for scenarios where a related DISPLAY TEXT command is
685        // followed immediately.
686        try {
687            Thread.sleep(10000);
688        } catch (InterruptedException e) {}
689    }
690
691    private void launchCallMsg() {
692        TextMessage msg = mCurrentCmd.getCallSettings().callMsg;
693        if (msg.text == null || msg.text.length() == 0) {
694            return;
695        }
696        msg.title = lastSelectedItem;
697
698        Toast toast = Toast.makeText(mContext.getApplicationContext(), msg.text,
699                Toast.LENGTH_LONG);
700        toast.setGravity(Gravity.BOTTOM, 0, 0);
701        toast.show();
702    }
703
704    private void launchIdleText() {
705        TextMessage msg = mCurrentCmd.geTextMessage();
706        if (msg == null) {
707            CatLog.d(this, "mCurrentCmd.getTextMessage is NULL");
708            return;
709        }
710        if (msg.text == null) {
711            mNotificationManager.cancel(STK_NOTIFICATION_ID);
712        } else {
713            Notification notification = new Notification();
714            RemoteViews contentView = new RemoteViews(
715                    PACKAGE_NAME,
716                    com.android.internal.R.layout.status_bar_latest_event_content);
717
718            notification.flags |= Notification.FLAG_NO_CLEAR;
719            notification.icon = com.android.internal.R.drawable.stat_notify_sim_toolkit;
720            // Set text and icon for the status bar and notification body.
721            if (!msg.iconSelfExplanatory) {
722                notification.tickerText = msg.text;
723                contentView.setTextViewText(com.android.internal.R.id.text,
724                        msg.text);
725            }
726            if (msg.icon != null) {
727                contentView.setImageViewBitmap(com.android.internal.R.id.icon,
728                        msg.icon);
729            } else {
730                contentView
731                        .setImageViewResource(
732                                com.android.internal.R.id.icon,
733                                com.android.internal.R.drawable.stat_notify_sim_toolkit);
734            }
735            notification.contentView = contentView;
736            notification.contentIntent = PendingIntent.getService(mContext, 0,
737                    new Intent(mContext, StkAppService.class), 0);
738
739            mNotificationManager.notify(STK_NOTIFICATION_ID, notification);
740        }
741    }
742
743    private void launchToneDialog() {
744        Intent newIntent = new Intent(this, ToneDialog.class);
745        newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
746                | Intent.FLAG_ACTIVITY_NO_HISTORY
747                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
748                | getFlagActivityNoUserAction(InitiatedByUserAction.unknown));
749        newIntent.putExtra("TEXT", mCurrentCmd.geTextMessage());
750        newIntent.putExtra("TONE", mCurrentCmd.getToneSettings());
751        startActivity(newIntent);
752    }
753
754    private String getItemName(int itemId) {
755        Menu menu = mCurrentCmd.getMenu();
756        if (menu == null) {
757            return null;
758        }
759        for (Item item : menu.items) {
760            if (item.id == itemId) {
761                return item.text;
762            }
763        }
764        return null;
765    }
766
767    private boolean removeMenu() {
768        try {
769            if (mCurrentMenu.items.size() == 1 &&
770                mCurrentMenu.items.get(0) == null) {
771                return true;
772            }
773        } catch (NullPointerException e) {
774            CatLog.d(this, "Unable to get Menu's items size");
775            return true;
776        }
777        return false;
778    }
779}
780