Update NPMS to notify AMS when network policy rules are updated.

Bug: 27803922
Test: runtest -c com.android.server.am.ActivityManagerInternalTest frameworks-services
      runtest -c com.android.server.NetworkPolicyManagerServiceTest frameworks-services

Change-Id: I357fd5c80b7e6d3e63df95397e328c52f233958b
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index b36b664..dbcdecc 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -234,4 +234,11 @@
      * @see android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY
      */
     public abstract void setHasOverlayUi(int pid, boolean hasOverlayUi);
+
+    /**
+     * Called after the network policy rules are updated by
+     * {@link com.android.server.net.NetworkPolicyManagerService} for a specific {@param uid} and
+     * {@param procStateSeq}.
+     */
+    public abstract void notifyNetworkPolicyRulesUpdated(int uid, long procStateSeq);
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index 88e0d03..8ed95ee 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -63,6 +63,7 @@
     static final boolean DEBUG_LOCKTASK = DEBUG_ALL || false;
     static final boolean DEBUG_LRU = DEBUG_ALL || false;
     static final boolean DEBUG_MU = DEBUG_ALL || false;
+    static final boolean DEBUG_NETWORK = DEBUG_ALL || false;
     static final boolean DEBUG_OOM_ADJ = DEBUG_ALL || false;
     static final boolean DEBUG_PAUSE = DEBUG_ALL || false;
     static final boolean DEBUG_POWER = DEBUG_ALL || false;
@@ -107,6 +108,7 @@
     static final String POSTFIX_LOCKTASK = (APPEND_CATEGORY_NAME) ? "_LockTask" : "";
     static final String POSTFIX_LRU = (APPEND_CATEGORY_NAME) ? "_LRU" : "";
     static final String POSTFIX_MU = "_MU";
+    static final String POSTFIX_NETWORK = "_Network";
     static final String POSTFIX_OOM_ADJ = (APPEND_CATEGORY_NAME) ? "_OomAdj" : "";
     static final String POSTFIX_PAUSE = (APPEND_CATEGORY_NAME) ? "_Pause" : "";
     static final String POSTFIX_POWER = (APPEND_CATEGORY_NAME) ? "_Power" : "";
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f722a8e..5d375d5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -78,6 +78,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER;
@@ -107,6 +108,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LRU;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_NETWORK;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_OOM_ADJ;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_POWER;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESSES;
@@ -421,6 +423,7 @@
     private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK;
     private static final String TAG_LRU = TAG + POSTFIX_LRU;
     private static final String TAG_MU = TAG + POSTFIX_MU;
+    private static final String TAG_NETWORK = TAG + POSTFIX_NETWORK;
     private static final String TAG_OOM_ADJ = TAG + POSTFIX_OOM_ADJ;
     private static final String TAG_POWER = TAG + POSTFIX_POWER;
     private static final String TAG_PROCESS_OBSERVERS = TAG + POSTFIX_PROCESS_OBSERVERS;
@@ -22901,7 +22904,8 @@
         }
     }
 
