wifi: Add idle shutdown for p2p interface.

In android S, if some system service (ex: Wfd) or privileged
application(ex: OEM application) init P2P without close it.
It will cause 3rd Nan application can't use because it owns
lower priority than system and privileged app.

Add idle shutdown p2p interface mechanism to avoid application
occupy the resource.

Bug: 184504089
Test: atest -c FrameworksWifiTests
Test: Manael to enable Wifi Direct page and check it will shutdown after
timeout.

Change-Id: I01db2cffb402a2fdc70f0ec60b8cae1078d8051d
diff --git a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
index 3c4963b..c3be161 100644
--- a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
+++ b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
@@ -74,6 +74,7 @@
 import android.os.Messenger;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.WorkSource;
@@ -94,6 +95,7 @@
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.internal.util.WakeupMessage;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.FrameworkFacade;
 import com.android.server.wifi.WifiGlobals;
@@ -139,6 +141,9 @@
  */
 public class WifiP2pServiceImpl extends IWifiP2pManager.Stub {
     private static final String TAG = "WifiP2pService";
+    @VisibleForTesting
+    public static final String P2P_IDLE_SHUTDOWN_MESSAGE_TIMEOUT_TAG = TAG
+            + " Idle Shutdown Message Timeout";
     private boolean mVerboseLoggingEnabled = false;
     private static final String NETWORKTYPE = "WIFI_P2P";
     @VisibleForTesting
@@ -156,6 +161,11 @@
     @VisibleForTesting
     static final int DEFAULT_GROUP_OWNER_INTENT = 6;
 
+    @VisibleForTesting
+    // It requires to over "DISCOVER_TIMEOUT_S(120)" or "GROUP_CREATING_WAIT_TIME_MS(120)".
+    // Otherwise it will cause interface down before function timeout.
+    static final long P2P_INTERFACE_IDLE_SHUTDOWN_TIMEOUT_MS = 150_000;
+
     private Context mContext;
 
     NetdWrapper mNetdWrapper;
@@ -254,6 +264,8 @@
     public static final int ENABLE_P2P                      =   BASE + 16;
     public static final int DISABLE_P2P                     =   BASE + 17;
     public static final int REMOVE_CLIENT_INFO              =   BASE + 18;
+    // idle shutdown message
+    public static final int CMD_P2P_IDLE_SHUTDOWN           =   BASE + 19;
 
     // Messages for interaction with IpClient.
     private static final int IPC_PRE_DHCP_ACTION            =   BASE + 30;
@@ -321,6 +333,10 @@
     // latter from leaking to apps
     private static final String ANONYMIZED_DEVICE_ADDRESS = "02:00:00:00:00:00";
 
+    // Idle shut down
+    @VisibleForTesting
+    public WakeupMessage mP2pIdleShutdownMessage;
+
     /**
      * Error code definition.
      * see the Table.8 in the WiFi Direct specification for the detail.
@@ -676,7 +692,6 @@
                 // fall-through here - won't clean up
             }
             mP2pStateMachine.sendMessage(ENABLE_P2P);
-
             return messenger;
         }
     }
@@ -879,6 +894,12 @@
             setLogOnlyTransitions(true);
 
             if (p2pSupported) {
+                // Init p2p idle shutdown message
+                mP2pIdleShutdownMessage = new WakeupMessage(mContext,
+                                  this.getHandler(),
+                                  P2P_IDLE_SHUTDOWN_MESSAGE_TIMEOUT_TAG,
+                                  CMD_P2P_IDLE_SHUTDOWN);
+
                 // Register for wifi on/off broadcasts
                 mContext.registerReceiver(new BroadcastReceiver() {
                     @Override
@@ -941,6 +962,29 @@
             }
         }
 
+        void scheduleIdleShutdown() {
+            if (mP2pIdleShutdownMessage != null) {
+                mP2pIdleShutdownMessage.cancel();
+                mP2pIdleShutdownMessage.schedule(SystemClock.elapsedRealtime()
+                        + P2P_INTERFACE_IDLE_SHUTDOWN_TIMEOUT_MS);
+                if (mVerboseLoggingEnabled) {
+                    Log.d(TAG, "IdleShutDown message (re)scheduled in "
+                            + (P2P_INTERFACE_IDLE_SHUTDOWN_TIMEOUT_MS / 1000) + "s");
+                }
+            }
+            mP2pStateMachine.getHandler().removeMessages(CMD_P2P_IDLE_SHUTDOWN);
+        }
+
+        void cancelIdleShutdown() {
+            if (mP2pIdleShutdownMessage != null) {
+                mP2pIdleShutdownMessage.cancel();
+                if (mVerboseLoggingEnabled) {
+                    Log.d(TAG, "IdleShutDown message canceled");
+                }
+            }
+            mP2pStateMachine.getHandler().removeMessages(CMD_P2P_IDLE_SHUTDOWN);
+        }
+
         void checkCoexUnsafeChannels() {
             List<CoexUnsafeChannel> unsafeChannels = null;
 
@@ -1925,11 +1969,23 @@
                 if (mVerboseLoggingEnabled) logd(getName());
                 mSavedPeerConfig.invalidate();
                 mDetailedState = NetworkInfo.DetailedState.IDLE;
+                scheduleIdleShutdown();
+            }
+
+            @Override
+            public void exit() {
+                cancelIdleShutdown();
             }
 
             @Override
             public boolean processMessage(Message message) {
                 if (mVerboseLoggingEnabled) logd(getName() + message.toString());
+                // Re-schedule the shutdown timer since we got the new operation.
+                // only handle commands from clients.
+                if (message.what > Protocol.BASE_WIFI_P2P_MANAGER
+                        && message.what < Protocol.BASE_WIFI_P2P_SERVICE) {
+                    scheduleIdleShutdown();
+                }
                 switch (message.what) {
                     case WifiP2pManager.CONNECT:
                         if (!mWifiPermissionsUtil.checkCanAccessWifiDirect(
@@ -1999,6 +2055,10 @@
                                     WifiP2pManager.ERROR);
                         }
                         break;
+                    case CMD_P2P_IDLE_SHUTDOWN:
+                        Log.d(TAG, "IdleShutDown message received");
+                        sendMessage(DISABLE_P2P);
+                        break;
                     case WifiP2pMonitor.P2P_GO_NEGOTIATION_REQUEST_EVENT:
                         config = (WifiP2pConfig) message.obj;
                         if (isConfigInvalid(config)) {
diff --git a/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pServiceImplTest.java b/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pServiceImplTest.java
index 6830bf7..7dce694 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pServiceImplTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pServiceImplTest.java
@@ -34,6 +34,7 @@
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.isNull;
@@ -50,6 +51,7 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
+import android.app.AlarmManager;
 import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -187,6 +189,7 @@
     @Spy FakeWifiLog mLog;
     @Spy MockWifiP2pMonitor mWifiMonitor;
     @Mock WifiGlobals mWifiGlobals;
+    @Mock AlarmManager mAlarmManager;
     CoexManager.CoexListener mCoexListener;
 
     private void generatorTestData() {
@@ -700,6 +703,9 @@
             verify(mWifiNative).setupInterface(any(), any(), eq(expectedRequestorWs));
             verify(mNetdWrapper).setInterfaceUp(anyString());
             verify(mWifiMonitor, atLeastOnce()).registerHandler(anyString(), anyInt(), any());
+            // Verify timer is scheduled
+            verify(mAlarmManager).setExact(anyInt(), anyLong(),
+                    eq(mWifiP2pServiceImpl.P2P_IDLE_SHUTDOWN_MESSAGE_TIMEOUT_TAG), any(), any());
         } else if (expectReplace) {
             verify(mWifiNative).replaceRequestorWs(expectedRequestorWs);
         } else {
@@ -818,6 +824,8 @@
         mClientMessenger =  new Messenger(mClientHandler);
         mLooper = new TestLooper();
 
+        when(mContext.getSystemService(Context.ALARM_SERVICE))
+                .thenReturn(mAlarmManager);
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mContext.getResources()).thenReturn(mResources);
         when(mContext.getSystemService(WifiManager.class)).thenReturn(mWifiManager);
@@ -4432,4 +4440,43 @@
 
         verify(mWifiNative).p2pGroupRemove(group.getInterface());
     }
+
+    /**
+     * Verify the idle timer is cancelled after leaving inactive state.
+     */
+    @Test
+    public void testIdleTimeoutCancelledAfterLeavingInactiveState() throws Exception {
+        forceP2pEnabled(mClient1);
+        sendChannelInfoUpdateMsg("testPkg1", "testFeature", mClient1, mClientMessenger);
+
+        mockPeersList();
+        sendConnectMsg(mClientMessenger, mTestWifiP2pPeerConfig);
+        verify(mWifiPermissionsUtil)
+                .checkCanAccessWifiDirect(eq("testPkg1"), eq("testFeature"), anyInt(), eq(false));
+
+        ArgumentCaptor<WifiP2pConfig> configCaptor =
+                ArgumentCaptor.forClass(WifiP2pConfig.class);
+        verify(mWifiP2pMetrics).startConnectionEvent(
+                eq(P2pConnectionEvent.CONNECTION_FRESH),
+                configCaptor.capture());
+        assertEquals(mTestWifiP2pPeerConfig.toString(), configCaptor.getValue().toString());
+        // Verify timer is cannelled
+        // Includes re-schedule 2 times(1. update channel info and 2. connect) and final one which
+        // leave the inactive state
+        verify(mAlarmManager, times(3)).setExact(anyInt(), anyLong(),
+                eq(mWifiP2pServiceImpl.P2P_IDLE_SHUTDOWN_MESSAGE_TIMEOUT_TAG), any(), any());
+        verify(mAlarmManager, times(3)).cancel(eq(mWifiP2pServiceImpl.mP2pIdleShutdownMessage));
+    }
+
+    /**
+     * Verify the interface down after idle timer is triggered.
+     */
+    @Test
+    public void testIdleTimeoutTriggered() throws Exception {
+        forceP2pEnabled(mClient1);
+        mWifiP2pServiceImpl.mP2pIdleShutdownMessage.onAlarm();
+        mLooper.dispatchAll();
+        verify(mWifiNative).teardownInterface();
+        verify(mWifiMonitor).stopMonitoring(anyString());
+    }
 }