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