blob: 6e6fea93c8032bc3823647d2b1332aa50da5d21c [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 android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.LocusId;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import com.android.server.LocalServices;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
/** A helper class that queries {@link UsageStatsManagerInternal}. */
class UsageStatsQueryHelper {
private final UsageStatsManagerInternal mUsageStatsManagerInternal;
private final int mUserId;
private final Function<String, PackageData> mPackageDataGetter;
// Activity name -> Conversation start event (LOCUS_ID_SET)
private final Map<ComponentName, UsageEvents.Event> mConvoStartEvents = new ArrayMap<>();
private long mLastEventTimestamp;
/**
* @param userId The user whose events are to be queried.
* @param packageDataGetter The function to get {@link PackageData} with a package name.
*/
UsageStatsQueryHelper(@UserIdInt int userId,
Function<String, PackageData> packageDataGetter) {
mUsageStatsManagerInternal = getUsageStatsManagerInternal();
mUserId = userId;
mPackageDataGetter = packageDataGetter;
}
/**
* Queries {@link UsageStatsManagerInternal} for the recent events occurred since {@code
* sinceTime} and adds the derived {@link Event}s into the corresponding package's event store,
*
* @return true if the query runs successfully and at least one event is found.
*/
boolean querySince(long sinceTime) {
UsageEvents usageEvents = mUsageStatsManagerInternal.queryEventsForUser(
mUserId, sinceTime, System.currentTimeMillis(), UsageEvents.SHOW_ALL_EVENT_DATA);
if (usageEvents == null) {
return false;
}
boolean hasEvents = false;
while (usageEvents.hasNextEvent()) {
UsageEvents.Event e = new UsageEvents.Event();
usageEvents.getNextEvent(e);
hasEvents = true;
mLastEventTimestamp = Math.max(mLastEventTimestamp, e.getTimeStamp());
String packageName = e.getPackageName();
PackageData packageData = mPackageDataGetter.apply(packageName);
if (packageData == null) {
continue;
}
switch (e.getEventType()) {
case UsageEvents.Event.SHORTCUT_INVOCATION:
addEventByShortcutId(packageData, e.getShortcutId(),
new Event(e.getTimeStamp(), Event.TYPE_SHORTCUT_INVOCATION));
break;
case UsageEvents.Event.LOCUS_ID_SET:
onInAppConversationEnded(packageData, e);
LocusId locusId = e.getLocusId() != null ? new LocusId(e.getLocusId()) : null;
if (locusId != null) {
if (packageData.getConversationStore().getConversationByLocusId(locusId)
!= null) {
ComponentName activityName =
new ComponentName(packageName, e.getClassName());
mConvoStartEvents.put(activityName, e);
}
}
break;
case UsageEvents.Event.ACTIVITY_PAUSED:
case UsageEvents.Event.ACTIVITY_STOPPED:
case UsageEvents.Event.ACTIVITY_DESTROYED:
onInAppConversationEnded(packageData, e);
break;
}
}
return hasEvents;
}
long getLastEventTimestamp() {
return mLastEventTimestamp;
}
/**
* Queries {@link UsageStatsManagerInternal} events for moving app to foreground between
* {@code startTime} and {@code endTime}.
*
* @return a list containing events moving app to foreground.
*/
static List<UsageEvents.Event> queryAppMovingToForegroundEvents(@UserIdInt int userId,
long startTime, long endTime) {
List<UsageEvents.Event> res = new ArrayList<>();
UsageEvents usageEvents = getUsageStatsManagerInternal().queryEventsForUser(userId,
startTime, endTime,
UsageEvents.HIDE_SHORTCUT_EVENTS | UsageEvents.HIDE_LOCUS_EVENTS);
if (usageEvents == null) {
return res;
}
while (usageEvents.hasNextEvent()) {
UsageEvents.Event e = new UsageEvents.Event();
usageEvents.getNextEvent(e);
if (e.getEventType() == UsageEvents.Event.ACTIVITY_RESUMED) {
res.add(e);
}
}
return res;
}
/**
* Queries {@link UsageStatsManagerInternal} for launch count of apps within {@code
* packageNameFilter} between {@code startTime} and {@code endTime}.obfuscateInstantApps
*
* @return a map which keys are package names and values are app launch counts.
*/
static Map<String, Integer> queryAppLaunchCount(@UserIdInt int userId, long startTime,
long endTime, Set<String> packageNameFilter) {
List<UsageStats> stats = getUsageStatsManagerInternal().queryUsageStatsForUser(userId,
UsageStatsManager.INTERVAL_BEST, startTime, endTime,
/* obfuscateInstantApps= */ false);
Map<String, Integer> aggregatedStats = new ArrayMap<>();
for (UsageStats stat : stats) {
String packageName = stat.getPackageName();
if (packageNameFilter.contains(packageName)) {
aggregatedStats.put(packageName,
aggregatedStats.getOrDefault(packageName, 0) + stat.getAppLaunchCount());
}
}
return aggregatedStats;
}
private void onInAppConversationEnded(@NonNull PackageData packageData,
@NonNull UsageEvents.Event endEvent) {
ComponentName activityName =
new ComponentName(endEvent.getPackageName(), endEvent.getClassName());
UsageEvents.Event startEvent = mConvoStartEvents.remove(activityName);
if (startEvent == null || startEvent.getTimeStamp() >= endEvent.getTimeStamp()) {
return;
}
long durationMillis = endEvent.getTimeStamp() - startEvent.getTimeStamp();
Event event = new Event.Builder(startEvent.getTimeStamp(), Event.TYPE_IN_APP_CONVERSATION)
.setDurationSeconds((int) (durationMillis / DateUtils.SECOND_IN_MILLIS))
.build();
addEventByLocusId(packageData, new LocusId(startEvent.getLocusId()), event);
}
private void addEventByShortcutId(PackageData packageData, String shortcutId, Event event) {
if (packageData.getConversationStore().getConversation(shortcutId) == null) {
return;
}
EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory(
EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
eventHistory.addEvent(event);
}
private void addEventByLocusId(PackageData packageData, LocusId locusId, Event event) {
if (packageData.getConversationStore().getConversationByLocusId(locusId) == null) {
return;
}
EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory(
EventStore.CATEGORY_LOCUS_ID_BASED, locusId.getId());
eventHistory.addEvent(event);
}
private static UsageStatsManagerInternal getUsageStatsManagerInternal() {
return LocalServices.getService(UsageStatsManagerInternal.class);
}
}