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.ListActivity;
     20 import android.app.ActionBar;
     21 import android.app.Activity;
     22 import android.content.BroadcastReceiver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.IntentFilter;
     26 import android.os.Bundle;
     27 import android.os.Handler;
     28 import android.os.Message;
     29 import android.support.v4.content.LocalBroadcastManager;
     30 import android.telephony.SubscriptionManager;
     31 import android.view.ContextMenu;
     32 import android.view.ContextMenu.ContextMenuInfo;
     33 import android.view.KeyEvent;
     34 import android.view.MenuItem;
     35 import android.view.View;
     36 import android.view.Window;
     37 import android.widget.AdapterView;
     38 import android.widget.ImageView;
     39 import android.widget.ListView;
     40 import android.widget.ProgressBar;
     41 import android.widget.TextView;
     42 
     43 import com.android.internal.telephony.cat.Item;
     44 import com.android.internal.telephony.cat.Menu;
     45 import com.android.internal.telephony.cat.CatLog;
     46 import android.telephony.TelephonyManager;
     47 
     48 /**
     49  * ListActivity used for displaying STK menus. These can be SET UP MENU and
     50  * SELECT ITEM menus. This activity is started multiple times with different
     51  * menu content.
     52  *
     53  */
     54 public class StkMenuActivity extends ListActivity implements View.OnCreateContextMenuListener {
     55     private Context mContext;
     56     private Menu mStkMenu = null;
     57     private int mState = STATE_MAIN;
     58     private boolean mAcceptUsersInput = true;
     59     private int mSlotId = -1;
     60     private boolean mIsResponseSent = false;
     61     Activity mInstance = null;
     62 
     63     private TextView mTitleTextView = null;
     64     private ImageView mTitleIconView = null;
     65     private ProgressBar mProgressView = null;
     66     private static final String className = new Object(){}.getClass().getEnclosingClass().getName();
     67     private static final String LOG_TAG = className.substring(className.lastIndexOf('.') + 1);
     68 
     69     private StkAppService appService = StkAppService.getInstance();
     70 
     71     // Keys for saving the state of the dialog in the bundle
     72     private static final String STATE_KEY = "state";
     73     private static final String MENU_KEY = "menu";
     74     private static final String ACCEPT_USERS_INPUT_KEY = "accept_users_input";
     75     private static final String RESPONSE_SENT_KEY = "response_sent";
     76 
     77     // Internal state values
     78     static final int STATE_INIT = 0;
     79     static final int STATE_MAIN = 1;
     80     static final int STATE_SECONDARY = 2;
     81 
     82     // Finish result
     83     static final int FINISH_CAUSE_NORMAL = 1;
     84     static final int FINISH_CAUSE_NULL_SERVICE = 2;
     85     static final int FINISH_CAUSE_NULL_MENU = 3;
     86 
     87     // message id for time out
     88     private static final int MSG_ID_TIMEOUT = 1;
     89     private static final int CONTEXT_MENU_HELP = 0;
     90 
     91     Handler mTimeoutHandler = new Handler() {
     92         @Override
     93         public void handleMessage(Message msg) {
     94             switch(msg.what) {
     95             case MSG_ID_TIMEOUT:
     96                 CatLog.d(LOG_TAG, "MSG_ID_TIMEOUT mState: " + mState);
     97                 if (mState == STATE_SECONDARY) {
     98                     appService.getStkContext(mSlotId).setPendingActivityInstance(mInstance);
     99                 }
    100                 sendResponse(StkAppService.RES_ID_TIMEOUT);
    101                 //finish();//We wait the following commands to trigger onStop of this activity.
    102                 break;
    103             }
    104         }
    105     };
    106 
    107     @Override
    108     public void onCreate(Bundle savedInstanceState) {
    109         super.onCreate(savedInstanceState);
    110 
    111         CatLog.d(LOG_TAG, "onCreate");
    112 
    113         ActionBar actionBar = getActionBar();
    114         actionBar.setCustomView(R.layout.stk_title);
    115         actionBar.setDisplayShowCustomEnabled(true);
    116 
    117         // Set the layout for this activity.
    118         setContentView(R.layout.stk_menu_list);
    119         mInstance = this;
    120         mTitleTextView = (TextView) findViewById(R.id.title_text);
    121         mTitleIconView = (ImageView) findViewById(R.id.title_icon);
    122         mProgressView = (ProgressBar) findViewById(R.id.progress_bar);
    123         mContext = getBaseContext();
    124         getListView().setOnCreateContextMenuListener(this);
    125 
    126         // appService can be null if this activity is automatically recreated by the system
    127         // with the saved instance state right after the phone process is killed.
    128         if (appService == null) {
    129             CatLog.d(LOG_TAG, "onCreate - appService is null");
    130             finish();
    131             return;
    132         }
    133 
    134         LocalBroadcastManager.getInstance(this).registerReceiver(mLocalBroadcastReceiver,
    135                 new IntentFilter(StkAppService.SESSION_ENDED));
    136         initFromIntent(getIntent());
    137         if (!SubscriptionManager.isValidSlotIndex(mSlotId)) {
    138             finish();
    139             return;
    140         }
    141     }
    142 
    143     @Override
    144     protected void onListItemClick(ListView l, View v, int position, long id) {
    145         super.onListItemClick(l, v, position, id);
    146 
    147         if (!mAcceptUsersInput) {
    148             CatLog.d(LOG_TAG, "mAcceptUsersInput:false");
    149             return;
    150         }
    151 
    152         Item item = getSelectedItem(position);
    153         if (item == null) {
    154             CatLog.d(LOG_TAG, "Item is null");
    155             return;
    156         }
    157 
    158         CatLog.d(LOG_TAG, "onListItemClick Id: " + item.id + ", mState: " + mState);
    159         // ONLY set SECONDARY menu. It will be finished when the following command is comming.
    160         if (mState == STATE_SECONDARY) {
    161             appService.getStkContext(mSlotId).setPendingActivityInstance(this);
    162         }
    163         cancelTimeOut();
    164         sendResponse(StkAppService.RES_ID_MENU_SELECTION, item.id, false);
    165         invalidateOptionsMenu();
    166     }
    167 
    168     @Override
    169     public boolean onKeyDown(int keyCode, KeyEvent event) {
    170         CatLog.d(LOG_TAG, "mAcceptUsersInput: " + mAcceptUsersInput);
    171         if (!mAcceptUsersInput) {
    172             return true;
    173         }
    174 
    175         switch (keyCode) {
    176         case KeyEvent.KEYCODE_BACK:
    177             CatLog.d(LOG_TAG, "KEYCODE_BACK - mState[" + mState + "]");
    178             switch (mState) {
    179             case STATE_SECONDARY:
    180                 CatLog.d(LOG_TAG, "STATE_SECONDARY");
    181                 cancelTimeOut();
    182                 appService.getStkContext(mSlotId).setPendingActivityInstance(this);
    183                 sendResponse(StkAppService.RES_ID_BACKWARD);
    184                 return true;
    185             case STATE_MAIN:
    186                 CatLog.d(LOG_TAG, "STATE_MAIN");
    187                 cancelTimeOut();
    188                 finish();
    189                 return true;
    190             }
    191             break;
    192         }
    193         return super.onKeyDown(keyCode, event);
    194     }
    195 
    196     @Override
    197     public void onRestart() {
    198         super.onRestart();
    199         CatLog.d(LOG_TAG, "onRestart, slot id: " + mSlotId);
    200     }
    201 
    202     @Override
    203     public void onResume() {
    204         super.onResume();
    205 
    206         CatLog.d(LOG_TAG, "onResume, slot id: " + mSlotId + "," + mState);
    207         appService.indicateMenuVisibility(true, mSlotId);
    208         if (mState == STATE_MAIN) {
    209             mStkMenu = appService.getMainMenu(mSlotId);
    210         } else {
    211             mStkMenu = appService.getMenu(mSlotId);
    212         }
    213         if (mStkMenu == null) {
    214             CatLog.d(LOG_TAG, "menu is null");
    215             cancelTimeOut();
    216             finish();
    217             return;
    218         }
    219         displayMenu();
    220         startTimeOut();
    221         invalidateOptionsMenu();
    222     }
    223 
    224     @Override
    225     public void onPause() {
    226         super.onPause();
    227         CatLog.d(LOG_TAG, "onPause, slot id: " + mSlotId + "," + mState);
    228         //If activity is finished in onResume and it reaults from null appService.
    229         if (appService != null) {
    230             appService.indicateMenuVisibility(false, mSlotId);
    231         } else {
    232             CatLog.d(LOG_TAG, "onPause: null appService.");
    233         }
    234 
    235         /*
    236          * do not cancel the timer here cancelTimeOut(). If any higher/lower
    237          * priority events such as incoming call, new sms, screen off intent,
    238          * notification alerts, user actions such as 'User moving to another activtiy'
    239          * etc.. occur during SELECT ITEM ongoing session,
    240          * this activity would receive 'onPause()' event resulting in
    241          * cancellation of the timer. As a result no terminal response is
    242          * sent to the card.
    243          */
    244 
    245     }
    246 
    247     @Override
    248     public void onStop() {
    249         super.onStop();
    250         CatLog.d(LOG_TAG, "onStop, slot id: " + mSlotId + "," + mIsResponseSent + "," + mState);
    251 
    252         // Nothing should be done here if this activity is being restarted now.
    253         if (isChangingConfigurations()) {
    254             return;
    255         }
    256 
    257         //The menu should stay in background, if
    258         //1. the dialog is pop up in the screen, but the user does not response to the dialog.
    259         //2. the menu activity enters Stop state (e.g pressing HOME key) but mIsResponseSent is false.
    260         if (mIsResponseSent) {
    261             // ONLY finish SECONDARY menu. MAIN menu should always stay in the root of stack.
    262             if (mState == STATE_SECONDARY) {
    263                 if (!appService.isStkDialogActivated(mContext)) {
    264                     CatLog.d(LOG_TAG, "STATE_SECONDARY finish.");
    265                     cancelTimeOut();//To avoid the timer time out and send TR again.
    266                     finish();
    267                 } else {
    268                      if (appService != null) {
    269                          appService.getStkContext(mSlotId).setPendingActivityInstance(this);
    270                      }
    271                 }
    272             }
    273         } else {
    274             if (appService != null) {
    275                 if (mState == STATE_SECONDARY) {
    276                     appService.getStkContext(mSlotId).setPendingActivityInstance(this);
    277                 }
    278             } else {
    279                 CatLog.d(LOG_TAG, "onStop: null appService.");
    280             }
    281         }
    282     }
    283 
    284     @Override
    285     public void onDestroy() {
    286         getListView().setOnCreateContextMenuListener(null);
    287         super.onDestroy();
    288         CatLog.d(LOG_TAG, "onDestroy" + ", " + mState);
    289         if (appService == null || !SubscriptionManager.isValidSlotIndex(mSlotId)) {
    290             return;
    291         }
    292         //isMenuPending: if input act is finish by stkappservice when OP_LAUNCH_APP again,
    293         //we can not send TR here, since the input cmd is waiting user to process.
    294         if (mState == STATE_SECONDARY && !mIsResponseSent && !appService.isMenuPending(mSlotId)) {
    295             // Avoid sending the terminal response while the activty is being restarted
    296             // due to some kind of configuration change.
    297             if (!isChangingConfigurations()) {
    298                 CatLog.d(LOG_TAG, "handleDestroy - Send End Session");
    299                 sendResponse(StkAppService.RES_ID_END_SESSION);
    300             }
    301         }
    302         LocalBroadcastManager.getInstance(this).unregisterReceiver(mLocalBroadcastReceiver);
    303     }
    304 
    305     @Override
    306     public boolean onCreateOptionsMenu(android.view.Menu menu) {
    307         super.onCreateOptionsMenu(menu);
    308         menu.add(0, StkApp.MENU_ID_END_SESSION, 1, R.string.menu_end_session);
    309         return true;
    310     }
    311 
    312     @Override
    313     public boolean onPrepareOptionsMenu(android.view.Menu menu) {
    314         super.onPrepareOptionsMenu(menu);
    315         boolean mainVisible = false;
    316 
    317         if (mState == STATE_SECONDARY && mAcceptUsersInput) {
    318             mainVisible = true;
    319         }
    320 
    321         menu.findItem(StkApp.MENU_ID_END_SESSION).setVisible(mainVisible);
    322 
    323         return mainVisible;
    324     }
    325 
    326     @Override
    327     public boolean onOptionsItemSelected(MenuItem item) {
    328         if (!mAcceptUsersInput) {
    329             return true;
    330         }
    331         switch (item.getItemId()) {
    332         case StkApp.MENU_ID_END_SESSION:
    333             cancelTimeOut();
    334             // send session end response.
    335             sendResponse(StkAppService.RES_ID_END_SESSION);
    336             cancelTimeOut();
    337             finish();
    338             return true;
    339         default:
    340             break;
    341         }
    342         return super.onOptionsItemSelected(item);
    343     }
    344 
    345     @Override
    346     public void onCreateContextMenu(ContextMenu menu, View v,
    347             ContextMenuInfo menuInfo) {
    348         CatLog.d(this, "onCreateContextMenu");
    349         boolean helpVisible = false;
    350         if (mStkMenu != null) {
    351             helpVisible = mStkMenu.helpAvailable;
    352         }
    353         if (helpVisible) {
    354             CatLog.d(this, "add menu");
    355             menu.add(0, CONTEXT_MENU_HELP, 0, R.string.help);
    356         }
    357     }
    358 
    359     @Override
    360     public boolean onContextItemSelected(MenuItem item) {
    361         AdapterView.AdapterContextMenuInfo info;
    362         try {
    363             info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
    364         } catch (ClassCastException e) {
    365             return false;
    366         }
    367         switch (item.getItemId()) {
    368             case CONTEXT_MENU_HELP:
    369                 cancelTimeOut();
    370                 int position = info.position;
    371                 CatLog.d(this, "Position:" + position);
    372                 Item stkItem = getSelectedItem(position);
    373                 if (stkItem != null) {
    374                     CatLog.d(this, "item id:" + stkItem.id);
    375                     sendResponse(StkAppService.RES_ID_MENU_SELECTION, stkItem.id, true);
    376                 }
    377                 return true;
    378 
    379             default:
    380                 return super.onContextItemSelected(item);
    381         }
    382     }
    383 
    384     @Override
    385     protected void onSaveInstanceState(Bundle outState) {
    386         CatLog.d(LOG_TAG, "onSaveInstanceState: " + mSlotId);
    387         outState.putInt(STATE_KEY, mState);
    388         outState.putParcelable(MENU_KEY, mStkMenu);
    389         outState.putBoolean(ACCEPT_USERS_INPUT_KEY, mAcceptUsersInput);
    390         outState.putBoolean(RESPONSE_SENT_KEY, mIsResponseSent);
    391     }
    392 
    393     @Override
    394     protected void onRestoreInstanceState(Bundle savedInstanceState) {
    395         CatLog.d(LOG_TAG, "onRestoreInstanceState: " + mSlotId);
    396         mState = savedInstanceState.getInt(STATE_KEY);
    397         mStkMenu = savedInstanceState.getParcelable(MENU_KEY);
    398         mAcceptUsersInput = savedInstanceState.getBoolean(ACCEPT_USERS_INPUT_KEY);
    399         if (!mAcceptUsersInput) {
    400             // Check the latest information as the saved instance state can be outdated.
    401             if ((mState == STATE_MAIN) && appService.isMainMenuAvailable(mSlotId)) {
    402                 mAcceptUsersInput = true;
    403             } else {
    404                 showProgressBar(true);
    405             }
    406         }
    407         mIsResponseSent = savedInstanceState.getBoolean(RESPONSE_SENT_KEY);
    408     }
    409 
    410     private void cancelTimeOut() {
    411         CatLog.d(LOG_TAG, "cancelTimeOut: " + mSlotId);
    412         mTimeoutHandler.removeMessages(MSG_ID_TIMEOUT);
    413     }
    414 
    415     private void startTimeOut() {
    416         if (mState == STATE_SECONDARY) {
    417             // Reset timeout.
    418             cancelTimeOut();
    419             CatLog.d(LOG_TAG, "startTimeOut: " + mSlotId);
    420             mTimeoutHandler.sendMessageDelayed(mTimeoutHandler
    421                     .obtainMessage(MSG_ID_TIMEOUT), StkApp.UI_TIMEOUT);
    422         }
    423     }
    424 
    425     // Bind list adapter to the items list.
    426     private void displayMenu() {
    427 
    428         if (mStkMenu != null) {
    429             String title = mStkMenu.title == null ? getString(R.string.app_name) : mStkMenu.title;
    430             // Display title & title icon
    431             if (mStkMenu.titleIcon != null) {
    432                 mTitleIconView.setImageBitmap(mStkMenu.titleIcon);
    433                 mTitleIconView.setVisibility(View.VISIBLE);
    434                 mTitleTextView.setVisibility(View.INVISIBLE);
    435                 if (!mStkMenu.titleIconSelfExplanatory) {
    436                     mTitleTextView.setText(title);
    437                     mTitleTextView.setVisibility(View.VISIBLE);
    438                 }
    439             } else {
    440                 mTitleIconView.setVisibility(View.GONE);
    441                 mTitleTextView.setVisibility(View.VISIBLE);
    442                 mTitleTextView.setText(title);
    443             }
    444             // create an array adapter for the menu list
    445             StkMenuAdapter adapter = new StkMenuAdapter(this,
    446                     mStkMenu.items, mStkMenu.itemsIconSelfExplanatory);
    447             // Bind menu list to the new adapter.
    448             setListAdapter(adapter);
    449             // Set default item
    450             setSelection(mStkMenu.defaultItem);
    451         }
    452     }
    453 
    454     private void showProgressBar(boolean show) {
    455         if (show) {
    456             mProgressView.setIndeterminate(true);
    457             mProgressView.setVisibility(View.VISIBLE);
    458         } else {
    459             mProgressView.setIndeterminate(false);
    460             mProgressView.setVisibility(View.GONE);
    461         }
    462     }
    463 
    464     private void initFromIntent(Intent intent) {
    465 
    466         if (intent != null) {
    467             mState = intent.getIntExtra("STATE", STATE_MAIN);
    468             mSlotId = intent.getIntExtra(StkAppService.SLOT_ID, -1);
    469             CatLog.d(LOG_TAG, "slot id: " + mSlotId + ", state: " + mState);
    470         } else {
    471             CatLog.d(LOG_TAG, "finish!");
    472             finish();
    473         }
    474     }
    475 
    476     private Item getSelectedItem(int position) {
    477         Item item = null;
    478         if (mStkMenu != null) {
    479             try {
    480                 item = mStkMenu.items.get(position);
    481             } catch (IndexOutOfBoundsException e) {
    482                 if (StkApp.DBG) {
    483                     CatLog.d(LOG_TAG, "IOOBE Invalid menu");
    484                 }
    485             } catch (NullPointerException e) {
    486                 if (StkApp.DBG) {
    487                     CatLog.d(LOG_TAG, "NPE Invalid menu");
    488                 }
    489             }
    490         }
    491         return item;
    492     }
    493 
    494     private void sendResponse(int resId) {
    495         sendResponse(resId, 0, false);
    496     }
    497 
    498     private void sendResponse(int resId, int itemId, boolean help) {
    499         CatLog.d(LOG_TAG, "sendResponse resID[" + resId + "] itemId[" + itemId +
    500             "] help[" + help + "]");
    501 
    502         // Disallow user operation temporarily until receiving the result of the response.
    503         mAcceptUsersInput = false;
    504         if (resId == StkAppService.RES_ID_MENU_SELECTION) {
    505             showProgressBar(true);
    506         }
    507 
    508         mIsResponseSent = true;
    509         Bundle args = new Bundle();
    510         args.putInt(StkAppService.OPCODE, StkAppService.OP_RESPONSE);
    511         args.putInt(StkAppService.SLOT_ID, mSlotId);
    512         args.putInt(StkAppService.RES_ID, resId);
    513         args.putInt(StkAppService.MENU_SELECTION, itemId);
    514         args.putBoolean(StkAppService.HELP, help);
    515         mContext.startService(new Intent(mContext, StkAppService.class)
    516                 .putExtras(args));
    517     }
    518 
    519     private final BroadcastReceiver mLocalBroadcastReceiver = new BroadcastReceiver() {
    520         @Override
    521         public void onReceive(Context context, Intent intent) {
    522             if (StkAppService.SESSION_ENDED.equals(intent.getAction())) {
    523                 int slotId = intent.getIntExtra(StkAppService.SLOT_ID, 0);
    524                 if ((mState == STATE_MAIN) && (mSlotId == slotId)) {
    525                     mAcceptUsersInput = true;
    526                     showProgressBar(false);
    527                 }
    528             }
    529         }
    530     };
    531 }
    532