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