Showing up network selection notification if out of service for a while.

1. In case network selection notification shows up repeatedly under
unstable network condition. Should check the service state is out of
service for a while then shows up No Service Notification.
2. Support multi-sim for network selection notification.
3. Dismiss network selection notification if sub id is inactive.

Bug: 72380747
Bug: 110119009
Test: In manual
      1. Check the Notification should not show up under Automatically
      select network mode.
      2. In case under Manual select mode, the Notification should not
      show up when selected a valid PLMN (Camped on).
      3. In case under Manual select mode, the Notification show up
      after 10 secends when selected a forbidden PLMN.

Change-Id: I94d8ea2f903e219ead8236eb17f364a306577a52
Merged-In: Ic37fa8edb1868dffff9c9d4d63c4ee4f9b38d8b0
diff --git a/src/com/android/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index 5aa87d0..de3fffc 100644
--- a/src/com/android/phone/NotificationMgr.java
+++ b/src/com/android/phone/NotificationMgr.java
@@ -30,7 +30,10 @@
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
 import android.os.PersistableBundle;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -50,6 +53,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.SparseArray;
 import android.widget.Toast;
 
 import com.android.internal.telephony.Phone;
@@ -89,6 +93,14 @@
     static final int DATA_DISCONNECTED_ROAMING_NOTIFICATION = 5;
     static final int SELECTED_OPERATOR_FAIL_NOTIFICATION = 6;
 
+    // Event for network selection notification.
+    private static final int EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION = 1;
+
+    private static final long NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS = 10000L;
+    private static final int NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIMES = 10;
+
+    private static final int STATE_UNKNOWN_SERVICE = -1;
+
     /** The singleton NotificationMgr instance. */
     private static NotificationMgr sInstance;
 
@@ -103,12 +115,36 @@
     private TelecomManager mTelecomManager;
     private TelephonyManager mTelephonyManager;
 
-    // used to track the notification of selected network unavailable
-    private boolean mSelectedUnavailableNotify = false;
+    // used to track the notification of selected network unavailable, per subscription id.
+    private SparseArray<Boolean> mSelectedUnavailableNotify = new SparseArray<>();
 
     // used to track whether the message waiting indicator is visible, per subscription id.
     private ArrayMap<Integer, Boolean> mMwiVisible = new ArrayMap<Integer, Boolean>();
 
+    // those flags are used to track whether to show network selection notification or not.
+    private SparseArray<Integer> mPreviousServiceState = new SparseArray<>();
+    private SparseArray<Long> mOOSTimestamp = new SparseArray<>();
+    private SparseArray<Integer> mPendingEventCounter = new SparseArray<>();
+    // maps each subId to selected network operator name.
+    private SparseArray<String> mSelectedNetworkOperatorName = new SparseArray<>();
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION:
+                    int subId = (int) msg.obj;
+                    TelephonyManager telephonyManager =
+                            mTelephonyManager.createForSubscriptionId(subId);
+                    if (telephonyManager.getServiceState() != null) {
+                        shouldShowNotification(telephonyManager.getServiceState().getState(),
+                                subId);
+                    }
+                    break;
+            }
+        }
+    };
+
     /**
      * Private constructor (this is a singleton).
      * @see #init(PhoneGlobals)
@@ -592,19 +628,21 @@
         intent.putExtra(GsmUmtsOptions.EXTRA_SUB_ID, subId);
         builder.setContentIntent(PendingIntent.getActivity(mContext, 0, intent, 0));
         mNotificationManager.notifyAsUser(
-                null /* tag */,
+                Integer.toString(subId) /* tag */,
                 SELECTED_OPERATOR_FAIL_NOTIFICATION,
                 builder.build(),
                 UserHandle.ALL);
+        mSelectedUnavailableNotify.put(subId, true);
     }
 
     /**
      * Turn off the network selection "no service" notification
      */
