Loading services/people/java/com/android/server/people/data/CallLogQueryHelper.java +1 −1 Original line number Diff line number Diff line Loading @@ -107,7 +107,7 @@ class CallLogQueryHelper { } @Event.EventType int eventType = CALL_TYPE_TO_EVENT_TYPE.get(callType); Event event = new Event.Builder(date, eventType) .setCallDetails(new Event.CallDetails(durationSeconds)) .setDurationSeconds((int) durationSeconds) .build(); mEventConsumer.accept(phoneNumber, event); return true; Loading services/people/java/com/android/server/people/data/ConversationStore.java +18 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,9 @@ class ConversationStore { // Phone Number -> Shortcut ID private final Map<String, String> mPhoneNumberToShortcutIdMap = new ArrayMap<>(); // Notification Channel ID -> Shortcut ID private final Map<String, String> mNotifChannelIdToShortcutIdMap = new ArrayMap<>(); void addOrUpdate(@NonNull ConversationInfo conversationInfo) { mConversationInfoMap.put(conversationInfo.getShortcutId(), conversationInfo); Loading @@ -57,6 +60,11 @@ class ConversationStore { if (phoneNumber != null) { mPhoneNumberToShortcutIdMap.put(phoneNumber, conversationInfo.getShortcutId()); } String notifChannelId = conversationInfo.getNotificationChannelId(); if (notifChannelId != null) { mNotifChannelIdToShortcutIdMap.put(notifChannelId, conversationInfo.getShortcutId()); } } void deleteConversation(@NonNull String shortcutId) { Loading @@ -79,6 +87,11 @@ class ConversationStore { if (phoneNumber != null) { mPhoneNumberToShortcutIdMap.remove(phoneNumber); } String notifChannelId = conversationInfo.getNotificationChannelId(); if (notifChannelId != null) { mNotifChannelIdToShortcutIdMap.remove(notifChannelId); } } void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) { Loading Loading @@ -106,4 +119,9 @@ class ConversationStore { ConversationInfo getConversationByPhoneNumber(@NonNull String phoneNumber) { return getConversation(mPhoneNumberToShortcutIdMap.get(phoneNumber)); } @Nullable ConversationInfo getConversationByNotificationChannelId(@NonNull String notifChannelId) { return getConversation(mNotifChannelIdToShortcutIdMap.get(notifChannelId)); } } services/people/java/com/android/server/people/data/DataManager.java +14 −41 Original line number Diff line number Diff line Loading @@ -24,8 +24,6 @@ import android.app.Notification; import android.app.Person; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; Loading Loading @@ -69,6 +67,7 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; /** * A class manages the lifecycle of the conversations and associated data, and exposes the methods Loading Loading @@ -96,7 +95,6 @@ public class DataManager { private final ContentObserver mMmsSmsContentObserver; private ShortcutServiceInternal mShortcutServiceInternal; private UsageStatsManagerInternal mUsageStatsManagerInternal; private ShortcutManager mShortcutManager; private UserManager mUserManager; Loading @@ -118,7 +116,6 @@ public class DataManager { /** Initialization. Called when the system services are up running. */ public void initialize() { mShortcutServiceInternal = LocalServices.getService(ShortcutServiceInternal.class); mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class); mShortcutManager = mContext.getSystemService(ShortcutManager.class); mUserManager = mContext.getSystemService(UserManager.class); Loading Loading @@ -385,36 +382,6 @@ public class DataManager { conversationStore.addOrUpdate(builder.build()); } @VisibleForTesting @WorkerThread void queryUsageStatsService(@UserIdInt int userId, long currentTime, long lastQueryTime) { UsageEvents usageEvents = mUsageStatsManagerInternal.queryEventsForUser( userId, lastQueryTime, currentTime, false, false); if (usageEvents == null) { return; } while (usageEvents.hasNextEvent()) { UsageEvents.Event e = new UsageEvents.Event(); usageEvents.getNextEvent(e); String packageName = e.getPackageName(); PackageData packageData = getPackage(packageName, userId); if (packageData == null) { continue; } if (e.getEventType() == UsageEvents.Event.SHORTCUT_INVOCATION) { String shortcutId = e.getShortcutId(); if (packageData.getConversationStore().getConversation(shortcutId) != null) { EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateShortcutEventHistory( shortcutId); eventHistory.addEvent( new Event(e.getTimeStamp(), Event.TYPE_SHORTCUT_INVOCATION)); } } } } @VisibleForTesting ContentObserver getContactsContentObserverForTesting(@UserIdInt int userId) { return mContactsContentObservers.get(userId); Loading Loading @@ -612,19 +579,20 @@ public class DataManager { */ private class UsageStatsQueryRunnable implements Runnable { private final int mUserId; private long mLastQueryTime; private final UsageStatsQueryHelper mUsageStatsQueryHelper; private long mLastEventTimestamp; private UsageStatsQueryRunnable(int userId) { mUserId = userId; mLastQueryTime = System.currentTimeMillis() - QUERY_EVENTS_MAX_AGE_MS; mUsageStatsQueryHelper = mInjector.createUsageStatsQueryHelper(userId, (packageName) -> getPackage(packageName, userId)); mLastEventTimestamp = System.currentTimeMillis() - QUERY_EVENTS_MAX_AGE_MS; } @Override public void run() { long currentTime = System.currentTimeMillis(); queryUsageStatsService(mUserId, currentTime, mLastQueryTime); mLastQueryTime = currentTime; if (mUsageStatsQueryHelper.querySince(mLastEventTimestamp)) { mLastEventTimestamp = mUsageStatsQueryHelper.getLastEventTimestamp(); } } } Loading Loading @@ -680,6 +648,11 @@ public class DataManager { return new SmsQueryHelper(context, eventConsumer); } UsageStatsQueryHelper createUsageStatsQueryHelper(@UserIdInt int userId, Function<String, PackageData> packageDataGetter) { return new UsageStatsQueryHelper(userId, packageDataGetter); } int getCallingUserId() { return Binder.getCallingUserHandle().getIdentifier(); } Loading services/people/java/com/android/server/people/data/Event.java +41 −36 Original line number Diff line number Diff line Loading @@ -18,14 +18,12 @@ package com.android.server.people.data; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.text.format.DateFormat; import android.util.ArraySet; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; import java.util.Set; /** An event representing the interaction with a specific conversation or app. */ Loading Loading @@ -55,6 +53,8 @@ public class Event { public static final int TYPE_CALL_MISSED = 12; public static final int TYPE_IN_APP_CONVERSATION = 13; @IntDef(prefix = { "TYPE_" }, value = { TYPE_SHORTCUT_INVOCATION, TYPE_NOTIFICATION_POSTED, Loading @@ -68,6 +68,7 @@ public class Event { TYPE_CALL_OUTGOING, TYPE_CALL_INCOMING, TYPE_CALL_MISSED, TYPE_IN_APP_CONVERSATION, }) @Retention(RetentionPolicy.SOURCE) public @interface EventType {} Loading Loading @@ -95,6 +96,7 @@ public class Event { CALL_EVENT_TYPES.add(TYPE_CALL_MISSED); ALL_EVENT_TYPES.add(TYPE_SHORTCUT_INVOCATION); ALL_EVENT_TYPES.add(TYPE_IN_APP_CONVERSATION); ALL_EVENT_TYPES.addAll(NOTIFICATION_EVENT_TYPES); ALL_EVENT_TYPES.addAll(SHARE_EVENT_TYPES); ALL_EVENT_TYPES.addAll(SMS_EVENT_TYPES); Loading @@ -105,18 +107,18 @@ public class Event { private final int mType; private final CallDetails mCallDetails; private final int mDurationSeconds; Event(long timestamp, @EventType int type) { mTimestamp = timestamp; mType = type; mCallDetails = null; mDurationSeconds = 0; } private Event(@NonNull Builder builder) { mTimestamp = builder.mTimestamp; mType = builder.mType; mCallDetails = builder.mCallDetails; mDurationSeconds = builder.mDurationSeconds; } public long getTimestamp() { Loading @@ -128,44 +130,48 @@ public class Event { } /** * Gets the {@link CallDetails} of the event. It is only available if the event type is one of * {@code CALL_EVENT_TYPES}, otherwise, it's always {@code null}. * Gets the duration of the event in seconds. It is only available for these events: * <ul> * <li>{@link #TYPE_CALL_INCOMING} * <li>{@link #TYPE_CALL_OUTGOING} * <li>{@link #TYPE_IN_APP_CONVERSATION} * </ul> * <p>For the other event types, it always returns {@code 0}. */ @Nullable public CallDetails getCallDetails() { return mCallDetails; public int getDurationSeconds() { return mDurationSeconds; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Event {"); sb.append("timestamp=").append(DateFormat.format("yyyy-MM-dd HH:mm:ss", mTimestamp)); sb.append(", type=").append(mType); if (mCallDetails != null) { sb.append(", callDetails=").append(mCallDetails); public boolean equals(Object obj) { if (this == obj) { return true; } sb.append("}"); return sb.toString(); if (!(obj instanceof Event)) { return false; } /** Type-specific details of a call event. */ public static class CallDetails { private final long mDurationSeconds; CallDetails(long durationSeconds) { mDurationSeconds = durationSeconds; Event other = (Event) obj; return mTimestamp == other.mTimestamp && mType == other.mType && mDurationSeconds == other.mDurationSeconds; } public long getDurationSeconds() { return mDurationSeconds; @Override public int hashCode() { return Objects.hash(mTimestamp, mType, mDurationSeconds); } @Override public String toString() { return "CallDetails {durationSeconds=" + mDurationSeconds + "}"; StringBuilder sb = new StringBuilder(); sb.append("Event {"); sb.append("timestamp=").append(DateFormat.format("yyyy-MM-dd HH:mm:ss", mTimestamp)); sb.append(", type=").append(mType); if (mDurationSeconds > 0) { sb.append(", durationSeconds=").append(mDurationSeconds); } sb.append("}"); return sb.toString(); } /** Builder class for {@link Event} objects. */ Loading @@ -175,16 +181,15 @@ public class Event { private final int mType; private CallDetails mCallDetails; private int mDurationSeconds; Builder(long timestamp, @EventType int type) { mTimestamp = timestamp; mType = type; } Builder setCallDetails(CallDetails callDetails) { Preconditions.checkArgument(CALL_EVENT_TYPES.contains(mType)); mCallDetails = callDetails; Builder setDurationSeconds(int durationSeconds) { mDurationSeconds = durationSeconds; return this; } Loading services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java 0 → 100644 +158 −0 Original line number Diff line number Diff line /* * 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.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.Map; 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 = LocalServices.getService(UsageStatsManagerInternal.class); 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(), false, false); 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.NOTIFICATION_INTERRUPTION: addEventByNotificationChannelId(packageData, e.getNotificationChannelId(), new Event(e.getTimeStamp(), Event.TYPE_NOTIFICATION_POSTED)); 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; } 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().getOrCreateShortcutEventHistory( shortcutId); eventHistory.addEvent(event); } private void addEventByLocusId(PackageData packageData, LocusId locusId, Event event) { if (packageData.getConversationStore().getConversationByLocusId(locusId) == null) { return; } EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateLocusEventHistory( locusId); eventHistory.addEvent(event); } private void addEventByNotificationChannelId(PackageData packageData, String notificationChannelId, Event event) { ConversationInfo conversationInfo = packageData.getConversationStore().getConversationByNotificationChannelId( notificationChannelId); if (conversationInfo == null) { return; } EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateShortcutEventHistory( conversationInfo.getShortcutId()); eventHistory.addEvent(event); } } Loading
services/people/java/com/android/server/people/data/CallLogQueryHelper.java +1 −1 Original line number Diff line number Diff line Loading @@ -107,7 +107,7 @@ class CallLogQueryHelper { } @Event.EventType int eventType = CALL_TYPE_TO_EVENT_TYPE.get(callType); Event event = new Event.Builder(date, eventType) .setCallDetails(new Event.CallDetails(durationSeconds)) .setDurationSeconds((int) durationSeconds) .build(); mEventConsumer.accept(phoneNumber, event); return true; Loading
services/people/java/com/android/server/people/data/ConversationStore.java +18 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,9 @@ class ConversationStore { // Phone Number -> Shortcut ID private final Map<String, String> mPhoneNumberToShortcutIdMap = new ArrayMap<>(); // Notification Channel ID -> Shortcut ID private final Map<String, String> mNotifChannelIdToShortcutIdMap = new ArrayMap<>(); void addOrUpdate(@NonNull ConversationInfo conversationInfo) { mConversationInfoMap.put(conversationInfo.getShortcutId(), conversationInfo); Loading @@ -57,6 +60,11 @@ class ConversationStore { if (phoneNumber != null) { mPhoneNumberToShortcutIdMap.put(phoneNumber, conversationInfo.getShortcutId()); } String notifChannelId = conversationInfo.getNotificationChannelId(); if (notifChannelId != null) { mNotifChannelIdToShortcutIdMap.put(notifChannelId, conversationInfo.getShortcutId()); } } void deleteConversation(@NonNull String shortcutId) { Loading @@ -79,6 +87,11 @@ class ConversationStore { if (phoneNumber != null) { mPhoneNumberToShortcutIdMap.remove(phoneNumber); } String notifChannelId = conversationInfo.getNotificationChannelId(); if (notifChannelId != null) { mNotifChannelIdToShortcutIdMap.remove(notifChannelId); } } void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) { Loading Loading @@ -106,4 +119,9 @@ class ConversationStore { ConversationInfo getConversationByPhoneNumber(@NonNull String phoneNumber) { return getConversation(mPhoneNumberToShortcutIdMap.get(phoneNumber)); } @Nullable ConversationInfo getConversationByNotificationChannelId(@NonNull String notifChannelId) { return getConversation(mNotifChannelIdToShortcutIdMap.get(notifChannelId)); } }
services/people/java/com/android/server/people/data/DataManager.java +14 −41 Original line number Diff line number Diff line Loading @@ -24,8 +24,6 @@ import android.app.Notification; import android.app.Person; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; Loading Loading @@ -69,6 +67,7 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; /** * A class manages the lifecycle of the conversations and associated data, and exposes the methods Loading Loading @@ -96,7 +95,6 @@ public class DataManager { private final ContentObserver mMmsSmsContentObserver; private ShortcutServiceInternal mShortcutServiceInternal; private UsageStatsManagerInternal mUsageStatsManagerInternal; private ShortcutManager mShortcutManager; private UserManager mUserManager; Loading @@ -118,7 +116,6 @@ public class DataManager { /** Initialization. Called when the system services are up running. */ public void initialize() { mShortcutServiceInternal = LocalServices.getService(ShortcutServiceInternal.class); mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class); mShortcutManager = mContext.getSystemService(ShortcutManager.class); mUserManager = mContext.getSystemService(UserManager.class); Loading Loading @@ -385,36 +382,6 @@ public class DataManager { conversationStore.addOrUpdate(builder.build()); } @VisibleForTesting @WorkerThread void queryUsageStatsService(@UserIdInt int userId, long currentTime, long lastQueryTime) { UsageEvents usageEvents = mUsageStatsManagerInternal.queryEventsForUser( userId, lastQueryTime, currentTime, false, false); if (usageEvents == null) { return; } while (usageEvents.hasNextEvent()) { UsageEvents.Event e = new UsageEvents.Event(); usageEvents.getNextEvent(e); String packageName = e.getPackageName(); PackageData packageData = getPackage(packageName, userId); if (packageData == null) { continue; } if (e.getEventType() == UsageEvents.Event.SHORTCUT_INVOCATION) { String shortcutId = e.getShortcutId(); if (packageData.getConversationStore().getConversation(shortcutId) != null) { EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateShortcutEventHistory( shortcutId); eventHistory.addEvent( new Event(e.getTimeStamp(), Event.TYPE_SHORTCUT_INVOCATION)); } } } } @VisibleForTesting ContentObserver getContactsContentObserverForTesting(@UserIdInt int userId) { return mContactsContentObservers.get(userId); Loading Loading @@ -612,19 +579,20 @@ public class DataManager { */ private class UsageStatsQueryRunnable implements Runnable { private final int mUserId; private long mLastQueryTime; private final UsageStatsQueryHelper mUsageStatsQueryHelper; private long mLastEventTimestamp; private UsageStatsQueryRunnable(int userId) { mUserId = userId; mLastQueryTime = System.currentTimeMillis() - QUERY_EVENTS_MAX_AGE_MS; mUsageStatsQueryHelper = mInjector.createUsageStatsQueryHelper(userId, (packageName) -> getPackage(packageName, userId)); mLastEventTimestamp = System.currentTimeMillis() - QUERY_EVENTS_MAX_AGE_MS; } @Override public void run() { long currentTime = System.currentTimeMillis(); queryUsageStatsService(mUserId, currentTime, mLastQueryTime); mLastQueryTime = currentTime; if (mUsageStatsQueryHelper.querySince(mLastEventTimestamp)) { mLastEventTimestamp = mUsageStatsQueryHelper.getLastEventTimestamp(); } } } Loading Loading @@ -680,6 +648,11 @@ public class DataManager { return new SmsQueryHelper(context, eventConsumer); } UsageStatsQueryHelper createUsageStatsQueryHelper(@UserIdInt int userId, Function<String, PackageData> packageDataGetter) { return new UsageStatsQueryHelper(userId, packageDataGetter); } int getCallingUserId() { return Binder.getCallingUserHandle().getIdentifier(); } Loading
services/people/java/com/android/server/people/data/Event.java +41 −36 Original line number Diff line number Diff line Loading @@ -18,14 +18,12 @@ package com.android.server.people.data; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.text.format.DateFormat; import android.util.ArraySet; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; import java.util.Set; /** An event representing the interaction with a specific conversation or app. */ Loading Loading @@ -55,6 +53,8 @@ public class Event { public static final int TYPE_CALL_MISSED = 12; public static final int TYPE_IN_APP_CONVERSATION = 13; @IntDef(prefix = { "TYPE_" }, value = { TYPE_SHORTCUT_INVOCATION, TYPE_NOTIFICATION_POSTED, Loading @@ -68,6 +68,7 @@ public class Event { TYPE_CALL_OUTGOING, TYPE_CALL_INCOMING, TYPE_CALL_MISSED, TYPE_IN_APP_CONVERSATION, }) @Retention(RetentionPolicy.SOURCE) public @interface EventType {} Loading Loading @@ -95,6 +96,7 @@ public class Event { CALL_EVENT_TYPES.add(TYPE_CALL_MISSED); ALL_EVENT_TYPES.add(TYPE_SHORTCUT_INVOCATION); ALL_EVENT_TYPES.add(TYPE_IN_APP_CONVERSATION); ALL_EVENT_TYPES.addAll(NOTIFICATION_EVENT_TYPES); ALL_EVENT_TYPES.addAll(SHARE_EVENT_TYPES); ALL_EVENT_TYPES.addAll(SMS_EVENT_TYPES); Loading @@ -105,18 +107,18 @@ public class Event { private final int mType; private final CallDetails mCallDetails; private final int mDurationSeconds; Event(long timestamp, @EventType int type) { mTimestamp = timestamp; mType = type; mCallDetails = null; mDurationSeconds = 0; } private Event(@NonNull Builder builder) { mTimestamp = builder.mTimestamp; mType = builder.mType; mCallDetails = builder.mCallDetails; mDurationSeconds = builder.mDurationSeconds; } public long getTimestamp() { Loading @@ -128,44 +130,48 @@ public class Event { } /** * Gets the {@link CallDetails} of the event. It is only available if the event type is one of * {@code CALL_EVENT_TYPES}, otherwise, it's always {@code null}. * Gets the duration of the event in seconds. It is only available for these events: * <ul> * <li>{@link #TYPE_CALL_INCOMING} * <li>{@link #TYPE_CALL_OUTGOING} * <li>{@link #TYPE_IN_APP_CONVERSATION} * </ul> * <p>For the other event types, it always returns {@code 0}. */ @Nullable public CallDetails getCallDetails() { return mCallDetails; public int getDurationSeconds() { return mDurationSeconds; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Event {"); sb.append("timestamp=").append(DateFormat.format("yyyy-MM-dd HH:mm:ss", mTimestamp)); sb.append(", type=").append(mType); if (mCallDetails != null) { sb.append(", callDetails=").append(mCallDetails); public boolean equals(Object obj) { if (this == obj) { return true; } sb.append("}"); return sb.toString(); if (!(obj instanceof Event)) { return false; } /** Type-specific details of a call event. */ public static class CallDetails { private final long mDurationSeconds; CallDetails(long durationSeconds) { mDurationSeconds = durationSeconds; Event other = (Event) obj; return mTimestamp == other.mTimestamp && mType == other.mType && mDurationSeconds == other.mDurationSeconds; } public long getDurationSeconds() { return mDurationSeconds; @Override public int hashCode() { return Objects.hash(mTimestamp, mType, mDurationSeconds); } @Override public String toString() { return "CallDetails {durationSeconds=" + mDurationSeconds + "}"; StringBuilder sb = new StringBuilder(); sb.append("Event {"); sb.append("timestamp=").append(DateFormat.format("yyyy-MM-dd HH:mm:ss", mTimestamp)); sb.append(", type=").append(mType); if (mDurationSeconds > 0) { sb.append(", durationSeconds=").append(mDurationSeconds); } sb.append("}"); return sb.toString(); } /** Builder class for {@link Event} objects. */ Loading @@ -175,16 +181,15 @@ public class Event { private final int mType; private CallDetails mCallDetails; private int mDurationSeconds; Builder(long timestamp, @EventType int type) { mTimestamp = timestamp; mType = type; } Builder setCallDetails(CallDetails callDetails) { Preconditions.checkArgument(CALL_EVENT_TYPES.contains(mType)); mCallDetails = callDetails; Builder setDurationSeconds(int durationSeconds) { mDurationSeconds = durationSeconds; return this; } Loading
services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java 0 → 100644 +158 −0 Original line number Diff line number Diff line /* * 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.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.Map; 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 = LocalServices.getService(UsageStatsManagerInternal.class); 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(), false, false); 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.NOTIFICATION_INTERRUPTION: addEventByNotificationChannelId(packageData, e.getNotificationChannelId(), new Event(e.getTimeStamp(), Event.TYPE_NOTIFICATION_POSTED)); 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; } 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().getOrCreateShortcutEventHistory( shortcutId); eventHistory.addEvent(event); } private void addEventByLocusId(PackageData packageData, LocusId locusId, Event event) { if (packageData.getConversationStore().getConversationByLocusId(locusId) == null) { return; } EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateLocusEventHistory( locusId); eventHistory.addEvent(event); } private void addEventByNotificationChannelId(PackageData packageData, String notificationChannelId, Event event) { ConversationInfo conversationInfo = packageData.getConversationStore().getConversationByNotificationChannelId( notificationChannelId); if (conversationInfo == null) { return; } EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateShortcutEventHistory( conversationInfo.getShortcutId()); eventHistory.addEvent(event); } }