WiFi not connecting to WiFi when screen OFF

    When framework toggle PNO too frequently, the lower layer
    (supplicant and driver) can have some corner case errors.
    Add minimum gap (5s) between two PNO toggle.

Bug: 24777453

Change-Id: Ic360b753c65b77831dc8395834b9ac125f1aa7a1
diff --git a/service/java/com/android/server/wifi/WifiNative.java b/service/java/com/android/server/wifi/WifiNative.java
index 2e25bae..c2dbdfd 100644
--- a/service/java/com/android/server/wifi/WifiNative.java
+++ b/service/java/com/android/server/wifi/WifiNative.java
@@ -35,7 +35,11 @@
 import android.util.Base64;
 import android.util.LocalLog;
 import android.util.Log;
-
+import android.content.Context;
+import android.content.Intent;
+import android.app.PendingIntent;
+import android.content.IntentFilter;
+import android.content.BroadcastReceiver;
 import com.android.server.connectivity.KeepalivePacketData;
 
 import java.io.ByteArrayOutputStream;
@@ -50,6 +54,7 @@
 import java.util.Locale;
 import java.util.zip.Deflater;
 import libcore.util.HexEncoding;
+import android.app.AlarmManager;
 /**
  * Native calls for bring up/shut down of the supplicant daemon and for
  * sending requests to the supplicant daemon
@@ -123,9 +128,18 @@
 
     private native String doStringCommandNative(String command);
 
-    public WifiNative(String interfaceName) {
+    private final Context mContext;
+    private final PnoMonitor mPnoMonitor;
+    public WifiNative(String interfaceName, Context context) {
         mInterfaceName = interfaceName;
+        mContext = context;
         mTAG = "WifiNative-" + interfaceName;
+        if (mContext != null) {
+            mPnoMonitor = new PnoMonitor();
+        } else {
+            mPnoMonitor = null;
+        }
+
         if (!interfaceName.equals("p2p0")) {
             mInterfacePrefix = "IFNAME=" + interfaceName + " ";
         } else {
@@ -134,6 +148,10 @@
         }
     }
 
+    public WifiNative(String interfaceName) {
+        this(interfaceName, null);
+    }
+
     void enableVerboseLogging(int verbose) {
         if (verbose > 0) {
             DBG = true;
@@ -656,14 +674,91 @@
             return doBooleanCommand("DRIVER COUNTRY");
     }
 
-    public boolean enableBackgroundScan(boolean enable) {
-        boolean ret;
-        if (enable) {
-            ret = doBooleanCommand("SET pno 1");
-        } else {
-            ret = doBooleanCommand("SET pno 0");
+    //PNO Monitor
+    private class PnoMonitor {
+        private static final int MINIMUM_PNO_GAP = 5 * 1000;
+        private static final String ACTION_TOGGLE_PNO =
+            "com.android.server.Wifi.action.TOGGLE_PNO";
+        long mLastPnoChangeTimeStamp = -1L;
+        boolean mExpectedPnoState = false;
+        boolean mCurrentPnoState = false;;
+        boolean mWaitForTimer = false;
+        final Object mPnoLock = new Object();
+        private final AlarmManager mAlarmManager =
+                (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+        private final PendingIntent mPnoIntent;
+
+        public PnoMonitor() {
+            Intent intent = new Intent(ACTION_TOGGLE_PNO, null);
+            intent.setPackage("android");
+            mPnoIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+
+            mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        synchronized(mPnoLock) {
+                            if (DBG) Log.d(mTAG, "PNO timer expire, PNO should change to " +
+                                    mExpectedPnoState);
+                            if (mCurrentPnoState != mExpectedPnoState) {
+                                if (DBG) Log.d(mTAG, "change PNO from " + mCurrentPnoState + " to "
+                                        + mExpectedPnoState);
+                                boolean ret = setPno(mExpectedPnoState);
+                                if (!ret) {
+                                    Log.e(mTAG, "set PNO failure");
+                                }
+                            } else {
+                                if (DBG) Log.d(mTAG, "Do not change PNO since current is expected");
+                            }
+                            mWaitForTimer = false;
+                        }
+                    }
+                },
+                new IntentFilter(ACTION_TOGGLE_PNO));
         }
-        return ret;
+
+        private boolean setPno(boolean enable) {
+            String cmd = enable ? "SET pno 1" : "SET pno 0";
+            boolean ret = doBooleanCommand(cmd);
+            mLastPnoChangeTimeStamp = System.currentTimeMillis();
+            if (ret) {
+                mCurrentPnoState = enable;
+            }
+            return ret;
+        }
+
+        public boolean enableBackgroundScan(boolean enable) {
+            synchronized(mPnoLock) {
+                if (mWaitForTimer) {
+                    //already has a timer
+                    mExpectedPnoState = enable;
+                    if (DBG) Log.d(mTAG, "update expected PNO to " +  mExpectedPnoState);
+                } else {
+                    if (mCurrentPnoState == enable) {
+                        return true;
+                    }
+                    long timeDifference = System.currentTimeMillis() - mLastPnoChangeTimeStamp;
+                    if (timeDifference >= MINIMUM_PNO_GAP) {
+                        return setPno(enable);
+                    } else {
+                        mExpectedPnoState = enable;
+                        mWaitForTimer = true;
+                        if (DBG) Log.d(mTAG, "start PNO timer with delay:" + timeDifference);
+                        mAlarmManager.set(AlarmManager.RTC_WAKEUP,
+                                System.currentTimeMillis() + timeDifference, mPnoIntent);
+                    }
+                }
+                return true;
+            }
+        }
+    }
+
+    public boolean enableBackgroundScan(boolean enable) {
+        if (mPnoMonitor != null) {
+            return mPnoMonitor.enableBackgroundScan(enable);
+        } else {
+            return false;
+        }
     }
 
     public void enableAutoConnect(boolean enable) {
diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java
index 7001a9a..22ac645 100644
--- a/service/java/com/android/server/wifi/WifiStateMachine.java
+++ b/service/java/com/android/server/wifi/WifiStateMachine.java
@@ -1119,7 +1119,7 @@
         mP2pSupported = mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_WIFI_DIRECT);
 
-        mWifiNative = new WifiNative(mInterfaceName);
+        mWifiNative = new WifiNative(mInterfaceName, mContext);
         mWifiConfigStore = new WifiConfigStore(context,this,  mWifiNative);
         mWifiAutoJoinController = new WifiAutoJoinController(context, this,
                 mWifiConfigStore, mWifiConnectionStatistics, mWifiNative);