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