-    private final class LocalService extends ActivityManagerInternal {
+    @VisibleForTesting
+    final class LocalService extends ActivityManagerInternal {
         @Override
         public void grantUriPermissionFromIntent(int callingUid, String targetPkg, Intent intent,
                 int targetUserId) {
@@ -23151,6 +23155,30 @@
                 updateOomAdjLocked(pr);
             }
         }
+
+        /**
+         * Called after the network policy rules are updated by
+         * {@link com.android.server.net.NetworkPolicyManagerService} for a specific {@param uid} and
+         * {@param procStateSeq}.
+         */
+        @Override
+        public void notifyNetworkPolicyRulesUpdated(int uid, long procStateSeq) {
+            if (DEBUG_NETWORK) {
+                Slog.d(TAG_NETWORK, "Got update from NPMS for uid: "
+                        + uid + " seq: " + procStateSeq);
+            }
+            synchronized (ActivityManagerService.this) {
+                final UidRecord record = mActiveUids.get(uid);
+                if (record == null) {
+                    if (DEBUG_NETWORK) {
+                        Slog.d(TAG_NETWORK, "No active uidRecord for uid: " + uid
+                                + " procStateSeq: " + procStateSeq);
+                    }
+                    return;
+                }
+                record.lastNetworkUpdatedProcStateSeq = procStateSeq;
+            }
+        }
     }
 
     private final class SleepTokenImpl extends SleepToken {
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index cf6c1e1..e002e97 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -40,6 +40,11 @@
      * when {@link #curProcState} changes from background to foreground or vice versa.
      */
     long curProcStateSeq;
+    /**
+     * Last seq number for which NetworkPolicyManagerService notified ActivityManagerService that
+     * network policies rules were updated.
+     */
+    long lastNetworkUpdatedProcStateSeq;
 
     static final int CHANGE_PROCSTATE = 0;
     static final int CHANGE_GONE = 1;
@@ -92,6 +97,8 @@
         sb.append(numProcs);
         sb.append(" curProcStateSeq:");
         sb.append(curProcStateSeq);
+        sb.append(" lastNetworkUpdatedProcStateSeq:");
+        sb.append(lastNetworkUpdatedProcStateSeq);
         sb.append("}");
         return sb.toString();
     }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 9d93cc7..76ee673 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -96,6 +96,7 @@
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.IActivityManager;
@@ -251,6 +252,13 @@
     private static final int VERSION_SWITCH_UID = 10;
     private static final int VERSION_LATEST = VERSION_SWITCH_UID;
 
+    /**
+     * Max items written to {@link #ProcStateSeqHistory}.
+     */
+    @VisibleForTesting
+    public static final int MAX_PROC_STATE_SEQ_HISTORY =
+            ActivityManager.isLowRamDeviceStatic() ? 50 : 200;
+
     @VisibleForTesting
     public static final int TYPE_WARNING = 0x1;
     @VisibleForTesting
@@ -412,6 +420,15 @@
 
     private final IPackageManager mIPm;
 
+    private ActivityManagerInternal mActivityManagerInternal;
+
+    /**
+     * This is used for debugging purposes. Whenever the IUidObserver.onUidStateChanged is called,
+     * the uid and procStateSeq will be written to this and will be printed as part of dump.
+     */
+    @VisibleForTesting
+    public ProcStateSeqHistory mObservedHistory
+            = new ProcStateSeqHistory(MAX_PROC_STATE_SEQ_HISTORY);
 
     // TODO: keep whitelist of system-critical services that should never have
     // rules enforced, such as system, phone, and radio UIDs.
@@ -628,6 +645,7 @@
                 }
             }
 
+            mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
             try {
                 mActivityManager.registerUidObserver(mUidObserver,
                         ActivityManager.UID_OBSERVER_PROCSTATE|ActivityManager.UID_OBSERVER_GONE,
@@ -724,7 +742,13 @@
             Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "onUidStateChanged");
             try {
                 synchronized (mUidRulesFirstLock) {
+                    // We received a uid state change callback, add it to the history so that it
+                    // will be useful for debugging.
+                    mObservedHistory.addProcStateSeqUL(uid, procStateSeq);
+                    // Now update the network policy rules as per the updated uid state.
                     updateUidStateUL(uid, procState);
+                    // Updating the network rules is done, so notify AMS about this.
+                    mActivityManagerInternal.notifyNetworkPolicyRulesUpdated(uid, procStateSeq);
                 }
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
@@ -2429,6 +2453,11 @@
                     fout.println();
                 }
                 fout.decreaseIndent();
+
+                fout.println("Observed uid state changes:");
+                fout.increaseIndent();
+                mObservedHistory.dumpUL(fout);
+                fout.decreaseIndent();
             }
         }
     }
@@ -3609,4 +3638,74 @@
             }
         }
     }
