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