1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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.ActionBar; 20 import android.app.Activity; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.res.Configuration; 24 import android.graphics.drawable.BitmapDrawable; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.os.Message; 28 import android.text.Editable; 29 import android.text.InputFilter; 30 import android.text.InputType; 31 import android.text.TextUtils; 32 import android.text.TextWatcher; 33 import android.text.method.PasswordTransformationMethod; 34 import android.view.inputmethod.EditorInfo; 35 import android.view.KeyEvent; 36 import android.view.Menu; 37 import android.view.MenuItem; 38 import android.view.View; 39 import android.view.Window; 40 import android.widget.Button; 41 import android.widget.ImageView; 42 import android.widget.PopupMenu; 43 import android.widget.TextView; 44 import android.widget.EditText; 45 import android.widget.TextView.BufferType; 46 import com.android.internal.telephony.cat.CatLog; 47 import com.android.internal.telephony.cat.FontSize; 48 import com.android.internal.telephony.cat.Input; 49 50 /** 51 * Display a request for a text input a long with a text edit form. 52 */ 53 public class StkInputActivity extends Activity implements View.OnClickListener, 54 TextWatcher { 55 56 // Members 57 private int mState; 58 private Context mContext; 59 private EditText mTextIn = null; 60 private TextView mPromptView = null; 61 private View mMoreOptions = null; 62 private PopupMenu mPopupMenu = null; 63 private View mYesNoLayout = null; 64 private View mNormalLayout = null; 65 66 // Constants 67 private static final String className = new Object(){}.getClass().getEnclosingClass().getName(); 68 private static final String LOG_TAG = className.substring(className.lastIndexOf('.') + 1); 69 70 private Input mStkInput = null; 71 private boolean mAcceptUsersInput = true; 72 // Constants 73 private static final int STATE_TEXT = 1; 74 private static final int STATE_YES_NO = 2; 75 76 static final String YES_STR_RESPONSE = "YES"; 77 static final String NO_STR_RESPONSE = "NO"; 78 79 // Font size factor values. 80 static final float NORMAL_FONT_FACTOR = 1; 81 static final float LARGE_FONT_FACTOR = 2; 82 static final float SMALL_FONT_FACTOR = (1 / 2); 83 84 // Keys for saving the state of the activity in the bundle 85 private static final String ACCEPT_USERS_INPUT_KEY = "accept_users_input"; 86 private static final String RESPONSE_SENT_KEY = "response_sent"; 87 private static final String INPUT_STRING_KEY = "input_string"; 88 89 // message id for time out 90 private static final int MSG_ID_TIMEOUT = 1; 91 private StkAppService appService = StkAppService.getInstance(); 92 93 private boolean mIsResponseSent = false; 94 private int mSlotId = -1; 95 Activity mInstance = null; 96 97 Handler mTimeoutHandler = new Handler() { 98 @Override 99 public void handleMessage(Message msg) { 100 switch(msg.what) { 101 case MSG_ID_TIMEOUT: 102 CatLog.d(LOG_TAG, "Msg timeout."); 103 mAcceptUsersInput = false; 104 appService.getStkContext(mSlotId).setPendingActivityInstance(mInstance); 105 sendResponse(StkAppService.RES_ID_TIMEOUT); 106 break; 107 } 108 } 109 }; 110 111 // Click listener to handle buttons press.. 112 public void onClick(View v) { 113 String input = null; 114 if (!mAcceptUsersInput) { 115 CatLog.d(LOG_TAG, "mAcceptUsersInput:false"); 116 return; 117 } 118 119 switch (v.getId()) { 120 case R.id.button_ok: 121 // Check that text entered is valid . 122 if (!verfiyTypedText()) { 123 CatLog.d(LOG_TAG, "handleClick, invalid text"); 124 return; 125 } 126 mAcceptUsersInput = false; 127 input = mTextIn.getText().toString(); 128 break; 129 case R.id.button_cancel: 130 mAcceptUsersInput = false; 131 cancelTimeOut(); 132 appService.getStkContext(mSlotId).setPendingActivityInstance(this); 133 sendResponse(StkAppService.RES_ID_END_SESSION); 134 return; 135 // Yes/No layout buttons. 136 case R.id.button_yes: 137 mAcceptUsersInput = false; 138 input = YES_STR_RESPONSE; 139 break; 140 case R.id.button_no: 141 mAcceptUsersInput = false; 142 input = NO_STR_RESPONSE; 143 break; 144 case R.id.more: 145 if (mPopupMenu == null) { 146 mPopupMenu = new PopupMenu(this, v); 147 Menu menu = mPopupMenu.getMenu(); 148 createOptionsMenuInternal(menu); 149 prepareOptionsMenuInternal(menu); 150 mPopupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { 151 public boolean onMenuItemClick(MenuItem item) { 152 optionsItemSelectedInternal(item); 153 return true; 154 } 155 }); 156 mPopupMenu.setOnDismissListener(new PopupMenu.OnDismissListener() { 157 public void onDismiss(PopupMenu menu) { 158 mPopupMenu = null; 159 } 160 }); 161 mPopupMenu.show(); 162 } 163 return; 164 default: 165 break; 166 } 167 CatLog.d(LOG_TAG, "handleClick, ready to response"); 168 cancelTimeOut(); 169 appService.getStkContext(mSlotId).setPendingActivityInstance(this); 170 sendResponse(StkAppService.RES_ID_INPUT, input, false); 171 } 172 173 @Override 174 public void onCreate(Bundle savedInstanceState) { 175 super.onCreate(savedInstanceState); 176 177 CatLog.d(LOG_TAG, "onCreate - mIsResponseSent[" + mIsResponseSent + "]"); 178 179 // appService can be null if this activity is automatically recreated by the system 180 // with the saved instance state right after the phone process is killed. 181 if (appService == null) { 182 CatLog.d(LOG_TAG, "onCreate - appService is null"); 183 finish(); 184 return; 185 } 186 187 ActionBar actionBar = null; 188 if (getResources().getBoolean(R.bool.show_menu_title_only_on_menu)) { 189 actionBar = getActionBar(); 190 if (actionBar != null) { 191 actionBar.hide(); 192 } 193 } 194 195 // Set the layout for this activity. 196 setContentView(R.layout.stk_input); 197 198 if (actionBar != null) { 199 mMoreOptions = findViewById(R.id.more); 200 mMoreOptions.setVisibility(View.VISIBLE); 201 mMoreOptions.setOnClickListener(this); 202 } 203 204 // Initialize members 205 mTextIn = (EditText) this.findViewById(R.id.in_text); 206 mPromptView = (TextView) this.findViewById(R.id.prompt); 207 mInstance = this; 208 // Set buttons listeners. 209 Button okButton = (Button) findViewById(R.id.button_ok); 210 Button cancelButton = (Button) findViewById(R.id.button_cancel); 211 Button yesButton = (Button) findViewById(R.id.button_yes); 212 Button noButton = (Button) findViewById(R.id.button_no); 213 214 okButton.setOnClickListener(this); 215 cancelButton.setOnClickListener(this); 216 yesButton.setOnClickListener(this); 217 noButton.setOnClickListener(this); 218 219 mYesNoLayout = findViewById(R.id.yes_no_layout); 220 mNormalLayout = findViewById(R.id.normal_layout); 221 initFromIntent(getIntent()); 222 mContext = getBaseContext(); 223 mAcceptUsersInput = true; 224 } 225 226 @Override 227 protected void onPostCreate(Bundle savedInstanceState) { 228 super.onPostCreate(savedInstanceState); 229 230 mTextIn.addTextChangedListener(this); 231 } 232 233 @Override 234 public void onResume() { 235 super.onResume(); 236 CatLog.d(LOG_TAG, "onResume - mIsResponseSent[" + mIsResponseSent + 237 "], slot id: " + mSlotId); 238 startTimeOut(); 239 } 240 241 @Override 242 public void onPause() { 243 super.onPause(); 244 CatLog.d(LOG_TAG, "onPause - mIsResponseSent[" + mIsResponseSent + "]"); 245 if (mPopupMenu != null) { 246 mPopupMenu.dismiss(); 247 } 248 } 249 250 @Override 251 public void onStop() { 252 super.onStop(); 253 CatLog.d(LOG_TAG, "onStop - mIsResponseSent[" + mIsResponseSent + "]"); 254 255 // Nothing should be done here if this activity is being restarted now. 256 if (isChangingConfigurations()) { 257 return; 258 } 259 260 // It is unnecessary to keep this activity if the response was already sent and 261 // this got invisible because of the other full-screen activity in this application. 262 if (mIsResponseSent && appService.isTopOfStack()) { 263 cancelTimeOut(); 264 finish(); 265 } else { 266 appService.getStkContext(mSlotId).setPendingActivityInstance(this); 267 } 268 } 269 270 @Override 271 public void onDestroy() { 272 super.onDestroy(); 273 CatLog.d(LOG_TAG, "onDestroy - before Send End Session mIsResponseSent[" + 274 mIsResponseSent + " , " + mSlotId + "]"); 275 if (appService == null) { 276 return; 277 } 278 // Avoid sending the terminal response while the activty is being restarted 279 // due to some kind of configuration change. 280 if (!isChangingConfigurations()) { 281 // If the input activity is finished by stkappservice 282 // when receiving OP_LAUNCH_APP from the other SIM, we can not send TR here, 283 // since the input cmd is waiting user to process. 284 if (!mIsResponseSent && !appService.isInputPending(mSlotId)) { 285 CatLog.d(LOG_TAG, "handleDestroy - Send End Session"); 286 sendResponse(StkAppService.RES_ID_END_SESSION); 287 } 288 cancelTimeOut(); 289 } 290 } 291 292 @Override 293 public void onConfigurationChanged(Configuration newConfig) { 294 super.onConfigurationChanged(newConfig); 295 if (mPopupMenu != null) { 296 mPopupMenu.dismiss(); 297 } 298 } 299 300 @Override 301 public boolean onKeyDown(int keyCode, KeyEvent event) { 302 if (!mAcceptUsersInput) { 303 CatLog.d(LOG_TAG, "mAcceptUsersInput:false"); 304 return true; 305 } 306 307 switch (keyCode) { 308 case KeyEvent.KEYCODE_BACK: 309 CatLog.d(LOG_TAG, "onKeyDown - KEYCODE_BACK"); 310 mAcceptUsersInput = false; 311 cancelTimeOut(); 312 appService.getStkContext(mSlotId).setPendingActivityInstance(this); 313 sendResponse(StkAppService.RES_ID_BACKWARD, null, false); 314 return true; 315 } 316 return super.onKeyDown(keyCode, event); 317 } 318 319 void sendResponse(int resId) { 320 sendResponse(resId, null, false); 321 } 322 323 void sendResponse(int resId, String input, boolean help) { 324 if (mSlotId == -1) { 325 CatLog.d(LOG_TAG, "slot id is invalid"); 326 return; 327 } 328 329 if (StkAppService.getInstance() == null) { 330 CatLog.d(LOG_TAG, "StkAppService is null, Ignore response: id is " + resId); 331 return; 332 } 333 334 if (mMoreOptions != null) { 335 mMoreOptions.setVisibility(View.INVISIBLE); 336 } 337 338 CatLog.d(LOG_TAG, "sendResponse resID[" + resId + "] input[*****] help[" 339 + help + "]"); 340 mIsResponseSent = true; 341 Bundle args = new Bundle(); 342 args.putInt(StkAppService.OPCODE, StkAppService.OP_RESPONSE); 343 args.putInt(StkAppService.SLOT_ID, mSlotId); 344 args.putInt(StkAppService.RES_ID, resId); 345 if (input != null) { 346 args.putString(StkAppService.INPUT, input); 347 } 348 args.putBoolean(StkAppService.HELP, help); 349 mContext.startService(new Intent(mContext, StkAppService.class) 350 .putExtras(args)); 351 } 352 353 @Override 354 public boolean onCreateOptionsMenu(android.view.Menu menu) { 355 super.onCreateOptionsMenu(menu); 356 createOptionsMenuInternal(menu); 357 return true; 358 } 359 360 private void createOptionsMenuInternal(Menu menu) { 361 menu.add(Menu.NONE, StkApp.MENU_ID_END_SESSION, 1, R.string.menu_end_session); 362 menu.add(0, StkApp.MENU_ID_HELP, 2, R.string.help); 363 } 364 365 @Override 366 public boolean onPrepareOptionsMenu(android.view.Menu menu) { 367 super.onPrepareOptionsMenu(menu); 368 prepareOptionsMenuInternal(menu); 369 return true; 370 } 371 372 private void prepareOptionsMenuInternal(Menu menu) { 373 menu.findItem(StkApp.MENU_ID_END_SESSION).setVisible(true); 374 menu.findItem(StkApp.MENU_ID_HELP).setVisible(mStkInput.helpAvailable); 375 } 376 377 @Override 378 public boolean onOptionsItemSelected(MenuItem item) { 379 if (optionsItemSelectedInternal(item)) { 380 return true; 381 } 382 return super.onOptionsItemSelected(item); 383 } 384 385 private boolean optionsItemSelectedInternal(MenuItem item) { 386 if (!mAcceptUsersInput) { 387 CatLog.d(LOG_TAG, "mAcceptUsersInput:false"); 388 return true; 389 } 390 switch (item.getItemId()) { 391 case StkApp.MENU_ID_END_SESSION: 392 mAcceptUsersInput = false; 393 cancelTimeOut(); 394 sendResponse(StkAppService.RES_ID_END_SESSION); 395 finish(); 396 return true; 397 case StkApp.MENU_ID_HELP: 398 mAcceptUsersInput = false; 399 cancelTimeOut(); 400 sendResponse(StkAppService.RES_ID_INPUT, "", true); 401 finish(); 402 return true; 403 } 404 return false; 405 } 406 407 @Override 408 protected void onSaveInstanceState(Bundle outState) { 409 CatLog.d(LOG_TAG, "onSaveInstanceState: " + mSlotId); 410 outState.putBoolean(ACCEPT_USERS_INPUT_KEY, mAcceptUsersInput); 411 outState.putBoolean(RESPONSE_SENT_KEY, mIsResponseSent); 412 outState.putString(INPUT_STRING_KEY, mTextIn.getText().toString()); 413 } 414 415 @Override 416 protected void onRestoreInstanceState(Bundle savedInstanceState) { 417 CatLog.d(LOG_TAG, "onRestoreInstanceState: " + mSlotId); 418 419 mAcceptUsersInput = savedInstanceState.getBoolean(ACCEPT_USERS_INPUT_KEY); 420 if ((mAcceptUsersInput == false) && (mMoreOptions != null)) { 421 mMoreOptions.setVisibility(View.INVISIBLE); 422 } 423 424 mIsResponseSent = savedInstanceState.getBoolean(RESPONSE_SENT_KEY); 425 426 String savedString = savedInstanceState.getString(INPUT_STRING_KEY); 427 if (!TextUtils.isEmpty(savedString)) { 428 mTextIn.setText(savedString); 429 } 430 } 431 432 public void beforeTextChanged(CharSequence s, int start, int count, 433 int after) { 434 } 435 436 public void onTextChanged(CharSequence s, int start, int before, int count) { 437 // Reset timeout. 438 startTimeOut(); 439 } 440 441 public void afterTextChanged(Editable s) { 442 } 443 444 private boolean verfiyTypedText() { 445 // If not enough input was typed in stay on the edit screen. 446 if (mTextIn.getText().length() < mStkInput.minLen) { 447 return false; 448 } 449 450 return true; 451 } 452 453 private void cancelTimeOut() { 454 mTimeoutHandler.removeMessages(MSG_ID_TIMEOUT); 455 } 456 457 private void startTimeOut() { 458 int duration = StkApp.calculateDurationInMilis(mStkInput.duration); 459 460 if (duration <= 0) { 461 duration = StkApp.UI_TIMEOUT; 462 } 463 cancelTimeOut(); 464 mTimeoutHandler.sendMessageDelayed(mTimeoutHandler 465 .obtainMessage(MSG_ID_TIMEOUT), duration); 466 } 467 468 private void configInputDisplay() { 469 TextView numOfCharsView = (TextView) findViewById(R.id.num_of_chars); 470 TextView inTypeView = (TextView) findViewById(R.id.input_type); 471 472 int inTypeId = R.string.alphabet; 473 474 // set the prompt. 475 if ((mStkInput.icon == null || !mStkInput.iconSelfExplanatory) 476 && !TextUtils.isEmpty(mStkInput.text)) { 477 mPromptView.setText(mStkInput.text); 478 mPromptView.setVisibility(View.VISIBLE); 479 } 480 481 // Set input type (alphabet/digit) info close to the InText form. 482 if (mStkInput.digitOnly) { 483 mTextIn.setKeyListener(StkDigitsKeyListener.getInstance()); 484 inTypeId = R.string.digits; 485 } 486 inTypeView.setText(inTypeId); 487 488 setTitle(R.string.app_name); 489 490 if (mStkInput.icon != null) { 491 ImageView imageView = (ImageView) findViewById(R.id.icon); 492 imageView.setImageBitmap(mStkInput.icon); 493 imageView.setVisibility(View.VISIBLE); 494 } 495 496 // Handle specific global and text attributes. 497 switch (mState) { 498 case STATE_TEXT: 499 int maxLen = mStkInput.maxLen; 500 int minLen = mStkInput.minLen; 501 mTextIn.setFilters(new InputFilter[] {new InputFilter.LengthFilter( 502 maxLen)}); 503 504 // Set number of chars info. 505 String lengthLimit = String.valueOf(minLen); 506 if (maxLen != minLen) { 507 lengthLimit = minLen + " - " + maxLen; 508 } 509 numOfCharsView.setText(lengthLimit); 510 511 if (!mStkInput.echo) { 512 mTextIn.setTransformationMethod(PasswordTransformationMethod 513 .getInstance()); 514 } 515 mTextIn.setImeOptions(EditorInfo.IME_FLAG_NO_FULLSCREEN); 516 // Set default text if present. 517 if (mStkInput.defaultText != null) { 518 mTextIn.setText(mStkInput.defaultText); 519 } else { 520 // make sure the text is cleared 521 mTextIn.setText("", BufferType.EDITABLE); 522 } 523 524 break; 525 case STATE_YES_NO: 526 // Set display mode - normal / yes-no layout 527 mYesNoLayout.setVisibility(View.VISIBLE); 528 mNormalLayout.setVisibility(View.GONE); 529 break; 530 } 531 } 532 533 private float getFontSizeFactor(FontSize size) { 534 final float[] fontSizes = 535 {NORMAL_FONT_FACTOR, LARGE_FONT_FACTOR, SMALL_FONT_FACTOR}; 536 537 return fontSizes[size.ordinal()]; 538 } 539 540 private void initFromIntent(Intent intent) { 541 // Get the calling intent type: text/key, and setup the 542 // display parameters. 543 CatLog.d(LOG_TAG, "initFromIntent - slot id: " + mSlotId); 544 if (intent != null) { 545 mStkInput = intent.getParcelableExtra("INPUT"); 546 mSlotId = intent.getIntExtra(StkAppService.SLOT_ID, -1); 547 CatLog.d(LOG_TAG, "onCreate - slot id: " + mSlotId); 548 if (mStkInput == null) { 549 finish(); 550 } else { 551 mState = mStkInput.yesNo ? STATE_YES_NO : 552 STATE_TEXT; 553 configInputDisplay(); 554 } 555 } else { 556 finish(); 557 } 558 } 559 } 560