diff options
| author | 2020-11-20 12:47:22 -0800 | |
|---|---|---|
| committer | 2020-11-23 10:56:43 -0800 | |
| commit | f177a8df86a9139806952a7d25f6af6717c5d4cc (patch) | |
| tree | a19291bf2a0fde4863c38da9d9982f355d49dc98 | |
| parent | 26acf279578786d84faa766c7fca01ad2bd6611d (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
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 { |