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