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