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