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