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.Activity;
20 import android.app.AlarmManager;
21 import android.app.AlertDialog;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.content.Intent;
25 import android.os.Bundle;
26 import android.os.SystemClock;
27 import android.telephony.SubscriptionManager;
28 import android.text.TextUtils;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.widget.ImageView;
32 import android.widget.TextView;
33 
34 import com.android.internal.telephony.cat.CatLog;
35 import com.android.internal.telephony.cat.TextMessage;
36 import com.android.internal.telephony.util.TelephonyUtils;
37 
38 /**
39  * AlertDialog used for DISPLAY TEXT commands.
40  *
41  */
42 public class StkDialogActivity extends Activity {
43     // members
44     private static final String LOG_TAG = StkDialogActivity.class.getSimpleName();
45     TextMessage mTextMsg = null;
46     private int mSlotId = -1;
47     private StkAppService appService = StkAppService.getInstance();
48     // Determines whether Terminal Response (TR) has been sent
49     private boolean mIsResponseSent = false;
50     // Utilize AlarmManager for real-time countdown
51     private static final String DIALOG_ALARM_TAG = LOG_TAG;
52     private static final long NO_DIALOG_ALARM = -1;
53     private long mAlarmTime = NO_DIALOG_ALARM;
54 
55     // Keys for saving the state of the dialog in the bundle
56     private static final String TEXT_KEY = "text";
57     private static final String ALARM_TIME_KEY = "alarm_time";
58     private static final String RESPONSE_SENT_KEY = "response_sent";
59     private static final String SLOT_ID_KEY = "slotid";
60 
61 
62     private AlertDialog mAlertDialog;
63 
64     @Override
onCreate(Bundle savedInstanceState)65     protected void onCreate(Bundle savedInstanceState) {
66         super.onCreate(savedInstanceState);
67 
68         CatLog.d(LOG_TAG, "onCreate, sim id: " + mSlotId);
69 
70         // appService can be null if this activity is automatically recreated by the system
71         // with the saved instance state right after the phone process is killed.
72         if (appService == null) {
73             CatLog.d(LOG_TAG, "onCreate - appService is null");
74             finish();
75             return;
76         }
77 
78         // New Dialog is created - set to no response sent
79         mIsResponseSent = false;
80 
81         AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
82 
83         alertDialogBuilder.setPositiveButton(R.string.button_ok, new
84                 DialogInterface.OnClickListener() {
85                     @Override
86                     public void onClick(DialogInterface dialog, int id) {
87                         CatLog.d(LOG_TAG, "OK Clicked!, mSlotId: " + mSlotId);
88                         sendResponse(StkAppService.RES_ID_CONFIRM, true);
89                     }
90                 });
91 
92         alertDialogBuilder.setNegativeButton(R.string.button_cancel, new
93                 DialogInterface.OnClickListener() {
94                     @Override
95                     public void onClick(DialogInterface dialog,int id) {
96                         CatLog.d(LOG_TAG, "Cancel Clicked!, mSlotId: " + mSlotId);
97                         sendResponse(StkAppService.RES_ID_CONFIRM, false);
98                     }
99                 });
100 
101         alertDialogBuilder.setOnCancelListener(new DialogInterface.OnCancelListener() {
102                     @Override
103                     public void onCancel(DialogInterface dialog) {
104                         CatLog.d(LOG_TAG, "Moving backward!, mSlotId: " + mSlotId);
105                         sendResponse(StkAppService.RES_ID_BACKWARD);
106                     }
107                 });
108 
109         alertDialogBuilder.create();
110 
111         initFromIntent(getIntent());
112         if (mTextMsg == null) {
113             finish();
114             return;
115         }
116 
117         if (!mTextMsg.responseNeeded) {
118             alertDialogBuilder.setNegativeButton(null, null);
119             // Register the instance of this activity because the dialog displayed for DISPLAY TEXT
120             // command with an immediate response object should disappear when the terminal receives
121             // a subsequent proactive command containing display data.
122             appService.getStkContext(mSlotId).setImmediateDialogInstance(this);
123         } else {
124             appService.getStkContext(mSlotId).setPendingDialogInstance(this);
125         }
126 
127         alertDialogBuilder.setTitle(mTextMsg.title);
128 
129         LayoutInflater inflater = this.getLayoutInflater();
130         View dialogView = inflater.inflate(R.layout.stk_msg_dialog, null);
131         alertDialogBuilder.setView(dialogView);
132         TextView tv = (TextView) dialogView.findViewById(R.id.message);
133         ImageView iv = (ImageView) dialogView.findViewById(R.id.icon);
134 
135         if (mTextMsg.icon != null) {
136             iv.setImageBitmap(mTextMsg.icon);
137         } else {
138             iv.setVisibility(View.GONE);
139         }
140 
141         // Per spec, only set text if the icon is not provided or not self-explanatory
142         if ((mTextMsg.icon == null || !mTextMsg.iconSelfExplanatory)
143                 && !TextUtils.isEmpty(mTextMsg.text)) {
144             tv.setText(mTextMsg.text);
145         } else {
146             tv.setVisibility(View.GONE);
147         }
148 
149         mAlertDialog = alertDialogBuilder.create();
150         mAlertDialog.setCanceledOnTouchOutside(false);
151         mAlertDialog.show();
152     }
153 
154     @Override
onResume()155     public void onResume() {
156         super.onResume();
157         CatLog.d(LOG_TAG, "onResume - mIsResponseSent[" + mIsResponseSent +
158                 "], sim id: " + mSlotId);
159         /*
160          * If the userClear flag is set and dialogduration is set to 0, the display Text
161          * should be displayed to user forever until some high priority event occurs
162          * (incoming call, MMI code execution etc as mentioned under section
163          * ETSI 102.223, 6.4.1)
164          */
165         if (StkApp.calculateDurationInMilis(mTextMsg.duration) == 0 &&
166                 !mTextMsg.responseNeeded && mTextMsg.userClear) {
167             CatLog.d(LOG_TAG, "User should clear text..showing message forever");
168             return;
169         }
170 
171         appService.setDisplayTextDlgVisibility(true, mSlotId);
172 
173         /*
174          * When another activity takes the foreground, we do not want the Terminal
175          * Response timer to be restarted when our activity resumes. Hence we will
176          * check if there is an existing timer, and resume it. In this way we will
177          * inform the SIM in correct time when there is no response from the User
178          * to a dialog.
179          */
180         if (mAlarmTime == NO_DIALOG_ALARM) {
181             startTimeOut();
182         }
183     }
184 
185     @Override
onPause()186     public void onPause() {
187         super.onPause();
188         CatLog.d(LOG_TAG, "onPause, sim id: " + mSlotId);
189         appService.setDisplayTextDlgVisibility(false, mSlotId);
190 
191         /*
192          * do not cancel the timer here cancelTimeOut(). If any higher/lower
193          * priority events such as incoming call, new sms, screen off intent,
194          * notification alerts, user actions such as 'User moving to another activtiy'
195          * etc.. occur during Display Text ongoing session,
196          * this activity would receive 'onPause()' event resulting in
197          * cancellation of the timer. As a result no terminal response is
198          * sent to the card.
199          */
200     }
201 
202     @Override
onStart()203     protected void onStart() {
204         CatLog.d(LOG_TAG, "onStart, sim id: " + mSlotId);
205         super.onStart();
206     }
207 
208     @Override
onStop()209     public void onStop() {
210         super.onStop();
211         CatLog.d(LOG_TAG, "onStop - before Send CONFIRM false mIsResponseSent[" +
212                 mIsResponseSent + "], sim id: " + mSlotId);
213     }
214 
215     @Override
onDestroy()216     public void onDestroy() {
217         super.onDestroy();
218         CatLog.d(LOG_TAG, "onDestroy - mIsResponseSent[" + mIsResponseSent +
219                 "], sim id: " + mSlotId);
220 
221         if (mAlertDialog != null && mAlertDialog.isShowing()) {
222             mAlertDialog.dismiss();
223             mAlertDialog = null;
224         }
225 
226         if (appService == null) {
227             return;
228         }
229         // if dialog activity is finished by stkappservice
230         // when receiving OP_LAUNCH_APP from the other SIM, we can not send TR here
231         // , since the dialog cmd is waiting user to process.
232         if (!isChangingConfigurations()) {
233             if (!mIsResponseSent && appService != null && !appService.isDialogPending(mSlotId)) {
234                 sendResponse(StkAppService.RES_ID_CONFIRM, false);
235             }
236         }
237         cancelTimeOut();
238     }
239 
240     @Override
onSaveInstanceState(Bundle outState)241     public void onSaveInstanceState(Bundle outState) {
242         super.onSaveInstanceState(outState);
243 
244         CatLog.d(LOG_TAG, "onSaveInstanceState");
245 
246         outState.putParcelable(TEXT_KEY, mTextMsg);
247         outState.putBoolean(RESPONSE_SENT_KEY, mIsResponseSent);
248         outState.putLong(ALARM_TIME_KEY, mAlarmTime);
249         outState.putInt(SLOT_ID_KEY, mSlotId);
250     }
251 
252     @Override
onRestoreInstanceState(Bundle savedInstanceState)253     public void onRestoreInstanceState(Bundle savedInstanceState) {
254         super.onRestoreInstanceState(savedInstanceState);
255 
256         CatLog.d(LOG_TAG, "onRestoreInstanceState");
257 
258         mTextMsg = savedInstanceState.getParcelable(TEXT_KEY);
259         mIsResponseSent = savedInstanceState.getBoolean(RESPONSE_SENT_KEY);
260         mAlarmTime = savedInstanceState.getLong(ALARM_TIME_KEY, NO_DIALOG_ALARM);
261         mSlotId = savedInstanceState.getInt(SLOT_ID_KEY);
262 
263         if (mAlarmTime != NO_DIALOG_ALARM) {
264             startTimeOut();
265         }
266 
267     }
268 
269     @Override
onNewIntent(Intent intent)270     protected void onNewIntent(Intent intent) {
271         CatLog.d(LOG_TAG, "onNewIntent - updating the same Dialog box");
272         setIntent(intent);
273     }
274 
275     @Override
finish()276     public void finish() {
277         super.finish();
278         // Unregister the instance for DISPLAY TEXT command with an immediate response object
279         // as it is unnecessary to ask the service to finish this anymore.
280         if ((appService != null) && (mTextMsg != null) && !mTextMsg.responseNeeded) {
281             if (SubscriptionManager.isValidSlotIndex(mSlotId)) {
282                 appService.getStkContext(mSlotId).setImmediateDialogInstance(null);
283             }
284         }
285     }
286 
sendResponse(int resId, boolean confirmed)287     private void sendResponse(int resId, boolean confirmed) {
288         cancelTimeOut();
289 
290         if (mSlotId == -1) {
291             CatLog.d(LOG_TAG, "sim id is invalid");
292             return;
293         }
294 
295         if (StkAppService.getInstance() == null) {
296             CatLog.d(LOG_TAG, "Ignore response: id is " + resId);
297             return;
298         }
299 
300         CatLog.d(LOG_TAG, "sendResponse resID[" + resId + "] confirmed[" + confirmed + "]");
301 
302         if (mTextMsg.responseNeeded) {
303             Bundle args = new Bundle();
304             args.putInt(StkAppService.OPCODE, StkAppService.OP_RESPONSE);
305             args.putInt(StkAppService.SLOT_ID, mSlotId);
306             args.putInt(StkAppService.RES_ID, resId);
307             args.putBoolean(StkAppService.CONFIRMATION, confirmed);
308             startService(new Intent(this, StkAppService.class).putExtras(args));
309             mIsResponseSent = true;
310         }
311         if (!isFinishing()) {
312             finish();
313         }
314 
315     }
316 
sendResponse(int resId)317     private void sendResponse(int resId) {
318         sendResponse(resId, true);
319     }
320 
initFromIntent(Intent intent)321     private void initFromIntent(Intent intent) {
322 
323         if (intent != null) {
324             mTextMsg = intent.getParcelableExtra("TEXT");
325             mSlotId = intent.getIntExtra(StkAppService.SLOT_ID, -1);
326         } else {
327             finish();
328         }
329 
330         CatLog.d(LOG_TAG, "initFromIntent - [" + (TelephonyUtils.IS_DEBUGGABLE ? mTextMsg : "********")
331                 + "], slot id: " + mSlotId);
332     }
333 
cancelTimeOut()334     private void cancelTimeOut() {
335         if (mAlarmTime != NO_DIALOG_ALARM) {
336             CatLog.d(LOG_TAG, "cancelTimeOut - slot id: " + mSlotId);
337             AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
338             am.cancel(mAlarmListener);
339             mAlarmTime = NO_DIALOG_ALARM;
340         }
341     }
342 
startTimeOut()343     private void startTimeOut() {
344         // No need to set alarm if device sent TERMINAL RESPONSE already
345         // and it is required to wait for user to clear the message.
346         if (mIsResponseSent || (mTextMsg.userClear && !mTextMsg.responseNeeded)) {
347             return;
348         }
349 
350         if (mAlarmTime == NO_DIALOG_ALARM) {
351             int duration = StkApp.calculateDurationInMilis(mTextMsg.duration);
352             // If no duration is specified, the timeout set by the terminal manufacturer is applied.
353             if (duration == 0) {
354                 if (mTextMsg.userClear) {
355                     duration = StkApp.DISP_TEXT_WAIT_FOR_USER_TIMEOUT;
356                 } else {
357                     duration = StkApp.DISP_TEXT_CLEAR_AFTER_DELAY_TIMEOUT;
358                 }
359             }
360             mAlarmTime = SystemClock.elapsedRealtime() + duration;
361         }
362 
363         CatLog.d(LOG_TAG, "startTimeOut: " + mAlarmTime + "ms, slot id: " + mSlotId);
364         AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
365         am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, mAlarmTime, DIALOG_ALARM_TAG,
366                 mAlarmListener, null);
367     }
368 
369     private final AlarmManager.OnAlarmListener mAlarmListener =
370             new AlarmManager.OnAlarmListener() {
371                 @Override
372                 public void onAlarm() {
373                     CatLog.d(LOG_TAG, "The alarm time is reached");
374                     mAlarmTime = NO_DIALOG_ALARM;
375                     sendResponse(StkAppService.RES_ID_TIMEOUT);
376                 }
377             };
378 }
379