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