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