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;
+ }
+}