• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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