1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.carlauncher.recents;
18 
19 import android.annotation.IntDef;
20 import android.content.pm.ApplicationInfo;
21 import android.content.pm.PackageManager;
22 import android.os.Build;
23 import android.util.Log;
24 
25 import com.android.car.carlauncher.CarLauncherStatsLog;
26 
27 import java.util.UUID;
28 
29 /**
30  * Helper class that directly interacts with CarLauncherStatsLog, a generated class that contains
31  * logging methods for CarRecentsActivity.
32  */
33 public class RecentsStatsLogHelper {
34     public static final String TAG = "RecentsStatsLogHelper";
35     private static RecentsStatsLogHelper sInstance;
36     private PackageManager mPackageManager;
37     private long mSessionId;
38     private long mStartTimeMs;
39     // Integer for taskIndex, to be logged by CarRecentsEventReported
40     public static final int UNSPECIFIED_INDEX = -1;
41     // Integer for totalTaskCount, same as above
42     public static final int UNSPECIFIED_COUNT = -1;
43     // String to be logged as packageName when packageName is not relevant to recents event
44     public static final String UNSPECIFIED_PACKAGE_NAME = "_PACKAGE_NAME_NOT_LOGGED";
45     // Uid to be logged as uid when packageName is not relevant or cannot be resolved
46     public static final int UNSPECIFIED_PACKAGE_UID = -1;
47 
48     /**
49      * IntDef representing enum values of CarRecentsEventReported.event_type.
50      */
51     @IntDef({
52             RecentsEventType.UNSPECIFIED,
53             RecentsEventType.SESSION_STARTED,
54             RecentsEventType.SESSION_FINISHED,
55             RecentsEventType.APP_LAUNCHED,
56             RecentsEventType.APP_DISMISSED,
57             RecentsEventType.CLEAR_ALL,
58     })
59     public @interface RecentsEventType {
60         int UNSPECIFIED = CarLauncherStatsLog.CAR_RECENTS_EVENT_REPORTED__EVENT_TYPE__UNSPECIFIED;
61         int SESSION_STARTED =
62                 CarLauncherStatsLog.CAR_RECENTS_EVENT_REPORTED__EVENT_TYPE__SESSION_STARTED;
63         int SESSION_FINISHED =
64                 CarLauncherStatsLog.CAR_RECENTS_EVENT_REPORTED__EVENT_TYPE__SESSION_FINISHED;
65         int APP_LAUNCHED =
66                 CarLauncherStatsLog.CAR_RECENTS_EVENT_REPORTED__EVENT_TYPE__APP_LAUNCHED;
67         int APP_DISMISSED =
68                 CarLauncherStatsLog.CAR_RECENTS_EVENT_REPORTED__EVENT_TYPE__APP_DISMISSED;
69         int CLEAR_ALL =
70                 CarLauncherStatsLog.CAR_RECENTS_EVENT_REPORTED__EVENT_TYPE__CLEAR_ALL;
71     }
72 
73     /**
74      * Returns the current logging instance of RecentsStatsLogHelper to write this devices'
75      * CarLauncherStatsModule.
76      *
77      * @return the logging instance of RecentsStatsLogHelper.
78      */
getInstance()79     public static RecentsStatsLogHelper getInstance() {
80         if (sInstance == null) {
81             sInstance = new RecentsStatsLogHelper();
82         }
83         return sInstance;
84     }
85 
setPackageManager(PackageManager packageManager)86     public void setPackageManager(PackageManager packageManager) {
87         mPackageManager = packageManager;
88     }
89 
90     /**
91      * Logs that a new recents session has started. Additionally, resets measurements and IDs such
92      * as session ID and start time.
93      */
logSessionStarted()94     public void logSessionStarted() {
95         mSessionId = UUID.randomUUID().getMostSignificantBits();
96         mStartTimeMs = System.currentTimeMillis();
97         writeCarRecentsEventReported(RecentsEventType.SESSION_STARTED);
98     }
99 
100     /**
101      * Logs that an app launch interaction has occurred, along with the launched app's package name,
102      * the total open task count in recents, and the launched app's position.
103      */
logAppLaunched(int totalTaskCount, int eventTaskIndex, String packageName)104     public void logAppLaunched(int totalTaskCount, int eventTaskIndex, String packageName) {
105         writeCarRecentsEventReported(
106                 /* eventType */ RecentsEventType.APP_LAUNCHED,
107                 /* totalTaskCount */ totalTaskCount,
108                 /* eventTaskIndex */ eventTaskIndex,
109                 /* packageName */ packageName);
110     }
111 
112     /**
113      * Logs that an app dismiss interaction has occurred, along with the dismissed app's package
114      * name, the total open task count in recents, and the dimissed app's position.
115      */
logAppDismissed(int totalTaskCount, int eventTaskIndex, String packageName)116     public void logAppDismissed(int totalTaskCount, int eventTaskIndex, String packageName) {
117         writeCarRecentsEventReported(
118                 /* eventType */ RecentsStatsLogHelper.RecentsEventType.APP_DISMISSED,
119                 /* totalTaskCount */ totalTaskCount,
120                 /* eventTaskIndex */ eventTaskIndex,
121                 /* packageName */ packageName);
122     }
123 
124     /**
125      * Logs that clear all has been logged, along with the total open task count in recents.
126      */
logClearAll(int totalTaskCount)127     public void logClearAll(int totalTaskCount) {
128         writeCarRecentsEventReported(
129                 /* eventType */ RecentsStatsLogHelper.RecentsEventType.CLEAR_ALL,
130                 /* totalTaskCount */ totalTaskCount,
131                 /* eventTaskIndex */ UNSPECIFIED_INDEX,
132                 /* packageName */ UNSPECIFIED_PACKAGE_NAME);
133     }
134 
135     /**
136      * Logs that the current recents session has finished.
137      */
logSessionFinished()138     public void logSessionFinished() {
139         writeCarRecentsEventReported(RecentsEventType.SESSION_FINISHED);
140     }
141 
142     /**
143      * Writes to CarRecentsEvent atom with {@code eventType} as the only field, and log all other
144      * fields as unspecified.
145      *
146      * @param eventType one of {@link RecentsEventType}
147      */
writeCarRecentsEventReported(int eventType)148     private void writeCarRecentsEventReported(int eventType) {
149         writeCarRecentsEventReported(eventType, /* totalTaskCount */ UNSPECIFIED_COUNT,
150                 /* eventTaskIndex */ UNSPECIFIED_INDEX, /* packageName */ UNSPECIFIED_PACKAGE_NAME);
151     }
152 
153     /**
154      * Writes to CarRecentsEvent atom with all the optional fields filled.
155      *
156      * @param eventType one of {@link RecentsEventType}
157      * @param totalTaskCount the number of tasks displayed in recents screen
158      * @param eventTaskIndex the index of the recents task of this interaction
159      * @param packageName the package name of the app interacted with
160      */
writeCarRecentsEventReported(int eventType, int totalTaskCount, int eventTaskIndex, String packageName)161     private void writeCarRecentsEventReported(int eventType, int totalTaskCount,
162             int eventTaskIndex, String packageName) {
163         if (Build.isDebuggable()) {
164             Log.v(TAG, "writing CAR_RECENTS_EVENT_REPORTED with eventType=" + eventType
165                     + ", packageName=" + packageName);
166         }
167         writeCarRecentsEventReported(
168                 /* sessionId */ mSessionId,
169                 /* eventId */ UUID.randomUUID().getMostSignificantBits(),
170                 /* eventType */ eventType,
171                 /* totalTaskCount */ totalTaskCount,
172                 /* taskIndex */ eventTaskIndex,
173                 /* timeToEventMs */ System.currentTimeMillis() - mStartTimeMs,
174                 /* packageUid */ getPackageUid(packageName));
175     }
176 
getPackageUid(String packageName)177     private int getPackageUid(String packageName) {
178         if (packageName == null) {
179             return UNSPECIFIED_PACKAGE_UID;
180         }
181         try {
182             ApplicationInfo appInfo = mPackageManager.getApplicationInfo(packageName,
183                     PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA));
184             return appInfo.uid;
185         } catch (PackageManager.NameNotFoundException e) {
186             Log.d(TAG, "getPackageUid() on " + packageName + " was not found");
187         }
188         return UNSPECIFIED_PACKAGE_UID;
189     }
190 
writeCarRecentsEventReported(long sessionId, long eventId, int eventType, int totalTaskCount, int eventTaskIndex, long timeToEventMs, int packageUid)191     private void writeCarRecentsEventReported(long sessionId, long eventId, int eventType,
192             int totalTaskCount, int eventTaskIndex, long timeToEventMs, int packageUid) {
193         CarLauncherStatsLog.write(
194                 /* atomId */ CarLauncherStatsLog.CAR_RECENTS_EVENT_REPORTED,
195                 /* session_id */ sessionId,
196                 /* event_id */ eventId,
197                 /* event_type */ eventType,
198                 /* total_task_count */ totalTaskCount,
199                 /* event_task_index */ eventTaskIndex,
200                 /* long time_to_event_millis */ timeToEventMs,
201                 /* package_uid */ packageUid);
202     }
203 }
204