StkAppService.java revision 50e0a1db4e558a062763ed78d9de300cb133ff82
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
17package com.android.stk;
18
19import android.app.AlertDialog;
20import android.app.Notification;
21import android.app.NotificationManager;
22import android.app.PendingIntent;
23import android.app.Service;
24import android.content.Context;
25import android.content.DialogInterface;
26import android.content.Intent;
27import android.graphics.Bitmap;
28import android.graphics.BitmapFactory;
29import android.net.Uri;
30import android.os.Bundle;
31import android.os.Handler;
32import android.os.IBinder;
33import android.os.Looper;
34import android.os.Message;
35import android.telephony.TelephonyManager;
36import android.view.Gravity;
37import android.view.LayoutInflater;
38import android.view.View;
39import android.view.Window;
40import android.view.WindowManager;
41import android.widget.ImageView;
42import android.widget.RemoteViews;
43import android.widget.TextView;
44import android.widget.Toast;
45
46import com.android.internal.telephony.cat.AppInterface;
47import com.android.internal.telephony.cat.Menu;
48import com.android.internal.telephony.cat.Item;
49import com.android.internal.telephony.cat.Input;
50import com.android.internal.telephony.cat.ResultCode;
51import com.android.internal.telephony.cat.CatCmdMessage;
52import com.android.internal.telephony.cat.CatCmdMessage.BrowserSettings;
53import com.android.internal.telephony.cat.CatLog;
54import com.android.internal.telephony.cat.CatResponseMessage;
55import com.android.internal.telephony.cat.TextMessage;
56
57import 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 */
64public 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
140        DelayedCmd(int id, CatCmdMessage msg) {
141            this.id = id;
142            this.msg = msg;
143        }
144    }
145
146    @Override
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
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
203    public void onDestroy() {
204        waitForLooper();
205        mServiceLooper.quit();
206    }
207
208    @Override
209    public IBinder onBind(Intent intent) {
210        return null;
211    }
212
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     */
225    void indicateMenuVisibility(boolean visibility) {
226        mMenuIsVisibile = visibility;
227    }
228
229    /*
230     * Package api used by StkMenuActivity to get its Menu parameter.
231     */
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     */
240    static StkAppService getInstance() {
241        return sInstance;
242    }
243
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
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
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
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
355    private void callDelayedMsg() {
356        Message msg = mServiceHandler.obtainMessage();
357        msg.arg1 = OP_DELAYED_MSG;
358        mServiceHandler.sendMessage(msg);
359    }
360
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
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
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     */
617    private int getFlagActivityNoUserAction(InitiatedByUserAction userAction) {
618        return ((userAction == InitiatedByUserAction.yes) | mMenuIsVisibile) ?
619                                                    0 : Intent.FLAG_ACTIVITY_NO_USER_ACTION;
620    }
621
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
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
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
662    private void launchEventMessage() {
663        launchEventMessage(mCurrentCmd.geTextMessage());
664    }
665
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
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
704    private void launchBrowser(BrowserSettings settings) {
705        if (settings == null) {
706            return;
707        }
708
709        Intent intent = new Intent(Intent.ACTION_VIEW);
710
711        Uri data;
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        } else {
722            // If no URL specified, just bring up the "home page".
723            //
724            // (Note we need to specify *something* in the intent's data field
725            // here, since if you fire off a VIEW intent with no data at all
726            // you'll get an activity chooser rather than the browser.  There's
727            // no specific URI that means "use the default home page", so
728            // instead let's just explicitly bring up http://google.com.)
729            data = Uri.parse("http://google.com/");
730        }
731        intent.setData(data);
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
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
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
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
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
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
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