blob: 7934d33f907d6a3a0235cb973b8db20a6854a5ef [file] [log] [blame]
/*
* 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.people.data;
import static com.android.server.people.data.TestUtils.timestamp;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.content.Context;
import android.content.LocusId;
import androidx.test.InstrumentationRegistry;
import com.android.server.LocalServices;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Predicate;
@RunWith(JUnit4.class)
public final class UsageStatsQueryHelperTest {
private static final int USER_ID_PRIMARY = 0;
private static final String PKG_NAME = "pkg";
private static final String ACTIVITY_NAME = "TestActivity";
private static final String SHORTCUT_ID = "abc";
private static final LocusId LOCUS_ID_1 = new LocusId("locus_1");
private static final LocusId LOCUS_ID_2 = new LocusId("locus_2");
@Mock
private UsageStatsManagerInternal mUsageStatsManagerInternal;
private TestPackageData mPackageData;
private UsageStatsQueryHelper mHelper;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
addLocalServiceMock(UsageStatsManagerInternal.class, mUsageStatsManagerInternal);
Context ctx = InstrumentationRegistry.getContext();
File testDir = new File(ctx.getCacheDir(), "testdir");
ScheduledExecutorService scheduledExecutorService = new MockScheduledExecutorService();
mPackageData = new TestPackageData(PKG_NAME, USER_ID_PRIMARY, pkg -> false, pkg -> false,
scheduledExecutorService, testDir);
mPackageData.mConversationStore.mConversationInfo = new ConversationInfo.Builder()
.setShortcutId(SHORTCUT_ID)
.setLocusId(LOCUS_ID_1)
.build();
mHelper = new UsageStatsQueryHelper(USER_ID_PRIMARY, pkg -> mPackageData);
}
@After
public void tearDown() {
LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
}
@Test
public void testQueryNoEvents() {
assertFalse(mHelper.querySince(50L));
}
@Test
public void testQueryShortcutInvocationEvent() {
addUsageEvents(createShortcutInvocationEvent(100L));
assertTrue(mHelper.querySince(50L));
assertEquals(100L, mHelper.getLastEventTimestamp());
Event expectedEvent = new Event(100L, Event.TYPE_SHORTCUT_INVOCATION);
List<Event> events = mPackageData.mEventStore.mShortcutEventHistory.queryEvents(
Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
assertEquals(1, events.size());
assertEquals(expectedEvent, events.get(0));
}
@Test
public void testInAppConversationSwitch() {
addUsageEvents(
createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()),
createLocusIdSetEvent(110_000L, LOCUS_ID_2.getId()));
assertTrue(mHelper.querySince(50_000L));
assertEquals(110_000L, mHelper.getLastEventTimestamp());
List<Event> events = mPackageData.mEventStore.mLocusEventHistory.queryEvents(
Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
assertEquals(1, events.size());
assertEquals(createInAppConversationEvent(100_000L, 10), events.get(0));
}
@Test
public void testInAppConversationExplicitlyEnd() {
addUsageEvents(
createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()),
createLocusIdSetEvent(110_000L, null));
assertTrue(mHelper.querySince(50_000L));
assertEquals(110_000L, mHelper.getLastEventTimestamp());
List<Event> events = mPackageData.mEventStore.mLocusEventHistory.queryEvents(
Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
assertEquals(1, events.size());
assertEquals(createInAppConversationEvent(100_000L, 10), events.get(0));
}
@Test
public void testInAppConversationImplicitlyEnd() {
addUsageEvents(
createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()),
createActivityStoppedEvent(110_000L));
assertTrue(mHelper.querySince(50_000L));
assertEquals(110_000L, mHelper.getLastEventTimestamp());
List<Event> events = mPackageData.mEventStore.mLocusEventHistory.queryEvents(
Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
assertEquals(1, events.size());
assertEquals(createInAppConversationEvent(100_000L, 10), events.get(0));
}
@Test
public void testMultipleInAppConversations() {
addUsageEvents(
createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()),
createLocusIdSetEvent(110_000L, LOCUS_ID_2.getId()),
createLocusIdSetEvent(130_000L, LOCUS_ID_1.getId()),
createActivityStoppedEvent(160_000L));
assertTrue(mHelper.querySince(50_000L));
assertEquals(160_000L, mHelper.getLastEventTimestamp());
List<Event> events = mPackageData.mEventStore.mLocusEventHistory.queryEvents(
Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
assertEquals(3, events.size());
assertEquals(createInAppConversationEvent(100_000L, 10), events.get(0));
assertEquals(createInAppConversationEvent(110_000L, 20), events.get(1));
assertEquals(createInAppConversationEvent(130_000L, 30), events.get(2));
}
private void addUsageEvents(UsageEvents.Event... events) {
UsageEvents usageEvents = new UsageEvents(Arrays.asList(events), new String[]{});
when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(),
eq(UsageEvents.SHOW_ALL_EVENT_DATA))).thenReturn(usageEvents);
}
private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
LocalServices.removeServiceForTest(clazz);
LocalServices.addService(clazz, mock);
}
private static UsageEvents.Event createShortcutInvocationEvent(long timestamp) {
UsageEvents.Event e = createUsageEvent(UsageEvents.Event.SHORTCUT_INVOCATION, timestamp);
e.mShortcutId = SHORTCUT_ID;
return e;
}
private static UsageEvents.Event createLocusIdSetEvent(long timestamp, String locusId) {
UsageEvents.Event e = createUsageEvent(UsageEvents.Event.LOCUS_ID_SET, timestamp);
e.mClass = ACTIVITY_NAME;
e.mLocusId = locusId;
return e;
}
private static UsageEvents.Event createActivityStoppedEvent(long timestamp) {
UsageEvents.Event e = createUsageEvent(UsageEvents.Event.ACTIVITY_STOPPED, timestamp);
e.mClass = ACTIVITY_NAME;
return e;
}
private static UsageEvents.Event createUsageEvent(int eventType, long timestamp) {
UsageEvents.Event e = new UsageEvents.Event(eventType, timestamp);
e.mPackage = PKG_NAME;
return e;
}
private static Event createInAppConversationEvent(long timestamp, int durationSeconds) {
return new Event.Builder(timestamp, Event.TYPE_IN_APP_CONVERSATION)
.setDurationSeconds(durationSeconds)
.build();
}
private static class TestConversationStore extends ConversationStore {
private ConversationInfo mConversationInfo;
TestConversationStore(File packageDir,
ScheduledExecutorService scheduledExecutorService) {
super(packageDir, scheduledExecutorService);
}
@Override
@Nullable
ConversationInfo getConversation(@Nullable String shortcutId) {
return mConversationInfo;
}
}
private static class TestPackageData extends PackageData {
private final TestConversationStore mConversationStore;
private final TestEventStore mEventStore;
TestPackageData(@NonNull String packageName, @UserIdInt int userId,
@NonNull Predicate<String> isDefaultDialerPredicate,
@NonNull Predicate<String> isDefaultSmsAppPredicate,
@NonNull ScheduledExecutorService scheduledExecutorService, @NonNull File rootDir) {
super(packageName, userId, isDefaultDialerPredicate, isDefaultSmsAppPredicate,
scheduledExecutorService, rootDir);
mConversationStore = new TestConversationStore(rootDir, scheduledExecutorService);
mEventStore = new TestEventStore(rootDir, scheduledExecutorService);
}
@Override
@NonNull
ConversationStore getConversationStore() {
return mConversationStore;
}
@Override
@NonNull
EventStore getEventStore() {
return mEventStore;
}
}
private static class TestEventStore extends EventStore {
private static final long CURRENT_TIMESTAMP = timestamp("01-30 18:50");
private static final EventIndex.Injector EVENT_INDEX_INJECTOR = new EventIndex.Injector() {
@Override
long currentTimeMillis() {
return CURRENT_TIMESTAMP;
}
};
private static final EventHistoryImpl.Injector EVENT_HISTORY_INJECTOR =
new EventHistoryImpl.Injector() {
@Override
EventIndex createEventIndex() {
return new EventIndex(EVENT_INDEX_INJECTOR);
}
};
private final EventHistoryImpl mShortcutEventHistory;
private final EventHistoryImpl mLocusEventHistory;
TestEventStore(File rootDir, ScheduledExecutorService scheduledExecutorService) {
super(rootDir, scheduledExecutorService);
mShortcutEventHistory = new TestEventHistoryImpl(EVENT_HISTORY_INJECTOR, rootDir,
scheduledExecutorService);
mLocusEventHistory = new TestEventHistoryImpl(EVENT_HISTORY_INJECTOR, rootDir,
scheduledExecutorService);
}
@Override
@NonNull
EventHistoryImpl getOrCreateEventHistory(@EventCategory int category, String key) {
if (category == EventStore.CATEGORY_SHORTCUT_BASED) {
return mShortcutEventHistory;
} else if (category == EventStore.CATEGORY_LOCUS_ID_BASED) {
return mLocusEventHistory;
}
throw new UnsupportedOperationException();
}
}
private static class TestEventHistoryImpl extends EventHistoryImpl {
private final List<Event> mEvents = new ArrayList<>();
TestEventHistoryImpl(Injector injector, File rootDir,
ScheduledExecutorService scheduledExecutorService) {
super(injector, rootDir, scheduledExecutorService);
}
@Override
@NonNull
public List<Event> queryEvents(Set<Integer> eventTypes, long startTime, long endTime) {
return mEvents;
}
@Override
void addEvent(Event event) {
mEvents.add(event);
}
}
}