Merge "Input data must be kept while activity is recreated"
diff --git a/Android.mk b/Android.mk
index a9e2d1d..135fcf9 100644
--- a/Android.mk
+++ b/Android.mk
@@ -10,6 +10,7 @@
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
 LOCAL_PACKAGE_NAME := Stk
+LOCAL_PRIVATE_PLATFORM_APIS := true
 LOCAL_CERTIFICATE := platform
 
 include $(BUILD_PACKAGE)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f4652da..ee434c0 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -28,6 +28,7 @@
     <uses-permission android:name="android.permission.GET_TASKS"/>
     <uses-permission android:name="android.permission.RECEIVE_STK_COMMANDS" />
     <uses-permission android:name="android.permission.SET_ACTIVITY_WATCHER" />
+    <uses-permission android:name="android.permission.USER_ACTIVITY" />
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
 
@@ -109,7 +110,6 @@
                 <action android:name= "com.android.internal.stk.session_end" />
                 <action android:name= "com.android.internal.stk.icc_status_change" />
                 <action android:name= "com.android.internal.stk.alpha_notify" />
-                <action android:name= "android.intent.action.LOCALE_CHANGED" />
             </intent-filter>
         </receiver>
 
diff --git a/res/layout/stk_input.xml b/res/layout/stk_input.xml
index c2a34aa..7f8f96d 100644
--- a/res/layout/stk_input.xml
+++ b/res/layout/stk_input.xml
@@ -52,7 +52,6 @@
                 android:layout_height="wrap_content"
                 android:textAppearance="?android:attr/textAppearanceMedium"
                 android:textColor="?android:attr/textColorPrimary"
-                android:gravity="center_horizontal"
                 android:paddingBottom="30dip" />
             <LinearLayout
                 android:layout_width="match_parent"
@@ -62,18 +61,16 @@
                     android:visibility="visible"
                     android:orientation="vertical"
                     android:layout_width="match_parent"
-                    android:layout_marginLeft="10dip"
-                    android:layout_marginRight="10dip"
                     android:layout_height="wrap_content">
                     <LinearLayout
                         android:id="@+id/input_restriction_info"
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:layout_marginLeft="4dip"
-                        android:orientation="horizontal">
+                        android:orientation="vertical">
                         <TextView
                             android:id="@+id/input_type"
-                            android:gravity="left"
+                            android:gravity="start"
                             android:textAppearance="?android:attr/textAppearanceMedium"
                             android:textColor="?android:attr/textColorSecondary"
                             android:layout_width="wrap_content"
@@ -83,29 +80,35 @@
                             android:textAppearance="?android:attr/textAppearanceMedium"
                             android:textColor="?android:attr/textColorSecondary"
                             android:layout_width="wrap_content"
-                            android:layout_height="wrap_content"
-                            android:layout_marginLeft="4dip" />
+                            android:layout_height="wrap_content" />
                     </LinearLayout>
                     <EditText
                         android:id="@+id/in_text"
                         android:layout_gravity="center_horizontal"
-                        android:layout_marginBottom="20dip"
+                        android:layout_marginBottom="16dip"
                         android:layout_width="match_parent"
                         android:layout_height="wrap_content" />
                     <LinearLayout
                         android:layout_width="match_parent"
                         android:layout_height="wrap_content"
                         android:gravity="end"
+                        android:layout_marginStart="4dip"
+                        android:layout_marginBottom="16dip"
                         android:orientation="horizontal">
                         <Button
                             android:id="@+id/button_cancel"
-                            android:layout_height="wrap_content"
+                            android:layout_height="48dip"
                             android:layout_width="wrap_content"
+                            android:layout_weight="1"
+                            android:gravity="center_vertical"
+                            android:paddingStart="0dip"
+                            style="@android:style/Widget.Material.Button.Borderless.Colored"
                             android:text="@string/button_cancel" />
                         <Button
                             android:id="@+id/button_ok"
