summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Kweku Adams <kwekua@google.com> 2020-11-20 12:47:22 -0800
committer Kweku Adams <kwekua@google.com> 2020-11-23 10:56:43 -0800
commitf177a8df86a9139806952a7d25f6af6717c5d4cc (patch)
treea19291bf2a0fde4863c38da9d9982f355d49dc98
parent26acf279578786d84faa766c7fca01ad2bd6611d (diff)
Create a UsageEventListener.
Creating the UsageEventListener to generalize conveying new usage events so that multiple components can be notified. Bug: 142281756 Bug: 171305774 Test: atest AppIdleHostTest Test: atest CtsUsageStatsTestCases:UsageStatsTest Test: atest FrameworksMockingServicesTests:UsageStatsServiceTest Test: atest FrameworksServicesTests:AppIdleHistoryTests Test: atest FrameworksServicesTests:AppStandbyControllerTests Change-Id: I176c476257600f0f4299a10f02b5c61332007b1c
-rw-r--r--apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java3
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java28
-rw-r--r--services/core/java/android/app/usage/UsageStatsManagerInternal.java14
-rw-r--r--services/tests/mockingservicestests/Android.bp1
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/usage/UsageStatsServiceTest.java134
-rw-r--r--services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java12
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java68
7 files changed, 248 insertions, 12 deletions
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index 398ccb69fbe8..2ce85ee57205 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -3,7 +3,6 @@ package com.android.server.usage;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.usage.AppStandbyInfo;
-import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManager.StandbyBuckets;
import android.app.usage.UsageStatsManager.SystemForcedReasons;
import android.content.Context;
@@ -68,8 +67,6 @@ public interface AppStandbyInternal {
*/
void postOneTimeCheckIdleStates();
- void reportEvent(UsageEvents.Event event, int userId);
-
void setLastJobRunTime(String packageName, int userId, long elapsedRealtime);
long getTimeSinceLastJobRun(String packageName, int userId);
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 1157ee905b86..0b0923a67de6 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -62,6 +62,7 @@ import android.app.usage.AppStandbyInfo;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManager.StandbyBuckets;
import android.app.usage.UsageStatsManager.SystemForcedReasons;
+import android.app.usage.UsageStatsManagerInternal;
import android.appwidget.AppWidgetManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -128,9 +129,10 @@ import java.util.concurrent.CountDownLatch;
* Manages the standby state of an app, listening to various events.
*
* Unit test:
- atest com.android.server.usage.AppStandbyControllerTests
+ * atest com.android.server.usage.AppStandbyControllerTests
*/
-public class AppStandbyController implements AppStandbyInternal {
+public class AppStandbyController
+ implements AppStandbyInternal, UsageStatsManagerInternal.UsageEventListener {
private static final String TAG = "AppStandbyController";
// Do not submit with true.
@@ -468,10 +470,21 @@ public class AppStandbyController implements AppStandbyInternal {
@VisibleForTesting
void setAppIdleEnabled(boolean enabled) {
+ // Don't call out to USM with the lock held. Also, register the listener before we
+ // change our internal state so no events fall through the cracks.
+ final UsageStatsManagerInternal usmi =
+ LocalServices.getService(UsageStatsManagerInternal.class);
+ if (enabled) {
+ usmi.registerListener(this);
+ } else {
+ usmi.unregisterListener(this);
+ }
+
synchronized (mAppIdleLock) {
if (mAppIdleEnabled != enabled) {
final boolean oldParoleState = isInParole();
mAppIdleEnabled = enabled;
+
if (isInParole() != oldParoleState) {
postParoleStateChanged();
}
@@ -489,6 +502,11 @@ public class AppStandbyController implements AppStandbyInternal {
mInjector.onBootPhase(phase);
if (phase == PHASE_SYSTEM_SERVICES_READY) {
Slog.d(TAG, "Setting app idle enabled state");
+
+ if (mAppIdleEnabled) {
+ LocalServices.getService(UsageStatsManagerInternal.class).registerListener(this);
+ }
+
// Observe changes to the threshold
ConstantsObserver settingsObserver = new ConstantsObserver(mHandler);
settingsObserver.start();
@@ -912,8 +930,10 @@ public class AppStandbyController implements AppStandbyInternal {
}
}
- @Override
- public void reportEvent(UsageEvents.Event event, int userId) {
+ /**
+ * Callback to inform listeners of a new event.
+ */
+ public void onUsageEvent(int userId, @NonNull UsageEvents.Event event) {
if (!mAppIdleEnabled) return;
final int eventType = event.getEventType();
if ((eventType == UsageEvents.Event.ACTIVITY_RESUMED
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index fa84427ac064..b2226d1e0fa3 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -326,4 +326,18 @@ public abstract class UsageStatsManagerInternal {
* @return {@code true} if the updating was successful, {@code false} otherwise
*/
public abstract boolean updatePackageMappingsData();
+
+ /**
+ * Listener interface for usage events.
+ */
+ public interface UsageEventListener {
+ /** Callback to inform listeners of a new usage event. */
+ void onUsageEvent(@UserIdInt int userId, @NonNull UsageEvents.Event event);
+ }
+
+ /** Register a listener that will be notified of every new usage event. */
+ public abstract void registerListener(@NonNull UsageEventListener listener);
+
+ /** Unregister a listener from being notified of every new usage event. */
+ public abstract void unregisterListener(@NonNull UsageEventListener listener);
}
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index a2e6698963a4..8fc5c085999e 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -20,6 +20,7 @@ android_test {
static_libs: [
"services.core",
"services.net",
+ "services.usage",
"service-jobscheduler",
"service-permission.impl",
"service-blobstore",
diff --git a/services/tests/mockingservicestests/src/com/android/server/usage/UsageStatsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/usage/UsageStatsServiceTest.java
new file mode 100644
index 000000000000..c9fcd0233bef
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/usage/UsageStatsServiceTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2020 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.usage;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.Context;
+import android.os.RemoteException;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class UsageStatsServiceTest {
+ private static final long TIMEOUT = 5000;
+
+ private UsageStatsService mService;
+
+ private MockitoSession mMockingSession;
+ @Mock
+ private Context mContext;
+
+ private static class TestInjector extends UsageStatsService.Injector {
+ AppStandbyInternal getAppStandbyController(Context context) {
+ return mock(AppStandbyInternal.class);
+ }
+ }
+
+ @Before
+ public void setUp() {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ IActivityManager activityManager = ActivityManager.getService();
+ spyOn(activityManager);
+ try {
+ doNothing().when(activityManager).registerUidObserver(any(), anyInt(), anyInt(), any());
+ } catch (RemoteException e) {
+ fail("registerUidObserver threw exception: " + e.getMessage());
+ }
+ mService = new UsageStatsService(mContext, new TestInjector());
+ spyOn(mService);
+ doNothing().when(mService).publishBinderServices();
+ mService.onStart();
+ }
+
+ @Test
+ public void testUsageEventListener() throws Exception {
+ TestUsageEventListener listener = new TestUsageEventListener();
+ UsageStatsManagerInternal usmi = LocalServices.getService(UsageStatsManagerInternal.class);
+ usmi.registerListener(listener);
+
+ UsageEvents.Event event = new UsageEvents.Event(UsageEvents.Event.CONFIGURATION_CHANGE, 10);
+ usmi.reportEvent("com.android.test", 10, event.getEventType());
+ listener.setExpectation(10, event);
+ listener.mCountDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS);
+
+ usmi.unregisterListener(listener);
+ listener.reset();
+
+ usmi.reportEvent("com.android.test", 0, UsageEvents.Event.CHOOSER_ACTION);
+ Thread.sleep(TIMEOUT);
+ assertNull(listener.mLastReceivedEvent);
+ }
+
+ private static class TestUsageEventListener implements
+ UsageStatsManagerInternal.UsageEventListener {
+ UsageEvents.Event mLastReceivedEvent;
+ int mLastReceivedUserId;
+ UsageEvents.Event mExpectedEvent;
+ int mExpectedUserId;
+ CountDownLatch mCountDownLatch;
+
+ @Override
+ public void onUsageEvent(int userId, UsageEvents.Event event) {
+ mLastReceivedUserId = userId;
+ mLastReceivedEvent = event;
+ if (mCountDownLatch != null && userId == mExpectedUserId
+ && event.getEventType() == mExpectedEvent.getEventType()) {
+ mCountDownLatch.countDown();
+ }
+ }
+
+ private void setExpectation(int userId, UsageEvents.Event event) {
+ mExpectedUserId = userId;
+ mExpectedEvent = event;
+ mCountDownLatch = new CountDownLatch(1);
+ }
+
+ private void reset() {
+ mLastReceivedUserId = mExpectedUserId = -1;
+ mLastReceivedEvent = mExpectedEvent = null;
+ mCountDownLatch = null;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 0ad669f32060..11fb0021be62 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -65,6 +65,7 @@ import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.usage.AppStandbyInfo;
import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManagerInternal;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.ContextWrapper;
@@ -86,9 +87,11 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
+import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@@ -425,11 +428,18 @@ public class AppStandbyControllerTests {
@Before
public void setUp() throws Exception {
+ LocalServices.addService(
+ UsageStatsManagerInternal.class, mock(UsageStatsManagerInternal.class));
MyContextWrapper myContext = new MyContextWrapper(InstrumentationRegistry.getContext());
mInjector = new MyInjector(myContext, Looper.getMainLooper());
mController = setupController();
}
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
+ }
+
@Test
public void testBoundWidgetPackageExempt() throws Exception {
assumeTrue(mInjector.getContext().getSystemService(AppWidgetManager.class) != null);
@@ -562,7 +572,7 @@ public class AppStandbyControllerTests {
UsageEvents.Event ev = new UsageEvents.Event();
ev.mPackage = packageName;
ev.mEventType = eventType;
- controller.reportEvent(ev, USER_ID);
+ controller.onUsageEvent(USER_ID, ev);
}
private int getStandbyBucket(AppStandbyController controller, String packageName) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 8e56e5bb2d85..aa36e47a359b 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -82,6 +82,7 @@ import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.CollectionUtils;
@@ -90,7 +91,6 @@ import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
import java.io.BufferedReader;
@@ -178,6 +178,8 @@ public class UsageStatsService extends SystemService implements
private final SparseArray<LinkedList<Event>> mReportedEvents = new SparseArray<>();
final SparseArray<ArraySet<String>> mUsageReporters = new SparseArray();
final SparseArray<ActivityData> mVisibleActivities = new SparseArray();
+ private final ArraySet<UsageStatsManagerInternal.UsageEventListener> mUsageEventListeners =
+ new ArraySet<>();
private static class ActivityData {
private final String mTaskRootPackage;
@@ -202,8 +204,24 @@ public class UsageStatsService extends SystemService implements
}
};
+ @VisibleForTesting
+ static class Injector {
+ AppStandbyInternal getAppStandbyController(Context context) {
+ return AppStandbyInternal.newAppStandbyController(
+ UsageStatsService.class.getClassLoader(), context);
+ }
+ }
+
+ private final Injector mInjector;
+
public UsageStatsService(Context context) {
+ this(context, new Injector());
+ }
+
+ @VisibleForTesting
+ UsageStatsService(Context context, Injector injector) {
super(context);
+ mInjector = injector;
}
@Override
@@ -214,8 +232,7 @@ public class UsageStatsService extends SystemService implements
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mHandler = new H(BackgroundThread.get().getLooper());
- mAppStandby = AppStandbyInternal.newAppStandbyController(
- UsageStatsService.class.getClassLoader(), getContext());
+ mAppStandby = mInjector.getAppStandbyController(getContext());
mAppTimeLimit = new AppTimeLimitController(
new AppTimeLimitController.TimeLimitCallbackListener() {
@@ -262,6 +279,11 @@ public class UsageStatsService extends SystemService implements
publishLocalService(UsageStatsManagerInternal.class, new LocalService());
publishLocalService(AppStandbyInternal.class, mAppStandby);
+ publishBinderServices();
+ }
+
+ @VisibleForTesting
+ void publishBinderServices() {
publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService());
}
@@ -928,7 +950,10 @@ public class UsageStatsService extends SystemService implements
service.reportEvent(event);
}
- mAppStandby.reportEvent(event, userId);
+ final int size = mUsageEventListeners.size();
+ for (int i = 0; i < size; ++i) {
+ mUsageEventListeners.valueAt(i).onUsageEvent(userId, event);
+ }
}
/**
@@ -1151,6 +1176,25 @@ public class UsageStatsService extends SystemService implements
}
}
+ /**
+ * Called via the local interface.
+ */
+ private void registerListener(@NonNull UsageStatsManagerInternal.UsageEventListener listener) {
+ synchronized (mLock) {
+ mUsageEventListeners.add(listener);
+ }
+ }
+
+ /**
+ * Called via the local interface.
+ */
+ private void unregisterListener(
+ @NonNull UsageStatsManagerInternal.UsageEventListener listener) {
+ synchronized (mLock) {
+ mUsageEventListeners.remove(listener);
+ }
+ }
+
private String buildFullToken(String packageName, String token) {
final StringBuilder sb = new StringBuilder(packageName.length() + token.length() + 1);
sb.append(packageName);
@@ -2317,6 +2361,22 @@ public class UsageStatsService extends SystemService implements
public boolean updatePackageMappingsData() {
return UsageStatsService.this.updatePackageMappingsData();
}
+
+ /**
+ * Register a listener that will be notified of every new usage event.
+ */
+ @Override
+ public void registerListener(@NonNull UsageEventListener listener) {
+ UsageStatsService.this.registerListener(listener);
+ }
+
+ /**
+ * Unregister a listener from being notified of every new usage event.
+ */
+ @Override
+ public void unregisterListener(@NonNull UsageEventListener listener) {
+ UsageStatsService.this.unregisterListener(listener);
+ }
}
private class MyPackageMonitor extends PackageMonitor {