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