-    private void cancelNetworkSelection() {
+    private void cancelNetworkSelection(int subId) {
         if (DBG) log("cancelNetworkSelection()...");
         mNotificationManager.cancelAsUser(
-                null /* tag */, SELECTED_OPERATOR_FAIL_NOTIFICATION, UserHandle.ALL);
+                Integer.toString(subId) /* tag */, SELECTED_OPERATOR_FAIL_NOTIFICATION,
+                UserHandle.ALL);
     }
 
     /**
@@ -645,18 +683,34 @@
                             + (isManualSelection ? selectedNetworkOperatorName : ""));
                 }
 
-                if (serviceState == ServiceState.STATE_OUT_OF_SERVICE && isManualSelection) {
-                    showNetworkSelection(selectedNetworkOperatorName, subId);
-                    mSelectedUnavailableNotify = true;
+                if (isManualSelection) {
+                    mSelectedNetworkOperatorName.put(subId, selectedNetworkOperatorName);
+                    shouldShowNotification(serviceState, subId);
                 } else {
-                    if (mSelectedUnavailableNotify) {
-                        cancelNetworkSelection();
-                        mSelectedUnavailableNotify = false;
-                    }
+                    dismissNetworkSelectionNotification(subId);
+                    clearUpNetworkSelectionNotificationParam(subId);
                 }
             } else {
                 if (DBG) log("updateNetworkSelection()..." + "state = " +
                         serviceState + " not updating network due to invalid subId " + subId);
+                dismissNetworkSelectionNotificationForInactiveSubId();
+            }
+        }
+    }
+
+    private void dismissNetworkSelectionNotification(int subId) {
+        if (mSelectedUnavailableNotify.get(subId, false)) {
+            cancelNetworkSelection(subId);
+            mSelectedUnavailableNotify.remove(subId);
+        }
+    }
+
+    private void dismissNetworkSelectionNotificationForInactiveSubId() {
+        for (int i = 0; i < mSelectedUnavailableNotify.size(); i++) {
+            int subId = mSelectedUnavailableNotify.keyAt(i);
+            if (!mSubscriptionManager.isActiveSubId(subId)) {
+                dismissNetworkSelectionNotification(subId);
+                clearUpNetworkSelectionNotificationParam(subId);
             }
         }
     }
@@ -677,4 +731,65 @@
     private void logi(String msg) {
         Log.i(LOG_TAG, msg);
     }
+
+    /**
+     * In case network selection notification shows up repeatedly under
+     * unstable network condition. The logic is to check whether or not
+     * the service state keeps in no service condition for at least
+     * {@link #NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS}.
+     * And checking {@link #NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIMES} times.
+     * To avoid the notification showing up for the momentary state.
+     */
+    private void shouldShowNotification(int serviceState, int subId) {
+        if (serviceState == ServiceState.STATE_OUT_OF_SERVICE) {
+            if (mPreviousServiceState.get(subId, STATE_UNKNOWN_SERVICE)
+                    != ServiceState.STATE_OUT_OF_SERVICE) {
+                mOOSTimestamp.put(subId, getTimeStamp());
+            }
+            if ((getTimeStamp() - mOOSTimestamp.get(subId, 0L)
+                    >= NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS)
+                    || mPendingEventCounter.get(subId, 0)
+                    > NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIMES) {
+                showNetworkSelection(mSelectedNetworkOperatorName.get(subId), subId);
+                clearUpNetworkSelectionNotificationParam(subId);
+            } else {
+                startPendingNetworkSelectionNotification(subId);
+            }
+        } else {
+            dismissNetworkSelectionNotification(subId);
+        }
+        mPreviousServiceState.put(subId, serviceState);
+        if (DBG) {
+            log("shouldShowNotification()..." + " subId = " + subId
+                    + " serviceState = " + serviceState
+                    + " mOOSTimestamp = " + mOOSTimestamp
+                    + " mPendingEventCounter = " + mPendingEventCounter);
+        }
+    }
+
+    private void startPendingNetworkSelectionNotification(int subId) {
+        if (!mHandler.hasMessages(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId)) {
+            if (DBG) {
+                log("startPendingNetworkSelectionNotification: subId = " + subId);
+            }
+            mHandler.sendMessageDelayed(
+                    mHandler.obtainMessage(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId),
+                    NETWORK_SELECTION_NOTIFICATION_MAX_PENDING_TIME_IN_MS);
+            mPendingEventCounter.put(subId, mPendingEventCounter.get(subId, 0) + 1);
+        }
+    }
+
+    private void clearUpNetworkSelectionNotificationParam(int subId) {
+        if (mHandler.hasMessages(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId)) {
+            mHandler.removeMessages(EVENT_PENDING_NETWORK_SELECTION_NOTIFICATION, subId);
+        }
+        mPreviousServiceState.remove(subId);
+        mOOSTimestamp.remove(subId);
+        mPendingEventCounter.remove(subId);
+        mSelectedNetworkOperatorName.remove(subId);
+    }
+
+    private static long getTimeStamp() {
+        return SystemClock.elapsedRealtime();
+    }
 }