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