summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java200
-rw-r--r--services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml4
-rw-r--r--services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleFgService.java113
4 files changed, 322 insertions, 7 deletions
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 68c4a7336745..673749c08318 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1841,7 +1841,7 @@ public final class ActiveServices {
ServiceState stracker = r.getTracker();
if (stracker != null) {
stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(),
- r.lastActivity);
+ SystemClock.uptimeMillis());
}
}
if (alreadyStartedOp) {
@@ -1863,7 +1863,7 @@ public final class ActiveServices {
ServiceState stracker = r.getTracker();
if (stracker != null) {
stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(),
- r.lastActivity);
+ SystemClock.uptimeMillis());
}
mAm.mAppOpsService.finishOperation(
AppOpsManager.getToken(mAm.mAppOpsService),
@@ -3765,6 +3765,7 @@ public final class ActiveServices {
}
}
+ final long now = SystemClock.uptimeMillis();
// Check to see if the service had been started as foreground, but being
// brought down before actually showing a notification. That is not allowed.
if (r.fgRequired) {
@@ -3774,8 +3775,7 @@ public final class ActiveServices {
r.fgWaiting = false;
ServiceState stracker = r.getTracker();
if (stracker != null) {
- stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(),
- r.lastActivity);
+ stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(), now);
}
mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),
AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null);
@@ -3834,8 +3834,7 @@ public final class ActiveServices {
decActiveForegroundAppLocked(smap, r);
ServiceState stracker = r.getTracker();
if (stracker != null) {
- stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(),
- r.lastActivity);
+ stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(), now);
}
mAm.mAppOpsService.finishOperation(
AppOpsManager.getToken(mAm.mAppOpsService),
@@ -3902,7 +3901,6 @@ public final class ActiveServices {
}
int memFactor = mAm.mProcessStats.getMemFactorLocked();
- long now = SystemClock.uptimeMillis();
if (r.tracker != null) {
r.tracker.setStarted(false, memFactor, now);
r.tracker.setBound(false, memFactor, now);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
index 129d2633ae92..e13597dc6982 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
@@ -19,23 +19,37 @@ package com.android.server.am;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.app.ActivityManager;
+import android.app.ActivityManager.OnUidImportanceListener;
import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.IActivityManager;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.DropBoxManager;
+import android.os.Handler;
import android.os.IBinder;
import android.os.IRemoteCallback;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.support.test.uiautomator.UiDevice;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.text.TextUtils;
+import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.FlakyTest;
@@ -65,6 +79,12 @@ public class ActivityManagerTest {
private static final long AWAIT_TIMEOUT = 2000;
private static final long CHECK_INTERVAL = 100;
+ private static final String TEST_FGS_CLASS =
+ "com.android.servicestests.apps.simpleservicetestapp.SimpleFgService";
+ private static final String ACTION_FGS_STATS_TEST =
+ "com.android.servicestests.apps.simpleservicetestapp.ACTION_FGS_STATS_TEST";
+ private static final String EXTRA_MESSENGER = "extra_messenger";
+
private IActivityManager mService;
private IRemoteCallback mCallback;
private Context mContext;
@@ -204,4 +224,184 @@ public class ActivityManagerTest {
public void onServiceDisconnected(ComponentName name) {
}
}
+
+ /**
+ * Note: This test actually only works in eng build. It'll always pass
+ * in user and userdebug build, because the expected exception won't be
+ * thrown in those builds.
+ */
+ @LargeTest
+ @Test
+ public void testFgsProcStatsTracker() throws Exception {
+ final PackageManager pm = mContext.getPackageManager();
+ final long timeout = 5000;
+ int uid = pm.getPackageUid(TEST_APP, 0);
+ final MyUidImportanceListener uidListener1 = new MyUidImportanceListener(uid);
+ final MyUidImportanceListener uidListener2 = new MyUidImportanceListener(uid);
+ final ActivityManager am = mContext.getSystemService(ActivityManager.class);
+ final CountDownLatch[] latchHolder = new CountDownLatch[1];
+ final H handler = new H(Looper.getMainLooper(), latchHolder);
+ final Messenger messenger = new Messenger(handler);
+ final DropBoxManager dbox = mContext.getSystemService(DropBoxManager.class);
+ final CountDownLatch dboxLatch = new CountDownLatch(1);
+ final BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String tag_wtf = "system_server_wtf";
+ if (tag_wtf.equals(intent.getStringExtra(DropBoxManager.EXTRA_TAG))) {
+ final DropBoxManager.Entry e = dbox.getNextEntry(tag_wtf, intent.getLongExtra(
+ DropBoxManager.EXTRA_TIME, 0) - 1);
+ final String text = e.getText(8192);
+ if (TextUtils.isEmpty(text)) {
+ return;
+ }
+ if (text.indexOf("can't store negative values") == -1) {
+ return;
+ }
+ dboxLatch.countDown();
+ }
+ }
+ };
+ try {
+ mContext.registerReceiver(receiver,
+ new IntentFilter(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED));
+ am.addOnUidImportanceListener(uidListener1,
+ RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE);
+ am.addOnUidImportanceListener(uidListener2, RunningAppProcessInfo.IMPORTANCE_GONE);
+ runShellCommand("cmd deviceidle whitelist +" + TEST_APP);
+ toggleScreenOn(true);
+
+ final Intent intent = new Intent(ACTION_FGS_STATS_TEST);
+ final ComponentName cn = ComponentName.unflattenFromString(
+ TEST_APP + "/" + TEST_FGS_CLASS);
+ final Bundle bundle = new Bundle();
+ intent.setComponent(cn);
+ bundle.putBinder(EXTRA_MESSENGER, messenger.getBinder());
+ intent.putExtras(bundle);
+
+ latchHolder[0] = new CountDownLatch(1);
+ mContext.startForegroundService(intent);
+ assertTrue("Timed out to start fg service", uidListener1.waitFor(
+ RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE, timeout));
+ assertTrue("Timed out to get the remote messenger", latchHolder[0].await(
+ timeout, TimeUnit.MILLISECONDS));
+
+ Thread.sleep(timeout);
+ latchHolder[0] = new CountDownLatch(1);
+ handler.sendRemoteMessage(H.MSG_STOP_FOREGROUND, 0, 0, null);
+ assertTrue("Timed out to wait for stop fg", latchHolder[0].await(
+ timeout, TimeUnit.MILLISECONDS));
+
+ Thread.sleep(timeout);
+ latchHolder[0] = new CountDownLatch(1);
+ handler.sendRemoteMessage(H.MSG_START_FOREGROUND, 0, 0, null);
+ assertTrue("Timed out to wait for start fg", latchHolder[0].await(
+ timeout, TimeUnit.MILLISECONDS));
+
+ toggleScreenOn(false);
+ latchHolder[0] = new CountDownLatch(1);
+ handler.sendRemoteMessage(H.MSG_STOP_FOREGROUND, 0, 0, null);
+ assertTrue("Timed out to wait for stop fg", latchHolder[0].await(
+ timeout, TimeUnit.MILLISECONDS));
+ assertFalse("There shouldn't be negative values", dboxLatch.await(
+ timeout * 2, TimeUnit.MILLISECONDS));
+ } finally {
+ toggleScreenOn(true);
+ runShellCommand("cmd deviceidle whitelist -" + TEST_APP);
+ am.removeOnUidImportanceListener(uidListener1);
+ am.removeOnUidImportanceListener(uidListener2);
+ am.forceStopPackage(TEST_APP);
+ mContext.unregisterReceiver(receiver);
+ }
+ }
+
+ /**
+ * Make sure the screen state.
+ */
+ private void toggleScreenOn(final boolean screenon) throws Exception {
+ if (screenon) {
+ runShellCommand("input keyevent KEYCODE_WAKEUP");
+ runShellCommand("wm dismiss-keyguard");
+ } else {
+ runShellCommand("input keyevent KEYCODE_SLEEP");
+ }
+ // Since the screen on/off intent is ordered, they will not be sent right now.
+ Thread.sleep(2_000);
+ }
+
+ private class H extends Handler {
+ static final int MSG_INIT = 0;
+ static final int MSG_DONE = 1;
+ static final int MSG_START_FOREGROUND = 2;
+ static final int MSG_STOP_FOREGROUND = 3;
+
+ private Messenger mRemoteMessenger;
+ private CountDownLatch[] mLatchHolder;
+
+ H(Looper looper, CountDownLatch[] latchHolder) {
+ super(looper);
+ mLatchHolder = latchHolder;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_INIT:
+ mRemoteMessenger = (Messenger) msg.obj;
+ mLatchHolder[0].countDown();
+ break;
+ case MSG_DONE:
+ mLatchHolder[0].countDown();
+ break;
+ }
+ }
+
+ void sendRemoteMessage(int what, int arg1, int arg2, Object obj) {
+ Message msg = Message.obtain();
+ msg.what = what;
+ msg.arg1 = arg1;
+ msg.arg2 = arg2;
+ msg.obj = obj;
+ try {
+ mRemoteMessenger.send(msg);
+ } catch (RemoteException e) {
+ }
+ msg.recycle();
+ }
+ }
+
+ private static class MyUidImportanceListener implements OnUidImportanceListener {
+ final CountDownLatch[] mLatchHolder = new CountDownLatch[1];
+ private final int mExpectedUid;
+ private int mExpectedImportance;
+ private int mCurrentImportance = RunningAppProcessInfo.IMPORTANCE_GONE;
+
+ MyUidImportanceListener(int uid) {
+ mExpectedUid = uid;
+ }
+
+ @Override
+ public void onUidImportance(int uid, int importance) {
+ if (uid == mExpectedUid) {
+ synchronized (this) {
+ if (importance == mExpectedImportance && mLatchHolder[0] != null) {
+ mLatchHolder[0].countDown();
+ }
+ mCurrentImportance = importance;
+ }
+ Log.i(TAG, "uid " + uid + " importance: " + importance);
+ }
+ }
+
+ boolean waitFor(int expectedImportance, long timeout) throws Exception {
+ synchronized (this) {
+ mExpectedImportance = expectedImportance;
+ if (mCurrentImportance == expectedImportance) {
+ return true;
+ }
+ mLatchHolder[0] = new CountDownLatch(1);
+ }
+ return mLatchHolder[0].await(timeout, TimeUnit.MILLISECONDS);
+ }
+ }
}
diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml
index 8789992280d0..799ec53a6e33 100644
--- a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml
+++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml
@@ -17,9 +17,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.servicestests.apps.simpleservicetestapp">
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+
<application>
<service android:name=".SimpleService"
android:exported="true" />
+ <service android:name=".SimpleFgService"
+ android:exported="true" />
</application>
</manifest>
diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleFgService.java b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleFgService.java
new file mode 100644
index 000000000000..ccfc0b7f0ef1
--- /dev/null
+++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleFgService.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021 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.servicestests.apps.simpleservicetestapp;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.R;
+
+public class SimpleFgService extends Service {
+ private static final String TAG = SimpleFgService.class.getSimpleName();
+ private static final String NOTIFICATION_CHANNEL_ID = TAG;
+ private static final int NOTIFICATION_ID = 1;
+
+ private static final int MSG_INIT = 0;
+ private static final int MSG_DONE = 1;
+ private static final int MSG_START_FOREGROUND = 2;
+ private static final int MSG_STOP_FOREGROUND = 3;
+
+ private static final String ACTION_FGS_STATS_TEST =
+ "com.android.servicestests.apps.simpleservicetestapp.ACTION_FGS_STATS_TEST";
+ private static final String EXTRA_MESSENGER = "extra_messenger";
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START_FOREGROUND: {
+ Log.i(TAG, "startForeground");
+ startForeground(NOTIFICATION_ID, mNotification);
+ sendRemoteMessage(MSG_DONE, 0, 0, null);
+ } break;
+ case MSG_STOP_FOREGROUND: {
+ Log.i(TAG, "stopForeground");
+ stopForeground(true);
+ sendRemoteMessage(MSG_DONE, 0, 0, null);
+ } break;
+ }
+ }
+ };
+ private final Messenger mMessenger = new Messenger(mHandler);
+
+ private Notification mNotification;
+ private Messenger mRemoteMessenger;
+
+ @Override
+ public void onCreate() {
+ Log.i(TAG, "onCreate");
+ final NotificationManager nm = getSystemService(NotificationManager.class);
+ nm.createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+ NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW));
+ mNotification = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+ .setContentTitle(TAG)
+ .setSmallIcon(R.drawable.ic_info)
+ .build();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.i(TAG, "onStartCommand");
+ startForeground(NOTIFICATION_ID, mNotification);
+ if (ACTION_FGS_STATS_TEST.equals(intent.getAction())) {
+ mRemoteMessenger = new Messenger(intent.getExtras().getBinder(EXTRA_MESSENGER));
+ sendRemoteMessage(MSG_INIT, 0, 0, mMessenger);
+ }
+ return START_NOT_STICKY;
+ }
+
+ private void sendRemoteMessage(int what, int arg1, int arg2, Object obj) {
+ final Message msg = Message.obtain();
+ msg.what = what;
+ msg.arg1 = arg1;
+ msg.arg2 = arg2;
+ msg.obj = obj;
+ try {
+ mRemoteMessenger.send(msg);
+ } catch (RemoteException e) {
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.i(TAG, "onDestroy");
+ mNotification = null;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}