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.AlertDialog; 20 import android.app.Notification; 21 import android.app.NotificationManager; 22 import android.app.PendingIntent; 23 import android.app.Service; 24 import android.content.Context; 25 import android.content.DialogInterface; 26 import android.content.Intent; 27 import android.graphics.Bitmap; 28 import android.graphics.BitmapFactory; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.IBinder; 33 import android.os.Looper; 34 import android.os.Message; 35 import android.telephony.TelephonyManager; 36 import android.view.Gravity; 37 import android.view.LayoutInflater; 38 import android.view.View; 39 import android.view.Window; 40 import android.view.WindowManager; 41 import android.widget.ImageView; 42 import android.widget.RemoteViews; 43 import android.widget.TextView; 44 import android.widget.Toast; 45 46 import com.android.internal.telephony.cat.AppInterface; 47 import com.android.internal.telephony.cat.Menu; 48 import com.android.internal.telephony.cat.Item; 49 import com.android.internal.telephony.cat.Input; 50 import com.android.internal.telephony.cat.ResultCode; 51 import com.android.internal.telephony.cat.CatCmdMessage; 52 import com.android.internal.telephony.cat.CatCmdMessage.BrowserSettings; 53 import com.android.internal.telephony.cat.CatLog; 54 import com.android.internal.telephony.cat.CatResponseMessage; 55 import com.android.internal.telephony.cat.TextMessage; 56 import com.android.internal.telephony.uicc.IccRefreshResponse; 57 import com.android.internal.telephony.uicc.IccCardStatus.CardState; 58 59 import java.util.LinkedList; 60 61 /** 62 * SIM toolkit application level service. Interacts with Telephopny messages, 63 * application's launch and user input from STK UI elements. 64 * 65 */ 66 public class StkAppService extends Service implements Runnable { 67 68 // members 69 private volatile Looper mServiceLooper; 70 private volatile ServiceHandler mServiceHandler; 71 private AppInterface mStkService; 72 private Context mContext = null; 73 private CatCmdMessage mMainCmd = null; 74 private CatCmdMessage mCurrentCmd = null; 75 private Menu mCurrentMenu = null; 76 private String lastSelectedItem = null; 77 private boolean mMenuIsVisibile = false; 78 private boolean responseNeeded = true; 79 private boolean mCmdInProgress = false; 80 private NotificationManager mNotificationManager = null; 81 private LinkedList<DelayedCmd> mCmdsQ = null; 82 private boolean launchBrowser = false; 83 private BrowserSettings mBrowserSettings = null; 84 static StkAppService sInstance = null; 85 86 // Used for setting FLAG_ACTIVITY_NO_USER_ACTION when 87 // creating an intent. 88 private enum InitiatedByUserAction { 89 yes, // The action was started via a user initiated action 90 unknown, // Not known for sure if user initated the action 91 } 92 93 // constants 94 static final String OPCODE = "op"; 95 static final String CMD_MSG = "cmd message"; 96 static final String RES_ID = "response id"; 97 static final String MENU_SELECTION = "menu selection"; 98 static final String INPUT = "input"; 99 static final String HELP = "help"; 100 static final String CONFIRMATION = "confirm"; 101 static final String CHOICE = "choice"; 102 103 // operations ids for different service functionality. 104 static final int OP_CMD = 1; 105 static final int OP_RESPONSE = 2; 106 static final int OP_LAUNCH_APP = 3; 107 static final int OP_END_SESSION = 4; 108 static final int OP_BOOT_COMPLETED = 5; 109 private static final int OP_DELAYED_MSG = 6; 110 static final int OP_CARD_STATUS_CHANGED = 7; 111 112 // Response ids 113 static final int RES_ID_MENU_SELECTION = 11; 114 static final int RES_ID_INPUT = 12; 115 static final int RES_ID_CONFIRM = 13; 116 static final int RES_ID_DONE = 14; 117 static final int RES_ID_CHOICE = 15; 118 119 static final int RES_ID_TIMEOUT = 20; 120 static final int RES_ID_BACKWARD = 21; 121 static final int RES_ID_END_SESSION = 22; 122 static final int RES_ID_EXIT = 23; 123 124 static final int YES = 1; 125 static final int NO = 0; 126 127 private static final String PACKAGE_NAME = "com.android.stk"; 128 private static final String MENU_ACTIVITY_NAME = 129 PACKAGE_NAME + ".StkMenuActivity"; 130 private static final String INPUT_ACTIVITY_NAME = 131 PACKAGE_NAME + ".StkInputActivity"; 132 133 // Notification id used to display Idle Mode text in NotificationManager. 134 private static final int STK_NOTIFICATION_ID = 333; 135 136 // Inner class used for queuing telephony messages (proactive commands, 137 // session end) while the service is busy processing a previous message. 138 private class DelayedCmd { 139 // members 140 int id; 141 CatCmdMessage msg; 142 143 DelayedCmd(int id, CatCmdMessage msg) { 144 this.id = id; 145 this.msg = msg; 146 } 147 } 148 149 @Override 150 public void onCreate() { 151 // Initialize members 152 mCmdsQ = new LinkedList<DelayedCmd>(); 153 Thread serviceThread = new Thread(null, this, "Stk App Service"); 154 serviceThread.start(); 155 mContext = getBaseContext(); 156 mNotificationManager = (NotificationManager) mContext 157 .getSystemService(Context.NOTIFICATION_SERVICE); 158 sInstance = this; 159 } 160 161 @Override 162 public void onStart(Intent intent, int startId) { 163 164 mStkService = com.android.internal.telephony.cat.CatService 165 .getInstance(); 166 167 if (mStkService == null) { 168 stopSelf(); 169 CatLog.d(this, " Unable to get Service handle"); 170 StkAppInstaller.unInstall(mContext); 171 return; 172 } 173 174 waitForLooper(); 175 // onStart() method can be passed a null intent 176 // TODO: replace onStart() with onStartCommand() 177 if (intent == null) { 178 return; 179 } 180 181 Bundle args = intent.getExtras(); 182 183 if (args == null) { 184 return; 185 } 186 187 Message msg = mServiceHandler.obtainMessage(); 188 msg.arg1 = args.getInt(OPCODE); 189 switch(msg.arg1) { 190 case OP_CMD: 191 msg.obj = args.getParcelable(CMD_MSG); 192 break; 193 case OP_RESPONSE: 194 case OP_CARD_STATUS_CHANGED: 195 msg.obj = args; 196 /* falls through */ 197 case OP_LAUNCH_APP: 198 case OP_END_SESSION: 199 case OP_BOOT_COMPLETED: 200 break; 201 default: 202 return; 203 } 204 mServiceHandler.sendMessage(msg); 205 } 206 207 @Override 208 public void onDestroy() { 209 waitForLooper(); 210 mServiceLooper.quit(); 211 } 212 213 @Override 214 public IBinder onBind(Intent intent) { 215 return null; 216 } 217 218 public void run() { 219 Looper.prepare(); 220 221 mServiceLooper = Looper.myLooper(); 222 mServiceHandler = new ServiceHandler(); 223 224 Looper.loop(); 225 } 226 227 /* 228 * Package api used by StkMenuActivity to indicate if its on the foreground. 229 */ 230 void indicateMenuVisibility(boolean visibility) { 231 mMenuIsVisibile = visibility; 232 } 233 234 /* 235 * Package api used by StkMenuActivity to get its Menu parameter. 236 */ 237 Menu getMenu() { 238 return mCurrentMenu; 239 } 240 241 /* 242 * Package api used by UI Activities and Dialogs to communicate directly 243 * with the service to deliver state information and parameters. 244 */ 245 static StkAppService getInstance() { 246 return sInstance; 247 } 248 249 private void waitForLooper() { 250 while (mServiceHandler == null) { 251 synchronized (this) { 252 try { 253 wait(100); 254 } catch (InterruptedException e) { 255 } 256 } 257 } 258 } 259 260 private final class ServiceHandler extends Handler { 261 @Override 262 public void handleMessage(Message msg) { 263 int opcode = msg.arg1; 264 265 switch (opcode) { 266 case OP_LAUNCH_APP: 267 if (mMainCmd == null) { 268 // nothing todo when no SET UP MENU command didn't arrive. 269 return; 270 } 271 launchMenuActivity(null); 272 break; 273 case OP_CMD: 274 CatCmdMessage cmdMsg = (CatCmdMessage) msg.obj; 275 // There are two types of commands: 276 // 1. Interactive - user's response is required. 277 // 2. Informative - display a message, no interaction with the user. 278 // 279 // Informative commands can be handled immediately without any delay. 280 // Interactive commands can't override each other. So if a command 281 // is already in progress, we need to queue the next command until 282 // the user has responded or a timeout expired. 283 if (!isCmdInteractive(cmdMsg)) { 284 handleCmd(cmdMsg); 285 } else { 286 if (!mCmdInProgress) { 287 mCmdInProgress = true; 288 handleCmd((CatCmdMessage) msg.obj); 289 } else { 290 mCmdsQ.addLast(new DelayedCmd(OP_CMD, 291 (CatCmdMessage) msg.obj)); 292 } 293 } 294 break; 295 case OP_RESPONSE: 296 if (responseNeeded) { 297 handleCmdResponse((Bundle) msg.obj); 298 } 299 // call delayed commands if needed. 300 if (mCmdsQ.size() != 0) { 301 callDelayedMsg(); 302 } else { 303 mCmdInProgress = false; 304 } 305 // reset response needed state var to its original value. 306 responseNeeded = true; 307 break; 308 case OP_END_SESSION: 309 if (!mCmdInProgress) { 310 mCmdInProgress = true; 311 handleSessionEnd(); 312 } else { 313 mCmdsQ.addLast(new DelayedCmd(OP_END_SESSION, null)); 314 } 315 break; 316 case OP_BOOT_COMPLETED: 317 CatLog.d(this, "OP_BOOT_COMPLETED"); 318 if (mMainCmd == null) { 319 StkAppInstaller.unInstall(mContext); 320 } 321 break; 322 case OP_DELAYED_MSG: 323 handleDelayedCmd(); 324 break; 325 case OP_CARD_STATUS_CHANGED: 326 CatLog.d(this, "Card/Icc Status change received"); 327 handleCardStatusChangeAndIccRefresh((Bundle) msg.obj); 328 break; 329 } 330 } 331 332 private void handleCardStatusChangeAndIccRefresh(Bundle args) { 333 boolean cardStatus = args.getBoolean(AppInterface.CARD_STATUS); 334 335 CatLog.d(this, "CardStatus: " + cardStatus); 336 if (cardStatus == false) { 337 CatLog.d(this, "CARD is ABSENT"); 338 // Uninstall STKAPP, Clear Idle text, Stop StkAppService 339 StkAppInstaller.unInstall(mContext); 340 mNotificationManager.cancel(STK_NOTIFICATION_ID); 341 stopSelf(); 342 } else { 343 IccRefreshResponse state = new IccRefreshResponse(); 344 state.refreshResult = args.getInt(AppInterface.REFRESH_RESULT); 345 346 CatLog.d(this, "Icc Refresh Result: "+ state.refreshResult); 347 if ((state.refreshResult == IccRefreshResponse.REFRESH_RESULT_INIT) || 348 (state.refreshResult == IccRefreshResponse.REFRESH_RESULT_RESET)) { 349 // Clear Idle Text 350 mNotificationManager.cancel(STK_NOTIFICATION_ID); 351 } 352 353 if (state.refreshResult == IccRefreshResponse.REFRESH_RESULT_RESET) { 354 // Uninstall STkmenu 355 StkAppInstaller.unInstall(mContext); 356 mCurrentMenu = null; 357 mMainCmd = null; 358 } 359 } 360 } 361 } 362 363 private boolean isCmdInteractive(CatCmdMessage cmd) { 364 switch (cmd.getCmdType()) { 365 case SEND_DTMF: 366 case SEND_SMS: 367 case SEND_SS: 368 case SEND_USSD: 369 case SET_UP_IDLE_MODE_TEXT: 370 case SET_UP_MENU: 371 case CLOSE_CHANNEL: 372 case RECEIVE_DATA: 373 case SEND_DATA: 374 return false; 375 } 376 377 return true; 378 } 379 380 private void handleDelayedCmd() { 381 if (mCmdsQ.size() != 0) { 382 DelayedCmd cmd = mCmdsQ.poll(); 383 switch (cmd.id) { 384 case OP_CMD: 385 handleCmd(cmd.msg); 386 break; 387 case OP_END_SESSION: 388 handleSessionEnd(); 389 break; 390 } 391 } 392 } 393 394 private void callDelayedMsg() { 395 Message msg = mServiceHandler.obtainMessage(); 396 msg.arg1 = OP_DELAYED_MSG; 397 mServiceHandler.sendMessage(msg); 398 } 399 400 private void handleSessionEnd() { 401 mCurrentCmd = mMainCmd; 402 lastSelectedItem = null; 403 // In case of SET UP MENU command which removed the app, don't 404 // update the current menu member. 405 if (mCurrentMenu != null && mMainCmd != null) { 406 mCurrentMenu = mMainCmd.getMenu(); 407 } 408 if (mMenuIsVisibile) { 409 launchMenuActivity(null); 410 } 411 if (mCmdsQ.size() != 0) { 412 callDelayedMsg(); 413 } else { 414 mCmdInProgress = false; 415 } 416 // In case a launch browser command was just confirmed, launch that url. 417 if (launchBrowser) { 418 launchBrowser = false; 419 launchBrowser(mBrowserSettings); 420 } 421 } 422 423 private void handleCmd(CatCmdMessage cmdMsg) { 424 if (cmdMsg == null) { 425 return; 426 } 427 // save local reference for state tracking. 428 mCurrentCmd = cmdMsg; 429 boolean waitForUsersResponse = true; 430 431 CatLog.d(this, cmdMsg.getCmdType().name()); 432 switch (cmdMsg.getCmdType()) { 433 case DISPLAY_TEXT: 434 TextMessage msg = cmdMsg.geTextMessage(); 435 responseNeeded = msg.responseNeeded; 436 waitForUsersResponse = msg.responseNeeded; 437 if (lastSelectedItem != null) { 438 msg.title = lastSelectedItem; 439 } else if (mMainCmd != null){ 440 msg.title = mMainCmd.getMenu().title; 441 } else { 442 // TODO: get the carrier name from the SIM 443 msg.title = ""; 444 } 445 launchTextDialog(); 446 break; 447 case SELECT_ITEM: 448 mCurrentMenu = cmdMsg.getMenu(); 449 launchMenuActivity(cmdMsg.getMenu()); 450 break; 451 case SET_UP_MENU: 452 mMainCmd = mCurrentCmd; 453 mCurrentMenu = cmdMsg.getMenu(); 454 if (removeMenu()) { 455 CatLog.d(this, "Uninstall App"); 456 mCurrentMenu = null; 457 StkAppInstaller.unInstall(mContext); 458 } else { 459 CatLog.d(this, "Install App"); 460 StkAppInstaller.install(mContext); 461 } 462 if (mMenuIsVisibile) { 463 launchMenuActivity(null); 464 } 465 break; 466 case GET_INPUT: 467 case GET_INKEY: 468 launchInputActivity(); 469 break; 470 case SET_UP_IDLE_MODE_TEXT: 471 waitForUsersResponse = false; 472 launchIdleText(); 473 break; 474 case SEND_DTMF: 475 case SEND_SMS: 476 case SEND_SS: 477 case SEND_USSD: 478 waitForUsersResponse = false; 479 launchEventMessage(); 480 break; 481 case LAUNCH_BROWSER: 482 launchConfirmationDialog(mCurrentCmd.geTextMessage()); 483 break; 484 case SET_UP_CALL: 485 launchConfirmationDialog(mCurrentCmd.getCallSettings().confirmMsg); 486 break; 487 case PLAY_TONE: 488 launchToneDialog(); 489 break; 490 case OPEN_CHANNEL: 491 launchOpenChannelDialog(); 492 break; 493 case CLOSE_CHANNEL: 494 case RECEIVE_DATA: 495 case SEND_DATA: 496 TextMessage m = mCurrentCmd.geTextMessage(); 497 498 if ((m != null) && (m.text == null)) { 499 switch(cmdMsg.getCmdType()) { 500 case CLOSE_CHANNEL: 501 m.text = getResources().getString(R.string.default_close_channel_msg); 502 break; 503 case RECEIVE_DATA: 504 m.text = getResources().getString(R.string.default_receive_data_msg); 505 break; 506 case SEND_DATA: 507 m.text = getResources().getString(R.string.default_send_data_msg); 508 break; 509 } 510 } 511 /* 512 * Display indication in the form of a toast to the user if required. 513 */ 514 launchEventMessage(); 515 break; 516 } 517 518 if (!waitForUsersResponse) { 519 if (mCmdsQ.size() != 0) { 520 callDelayedMsg(); 521 } else { 522 mCmdInProgress = false; 523 } 524 } 525 } 526 527 private void handleCmdResponse(Bundle args) { 528 if (mCurrentCmd == null) { 529 return; 530 } 531 if (mStkService == null) { 532 mStkService = com.android.internal.telephony.cat.CatService.getInstance(); 533 if (mStkService == null) { 534 // This should never happen (we should be responding only to a message 535 // that arrived from StkService). It has to exist by this time 536 throw new RuntimeException("mStkService is null when we need to send response"); 537 } 538 } 539 540 CatResponseMessage resMsg = new CatResponseMessage(mCurrentCmd); 541 542 // set result code 543 boolean helpRequired = args.getBoolean(HELP, false); 544 boolean confirmed = false; 545 546 switch(args.getInt(RES_ID)) { 547 case RES_ID_MENU_SELECTION: 548 CatLog.d(this, "RES_ID_MENU_SELECTION"); 549 int menuSelection = args.getInt(MENU_SELECTION); 550 switch(mCurrentCmd.getCmdType()) { 551 case SET_UP_MENU: 552 case SELECT_ITEM: 553 lastSelectedItem = getItemName(menuSelection); 554 if (helpRequired) { 555 resMsg.setResultCode(ResultCode.HELP_INFO_REQUIRED); 556 } else { 557 resMsg.setResultCode(ResultCode.OK); 558 } 559 resMsg.setMenuSelection(menuSelection); 560 break; 561 } 562 break; 563 case RES_ID_INPUT: 564 CatLog.d(this, "RES_ID_INPUT"); 565 String input = args.getString(INPUT); 566 Input cmdInput = mCurrentCmd.geInput(); 567 if (cmdInput != null && cmdInput.yesNo) { 568 boolean yesNoSelection = input 569 .equals(StkInputActivity.YES_STR_RESPONSE); 570 resMsg.setYesNo(yesNoSelection); 571 } else { 572 if (helpRequired) { 573 resMsg.setResultCode(ResultCode.HELP_INFO_REQUIRED); 574 } else { 575 resMsg.setResultCode(ResultCode.OK); 576 resMsg.setInput(input); 577 } 578 } 579 break; 580 case RES_ID_CONFIRM: 581 CatLog.d(this, "RES_ID_CONFIRM"); 582 confirmed = args.getBoolean(CONFIRMATION); 583 switch (mCurrentCmd.getCmdType()) { 584 case DISPLAY_TEXT: 585 resMsg.setResultCode(confirmed ? ResultCode.OK 586 : ResultCode.UICC_SESSION_TERM_BY_USER); 587 break; 588 case LAUNCH_BROWSER: 589 resMsg.setResultCode(confirmed ? ResultCode.OK 590 : ResultCode.UICC_SESSION_TERM_BY_USER); 591 if (confirmed) { 592 launchBrowser = true; 593 mBrowserSettings = mCurrentCmd.getBrowserSettings(); 594 } 595 break; 596 case SET_UP_CALL: 597 resMsg.setResultCode(ResultCode.OK); 598 resMsg.setConfirmation(confirmed); 599 if (confirmed) { 600 launchEventMessage(mCurrentCmd.getCallSettings().callMsg); 601 } 602 break; 603 } 604 break; 605 case RES_ID_DONE: 606 resMsg.setResultCode(ResultCode.OK); 607 break; 608 case RES_ID_BACKWARD: 609 CatLog.d(this, "RES_ID_BACKWARD"); 610 resMsg.setResultCode(ResultCode.BACKWARD_MOVE_BY_USER); 611 break; 612 case RES_ID_END_SESSION: 613 CatLog.d(this, "RES_ID_END_SESSION"); 614 resMsg.setResultCode(ResultCode.UICC_SESSION_TERM_BY_USER); 615 break; 616 case RES_ID_TIMEOUT: 617 CatLog.d(this, "RES_ID_TIMEOUT"); 618 // GCF test-case 27.22.4.1.1 Expected Sequence 1.5 (DISPLAY TEXT, 619 // Clear message after delay, successful) expects result code OK. 620 // If the command qualifier specifies no user response is required 621 // then send OK instead of NO_RESPONSE_FROM_USER 622 if ((mCurrentCmd.getCmdType().value() == AppInterface.CommandType.DISPLAY_TEXT 623 .value()) 624 && (mCurrentCmd.geTextMessage().userClear == false)) { 625 resMsg.setResultCode(ResultCode.OK); 626 } else { 627 resMsg.setResultCode(ResultCode.NO_RESPONSE_FROM_USER); 628 } 629 break; 630 case RES_ID_CHOICE: 631 int choice = args.getInt(CHOICE); 632 CatLog.d(this, "User Choice=" + choice); 633 switch (choice) { 634 case YES: 635 resMsg.setResultCode(ResultCode.OK); 636 confirmed = true; 637 break; 638 case NO: 639 resMsg.setResultCode(ResultCode.USER_NOT_ACCEPT); 640 break; 641 } 642 643 if (mCurrentCmd.getCmdType().value() == AppInterface.CommandType.OPEN_CHANNEL 644 .value()) { 645 resMsg.setConfirmation(confirmed); 646 } 647 break; 648 649 default: 650 CatLog.d(this, "Unknown result id"); 651 return; 652 } 653 mStkService.onCmdResponse(resMsg); 654 } 655 656 /** 657 * Returns 0 or FLAG_ACTIVITY_NO_USER_ACTION, 0 means the user initiated the action. 658 * 659 * @param userAction If the userAction is yes then we always return 0 otherwise 660 * mMenuIsVisible is used to determine what to return. If mMenuIsVisible is true 661 * then we are the foreground app and we'll return 0 as from our perspective a 662 * user action did cause. If it's false than we aren't the foreground app and 663 * FLAG_ACTIVITY_NO_USER_ACTION is returned. 664 * 665 * @return 0 or FLAG_ACTIVITY_NO_USER_ACTION 666 */ 667 private int getFlagActivityNoUserAction(InitiatedByUserAction userAction) { 668 return ((userAction == InitiatedByUserAction.yes) | mMenuIsVisibile) ? 669 0 : Intent.FLAG_ACTIVITY_NO_USER_ACTION; 670 } 671 672 private void launchMenuActivity(Menu menu) { 673 Intent newIntent = new Intent(Intent.ACTION_VIEW); 674 newIntent.setClassName(PACKAGE_NAME, MENU_ACTIVITY_NAME); 675 int intentFlags = Intent.FLAG_ACTIVITY_NEW_TASK 676 | Intent.FLAG_ACTIVITY_CLEAR_TOP; 677 if (menu == null) { 678 // We assume this was initiated by the user pressing the tool kit icon 679 intentFlags |= getFlagActivityNoUserAction(InitiatedByUserAction.yes); 680 681 newIntent.putExtra("STATE", StkMenuActivity.STATE_MAIN); 682 } else { 683 // We don't know and we'll let getFlagActivityNoUserAction decide. 684 intentFlags |= getFlagActivityNoUserAction(InitiatedByUserAction.unknown); 685 686 newIntent.putExtra("STATE", StkMenuActivity.STATE_SECONDARY); 687 } 688 newIntent.setFlags(intentFlags); 689 mContext.startActivity(newIntent); 690 } 691 692 private void launchInputActivity() { 693 Intent newIntent = new Intent(Intent.ACTION_VIEW); 694 newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 695 | getFlagActivityNoUserAction(InitiatedByUserAction.unknown)); 696 newIntent.setClassName(PACKAGE_NAME, INPUT_ACTIVITY_NAME); 697 newIntent.putExtra("INPUT", mCurrentCmd.geInput()); 698 mContext.startActivity(newIntent); 699 } 700 701 private void launchTextDialog() { 702 Intent newIntent = new Intent(this, StkDialogActivity.class); 703 newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 704 | Intent.FLAG_ACTIVITY_CLEAR_TOP 705 | Intent.FLAG_ACTIVITY_NO_HISTORY 706 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 707 | getFlagActivityNoUserAction(InitiatedByUserAction.unknown)); 708 newIntent.putExtra("TEXT", mCurrentCmd.geTextMessage()); 709 startActivity(newIntent); 710 } 711 712 private void launchEventMessage() { 713 launchEventMessage(mCurrentCmd.geTextMessage()); 714 } 715 716 private void launchEventMessage(TextMessage msg) { 717 if (msg == null || msg.text == null) { 718 return; 719 } 720 Toast toast = new Toast(mContext.getApplicationContext()); 721 LayoutInflater inflate = (LayoutInflater) mContext 722 .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 723 View v = inflate.inflate(R.layout.stk_event_msg, null); 724 TextView tv = (TextView) v 725 .findViewById(com.android.internal.R.id.message); 726 ImageView iv = (ImageView) v 727 .findViewById(com.android.internal.R.id.icon); 728 if (msg.icon != null) { 729 iv.setImageBitmap(msg.icon); 730 } else { 731 iv.setVisibility(View.GONE); 732 } 733 if (!msg.iconSelfExplanatory) { 734 tv.setText(msg.text); 735 } 736 737 toast.setView(v); 738 toast.setDuration(Toast.LENGTH_LONG); 739 toast.setGravity(Gravity.BOTTOM, 0, 0); 740 toast.show(); 741 } 742 743 private void launchConfirmationDialog(TextMessage msg) { 744 msg.title = lastSelectedItem; 745 Intent newIntent = new Intent(this, StkDialogActivity.class); 746 newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 747 | Intent.FLAG_ACTIVITY_NO_HISTORY 748 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 749 | getFlagActivityNoUserAction(InitiatedByUserAction.unknown)); 750 newIntent.putExtra("TEXT", msg); 751 startActivity(newIntent); 752 } 753 754 private void launchBrowser(BrowserSettings settings) { 755 if (settings == null) { 756 return; 757 } 758 759 Intent intent = null; 760 Uri data = null; 761 762 if (settings.url != null) { 763 CatLog.d(this, "settings.url = " + settings.url); 764 if ((settings.url.startsWith("http://") || (settings.url.startsWith("https://")))) { 765 data = Uri.parse(settings.url); 766 } else { 767 String modifiedUrl = "http://" + settings.url; 768 CatLog.d(this, "modifiedUrl = " + modifiedUrl); 769 data = Uri.parse(modifiedUrl); 770 } 771 } 772 if (data != null) { 773 intent = new Intent(Intent.ACTION_VIEW); 774 intent.setData(data); 775 } else { 776 // if the command did not contain a URL, 777 // launch the browser to the default homepage. 778 CatLog.d(this, "launch browser with default URL "); 779 intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, 780 Intent.CATEGORY_APP_BROWSER); 781 } 782 783 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 784 switch (settings.mode) { 785 case USE_EXISTING_BROWSER: 786 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 787 break; 788 case LAUNCH_NEW_BROWSER: 789 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); 790 break; 791 case LAUNCH_IF_NOT_ALREADY_LAUNCHED: 792 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 793 break; 794 } 795 // start browser activity 796 startActivity(intent); 797 // a small delay, let the browser start, before processing the next command. 798 // this is good for scenarios where a related DISPLAY TEXT command is 799 // followed immediately. 800 try { 801 Thread.sleep(10000); 802 } catch (InterruptedException e) {} 803 } 804 805 private void launchIdleText() { 806 TextMessage msg = mCurrentCmd.geTextMessage(); 807 808 if (msg == null) { 809 CatLog.d(this, "mCurrent.getTextMessage is NULL"); 810 mNotificationManager.cancel(STK_NOTIFICATION_ID); 811 return; 812 } 813 if (msg.text == null) { 814 mNotificationManager.cancel(STK_NOTIFICATION_ID); 815 } else { 816 PendingIntent pendingIntent = PendingIntent.getService(mContext, 0, 817 new Intent(mContext, StkAppService.class), 0); 818 819 final Notification.Builder notificationBuilder = new Notification.Builder( 820 StkAppService.this); 821 if (mMainCmd != null && mMainCmd.getMenu() != null) { 822 notificationBuilder.setContentTitle(mMainCmd.getMenu().title); 823 } else { 824 notificationBuilder.setContentTitle(""); 825 } 826 notificationBuilder 827 .setSmallIcon(com.android.internal.R.drawable.stat_notify_sim_toolkit); 828 notificationBuilder.setContentIntent(pendingIntent); 829 notificationBuilder.setOngoing(true); 830 // Set text and icon for the status bar and notification body. 831 if (!msg.iconSelfExplanatory) { 832 notificationBuilder.setContentText(msg.text); 833 notificationBuilder.setTicker(msg.text); 834 } 835 if (msg.icon != null) { 836 notificationBuilder.setLargeIcon(msg.icon); 837 } else { 838 Bitmap bitmapIcon = BitmapFactory.decodeResource(StkAppService.this 839 .getResources().getSystem(), 840 com.android.internal.R.drawable.stat_notify_sim_toolkit); 841 notificationBuilder.setLargeIcon(bitmapIcon); 842 } 843 notificationBuilder.setColor(mContext.getResources().getColor( 844 com.android.internal.R.color.system_notification_accent_color)); 845 mNotificationManager.notify(STK_NOTIFICATION_ID, notificationBuilder.build()); 846 } 847 } 848 849 private void launchToneDialog() { 850 Intent newIntent = new Intent(this, ToneDialog.class); 851 newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 852 | Intent.FLAG_ACTIVITY_NO_HISTORY 853 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 854 | getFlagActivityNoUserAction(InitiatedByUserAction.unknown)); 855 newIntent.putExtra("TEXT", mCurrentCmd.geTextMessage()); 856 newIntent.putExtra("TONE", mCurrentCmd.getToneSettings()); 857 startActivity(newIntent); 858 } 859 860 private void launchOpenChannelDialog() { 861 TextMessage msg = mCurrentCmd.geTextMessage(); 862 if (msg == null) { 863 CatLog.d(this, "msg is null, return here"); 864 return; 865 } 866 867 msg.title = getResources().getString(R.string.stk_dialog_title); 868 if (msg.text == null) { 869 msg.text = getResources().getString(R.string.default_open_channel_msg); 870 } 871 872 final AlertDialog dialog = new AlertDialog.Builder(mContext) 873 .setIconAttribute(android.R.attr.alertDialogIcon) 874 .setTitle(msg.title) 875 .setMessage(msg.text) 876 .setCancelable(false) 877 .setPositiveButton(getResources().getString(R.string.stk_dialog_accept), 878 new DialogInterface.OnClickListener() { 879 public void onClick(DialogInterface dialog, int which) { 880 Bundle args = new Bundle(); 881 args.putInt(RES_ID, RES_ID_CHOICE); 882 args.putInt(CHOICE, YES); 883 Message message = mServiceHandler.obtainMessage(); 884 message.arg1 = OP_RESPONSE; 885 message.obj = args; 886 mServiceHandler.sendMessage(message); 887 } 888 }) 889 .setNegativeButton(getResources().getString(R.string.stk_dialog_reject), 890 new DialogInterface.OnClickListener() { 891 public void onClick(DialogInterface dialog, int which) { 892 Bundle args = new Bundle(); 893 args.putInt(RES_ID, RES_ID_CHOICE); 894 args.putInt(CHOICE, NO); 895 Message message = mServiceHandler.obtainMessage(); 896 message.arg1 = OP_RESPONSE; 897 message.obj = args; 898 mServiceHandler.sendMessage(message); 899 } 900 }) 901 .create(); 902 903 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 904 if (!mContext.getResources().getBoolean( 905 com.android.internal.R.bool.config_sf_slowBlur)) { 906 dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND); 907 } 908 909 dialog.show(); 910 } 911 912 private void launchTransientEventMessage() { 913 TextMessage msg = mCurrentCmd.geTextMessage(); 914 if (msg == null) { 915 CatLog.d(this, "msg is null, return here"); 916 return; 917 } 918 919 msg.title = getResources().getString(R.string.stk_dialog_title); 920 921 final AlertDialog dialog = new AlertDialog.Builder(mContext) 922 .setIconAttribute(android.R.attr.alertDialogIcon) 923 .setTitle(msg.title) 924 .setMessage(msg.text) 925 .setCancelable(false) 926 .setPositiveButton(getResources().getString(android.R.string.ok), 927 new DialogInterface.OnClickListener() { 928 public void onClick(DialogInterface dialog, int which) { 929 } 930 }) 931 .create(); 932 933 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 934 if (!mContext.getResources().getBoolean( 935 com.android.internal.R.bool.config_sf_slowBlur)) { 936 dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND); 937 } 938 939 dialog.show(); 940 } 941 942 private String getItemName(int itemId) { 943 Menu menu = mCurrentCmd.getMenu(); 944 if (menu == null) { 945 return null; 946 } 947 for (Item item : menu.items) { 948 if (item.id == itemId) { 949 return item.text; 950 } 951 } 952 return null; 953 } 954 955 private boolean removeMenu() { 956 try { 957 if (mCurrentMenu.items.size() == 1 && 958 mCurrentMenu.items.get(0) == null) { 959 return true; 960 } 961 } catch (NullPointerException e) { 962 CatLog.d(this, "Unable to get Menu's items size"); 963 return true; 964 } 965 return false; 966 } 967 } 968