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