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