diff --git a/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java b/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java new file mode 100644 index 0000000000000..4d04a7af4693f --- /dev/null +++ b/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java @@ -0,0 +1,106 @@ +/* + * 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 android.app.usage; + +import static junit.framework.Assert.fail; + +import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.util.ArrayUtils; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.reflect.Field; + +/** + * These tests verify that all fields defined in {@link UsageStats} and {@link UsageEvents.Event} + * are all known fields. This ensures that newly added fields or refactorings are accounted for in + * the usagestatsservice.proto and usagestatsservice_v2.proto files. + * + * Note: verification for {@link com.android.server.usage.IntervalStats} fields is located in + * {@link com.android.server.usage.IntervalStatsTests}. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class UsageStatsPersistenceTest { + + // All fields in this list are defined in UsageStats and persisted - please ensure they're + // defined correctly in both usagestatsservice.proto and usagestatsservice_v2.proto + private static final String[] USAGESTATS_PERSISTED_FIELDS = {"mBeginTimeStamp", "mEndTimeStamp", + "mPackageName", "mPackageToken", "mLastEvent", "mAppLaunchCount", "mChooserCounts", + "mLastTimeUsed", "mTotalTimeInForeground", "mLastTimeForegroundServiceUsed", + "mTotalTimeForegroundServiceUsed", "mLastTimeVisible", "mTotalTimeVisible"}; + // All fields in this list are defined in UsageStats but not persisted + private static final String[] USAGESTATS_IGNORED_FIELDS = {"CREATOR", "mActivities", + "mForegroundServices", "mLaunchCount", "mChooserCountsObfuscated"}; + + @Test + public void testUsageStatsFields() { + final UsageStats stats = new UsageStats(); + final Field[] fields = stats.getClass().getDeclaredFields(); + for (Field field : fields) { + if (!(ArrayUtils.contains(USAGESTATS_PERSISTED_FIELDS, field.getName()) + || ArrayUtils.contains(USAGESTATS_IGNORED_FIELDS, field.getName()))) { + fail("Found an unknown field: " + field.getName() + ". Please correctly update " + + "either USAGESTATS_PERSISTED_FIELDS or USAGESTATS_IGNORED_FIELDS."); + } + } + } + + // All fields in this list are defined in UsageEvents.Event and persisted - please ensure + // they're defined correctly in both usagestatsservice.proto and usagestatsservice_v2.proto + private static final String[] USAGEEVENTS_PERSISTED_FIELDS = {"mPackage", "mPackageToken", + "mClass", "mClassToken", "mTimeStamp", "mFlags", "mEventType", "mConfiguration", + "mShortcutId", "mShortcutIdToken", "mBucketAndReason", "mInstanceId", + "mNotificationChannelId", "mNotificationChannelIdToken", "mTaskRootPackage", + "mTaskRootPackageToken", "mTaskRootClass", "mTaskRootClassToken", "mLocusId", + "mLocusIdToken"}; + // All fields in this list are defined in UsageEvents.Event but not persisted + private static final String[] USAGEEVENTS_IGNORED_FIELDS = {"mAction", "mContentAnnotations", + "mContentType", "DEVICE_EVENT_PACKAGE_NAME", "FLAG_IS_PACKAGE_INSTANT_APP", + "VALID_FLAG_BITS", "UNASSIGNED_TOKEN", "MAX_EVENT_TYPE"}; + // All fields in this list are final constants defining event types and not persisted + private static final String[] EVENT_TYPES = {"NONE", "ACTIVITY_DESTROYED", "ACTIVITY_PAUSED", + "ACTIVITY_RESUMED", "ACTIVITY_STOPPED", "CHOOSER_ACTION", "CONFIGURATION_CHANGE", + "CONTINUE_PREVIOUS_DAY", "CONTINUING_FOREGROUND_SERVICE", "DEVICE_SHUTDOWN", + "DEVICE_STARTUP", "END_OF_DAY", "FLUSH_TO_DISK", "FOREGROUND_SERVICE_START", + "FOREGROUND_SERVICE_STOP", "KEYGUARD_HIDDEN", "KEYGUARD_SHOWN", "LOCUS_ID_SET", + "MOVE_TO_BACKGROUND", "MOVE_TO_FOREGROUND", "NOTIFICATION_INTERRUPTION", + "NOTIFICATION_SEEN", "ROLLOVER_FOREGROUND_SERVICE", "SCREEN_INTERACTIVE", + "SCREEN_NON_INTERACTIVE", "SHORTCUT_INVOCATION", "SLICE_PINNED", "SLICE_PINNED_PRIV", + "STANDBY_BUCKET_CHANGED", "SYSTEM_INTERACTION", "USER_INTERACTION", "USER_STOPPED", + "USER_UNLOCKED"}; + + @Test + public void testUsageEventsFields() { + final UsageEvents.Event event = new UsageEvents.Event(); + final Field[] fields = event.getClass().getDeclaredFields(); + for (Field field : fields) { + final String name = field.getName(); + if (!(ArrayUtils.contains(USAGEEVENTS_PERSISTED_FIELDS, name) + || ArrayUtils.contains(USAGEEVENTS_IGNORED_FIELDS, name) + || ArrayUtils.contains(EVENT_TYPES, name))) { + fail("Found an unknown field: " + name + ". Please correctly update either " + + "USAGEEVENTS_PERSISTED_FIELDS or USAGEEVENTS_IGNORED_FIELDS. If this " + + "field is a new event type, please update EVENT_TYPES instead."); + } + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java b/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java index 5d849c114e69b..2be3f1e818977 100644 --- a/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java @@ -19,6 +19,7 @@ import static android.app.usage.UsageEvents.Event.MAX_EVENT_TYPE; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; import android.app.usage.UsageEvents; import android.content.res.Configuration; @@ -26,9 +27,12 @@ import android.test.suitebuilder.annotation.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.util.ArrayUtils; + import org.junit.Test; import org.junit.runner.RunWith; +import java.lang.reflect.Field; import java.util.Locale; @RunWith(AndroidJUnit4.class) @@ -191,4 +195,27 @@ public class IntervalStatsTests { assertTrue(intervalStats.events.size() > NUMBER_OF_EVENTS - NUMBER_OF_EVENTS_PER_PACKAGE); assertEquals(intervalStats.packageStats.size(), NUMBER_OF_PACKAGES); } + + // All fields in this list are defined in IntervalStats and persisted - please ensure they're + // defined correctly in both usagestatsservice.proto and usagestatsservice_v2.proto + private static final String[] INTERVALSTATS_PERSISTED_FIELDS = {"beginTime", "endTime", + "mStringCache", "majorVersion", "minorVersion", "interactiveTracker", + "nonInteractiveTracker", "keyguardShownTracker", "keyguardHiddenTracker", + "packageStats", "configurations", "activeConfiguration", "events"}; + // All fields in this list are defined in IntervalStats but not persisted + private static final String[] INTERVALSTATS_IGNORED_FIELDS = {"lastTimeSaved", + "packageStatsObfuscated", "CURRENT_MAJOR_VERSION", "CURRENT_MINOR_VERSION", "TAG"}; + + @Test + public void testIntervalStatsFieldsAreKnown() { + final IntervalStats stats = new IntervalStats(); + final Field[] fields = stats.getClass().getDeclaredFields(); + for (Field field : fields) { + if (!(ArrayUtils.contains(INTERVALSTATS_PERSISTED_FIELDS, field.getName()) + || ArrayUtils.contains(INTERVALSTATS_IGNORED_FIELDS, field.getName()))) { + fail("Found an unknown field: " + field.getName() + ". Please correctly update " + + "either INTERVALSTATS_PERSISTED_FIELDS or INTERVALSTATS_IGNORED_FIELDS."); + } + } + } }