+
+    /**
+     * This class is used for storing and dumping the last {@link #MAX_PROC_STATE_SEQ_HISTORY}
+     * (uid, procStateSeq) pairs.
+     */
+    @VisibleForTesting
+    public static final class ProcStateSeqHistory {
+        private static final int INVALID_UID = -1;
+
+        /**
+         * Denotes maximum number of items this history can hold.
+         */
+        private final int mMaxCapacity;
+        /**
+         * Used for storing the uid information.
+         */
+        private final int[] mUids;
+        /**
+         * Used for storing the sequence numbers associated with {@link #mUids}.
+         */
+        private final long[] mProcStateSeqs;
+        /**
+         * Points to the next available slot for writing (uid, procStateSeq) pair.
+         */
+        private int mHistoryNext;
+
+        public ProcStateSeqHistory(int maxCapacity) {
+            mMaxCapacity = maxCapacity;
+            mUids = new int[mMaxCapacity];
+            Arrays.fill(mUids, INVALID_UID);
+            mProcStateSeqs = new long[mMaxCapacity];
+        }
+
+        @GuardedBy("mUidRulesFirstLock")
+        public void addProcStateSeqUL(int uid, long procStateSeq) {
+            mUids[mHistoryNext] = uid;
+            mProcStateSeqs[mHistoryNext] = procStateSeq;
+            mHistoryNext = increaseNext(mHistoryNext, 1);
+        }
+
+        @GuardedBy("mUidRulesFirstLock")
+        public void dumpUL(IndentingPrintWriter fout) {
+            if (mUids[0] == INVALID_UID) {
+                fout.println("NONE");
+                return;
+            }
+            int index = mHistoryNext;
+            do {
+                index = increaseNext(index, -1);
+                if (mUids[index] == INVALID_UID) {
+                    break;
+                }
+                fout.println(getString(mUids[index], mProcStateSeqs[index]));
+            } while (index != mHistoryNext);
+        }
+
+        public static String getString(int uid, long procStateSeq) {
+            return "UID=" + uid + " procStateSeq=" + procStateSeq;
+        }
+
+        private int increaseNext(int next, int increment) {
+            next += increment;
+            if (next >= mMaxCapacity) {
+                next = 0;
+            } else if (next < 0) {
+                next = mMaxCapacity - 1;
+            }
+            return next;
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index a9b2ae5..f8d105e 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -32,6 +32,8 @@
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 import static android.text.format.Time.TIMEZONE_UTC;
 
+import static com.android.server.net.NetworkPolicyManagerService.MAX_PROC_STATE_SEQ_HISTORY;
+import static com.android.server.net.NetworkPolicyManagerService.ProcStateSeqHistory;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT_SNOOZED;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING;
@@ -58,6 +60,7 @@
 
 import android.Manifest;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
 import android.app.IActivityManager;
 import android.app.INotificationManager;
 import android.app.IUidObserver;
@@ -95,6 +98,7 @@
 import android.util.Log;
 import android.util.TrustedTime;
 
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.BroadcastInterceptingContext.FutureIntent;
 import com.android.server.net.NetworkPolicyManagerInternal;
@@ -120,10 +124,12 @@
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.io.PrintWriter;
 import java.lang.annotation.Annotation;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -160,6 +166,8 @@
     private static final String TEST_IFACE = "test0";
     private static final String TEST_SSID = "AndroidAP";
 
+    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
     private static NetworkTemplate sTemplateWifi = NetworkTemplate.buildTemplateWifi(TEST_SSID);
 
     /**
@@ -186,6 +194,8 @@
     private @Mock PackageManager mPackageManager;
     private @Mock IPackageManager mIpm;
 
+    private static ActivityManagerInternal mActivityManagerInternal;
+
     private IUidObserver mUidObserver;
     private INetworkManagementEventObserver mNetworkObserver;
 
@@ -222,6 +232,7 @@
         final UsageStatsManagerInternal usageStats =
                 addLocalServiceMock(UsageStatsManagerInternal.class);
         when(usageStats.getIdleUidsForUser(anyInt())).thenReturn(new int[]{});
+        mActivityManagerInternal = addLocalServiceMock(ActivityManagerInternal.class);
     }
 
     @Before
@@ -961,6 +972,75 @@
         }
     }
 
+    @Test
+    public void testOnUidStateChanged_notifyAMS() throws Exception {
+        final long procStateSeq = 222;
+        mUidObserver.onUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_SERVICE,
+                procStateSeq);
+        verify(mActivityManagerInternal).notifyNetworkPolicyRulesUpdated(UID_A, procStateSeq);
+
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        final IndentingPrintWriter writer = new IndentingPrintWriter(
+                new PrintWriter(outputStream), " ");
+        mService.mObservedHistory.dumpUL(writer);
+        writer.flush();
+        assertEquals(ProcStateSeqHistory.getString(UID_A, procStateSeq),
+                outputStream.toString().trim());
+    }
+
+    @Test
+    public void testProcStateHistory() {
+        // Verify dump works correctly with no elements added.
+        verifyProcStateHistoryDump(0);
+
+        // Add items upto half of the max capacity and verify that dump works correctly.
+        verifyProcStateHistoryDump(MAX_PROC_STATE_SEQ_HISTORY / 2);
+
+        // Add items upto the max capacity and verify that dump works correctly.
+        verifyProcStateHistoryDump(MAX_PROC_STATE_SEQ_HISTORY);
+
+        // Add more items than max capacity and verify that dump works correctly.
+        verifyProcStateHistoryDump(MAX_PROC_STATE_SEQ_HISTORY + MAX_PROC_STATE_SEQ_HISTORY / 2);
+
+    }
+
+    private void verifyProcStateHistoryDump(int count) {
+        final ProcStateSeqHistory history = new ProcStateSeqHistory(MAX_PROC_STATE_SEQ_HISTORY);
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        final IndentingPrintWriter writer = new IndentingPrintWriter(
+                new PrintWriter(outputStream), " ");
+
+        if (count == 0) {
+            // Verify with no uid info written to history.
+            history.dumpUL(writer);
+            writer.flush();
+            assertEquals("When no uid info is there, dump should contain NONE",
+                    "NONE", outputStream.toString().trim());
+            return;
+        }
+
+        int uid = 111;
+        long procStateSeq = 222;
+        // Add count items and verify dump works correctly.
+        for (int i = 0; i < count; ++i) {
+            uid++;
+            procStateSeq++;
+            history.addProcStateSeqUL(uid, procStateSeq);
+        }
+        history.dumpUL(writer);
+        writer.flush();
+        final String[] uidsDump = outputStream.toString().split(LINE_SEPARATOR);
+        // Dump will have at most MAX_PROC_STATE_SEQ_HISTORY items.
+        final int expectedCount = (count < MAX_PROC_STATE_SEQ_HISTORY)
+                ? count : MAX_PROC_STATE_SEQ_HISTORY;
+        assertEquals(expectedCount, uidsDump.length);
+        for (int i = 0; i < expectedCount; ++i) {
+            assertEquals(ProcStateSeqHistory.getString(uid, procStateSeq), uidsDump[i]);
+            uid--;
+            procStateSeq--;
+        }
+    }
+
     private static long parseTime(String time) {
         final Time result = new Time();
         result.parse3339(time);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
new file mode 100644
index 0000000..6a050ad
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.am;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.ActivityManagerInternal;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.AppOpsService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link ActivityManagerInternal}.
+ *
+ * To run the tests, use
+ *
+ * runtest -c com.android.server.am.ActivityManagerInternalTest frameworks-services
+ *
+ * or the following steps:
+ *
+ * Build: m FrameworksServicesTests
+ * Install: adb install -r \
+ *     ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
+ * Run: adb shell am instrument -e class com.android.server.am.ActivityManagerInternalTest -w \
+ *     com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ActivityManagerInternalTest {
+    private ActivityManagerService mAms;
+    private ActivityManagerInternal mAmi;
+    @Before
+    public void setUp() {
+        mAms = new ActivityManagerService((AppOpsService) null);
+        mAmi = mAms.new LocalService();
+    }
+
+    @Test
+    public void testNotifyNetworkPolicyRulesUpdated() {
+        // For checking there is no crash when there are no active uid records.
+        mAmi.notifyNetworkPolicyRulesUpdated(111, 11);
+
+        // Insert active uid records.
+        final UidRecord record1 = addActiveUidRecord(222, 22);
+        final UidRecord record2 = addActiveUidRecord(333, 33);
+        // Notify that network policy rules are updated for uid 222.
+        mAmi.notifyNetworkPolicyRulesUpdated(222, 44);
+        assertEquals("UidRecord for uid 222 should be updated",
+                44L, record1.lastNetworkUpdatedProcStateSeq);
+        assertEquals("UidRecord for uid 333 should not be updated",
+                33L, record2.lastNetworkUpdatedProcStateSeq);
+    }
+
+    private UidRecord addActiveUidRecord(int uid, long lastNetworkUpdatedProcStateSeq) {
+        final UidRecord record = new UidRecord(uid);
+        record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq;
+        mAms.mActiveUids.put(uid, record);
+        return record;
+    }
+}