Home | History | Annotate | Download | only in stk
      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.cat.AppInterface;
     41 import com.android.internal.telephony.cat.Menu;
     42 import com.android.internal.telephony.cat.Item;
     43 import com.android.internal.telephony.cat.Input;
     44 import com.android.internal.telephony.cat.ResultCode;
     45 import com.android.internal.telephony.cat.CatCmdMessage;
     46 import com.android.internal.telephony.cat.CatCmdMessage.BrowserSettings;
     47 import com.android.internal.telephony.cat.CatLog;
     48 import com.android.internal.telephony.cat.CatResponseMessage;
     49 import com.android.internal.telephony.cat.TextMessage;
     50 
     51 import 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  */
     58 public 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             CatLog.d(this, "settings.url = " + settings.url);
    657             if (settings.url.startsWith("http://")) {
    658                 data = Uri.parse(settings.url);
    659             } else {
    660                 String modifiedUrl = "http://" + settings.url;
    661                 CatLog.d(this, "modifiedUrl = " + modifiedUrl);
    662                 data = Uri.parse(modifiedUrl);
    663             }
    664         } else {
    665             // If no URL specified, just bring up the "home page".
    666             //
    667             // (Note we need to specify *something* in the intent's data field
    668             // here, since if you fire off a VIEW intent with no data at all
    669             // you'll get an activity chooser rather than the browser.  There's
    670             // no specific URI that means "use the default home page", so
    671             // instead let's just explicitly bring up http://google.com.)
    672             data = Uri.parse("http://google.com/");
    673         }
    674         intent.setData(data);
    675 
    676         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    677         switch (settings.mode) {
    678         case USE_EXISTING_BROWSER:
    679             intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    680             break;
    681         case LAUNCH_NEW_BROWSER:
    682             intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
    683             break;
    684         case LAUNCH_IF_NOT_ALREADY_LAUNCHED:
    685             intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    686             break;
    687         }
    688         // start browser activity
    689         startActivity(intent);
    690         // a small delay, let the browser start, before processing the next command.
    691         // this is good for scenarios where a related DISPLAY TEXT command is
    692         // followed immediately.
    693         try {
    694             Thread.sleep(10000);
    695         } catch (InterruptedException e) {}
    696     }
    697 
    698     private void launchCallMsg() {
    699         TextMessage msg = mCurrentCmd.getCallSettings().callMsg;
    700         if (msg.text == null || msg.text.length() == 0) {
    701             return;
    702         }
    703         msg.title = lastSelectedItem;
    704 
    705         Toast toast = Toast.makeText(mContext.getApplicationContext(), msg.text,
    706                 Toast.LENGTH_LONG);
    707         toast.setGravity(Gravity.BOTTOM, 0, 0);
    708         toast.show();
    709     }
    710 
    711     private void launchIdleText() {
    712         TextMessage msg = mCurrentCmd.geTextMessage();
    713         if (msg == null) {
    714             CatLog.d(this, "mCurrentCmd.getTextMessage is NULL");
    715             return;
    716         }
    717         if (msg.text == null) {
    718             mNotificationManager.cancel(STK_NOTIFICATION_ID);
    719         } else {
    720             Notification notification = new Notification();
    721             RemoteViews contentView = new RemoteViews(
    722                     PACKAGE_NAME,
    723                     com.android.internal.R.layout.status_bar_latest_event_content);
    724 
    725             notification.flags |= Notification.FLAG_NO_CLEAR;
    726             notification.icon = com.android.internal.R.drawable.stat_notify_sim_toolkit;
    727             // Set text and icon for the status bar and notification body.
    728             if (!msg.iconSelfExplanatory) {
    729                 notification.tickerText = msg.text;
    730                 contentView.setTextViewText(com.android.internal.R.id.text,
    731                         msg.text);
    732             }
    733             if (msg.icon != null) {
    734                 contentView.setImageViewBitmap(com.android.internal.R.id.icon,
    735                         msg.icon);
    736             } else {
    737                 contentView
    738                         .setImageViewResource(
    739                                 com.android.internal.R.id.icon,
    740                                 com.android.internal.R.drawable.stat_notify_sim_toolkit);
    741             }
    742             notification.contentView = contentView;
    743             notification.contentIntent = PendingIntent.getService(mContext, 0,
    744                     new Intent(mContext, StkAppService.class), 0);
    745 
    746             mNotificationManager.notify(STK_NOTIFICATION_ID, notification);
    747         }
    748     }
    749 
    750     private void launchToneDialog() {
    751         Intent newIntent = new Intent(this, ToneDialog.class);
    752         newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
    753                 | Intent.FLAG_ACTIVITY_NO_HISTORY
    754                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
    755                 | getFlagActivityNoUserAction(InitiatedByUserAction.unknown));
    756         newIntent.putExtra("TEXT", mCurrentCmd.geTextMessage());
    757         newIntent.putExtra("TONE", mCurrentCmd.getToneSettings());
    758         startActivity(newIntent);
    759     }
    760 
    761     private String getItemName(int itemId) {
    762         Menu menu = mCurrentCmd.getMenu();
    763         if (menu == null) {
    764             return null;
    765         }
    766         for (Item item : menu.items) {
    767             if (item.id == itemId) {
    768                 return item.text;
    769             }
    770         }
    771         return null;
    772     }
    773 
    774     private boolean removeMenu() {
    775         try {
    776             if (mCurrentMenu.items.size() == 1 &&
    777                 mCurrentMenu.items.get(0) == null) {
    778                 return true;
    779             }
    780         } catch (NullPointerException e) {
    781             CatLog.d(this, "Unable to get Menu's items size");
    782             return true;
    783         }
    784         return false;
    785     }
    786 }
    787