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