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.server.companion.virtual;
18 
19 import android.content.Context;
20 import android.content.pm.PackageManager;
21 import android.os.Binder;
22 import android.util.SparseArray;
23 
24 import java.io.PrintWriter;
25 import java.time.Instant;
26 import java.time.ZoneId;
27 import java.time.format.DateTimeFormatter;
28 import java.util.ArrayDeque;
29 
30 final class VirtualDeviceLog {
31     public static int TYPE_CREATED = 0x0;
32     public static int TYPE_CLOSED = 0x1;
33 
34     private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern(
35             "MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
36     private static final int MAX_ENTRIES = 16;
37 
38     private final Context mContext;
39     private final ArrayDeque<LogEntry> mLogEntries = new ArrayDeque<>();
40 
VirtualDeviceLog(Context context)41     VirtualDeviceLog(Context context) {
42         mContext = context;
43     }
44 
logCreated(int deviceId, int ownerUid)45     void logCreated(int deviceId, int ownerUid) {
46         final long token = Binder.clearCallingIdentity();
47         try {
48             if (!Flags.dumpHistory()) {
49                 return;
50             }
51             addEntry(new LogEntry(TYPE_CREATED, deviceId, System.currentTimeMillis(), ownerUid));
52         } finally {
53             Binder.restoreCallingIdentity(token);
54         }
55     }
56 
logClosed(int deviceId, int ownerUid)57     void logClosed(int deviceId, int ownerUid) {
58         final long token = Binder.clearCallingIdentity();
59         try {
60             if (!Flags.dumpHistory()) {
61                 return;
62             }
63             addEntry(new LogEntry(TYPE_CLOSED, deviceId, System.currentTimeMillis(), ownerUid));
64         } finally {
65             Binder.restoreCallingIdentity(token);
66         }
67     }
68 
addEntry(LogEntry entry)69     private void addEntry(LogEntry entry) {
70         mLogEntries.push(entry);
71         if (mLogEntries.size() > MAX_ENTRIES) {
72             mLogEntries.removeLast();
73         }
74     }
75 
dump(PrintWriter pw)76     void dump(PrintWriter pw) {
77         final long token = Binder.clearCallingIdentity();
78         try {
79             if (!Flags.dumpHistory()) {
80                 return;
81             }
82             pw.println("VirtualDevice Log:");
83             UidToPackageNameCache packageNameCache = new UidToPackageNameCache(
84                     mContext.getPackageManager());
85             for (LogEntry logEntry : mLogEntries) {
86                 logEntry.dump(pw, "  ", packageNameCache);
87 
88             }
89         } finally {
90             Binder.restoreCallingIdentity(token);
91         }
92     }
93 
94     static class LogEntry {
95         private final int mType;
96         private final int mDeviceId;
97         private final long mTimestamp;
98         private final int mOwnerUid;
99 
LogEntry(int type, int deviceId, long timestamp, int ownerUid)100         LogEntry(int type, int deviceId, long timestamp, int ownerUid) {
101             this.mType = type;
102             this.mDeviceId = deviceId;
103             this.mTimestamp = timestamp;
104             this.mOwnerUid = ownerUid;
105         }
106 
dump(PrintWriter pw, String prefix, UidToPackageNameCache packageNameCache)107         void dump(PrintWriter pw, String prefix, UidToPackageNameCache packageNameCache) {
108             StringBuilder sb = new StringBuilder(prefix);
109             sb.append(DATE_FORMAT.format(Instant.ofEpochMilli(mTimestamp)));
110             sb.append(" - ");
111             sb.append(mType == TYPE_CREATED ? "START" : "CLOSE");
112             sb.append(" Device ID: ");
113             sb.append(mDeviceId);
114             sb.append(" ");
115             sb.append(mOwnerUid);
116             sb.append(" (");
117             sb.append(packageNameCache.getPackageName(mOwnerUid));
118             sb.append(")");
119             pw.println(sb);
120         }
121     }
122 
123     private static class UidToPackageNameCache {
124         private final PackageManager mPackageManager;
125         private final SparseArray<String> mUidToPackagesCache = new SparseArray<>();
126 
UidToPackageNameCache(PackageManager packageManager)127         public UidToPackageNameCache(PackageManager packageManager) {
128             mPackageManager = packageManager;
129         }
130 
getPackageName(int ownerUid)131         String getPackageName(int ownerUid) {
132             String[] packages;
133             if (mUidToPackagesCache.contains(ownerUid)) {
134                 return mUidToPackagesCache.get(ownerUid);
135             } else {
136                 packages = mPackageManager.getPackagesForUid(ownerUid);
137                 String packageName = "";
138                 if (packages != null && packages.length > 0) {
139                     packageName = packages[0];
140                     if (packages.length > 1) {
141                         StringBuilder sb = new StringBuilder();
142                         sb.append(packageName)
143                                 .append(",...");
144                         packageName = sb.toString();
145                     }
146                 }
147                 mUidToPackagesCache.put(ownerUid, packageName);
148                 return packageName;
149             }
150         }
151     }
152 }
153