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