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