Fix potential negative duration in the ServiceState tracker
Use the current time while marking the end of foreground service. Bug: 174786928 Bug: 175024931 Bug: 140997620 Test: atest FrameworksServicesTests:ActivityManagerTest Change-Id: I0061b5ff202d46a3f85b22af42a8a6084151fc64
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user