• 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.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 
DelayedCmd(int id, CatCmdMessage msg)138         DelayedCmd(int id, CatCmdMessage msg) {
139             this.id = id;
140             this.msg = msg;
141         }
142     }
143 
144     @Override
onCreate()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
onStart(Intent intent, int startId)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
onDestroy()206     public void onDestroy() {
207         waitForLooper();
208         mServiceLooper.quit();
209     }
210 
211     @Override
onBind(Intent intent)212     public IBinder onBind(Intent intent) {
213         return null;
214     }
215 
run()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      */
indicateMenuVisibility(boolean visibility)228     void indicateMenuVisibility(boolean visibility) {
229         mMenuIsVisibile = visibility;
230     }
231 
232     /*
233      * Package api used by StkMenuActivity to get its Menu parameter.
234      */
getMenu()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      */
getInstance()243     static StkAppService getInstance() {
244         return sInstance;
245     }
246 
waitForLooper()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
handleMessage(Message msg)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 
isCmdInteractive(CatCmdMessage cmd)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 
handleDelayedCmd()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 
callDelayedMsg()358     private void callDelayedMsg() {
359         Message msg = mServiceHandler.obtainMessage();
360         msg.arg1 = OP_DELAYED_MSG;
361         mServiceHandler.sendMessage(msg);
362     }
363 
handleSessionEnd()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 
handleCmd(CatCmdMessage cmdMsg)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 
handleCmdResponse(Bundle args)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      */
getFlagActivityNoUserAction(InitiatedByUserAction userAction)610     private int getFlagActivityNoUserAction(InitiatedByUserAction userAction) {
611         return ((userAction == InitiatedByUserAction.yes) | mMenuIsVisibile) ?
612                                                     0 : Intent.FLAG_ACTIVITY_NO_USER_ACTION;
613     }
614 
launchMenuActivity(Menu menu)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 
launchInputActivity()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 
launchTextDialog()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 
launchEventMessage()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 
launchConfirmationDialog(TextMessage msg)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 
launchBrowser(BrowserSettings settings)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 
launchCallMsg()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 
launchIdleText()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 
launchToneDialog()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 
launchOpenChannelDialog()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 
launchTransientEventMessage()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 
getItemName(int itemId)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 
removeMenu()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