-                            android:layout_height="wrap_content"
+                            android:layout_height="48dip"
                             android:layout_width="wrap_content"
+                            style="@android:style/Widget.Material.Button.Colored"
                             android:text="@string/button_ok" />
                     </LinearLayout>
                 </LinearLayout>
@@ -120,11 +123,13 @@
                         android:id="@+id/button_no"
                         android:layout_height="wrap_content"
                         android:layout_width="wrap_content"
+                        style="@android:style/Widget.Material.Button.Borderless.Colored"
                         android:text="@string/button_no" />
                     <Button
                         android:id="@+id/button_yes"
                         android:layout_height="wrap_content"
                         android:layout_width="wrap_content"
+                        style="@android:style/Widget.Material.Button.Colored"
                         android:text="@string/button_yes" />
                 </LinearLayout>
             </LinearLayout>
diff --git a/src/com/android/stk/StkAppService.java b/src/com/android/stk/StkAppService.java
index a85402c..a5a0517 100644
--- a/src/com/android/stk/StkAppService.java
+++ b/src/com/android/stk/StkAppService.java
@@ -50,17 +50,21 @@
 import android.os.PersistableBundle;
 import android.os.PowerManager;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.Vibrator;
 import android.provider.Settings;
 import android.support.v4.content.LocalBroadcastManager;
 import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.view.Gravity;
+import android.view.IWindowManager;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
+import android.view.WindowManagerPolicy;
 import android.widget.ImageView;
 import android.widget.TextView;
 import android.widget.Toast;
@@ -93,6 +97,8 @@
                    SetupEventListConstants.IDLE_SCREEN_AVAILABLE_EVENT;
 import static com.android.internal.telephony.cat.CatCmdMessage.
                    SetupEventListConstants.LANGUAGE_SELECTION_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage.
