Improve the logic to determine the idle mode

The definition of the idle state is that idle screen is available in the
foreground of the screen according to ETSI TS 102.384 TC 27.22.4.22 "SET
UP IDLE MODE TEXT" and TC 27.22.7.6 "Idle screen available event". It is
required to detect which application is in the foreground to pass the
test cases. Intent.ACTION_SCREEN_OFF is not appropriate for that.

Bug: 67521651
Test: Confirmed that the test cases mentioned above can be passed.

Change-Id: I479100761843e91a7f1269d5258aef1684791c57
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;
         }
     }