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 com.android.internal.telephony.cat.CatLog;
20import com.android.internal.telephony.cat.TextMessage;
21
22import android.app.Activity;
23import android.app.AlarmManager;
24import android.app.AlertDialog;
25import android.app.PendingIntent;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.BroadcastReceiver;
29import android.content.Context;
30import android.content.DialogInterface;
31import android.view.KeyEvent;
32
33import android.os.Bundle;
34import android.os.SystemClock;
35
36/**
37 * AlertDialog used for DISPLAY TEXT commands.
38 *
39 */
40public class StkDialogActivity extends Activity {
41    // members
42    private static final String className = new Object(){}.getClass().getEnclosingClass().getName();
43    private static final String LOG_TAG = className.substring(className.lastIndexOf('.') + 1);
44    TextMessage mTextMsg = null;
45    private int mSlotId = -1;
46    private StkAppService appService = StkAppService.getInstance();
47    // Determines whether Terminal Response (TR) has been sent
48    private boolean mIsResponseSent = false;
49    private Context mContext;
50    // Utilize AlarmManager for real-time countdown
51    private PendingIntent mTimeoutIntent;
52    private AlarmManager mAlarmManager;
53    private final static String ALARM_TIMEOUT = "com.android.stk.DIALOG_ALARM_TIMEOUT";
54
55    //keys) for saving the state of the dialog in the icicle
56    private static final String TEXT = "text";
57
58    private AlertDialog.Builder alertDialogBuilder;
59
60    @Override
61    protected void onCreate(Bundle icicle) {
62        super.onCreate(icicle);
63
64        CatLog.d(LOG_TAG, "onCreate, sim id: " + mSlotId);
65
66        // appService can be null if this activity is automatically recreated by the system
67        // with the saved instance state right after the phone process is killed.
68        if (appService == null) {
69            CatLog.d(LOG_TAG, "onCreate - appService is null");
70            finish();
71            return;
72        }
73
74        // New Dialog is created - set to no response sent
75        mIsResponseSent = false;
76
77        alertDialogBuilder = new AlertDialog.Builder(this);
78
79        alertDialogBuilder.setPositiveButton(R.string.button_ok, new
80                DialogInterface.OnClickListener() {
81                    @Override
82                    public void onClick(DialogInterface dialog, int id) {
83                        CatLog.d(LOG_TAG, "OK Clicked!, mSlotId: " + mSlotId);
84                        cancelTimeOut();
85                        sendResponse(StkAppService.RES_ID_CONFIRM, true);
86                        finish();
87                    }
88                });
89
90        alertDialogBuilder.setNegativeButton(R.string.button_cancel, new
91                DialogInterface.OnClickListener() {
92                    @Override
93                    public void onClick(DialogInterface dialog,int id) {
94                        CatLog.d(LOG_TAG, "Cancel Clicked!, mSlotId: " + mSlotId);
95                        cancelTimeOut();
96                        sendResponse(StkAppService.RES_ID_CONFIRM, false);
97                        finish();
98                    }
99                });
100        alertDialogBuilder.create();
101
102        mContext = getBaseContext();
103        IntentFilter intentFilter = new IntentFilter();
104        intentFilter.addAction(ALARM_TIMEOUT);
105        mContext.registerReceiver(mBroadcastReceiver, intentFilter);
106        mAlarmManager =(AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
107
108        setFinishOnTouchOutside(false);
109    }
110
111    @Override
112    public boolean onKeyDown(int keyCode, KeyEvent event) {
113        switch (keyCode) {
114            case KeyEvent.KEYCODE_BACK:
115                CatLog.d(LOG_TAG, "onKeyDown - KEYCODE_BACK");
116                cancelTimeOut();
117                sendResponse(StkAppService.RES_ID_BACKWARD);
118                finish();
119                break;
120        }
121        return false;
122    }
123
124    @Override
125    public void onResume() {
126        super.onResume();
127        CatLog.d(LOG_TAG, "onResume - mIsResponseSent[" + mIsResponseSent +
128                "], sim id: " + mSlotId);
129
130        initFromIntent(getIntent());
131        if (mTextMsg == null) {
132            finish();
133            return;
134        }
135
136        alertDialogBuilder.setTitle(mTextMsg.title);
137
138        if (!(mTextMsg.iconSelfExplanatory && mTextMsg.icon != null)) {
139            alertDialogBuilder.setMessage(mTextMsg.text);
140        }
141        alertDialogBuilder.show();
142
143        /*
144         * If the userClear flag is set and dialogduration is set to 0, the display Text
145         * should be displayed to user forever until some high priority event occurs
146         * (incoming call, MMI code execution etc as mentioned under section
147         * ETSI 102.223, 6.4.1)
148         */
149        if (StkApp.calculateDurationInMilis(mTextMsg.duration) == 0 &&
150                !mTextMsg.responseNeeded && mTextMsg.userClear) {
151            CatLog.d(LOG_TAG, "User should clear text..showing message forever");
152            return;
153        }
154
155        appService.setDisplayTextDlgVisibility(true, mSlotId);
156
157        /*
158         * When another activity takes the foreground, we do not want the Terminal
159         * Response timer to be restarted when our activity resumes. Hence we will
160         * check if there is an existing timer, and resume it. In this way we will
161         * inform the SIM in correct time when there is no response from the User
162         * to a dialog.
163         */
164        if (mTimeoutIntent != null) {
165            CatLog.d(LOG_TAG, "Pending Alarm! Let it finish counting down...");
166        }
167        else {
168            CatLog.d(LOG_TAG, "No Pending Alarm! OK to start timer...");
169            startTimeOut(mTextMsg.userClear);
170        }
171    }
172
173    @Override
174    public void onPause() {
175        super.onPause();
176        CatLog.d(LOG_TAG, "onPause, sim id: " + mSlotId);
177        appService.setDisplayTextDlgVisibility(false, mSlotId);
178
179        /*
180         * do not cancel the timer here cancelTimeOut(). If any higher/lower
181         * priority events such as incoming call, new sms, screen off intent,
182         * notification alerts, user actions such as 'User moving to another activtiy'
183         * etc.. occur during Display Text ongoing session,
184         * this activity would receive 'onPause()' event resulting in
185         * cancellation of the timer. As a result no terminal response is
186         * sent to the card.
187         */
188    }
189
190    @Override
191    protected void onStart() {
192        CatLog.d(LOG_TAG, "onStart, sim id: " + mSlotId);
193        super.onStart();
194    }
195
196    @Override
197    public void onStop() {
198        super.onStop();
199        CatLog.d(LOG_TAG, "onStop - before Send CONFIRM false mIsResponseSent[" +
200                mIsResponseSent + "], sim id: " + mSlotId);
201        if (!mTextMsg.responseNeeded) {
202            return;
203        }
204        if (!mIsResponseSent) {
205            appService.getStkContext(mSlotId).setPendingDialogInstance(this);
206        } else {
207            CatLog.d(LOG_TAG, "finish.");
208            appService.getStkContext(mSlotId).setPendingDialogInstance(null);
209            cancelTimeOut();
210            finish();
211        }
212    }
213
214    @Override
215    public void onDestroy() {
216        super.onDestroy();
217        CatLog.d(LOG_TAG, "onDestroy - mIsResponseSent[" + mIsResponseSent +
218                "], sim id: " + mSlotId);
219        if (appService == null) {
220            return;
221        }
222        // if dialog activity is finished by stkappservice
223        // when receiving OP_LAUNCH_APP from the other SIM, we can not send TR here
224        // , since the dialog cmd is waiting user to process.
225        if (!mIsResponseSent && !appService.isDialogPending(mSlotId)) {
226            sendResponse(StkAppService.RES_ID_CONFIRM, false);
227        }
228        cancelTimeOut();
229        // Cleanup broadcast receivers to avoid leaks
230        if (mBroadcastReceiver != null) {
231            unregisterReceiver(mBroadcastReceiver);
232        }
233    }
234
235    @Override
236    public void onSaveInstanceState(Bundle outState) {
237        CatLog.d(LOG_TAG, "onSaveInstanceState");
238
239        super.onSaveInstanceState(outState);
240
241        outState.putParcelable(TEXT, mTextMsg);
242    }
243
244    @Override
245    public void onRestoreInstanceState(Bundle savedInstanceState) {
246        super.onRestoreInstanceState(savedInstanceState);
247
248        mTextMsg = savedInstanceState.getParcelable(TEXT);
249        CatLog.d(LOG_TAG, "onRestoreInstanceState - [" + mTextMsg + "]");
250    }
251
252    @Override
253    protected void onNewIntent(Intent intent) {
254        CatLog.d(LOG_TAG, "onNewIntent - updating the same Dialog box");
255        setIntent(intent);
256    }
257
258    private void sendResponse(int resId, boolean confirmed) {
259        if (mSlotId == -1) {
260            CatLog.d(LOG_TAG, "sim id is invalid");
261            return;
262        }
263
264        if (StkAppService.getInstance() == null) {
265            CatLog.d(LOG_TAG, "Ignore response: id is " + resId);
266            return;
267        }
268
269        CatLog.d(LOG_TAG, "sendResponse resID[" + resId + "] confirmed[" + confirmed + "]");
270
271        if (mTextMsg.responseNeeded) {
272            Bundle args = new Bundle();
273            args.putInt(StkAppService.OPCODE, StkAppService.OP_RESPONSE);
274            args.putInt(StkAppService.SLOT_ID, mSlotId);
275            args.putInt(StkAppService.RES_ID, resId);
276            args.putBoolean(StkAppService.CONFIRMATION, confirmed);
277            startService(new Intent(this, StkAppService.class).putExtras(args));
278            mIsResponseSent = true;
279        }
280    }
281
282    private void sendResponse(int resId) {
283        sendResponse(resId, true);
284    }
285
286    private void initFromIntent(Intent intent) {
287
288        if (intent != null) {
289            mTextMsg = intent.getParcelableExtra("TEXT");
290            mSlotId = intent.getIntExtra(StkAppService.SLOT_ID, -1);
291        } else {
292            finish();
293        }
294
295        CatLog.d(LOG_TAG, "initFromIntent - [" + mTextMsg + "], sim id: " + mSlotId);
296    }
297
298    private void cancelTimeOut() {
299        CatLog.d(LOG_TAG, "cancelTimeOut: " + mSlotId);
300        if (mTimeoutIntent != null) {
301            mAlarmManager.cancel(mTimeoutIntent);
302            mTimeoutIntent = null;
303        }
304    }
305
306    private void startTimeOut(boolean waitForUserToClear) {
307
308        // Reset timeout.
309        cancelTimeOut();
310        int dialogDuration = StkApp.calculateDurationInMilis(mTextMsg.duration);
311        // If duration is specified, this has priority. If not, set timeout
312        // according to condition given by the card.
313        if (mTextMsg.userClear == true && mTextMsg.responseNeeded == false) {
314            return;
315        } else {
316            // userClear = false. will disappear after a while.
317            if (dialogDuration == 0) {
318                if (waitForUserToClear) {
319                    dialogDuration = StkApp.DISP_TEXT_WAIT_FOR_USER_TIMEOUT;
320                } else {
321                    dialogDuration = StkApp.DISP_TEXT_CLEAR_AFTER_DELAY_TIMEOUT;
322                }
323            }
324            CatLog.d(LOG_TAG, "startTimeOut: " + mSlotId);
325            Intent mAlarmIntent = new Intent(ALARM_TIMEOUT);
326            mAlarmIntent.putExtra(StkAppService.SLOT_ID, mSlotId);
327            mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, mAlarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);
328
329            // Try to use a more stringent timer not affected by system sleep.
330            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
331                mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
332                        SystemClock.elapsedRealtime() + dialogDuration, mTimeoutIntent);
333            }
334            else {
335                mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
336                        SystemClock.elapsedRealtime() + dialogDuration, mTimeoutIntent);
337            }
338        }
339    }
340
341    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
342        @Override public void onReceive(Context context, Intent intent) {
343            String action = intent.getAction();
344            int slotID = intent.getIntExtra(StkAppService.SLOT_ID, 0);
345
346            if (action == null || slotID != mSlotId) return;
347            CatLog.d(LOG_TAG, "onReceive, action=" + action + ", sim id: " + slotID);
348            if (action.equals(ALARM_TIMEOUT)) {
349                CatLog.d(LOG_TAG, "ALARM_TIMEOUT rcvd");
350                mTimeoutIntent = null;
351                sendResponse(StkAppService.RES_ID_TIMEOUT);
352                finish();
353            }
354        }
355    };
356}
357