+                   SetupEventListConstants.USER_ACTIVITY_EVENT;
 
 /**
  * SIM toolkit application level service. Interacts with Telephopny messages,
@@ -124,6 +130,7 @@
         protected int mOpCode = -1;
         private Activity mActivityInstance = null;
         private Activity mDialogInstance = null;
+        private Activity mImmediateDialogInstance = null;
         private int mSlotId = 0;
         private SetupEventListSettings mSetupEventListSettings = null;
         private boolean mClearSelectItem = false;
@@ -149,6 +156,15 @@
                     mDialogInstance);
             return mDialogInstance;
         }
+        final synchronized void setImmediateDialogInstance(Activity act) {
+            CatLog.d(this, "setImmediateDialogInstance act : " + mSlotId + ", " + act);
+            callSetActivityInstMsg(OP_SET_IMMED_DAL_INST, mSlotId, act);
+        }
+        final synchronized Activity getImmediateDialogInstance() {
+            CatLog.d(this, "getImmediateDialogInstance act : " + mSlotId + ", " +
+                    mImmediateDialogInstance);
+            return mImmediateDialogInstance;
+        }
     }
 
     private volatile Looper mServiceLooper;
@@ -160,8 +176,10 @@
     private StkContext[] mStkContext = null;
     private int mSimCount = 0;
     private IProcessObserver.Stub mProcessObserver = null;
+    private BroadcastReceiver mLocaleChangeReceiver = null;
     private TonePlayer mTonePlayer = null;
     private Vibrator mVibrator = null;
+    private BroadcastReceiver mUserActivityReceiver = null;
 
     // Used for setting FLAG_ACTIVITY_NO_USER_ACTION when
     // creating an intent.
@@ -205,6 +223,7 @@
     static final int OP_LOCALE_CHANGED = 10;
     static final int OP_ALPHA_NOTIFY = 11;
     static final int OP_IDLE_SCREEN = 12;
+    static final int OP_SET_IMMED_DAL_INST = 13;
 
     //Invalid SetupEvent
     static final int INVALID_SETUP_EVENT = 0xFF;
@@ -218,6 +237,9 @@
     // Message id to remove stop tone message from queue.
     private static final int STOP_TONE_WHAT = 100;
 
+    // Message id to send user activity event to card.
+    private static final int OP_USER_ACTIVITY = 20;
+
     // Response ids
     static final int RES_ID_MENU_SELECTION = 11;
     static final int RES_ID_INPUT = 12;
@@ -377,7 +399,9 @@
     @Override
     public void onDestroy() {
         CatLog.d(LOG_TAG, "onDestroy()");
+        unregisterUserActivityReceiver();
         unregisterProcessObserver();
+        unregisterLocaleChangeReceiver();
         sInstance = null;
         waitForLooper();
         mServiceLooper.quit();
@@ -621,11 +645,15 @@
                 }
                 break;
             case OP_SET_DAL_INST:
-                Activity dal = new Activity();
+                Activity dal = (Activity) msg.obj;
                 CatLog.d(LOG_TAG, "Set dialog instance. " + dal);
-                dal = (Activity) msg.obj;
                 mStkContext[slotId].mDialogInstance = dal;
                 break;
+            case OP_SET_IMMED_DAL_INST:
+                Activity immedDal = (Activity) msg.obj;
+                CatLog.d(LOG_TAG, "Set dialog instance for immediate response. " + immedDal);
+                mStkContext[slotId].mImmediateDialogInstance = immedDal;
+                break;
             case OP_LOCALE_CHANGED:
                 CatLog.d(this, "Locale Changed");
                 for (int slot = PhoneConstants.SIM_ID_1; slot < mSimCount; slot++) {
@@ -649,6 +677,11 @@
                 CatLog.d(this, "Stop tone");
                 handleStopTone(msg, slotId);
                 break;
+            case OP_USER_ACTIVITY:
+                for (int slot = PhoneConstants.SIM_ID_1; slot < mSimCount; slot++) {
+                    checkForSetupEvent(USER_ACTIVITY_EVENT, null, slot);
+                }
+                break;
             }
         }
 
@@ -1377,6 +1410,21 @@
         }
     }
 
+    @Override
+    public void startActivity(Intent intent) {
+        int slotId = intent.getIntExtra(SLOT_ID, SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+        // Close the dialog displayed for DISPLAY TEXT command with an immediate response object
+        // before new dialog is displayed.
+        if (SubscriptionManager.isValidSlotIndex(slotId)) {
+            Activity dialog = mStkContext[slotId].getImmediateDialogInstance();
+            if (dialog != null) {
+                CatLog.d(LOG_TAG, "finish dialog for immediate response.");
+                dialog.finish();
+            }
+        }
+        super.startActivity(intent);
+    }
+
     private void launchMenuActivity(Menu menu, int slotId) {
         Intent newIntent = new Intent(Intent.ACTION_VIEW);
         String targetActivity = STK_MENU_ACTIVITY_NAME;
@@ -1411,7 +1459,7 @@
         newIntent.putExtra(SLOT_ID, slotId);
         newIntent.setData(uriData);
         newIntent.setFlags(intentFlags);
-        mContext.startActivity(newIntent);
+        startActivity(newIntent);
     }
 
     private void launchInputActivity(int slotId) {
@@ -1433,7 +1481,7 @@
         if (input != null) {
             notifyUserIfNecessary(slotId, input.text);
         }
-        mContext.startActivity(newIntent);
+        startActivity(newIntent);
     }
 
     private void launchTextDialog(int slotId) {
@@ -1616,11 +1664,28 @@
     }
 
     private void unregisterEvent(int event, int slotId) {
+        for (int slot = PhoneConstants.SIM_ID_1; slot < mSimCount; slot++) {
+            if (slot != slotId) {
+                if (mStkContext[slot].mSetupEventListSettings != null) {
+                    if (findEvent(event, mStkContext[slot].mSetupEventListSettings.eventList)) {
+                        // The specified event shall never be canceled
+                        // if there is any other SIM card which requests the event.
+                        return;
+                    }
+                }
+            }
+        }
+
         switch (event) {
+            case USER_ACTIVITY_EVENT:
+                unregisterUserActivityReceiver();
+                break;
             case IDLE_SCREEN_AVAILABLE_EVENT:
                 unregisterProcessObserver(AppInterface.CommandType.SET_UP_EVENT_LIST, slotId);
                 break;
             case LANGUAGE_SELECTION_EVENT:
+                unregisterLocaleChangeReceiver();
+                break;
             default:
                 break;
         }
@@ -1632,16 +1697,53 @@
         }
         for (int event : mStkContext[slotId].mSetupEventListSettings.eventList) {
             switch (event) {
+                case USER_ACTIVITY_EVENT:
+                    registerUserActivityReceiver();
+                    break;
                 case IDLE_SCREEN_AVAILABLE_EVENT:
                     registerProcessObserver();
                     break;
                 case LANGUAGE_SELECTION_EVENT:
+                    registerLocaleChangeReceiver();
+                    break;
                 default:
                     break;
             }
         }
     }
 
+    private synchronized void registerUserActivityReceiver() {
+        if (mUserActivityReceiver == null) {
+            mUserActivityReceiver = new BroadcastReceiver() {
+                @Override public void onReceive(Context context, Intent intent) {
+                    if (WindowManagerPolicy.ACTION_USER_ACTIVITY_NOTIFICATION.equals(
+                            intent.getAction())) {
+                        Message message = mServiceHandler.obtainMessage();
+                        message.arg1 = OP_USER_ACTIVITY;
+                        mServiceHandler.sendMessage(message);
+                        unregisterUserActivityReceiver();
+                    }
+                }
+            };
+            registerReceiver(mUserActivityReceiver, new IntentFilter(
+                    WindowManagerPolicy.ACTION_USER_ACTIVITY_NOTIFICATION));
+            try {
+                IWindowManager wm = IWindowManager.Stub.asInterface(
+                        ServiceManager.getService(Context.WINDOW_SERVICE));
+                wm.requestUserActivityNotification();
+            } catch (RemoteException e) {
+                CatLog.e(this, "failed to init WindowManager:" + e);
+            }
+        }
+    }
+
+    private synchronized void unregisterUserActivityReceiver() {
+        if (mUserActivityReceiver != null) {
+            unregisterReceiver(mUserActivityReceiver);
+            mUserActivityReceiver = null;
+        }
+    }
+
     private synchronized void registerProcessObserver() {
         if (mProcessObserver == null) {
             try {
@@ -1705,6 +1807,28 @@
         }
     }
 
+    private synchronized void registerLocaleChangeReceiver() {
+        if (mLocaleChangeReceiver == null) {
+            mLocaleChangeReceiver = new BroadcastReceiver() {
+                @Override public void onReceive(Context context, Intent intent) {
+                    if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
+                        Message message = mServiceHandler.obtainMessage();
+                        message.arg1 = OP_LOCALE_CHANGED;
+                        mServiceHandler.sendMessage(message);
+                    }
+                }
+            };
+            registerReceiver(mLocaleChangeReceiver, new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
+        }
+    }
+
+    private synchronized void unregisterLocaleChangeReceiver() {
+        if (mLocaleChangeReceiver != null) {
+            unregisterReceiver(mLocaleChangeReceiver);
+            mLocaleChangeReceiver = null;
+        }
+    }
+
     private void sendSetUpEventResponse(int event, byte[] addedInfo, int slotId) {
         CatLog.d(this, "sendSetUpEventResponse: event : " + event + "slotId = " + slotId);
 
@@ -1741,6 +1865,7 @@
                 CatLog.d(this, " Event " + event + "exists in the EventList");
 
                 switch (event) {
+                    case USER_ACTIVITY_EVENT:
                     case IDLE_SCREEN_AVAILABLE_EVENT:
                         sendSetUpEventResponse(event, addedInfo, slotId);
                         removeSetUpEvent(event, slotId);
@@ -1778,6 +1903,11 @@
                     mStkContext[slotId].mSetupEventListSettings.eventList[i] = INVALID_SETUP_EVENT;
 
                     switch (event) {
+                        case USER_ACTIVITY_EVENT:
+                            // The broadcast receiver can be unregistered
+                            // as the event has already been sent to the card.
+                            unregisterUserActivityReceiver();
+                            break;
                         case IDLE_SCREEN_AVAILABLE_EVENT:
                             // The process observer can be unregistered
                             // as the idle screen has already been available.
diff --git a/src/com/android/stk/StkCmdReceiver.java b/src/com/android/stk/StkCmdReceiver.java
index d011eee..aeb4e22 100644
--- a/src/com/android/stk/StkCmdReceiver.java
+++ b/src/com/android/stk/StkCmdReceiver.java
@@ -45,8 +45,6 @@
             handleAction(context, intent, StkAppService.OP_END_SESSION);
         } else if (action.equals(AppInterface.CAT_ICC_STATUS_CHANGE)) {
             handleAction(context, intent, StkAppService.OP_CARD_STATUS_CHANGED);
-        } else if (action.equals(Intent.ACTION_LOCALE_CHANGED)) {
-            handleLocaleChange(context);
         } else if (action.equals(AppInterface.CAT_ALPHA_NOTIFY_ACTION)) {
             handleAction(context, intent, StkAppService.OP_ALPHA_NOTIFY);
         }
@@ -86,11 +84,4 @@
         toService.putExtras(args);
         context.startService(toService);
     }
-
-    private void handleLocaleChange(Context context) {
-        Bundle args = new Bundle();
-        args.putInt(StkAppService.OPCODE, StkAppService.OP_LOCALE_CHANGED);
-        context.startService(new Intent(context, StkAppService.class)
-                .putExtras(args));
-    }
 }
diff --git a/src/com/android/stk/StkDialogActivity.java b/src/com/android/stk/StkDialogActivity.java
index cdc3c17..be43747 100644
--- a/src/com/android/stk/StkDialogActivity.java
+++ b/src/com/android/stk/StkDialogActivity.java
@@ -32,6 +32,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.SystemClock;
+import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -125,6 +126,10 @@
 
         if (!mTextMsg.responseNeeded) {
             alertDialogBuilder.setNegativeButton(null, null);
+            // Register the instance of this activity because the dialog displayed for DISPLAY TEXT
+            // command with an immediate response object should disappear when the terminal receives
+            // a subsequent proactive command containing display data.
+            appService.getStkContext(mSlotId).setImmediateDialogInstance(this);
         }
 
         alertDialogBuilder.setTitle(mTextMsg.title);
@@ -301,6 +306,18 @@
         setIntent(intent);
     }
 
+    @Override
+    public void finish() {
+        super.finish();
+        // Unregister the instance for DISPLAY TEXT command with an immediate response object
+        // as it is unnecessary to ask the service to finish this anymore.
+        if ((appService != null) && (mTextMsg != null) && !mTextMsg.responseNeeded) {
+            if (SubscriptionManager.isValidSlotIndex(mSlotId)) {
+                appService.getStkContext(mSlotId).setImmediateDialogInstance(null);
+            }
+        }
+    }
+
     private void sendResponse(int resId, boolean confirmed) {
         if (mSlotId == -1) {
             CatLog.d(LOG_TAG, "sim id is invalid");
diff --git a/src/com/android/stk/StkMenuActivity.java b/src/com/android/stk/StkMenuActivity.java
index 9f268d1..bc12eee 100644
--- a/src/com/android/stk/StkMenuActivity.java
+++ b/src/com/android/stk/StkMenuActivity.java
@@ -68,6 +68,12 @@
 
     private StkAppService appService = StkAppService.getInstance();
 
+    // Keys for saving the state of the dialog in the bundle
+    private static final String STATE_KEY = "state";
+    private static final String MENU_KEY = "menu";
+    private static final String ACCEPT_USERS_INPUT_KEY = "accept_users_input";
+    private static final String RESPONSE_SENT_KEY = "response_sent";
+
     // Internal state values
     static final int STATE_INIT = 0;
     static final int STATE_MAIN = 1;
@@ -99,8 +105,8 @@
     };
 
     @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
 
         CatLog.d(LOG_TAG, "onCreate");
 
@@ -242,6 +248,12 @@
     public void onStop() {
         super.onStop();
         CatLog.d(LOG_TAG, "onStop, slot id: " + mSlotId + "," + mIsResponseSent + "," + mState);
+
+        // Nothing should be done here if this activity is being restarted now.
+        if (isChangingConfigurations()) {
+            return;
+        }
+
         //The menu should stay in background, if
         //1. the dialog is pop up in the screen, but the user does not response to the dialog.
         //2. the menu activity enters Stop state (e.g pressing HOME key) but mIsResponseSent is false.
@@ -280,8 +292,12 @@
         //isMenuPending: if input act is finish by stkappservice when OP_LAUNCH_APP again,
         //we can not send TR here, since the input cmd is waiting user to process.
         if (mState == STATE_SECONDARY && !mIsResponseSent && !appService.isMenuPending(mSlotId)) {
-            CatLog.d(LOG_TAG, "handleDestroy - Send End Session");
-            sendResponse(StkAppService.RES_ID_END_SESSION);
+            // Avoid sending the terminal response while the activty is being restarted
+            // due to some kind of configuration change.
+            if (!isChangingConfigurations()) {
+                CatLog.d(LOG_TAG, "handleDestroy - Send End Session");
+                sendResponse(StkAppService.RES_ID_END_SESSION);
+            }
         }
         LocalBroadcastManager.getInstance(this).unregisterReceiver(mLocalBroadcastReceiver);
     }
@@ -368,17 +384,18 @@
     @Override
     protected void onSaveInstanceState(Bundle outState) {
         CatLog.d(LOG_TAG, "onSaveInstanceState: " + mSlotId);
-        outState.putInt("STATE", mState);
-        outState.putParcelable("MENU", mStkMenu);
-        outState.putBoolean("ACCEPT_USERS_INPUT", mAcceptUsersInput);
+        outState.putInt(STATE_KEY, mState);
+        outState.putParcelable(MENU_KEY, mStkMenu);
+        outState.putBoolean(ACCEPT_USERS_INPUT_KEY, mAcceptUsersInput);
+        outState.putBoolean(RESPONSE_SENT_KEY, mIsResponseSent);
     }
 
     @Override
     protected void onRestoreInstanceState(Bundle savedInstanceState) {
         CatLog.d(LOG_TAG, "onRestoreInstanceState: " + mSlotId);
-        mState = savedInstanceState.getInt("STATE");
-        mStkMenu = savedInstanceState.getParcelable("MENU");
-        mAcceptUsersInput = savedInstanceState.getBoolean("ACCEPT_USERS_INPUT");
+        mState = savedInstanceState.getInt(STATE_KEY);
+        mStkMenu = savedInstanceState.getParcelable(MENU_KEY);
+        mAcceptUsersInput = savedInstanceState.getBoolean(ACCEPT_USERS_INPUT_KEY);
         if (!mAcceptUsersInput) {
             // Check the latest information as the saved instance state can be outdated.
             if ((mState == STATE_MAIN) && appService.isMainMenuAvailable(mSlotId)) {
@@ -387,6 +404,7 @@
                 showProgressBar(true);
             }
         }
+        mIsResponseSent = savedInstanceState.getBoolean(RESPONSE_SENT_KEY);
     }
 
     private void cancelTimeOut() {