Merge "Improve the logic to determine the idle mode"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
old mode 100755
new mode 100644
index aef271d..e3419a9
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -27,6 +27,7 @@
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <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" />
 
     <application android:icon="@drawable/ic_launcher_sim_toolkit"
         android:label="@string/app_name"
diff --git a/src/com/android/stk/StkAppService.java b/src/com/android/stk/StkAppService.java
index 4e8ae9a..2e51e21 100644
--- a/src/com/android/stk/StkAppService.java
+++ b/src/com/android/stk/StkAppService.java
@@ -25,9 +25,13 @@
 import android.app.PendingIntent;
 import android.app.Service;
 import android.app.Activity;
+import android.app.ActivityManagerNative;
+import android.app.IProcessObserver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
@@ -42,6 +46,7 @@
 import android.os.Parcel;
 import android.os.PersistableBundle;
 import android.os.PowerManager;
+import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.Vibrator;
 import android.provider.Settings;
@@ -120,6 +125,7 @@
         private boolean mDisplayTextDlgIsVisibile = false;
         private CatCmdMessage mCurrentSetupEventCmd = null;
         private CatCmdMessage mIdleModeTextCmd = null;
+        private boolean mIdleModeTextVisible = false;
         final synchronized void setPendingActivityInstance(Activity act) {
             CatLog.d(this, "setPendingActivityInstance act : " + mSlotId + ", " + act);
             callSetActivityInstMsg(OP_SET_ACT_INST, mSlotId, act);
@@ -157,8 +163,7 @@
     private AppInterface[] mStkService = null;
     private StkContext[] mStkContext = null;
     private int mSimCount = 0;
-    private PowerManager mPowerManager = null;
-    private StkCmdReceiver mStkCmdReceiver = null;
+    private IProcessObserver.Stub mProcessObserver = null;
     private TonePlayer mTonePlayer = null;
     private Vibrator mVibrator = null;
 
@@ -277,9 +282,7 @@
         CatLog.d(LOG_TAG, "simCount: " + mSimCount);
         mStkService = new AppInterface[mSimCount];
         mStkContext = new StkContext[mSimCount];
-        mPowerManager = (PowerManager)getSystemService(Context.POWER_SERVICE);
-        mStkCmdReceiver = new StkCmdReceiver();
-        registerReceiver(mStkCmdReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+
         for (i = 0; i < mSimCount; i++) {
             CatLog.d(LOG_TAG, "slotId: " + i);
             mStkService[i] = CatService.getInstance(i);
@@ -373,11 +376,7 @@
     @Override
     public void onDestroy() {
         CatLog.d(LOG_TAG, "onDestroy()");
-        if (mStkCmdReceiver != null) {
-            unregisterReceiver(mStkCmdReceiver);
-            mStkCmdReceiver = null;
-        }
-        mPowerManager = null;
+        unregisterProcessObserver();
         sInstance = null;
         waitForLooper();
         mServiceLooper.quit();
@@ -657,7 +656,7 @@
             if (cardStatus == false) {
                 CatLog.d(LOG_TAG, "CARD is ABSENT");
                 // Uninstall STKAPP, Clear Idle text, Stop StkAppService
-                mNotificationManager.cancel(getNotificationId(slotId));
+                cancelIdleText(slotId);
                 mStkContext[slotId].mCurrentMenu = null;
                 mStkContext[slotId].mMainCmd = null;
                 if (isAllOtherCardsAbsent(slotId)) {
@@ -673,7 +672,7 @@
                 if ((state.refreshResult == IccRefreshResponse.REFRESH_RESULT_INIT) ||
                     (state.refreshResult == IccRefreshResponse.REFRESH_RESULT_RESET)) {
                     // Clear Idle Text
-                    mNotificationManager.cancel(getNotificationId(slotId));
+                    cancelIdleText(slotId);
                 }
 
                 if (state.refreshResult == IccRefreshResponse.REFRESH_RESULT_RESET) {
@@ -707,12 +706,31 @@
         }
     }
 
-    /*
-     * If the device is not in an interactive state, we can assume
-     * that the screen is idle.
-     */
-    private boolean isScreenIdle() {
-        return (!mPowerManager.isInteractive());
+    /* package */ boolean isScreenIdle() {
+        ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
+        List<RunningTaskInfo> tasks = am.getRunningTasks(1);
+        if (tasks == null || tasks.isEmpty()) {
+            return false;
+        }
+
+        String top = tasks.get(0).topActivity.getPackageName();
+        if (top == null) {
+            return false;
+        }
+
+        // We can assume that the screen is idle if the home application is in the foreground.
+        final Intent intent = new Intent(Intent.ACTION_MAIN, null);
+        intent.addCategory(Intent.CATEGORY_HOME);
+
+        ResolveInfo info = getPackageManager().resolveActivity(intent,
+                PackageManager.MATCH_DEFAULT_ONLY);
+        if (info != null) {
+            if (top.equals(info.activityInfo.packageName)) {
+                return true;
+            }
+        }
+
+        return false;
     }
 
     private void handleIdleScreen(int slotId) {
@@ -722,8 +740,9 @@
         CatLog.d(this, "Need to send IDLE SCREEN Available event to SIM");
         checkForSetupEvent(IDLE_SCREEN_AVAILABLE_EVENT, null, slotId);
 
-        if (mStkContext[slotId].mIdleModeTextCmd != null) {
-           launchIdleText(slotId);
+        if (mStkContext[slotId].mIdleModeTextCmd != null
+                && !mStkContext[slotId].mIdleModeTextVisible) {
+            launchIdleText(slotId);
         }
     }
 
@@ -977,14 +996,17 @@
             waitForUsersResponse = false;
             mStkContext[slotId].mIdleModeTextCmd = mStkContext[slotId].mCurrentCmd;
             TextMessage idleModeText = mStkContext[slotId].mCurrentCmd.geTextMessage();
-            if (idleModeText == null) {
-                launchIdleText(slotId);
-                mStkContext[slotId].mIdleModeTextCmd = null;
+            if (idleModeText == null || TextUtils.isEmpty(idleModeText.text)) {
+                cancelIdleText(slotId);
             }
             mStkContext[slotId].mCurrentCmd = mStkContext[slotId].mMainCmd;
-            if ((mStkContext[slotId].mIdleModeTextCmd != null) && isScreenIdle()) {
-                CatLog.d(this, "set up idle mode");
-                launchIdleText(slotId);
+            if (mStkContext[slotId].mIdleModeTextCmd != null) {
+                if (mStkContext[slotId].mIdleModeTextVisible || isScreenIdle()) {
+                    CatLog.d(this, "set up idle mode");
+                    launchIdleText(slotId);
+                } else {
+                    registerProcessObserver();
+                }
             }
             break;
         case SEND_DTMF:
@@ -1069,10 +1091,7 @@
             launchEventMessage(slotId);
             break;
         case SET_UP_EVENT_LIST:
-            mStkContext[slotId].mSetupEventListSettings =
-                    mStkContext[slotId].mCurrentCmd.getSetEventList();
-            mStkContext[slotId].mCurrentSetupEventCmd = mStkContext[slotId].mCurrentCmd;
-            mStkContext[slotId].mCurrentCmd = mStkContext[slotId].mMainCmd;
+            replaceEventList(slotId);
             if (isScreenIdle()) {
                 CatLog.d(this," Check if IDLE_SCREEN_AVAILABLE_EVENT is present in List");
                 checkForSetupEvent(IDLE_SCREEN_AVAILABLE_EVENT, null, slotId);
@@ -1437,6 +1456,123 @@
         return activated;
     }
 
+    private void replaceEventList(int slotId) {
+        if (mStkContext[slotId].mSetupEventListSettings != null) {
+            for (int current : mStkContext[slotId].mSetupEventListSettings.eventList) {
+                if (current != INVALID_SETUP_EVENT) {
+                    // Cancel the event notification if it is not listed in the new event list.
+                    if ((mStkContext[slotId].mCurrentCmd.getSetEventList() == null)
+                            || !findEvent(current, mStkContext[slotId].mCurrentCmd
+                            .getSetEventList().eventList)) {
+                        unregisterEvent(current, slotId);
+                    }
+                }
+            }
+        }
+        mStkContext[slotId].mSetupEventListSettings
+                = mStkContext[slotId].mCurrentCmd.getSetEventList();
+        mStkContext[slotId].mCurrentSetupEventCmd = mStkContext[slotId].mCurrentCmd;
+        mStkContext[slotId].mCurrentCmd = mStkContext[slotId].mMainCmd;
+        registerEvents(slotId);
+    }
+
+    private boolean findEvent(int event, int[] eventList) {
+        for (int content : eventList) {
+            if (content == event) return true;
+        }
+        return false;
+    }
+
+    private void unregisterEvent(int event, int slotId) {
+        switch (event) {
+            case IDLE_SCREEN_AVAILABLE_EVENT:
+                unregisterProcessObserver(AppInterface.CommandType.SET_UP_EVENT_LIST, slotId);
+                break;
+            case LANGUAGE_SELECTION_EVENT:
+            default:
+                break;
+        }
+    }
+
+    private void registerEvents(int slotId) {
+        if (mStkContext[slotId].mSetupEventListSettings == null) {
+            return;
+        }
+        for (int event : mStkContext[slotId].mSetupEventListSettings.eventList) {
+            switch (event) {
+                case IDLE_SCREEN_AVAILABLE_EVENT:
+                    registerProcessObserver();
+                    break;
+                case LANGUAGE_SELECTION_EVENT:
+                default:
+                    break;
+            }
+        }
+    }
+
+    private synchronized void registerProcessObserver() {
+        if (mProcessObserver == null) {
+            try {
+                IProcessObserver.Stub observer = new IProcessObserver.Stub() {
+                    @Override
+                    public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) {
+                        if (isScreenIdle()) {
+                            Message message = mServiceHandler.obtainMessage();
+                            message.arg1 = OP_IDLE_SCREEN;
+                            mServiceHandler.sendMessage(message);
+                            unregisterProcessObserver();
+                        }
+                    }
+
+                    @Override
+                    public void onProcessDied(int pid, int uid) {
+                    }
+                };
+                ActivityManagerNative.getDefault().registerProcessObserver(observer);
+                mProcessObserver = observer;
+            } catch (RemoteException e) {
+                CatLog.d(this, "Failed to register the process observer");
+            }
+        }
+    }
+
+    private void unregisterProcessObserver(AppInterface.CommandType command, int slotId) {
+        // Check if there is any pending command which still needs the process observer
+        // except for the current command and slot.
+        for (int slot = PhoneConstants.SIM_ID_1; slot < mSimCount; slot++) {
+            if (command != AppInterface.CommandType.SET_UP_IDLE_MODE_TEXT || slot != slotId) {
+                if (mStkContext[slot].mIdleModeTextCmd != null
+                        && !mStkContext[slot].mIdleModeTextVisible) {
+                    // Keep the process observer registered
+                    // as there is an idle mode text which has not been visible yet.
+                    return;
+                }
+            }
+            if (command != AppInterface.CommandType.SET_UP_EVENT_LIST || slot != slotId) {
+                if (mStkContext[slot].mSetupEventListSettings != null) {
+                    if (findEvent(IDLE_SCREEN_AVAILABLE_EVENT,
+                                mStkContext[slot].mSetupEventListSettings.eventList)) {
+                        // Keep the process observer registered
+                        // as there is a SIM card which still want IDLE SCREEN AVAILABLE event.
+                        return;
+                    }
+                }
+            }
+        }
+        unregisterProcessObserver();
+    }
+
+    private synchronized void unregisterProcessObserver() {
+        if (mProcessObserver != null) {
+            try {
+                ActivityManagerNative.getDefault().unregisterProcessObserver(mProcessObserver);
+                mProcessObserver = null;
+            } catch (RemoteException e) {
+                CatLog.d(this, "Failed to unregister the process observer");
+            }
+        }
+    }
+
     private void sendSetUpEventResponse(int event, byte[] addedInfo, int slotId) {
         CatLog.d(this, "sendSetUpEventResponse: event : " + event + "slotId = " + slotId);
 
@@ -1498,7 +1634,7 @@
         }
     }
 
-    private void  removeSetUpEvent(int event, int slotId) {
+    private void removeSetUpEvent(int event, int slotId) {
         CatLog.d(this, "Remove Event :" + event);
 
         if (mStkContext[slotId].mSetupEventListSettings != null) {
@@ -1508,6 +1644,16 @@
             for (int i = 0; i < mStkContext[slotId].mSetupEventListSettings.eventList.length; i++) {
                 if (event == mStkContext[slotId].mSetupEventListSettings.eventList[i]) {
                     mStkContext[slotId].mSetupEventListSettings.eventList[i] = INVALID_SETUP_EVENT;
+
+                    switch (event) {
+                        case IDLE_SCREEN_AVAILABLE_EVENT:
+                            // The process observer can be unregistered
+                            // as the idle screen has already been available.
+                            unregisterProcessObserver();
+                            break;
+                        default:
+                            break;
+                    }
                     break;
                 }
             }
@@ -1627,15 +1773,17 @@
         } catch (InterruptedException e) {}
     }
 
+    private void cancelIdleText(int slotId) {
+        unregisterProcessObserver(AppInterface.CommandType.SET_UP_IDLE_MODE_TEXT, slotId);
+        mNotificationManager.cancel(getNotificationId(slotId));
+        mStkContext[slotId].mIdleModeTextCmd = null;
+        mStkContext[slotId].mIdleModeTextVisible = false;
+    }
+
     private void launchIdleText(int slotId) {
         TextMessage msg = mStkContext[slotId].mIdleModeTextCmd.geTextMessage();
 
-        if (msg == null || msg.text ==null) {
-            CatLog.d(LOG_TAG,  msg == null ? "mCurrent.getTextMessage is NULL"
-                    : "mCurrent.getTextMessage.text is NULL");
-            mNotificationManager.cancel(getNotificationId(slotId));
-            return;
-        } else {
+        if (msg != null && !TextUtils.isEmpty(msg.text)) {
             CatLog.d(LOG_TAG, "launchIdleText - text[" + msg.text
                     + "] iconSelfExplanatory[" + msg.iconSelfExplanatory
                     + "] icon[" + msg.icon + "], sim id: " + slotId);
@@ -1672,6 +1820,7 @@
             notificationBuilder.setColor(mContext.getResources().getColor(
                     com.android.internal.R.color.system_notification_accent_color));
             mNotificationManager.notify(getNotificationId(slotId), notificationBuilder.build());
+            mStkContext[slotId].mIdleModeTextVisible = true;
         }
     }
 
diff --git a/src/com/android/stk/StkCmdReceiver.java b/src/com/android/stk/StkCmdReceiver.java
index fd6f382..d011eee 100644
--- a/src/com/android/stk/StkCmdReceiver.java
+++ b/src/com/android/stk/StkCmdReceiver.java
@@ -49,8 +49,6 @@
             handleLocaleChange(context);
         } else if (action.equals(AppInterface.CAT_ALPHA_NOTIFY_ACTION)) {
             handleAction(context, intent, StkAppService.OP_ALPHA_NOTIFY);
-        } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
-            handleIdleScreen(context);
         }
     }
 
@@ -95,11 +93,4 @@
         context.startService(new Intent(context, StkAppService.class)
                 .putExtras(args));
     }
-
-    private void handleIdleScreen(Context context) {
-        Bundle args = new Bundle();
-        args.putInt(StkAppService.OPCODE, StkAppService.OP_IDLE_SCREEN);
-        context.startService(new Intent(context, StkAppService.class)
-                .putExtras(args));
-    }
 }