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 com.android.internal.telephony.cat.CatLog; 20 import com.android.internal.telephony.cat.TextMessage; 21 22 import android.app.Activity; 23 import android.app.AlarmManager; 24 import android.app.AlertDialog; 25 import android.app.PendingIntent; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.content.BroadcastReceiver; 29 import android.content.Context; 30 import android.content.DialogInterface; 31 32 import android.os.Build; 33 import android.os.Bundle; 34 import android.os.SystemClock; 35 import android.telephony.SubscriptionManager; 36 import android.text.TextUtils; 37 import android.view.LayoutInflater; 38 import android.view.View; 39 import android.widget.ImageView; 40 import android.widget.TextView; 41 42 /** 43 * AlertDialog used for DISPLAY TEXT commands. 44 * 45 */ 46 public class StkDialogActivity extends Activity { 47 // members 48 private static final String className = new Object(){}.getClass().getEnclosingClass().getName(); 49 private static final String LOG_TAG = className.substring(className.lastIndexOf('.') + 1); 50 TextMessage mTextMsg = null; 51 private int mSlotId = -1; 52 private StkAppService appService = StkAppService.getInstance(); 53 // Determines whether Terminal Response (TR) has been sent 54 private boolean mIsResponseSent = false; 55 private Context mContext; 56 // Utilize AlarmManager for real-time countdown 57 private PendingIntent mTimeoutIntent; 58 private AlarmManager mAlarmManager; 59 private final static String ALARM_TIMEOUT = "com.android.stk.DIALOG_ALARM_TIMEOUT"; 60 61 // Keys for saving the state of the dialog in the bundle 62 private static final String TEXT_KEY = "text"; 63 private static final String TIMEOUT_INTENT_KEY = "timeout"; 64 private static final String SLOT_ID_KEY = "slotid"; 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 cancelTimeOut(); 93 sendResponse(StkAppService.RES_ID_CONFIRM, true); 94 finish(); 95 } 96 }); 97 98 alertDialogBuilder.setNegativeButton(R.string.button_cancel, new 99 DialogInterface.OnClickListener() { 100 @Override 101 public void onClick(DialogInterface dialog,int id) { 102 CatLog.d(LOG_TAG, "Cancel Clicked!, mSlotId: " + mSlotId); 103 cancelTimeOut(); 104 sendResponse(StkAppService.RES_ID_CONFIRM, false); 105 finish(); 106 } 107 }); 108 109 alertDialogBuilder.setOnCancelListener(new DialogInterface.OnCancelListener() { 110 @Override 111 public void onCancel(DialogInterface dialog) { 112 CatLog.d(LOG_TAG, "Moving backward!, mSlotId: " + mSlotId); 113 cancelTimeOut(); 114 sendResponse(StkAppService.RES_ID_BACKWARD); 115 finish(); 116 } 117 }); 118 119 alertDialogBuilder.create(); 120 121 initFromIntent(getIntent()); 122 if (mTextMsg == null) { 123 finish(); 124 return; 125 } 126 127 if (!mTextMsg.responseNeeded) { 128 alertDialogBuilder.setNegativeButton(null, null); 129 // Register the instance of this activity because the dialog displayed for DISPLAY TEXT 130 // command with an immediate response object should disappear when the terminal receives 131 // a subsequent proactive command containing display data. 132 appService.getStkContext(mSlotId).setImmediateDialogInstance(this); 133 } 134 135 alertDialogBuilder.setTitle(mTextMsg.title); 136 137 LayoutInflater inflater = this.getLayoutInflater(); 138 View dialogView = inflater.inflate(R.layout.stk_msg_dialog, null); 139 alertDialogBuilder.setView(dialogView); 140 TextView tv = (TextView) dialogView.findViewById(R.id.message); 141 ImageView iv = (ImageView) dialogView.findViewById(R.id.icon); 142 143 if (mTextMsg.icon != null) { 144 iv.setImageBitmap(mTextMsg.icon); 145 } else { 146 iv.setVisibility(View.GONE); 147 } 148 149 // Per spec, only set text if the icon is not provided or not self-explanatory 150 if ((mTextMsg.icon == null || !mTextMsg.iconSelfExplanatory) 151 && !TextUtils.isEmpty(mTextMsg.text)) { 152 tv.setText(mTextMsg.text); 153 } else { 154 tv.setVisibility(View.GONE); 155 } 156 157 mAlertDialog = alertDialogBuilder.create(); 158 mAlertDialog.setCanceledOnTouchOutside(false); 159 mAlertDialog.show(); 160 161 mContext = getBaseContext(); 162 IntentFilter intentFilter = new IntentFilter(); 163 intentFilter.addAction(ALARM_TIMEOUT); 164 mContext.registerReceiver(mBroadcastReceiver, intentFilter); 165 mAlarmManager =(AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); 166 } 167 168 @Override onResume()169 public void onResume() { 170 super.onResume(); 171 CatLog.d(LOG_TAG, "onResume - mIsResponseSent[" + mIsResponseSent + 172 "], sim id: " + mSlotId); 173 174 /* 175 * If the userClear flag is set and dialogduration is set to 0, the display Text 176 * should be displayed to user forever until some high priority event occurs 177 * (incoming call, MMI code execution etc as mentioned under section 178 * ETSI 102.223, 6.4.1) 179 */ 180 if (StkApp.calculateDurationInMilis(mTextMsg.duration) == 0 && 181 !mTextMsg.responseNeeded && mTextMsg.userClear) { 182 CatLog.d(LOG_TAG, "User should clear text..showing message forever"); 183 return; 184 } 185 186 appService.setDisplayTextDlgVisibility(true, mSlotId); 187 188 /* 189 * When another activity takes the foreground, we do not want the Terminal 190 * Response timer to be restarted when our activity resumes. Hence we will 191 * check if there is an existing timer, and resume it. In this way we will 192 * inform the SIM in correct time when there is no response from the User 193 * to a dialog. 194 */ 195 if (mTimeoutIntent != null) { 196 CatLog.d(LOG_TAG, "Pending Alarm! Let it finish counting down..."); 197 } 198 else { 199 CatLog.d(LOG_TAG, "No Pending Alarm! OK to start timer..."); 200 startTimeOut(mTextMsg.userClear); 201 } 202 } 203 204 @Override onPause()205 public void onPause() { 206 super.onPause(); 207 CatLog.d(LOG_TAG, "onPause, sim id: " + mSlotId); 208 appService.setDisplayTextDlgVisibility(false, mSlotId); 209 210 /* 211 * do not cancel the timer here cancelTimeOut(). If any higher/lower 212 * priority events such as incoming call, new sms, screen off intent, 213 * notification alerts, user actions such as 'User moving to another activtiy' 214 * etc.. occur during Display Text ongoing session, 215 * this activity would receive 'onPause()' event resulting in 216 * cancellation of the timer. As a result no terminal response is 217 * sent to the card. 218 */ 219 } 220 221 @Override onStart()222 protected void onStart() { 223 CatLog.d(LOG_TAG, "onStart, sim id: " + mSlotId); 224 super.onStart(); 225 } 226 227 @Override onStop()228 public void onStop() { 229 super.onStop(); 230 CatLog.d(LOG_TAG, "onStop - before Send CONFIRM false mIsResponseSent[" + 231 mIsResponseSent + "], sim id: " + mSlotId); 232 233 // Avoid calling finish() or setPendingDialogInstance() 234 // if the activity is being restarted now. 235 if (isChangingConfigurations()) { 236 return; 237 } 238 239 if (!mTextMsg.responseNeeded) { 240 return; 241 } 242 if (!mIsResponseSent) { 243 appService.getStkContext(mSlotId).setPendingDialogInstance(this); 244 } else { 245 CatLog.d(LOG_TAG, "finish."); 246 appService.getStkContext(mSlotId).setPendingDialogInstance(null); 247 cancelTimeOut(); 248 finish(); 249 } 250 } 251 252 @Override onDestroy()253 public void onDestroy() { 254 super.onDestroy(); 255 CatLog.d(LOG_TAG, "onDestroy - mIsResponseSent[" + mIsResponseSent + 256 "], sim id: " + mSlotId); 257 258 if (mAlertDialog != null && mAlertDialog.isShowing()) { 259 mAlertDialog.dismiss(); 260 mAlertDialog = null; 261 } 262 263 if (appService == null) { 264 return; 265 } 266 // if dialog activity is finished by stkappservice 267 // when receiving OP_LAUNCH_APP from the other SIM, we can not send TR here 268 // , since the dialog cmd is waiting user to process. 269 if (!isChangingConfigurations()) { 270 if (!mIsResponseSent && appService != null && !appService.isDialogPending(mSlotId)) { 271 sendResponse(StkAppService.RES_ID_CONFIRM, false); 272 } 273 cancelTimeOut(); 274 } 275 // Cleanup broadcast receivers to avoid leaks 276 if (mBroadcastReceiver != null) { 277 unregisterReceiver(mBroadcastReceiver); 278 } 279 } 280 281 @Override onSaveInstanceState(Bundle outState)282 public void onSaveInstanceState(Bundle outState) { 283 CatLog.d(LOG_TAG, "onSaveInstanceState"); 284 285 super.onSaveInstanceState(outState); 286 287 outState.putParcelable(TEXT_KEY, mTextMsg); 288 outState.putParcelable(TIMEOUT_INTENT_KEY, mTimeoutIntent); 289 outState.putInt(SLOT_ID_KEY, mSlotId); 290 } 291 292 @Override onRestoreInstanceState(Bundle savedInstanceState)293 public void onRestoreInstanceState(Bundle savedInstanceState) { 294 super.onRestoreInstanceState(savedInstanceState); 295 296 mTextMsg = savedInstanceState.getParcelable(TEXT_KEY); 297 mTimeoutIntent = savedInstanceState.getParcelable(TIMEOUT_INTENT_KEY); 298 mSlotId = savedInstanceState.getInt(SLOT_ID_KEY); 299 appService.getStkContext(mSlotId).setPendingDialogInstance(this); 300 CatLog.d(LOG_TAG, "onRestoreInstanceState - [" + mTextMsg + "]"); 301 } 302 303 @Override onNewIntent(Intent intent)304 protected void onNewIntent(Intent intent) { 305 CatLog.d(LOG_TAG, "onNewIntent - updating the same Dialog box"); 306 setIntent(intent); 307 } 308 309 @Override finish()310 public void finish() { 311 super.finish(); 312 // Unregister the instance for DISPLAY TEXT command with an immediate response object 313 // as it is unnecessary to ask the service to finish this anymore. 314 if ((appService != null) && (mTextMsg != null) && !mTextMsg.responseNeeded) { 315 if (SubscriptionManager.isValidSlotIndex(mSlotId)) { 316 appService.getStkContext(mSlotId).setImmediateDialogInstance(null); 317 } 318 } 319 } 320 sendResponse(int resId, boolean confirmed)321 private void sendResponse(int resId, boolean confirmed) { 322 if (mSlotId == -1) { 323 CatLog.d(LOG_TAG, "sim id is invalid"); 324 return; 325 } 326 327 if (StkAppService.getInstance() == null) { 328 CatLog.d(LOG_TAG, "Ignore response: id is " + resId); 329 return; 330 } 331 332 CatLog.d(LOG_TAG, "sendResponse resID[" + resId + "] confirmed[" + confirmed + "]"); 333 334 if (mTextMsg.responseNeeded) { 335 Bundle args = new Bundle(); 336 args.putInt(StkAppService.OPCODE, StkAppService.OP_RESPONSE); 337 args.putInt(StkAppService.SLOT_ID, mSlotId); 338 args.putInt(StkAppService.RES_ID, resId); 339 args.putBoolean(StkAppService.CONFIRMATION, confirmed); 340 startService(new Intent(this, StkAppService.class).putExtras(args)); 341 mIsResponseSent = true; 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 CatLog.d(LOG_TAG, "cancelTimeOut: " + mSlotId); 364 if (mTimeoutIntent != null) { 365 mAlarmManager.cancel(mTimeoutIntent); 366 mTimeoutIntent = null; 367 } 368 } 369 startTimeOut(boolean waitForUserToClear)370 private void startTimeOut(boolean waitForUserToClear) { 371 372 // Reset timeout. 373 cancelTimeOut(); 374 int dialogDuration = StkApp.calculateDurationInMilis(mTextMsg.duration); 375 // If duration is specified, this has priority. If not, set timeout 376 // according to condition given by the card. 377 if (mTextMsg.userClear == true && mTextMsg.responseNeeded == false) { 378 return; 379 } else { 380 // userClear = false. will disappear after a while. 381 if (dialogDuration == 0) { 382 if (waitForUserToClear) { 383 dialogDuration = StkApp.DISP_TEXT_WAIT_FOR_USER_TIMEOUT; 384 } else { 385 dialogDuration = StkApp.DISP_TEXT_CLEAR_AFTER_DELAY_TIMEOUT; 386 } 387 } 388 CatLog.d(LOG_TAG, "startTimeOut: " + mSlotId); 389 Intent mAlarmIntent = new Intent(ALARM_TIMEOUT); 390 mAlarmIntent.putExtra(StkAppService.SLOT_ID, mSlotId); 391 mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, mAlarmIntent, PendingIntent.FLAG_CANCEL_CURRENT); 392 393 // Try to use a more stringent timer not affected by system sleep. 394 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { 395 mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, 396 SystemClock.elapsedRealtime() + dialogDuration, mTimeoutIntent); 397 } 398 else { 399 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 400 SystemClock.elapsedRealtime() + dialogDuration, mTimeoutIntent); 401 } 402 } 403 } 404 405 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 406 @Override public void onReceive(Context context, Intent intent) { 407 String action = intent.getAction(); 408 int slotID = intent.getIntExtra(StkAppService.SLOT_ID, 0); 409 410 if (action == null || slotID != mSlotId) return; 411 CatLog.d(LOG_TAG, "onReceive, action=" + action + ", sim id: " + slotID); 412 if (action.equals(ALARM_TIMEOUT)) { 413 CatLog.d(LOG_TAG, "ALARM_TIMEOUT rcvd"); 414 mTimeoutIntent = null; 415 sendResponse(StkAppService.RES_ID_TIMEOUT); 416 finish(); 417 } 418 } 419 }; 420 } 421