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.traceur; 18 19 import android.content.ContentResolver; 20 import android.content.Context; 21 import android.content.pm.LauncherApps; 22 import android.os.FileUtils; 23 import android.os.RemoteException; 24 import android.provider.Settings; 25 import android.system.Os; 26 import android.util.Log; 27 import android.view.WindowManagerGlobal; 28 29 import com.android.internal.inputmethod.ImeTracing; 30 31 import java.io.File; 32 import java.util.ArrayList; 33 import java.util.List; 34 35 /** 36 * Utility functions for managing Winscope traces that have not been migrated to perfetto yet. 37 */ 38 public class WinscopeUtils { 39 private static final String TAG = "Traceur"; 40 private static final String SETTINGS_VIEW_CAPTURE_ENABLED = "view_capture_enabled"; 41 private static final String VIEW_CAPTURE_FILE_SUFFIX = ".vc"; 42 private static final String WM_TRACE_DIR = "/data/misc/wmtrace/"; 43 private static final File[] CONSISTENTLY_NAMED_TRACE_FILES = { 44 new File(WM_TRACE_DIR + "wm_trace.winscope"), 45 new File(WM_TRACE_DIR + "wm_log.winscope"), 46 new File(WM_TRACE_DIR + "ime_trace_clients.winscope"), 47 new File(WM_TRACE_DIR + "ime_trace_managerservice.winscope"), 48 new File(WM_TRACE_DIR + "ime_trace_service.winscope"), 49 }; 50 traceStart(Context context, boolean isWinscopeTracingEnabled)51 public static void traceStart(Context context, boolean isWinscopeTracingEnabled) { 52 // Ensure all tracing sessions are initially stopped (buffers don't contain old data) 53 traceStop(context); 54 55 // Ensure there are no stale files on disk that could be 56 // picked up later by WinscopeUtils#traceDump() 57 deleteTempTraceFiles(); 58 59 if (isWinscopeTracingEnabled) { 60 setWindowTraceEnabled(true); 61 setImeTraceEnabled(true); 62 setViewCaptureEnabled(context, true); 63 } 64 } 65 traceStop(Context context)66 public static void traceStop(Context context) { 67 if (isWindowTraceEnabled()) { 68 setWindowTraceEnabled(false); 69 } 70 if (isImeTraceEnabled()) { 71 setImeTraceEnabled(false); 72 } 73 if (isViewCaptureEnabled(context.getContentResolver())) { 74 setViewCaptureEnabled(context, false); 75 } 76 } 77 78 /** 79 * This method is responsible for getting all trace files that will be useful for debugging so 80 * they can be inspected using go/winscope. In the /data/misc/wmtrace directory where these 81 * traces are stored, .vc (ViewCapture) files are named on a per-app basis, which means we have 82 * to collect X amount of them. In contrast, the winscope files are named the same everytime, 83 * and we simply have to check a predetermined set of file paths. 84 * 85 * @return File handles to all the useful trace files in the /data/misc/wmtrace directory. 86 */ getTraceFilesFromWmTraceDir()87 private static List<File> getTraceFilesFromWmTraceDir() { 88 List<File> traceFiles = new ArrayList<>(); 89 File[] wmTraceFiles = new File(WM_TRACE_DIR).listFiles(); 90 if (wmTraceFiles != null) { 91 for (File possibleViewCaptureFile : wmTraceFiles) { 92 if (possibleViewCaptureFile.isFile() 93 && possibleViewCaptureFile.getName().endsWith(VIEW_CAPTURE_FILE_SUFFIX)) { 94 traceFiles.add(possibleViewCaptureFile); 95 } 96 } 97 } 98 for (File possibleWinscopeTraceFile : CONSISTENTLY_NAMED_TRACE_FILES) { 99 if (possibleWinscopeTraceFile.exists()) { 100 traceFiles.add(possibleWinscopeTraceFile); 101 } 102 } 103 104 return traceFiles; 105 } 106 traceDump(Context context, String perfettoFilename)107 public static List<File> traceDump(Context context, String perfettoFilename) { 108 traceStop(context); 109 110 ArrayList<File> files = new ArrayList(); 111 112 for (File tempFile : getTraceFilesFromWmTraceDir()) { 113 try { 114 // Make a copy of the winscope traces to change SELinux file context from 115 // "wm_trace_data_file" to "trace_data_file", otherwise other apps might not be able 116 // to read the trace files shared by Traceur. 117 File outFile = getOutputFile(perfettoFilename, tempFile); 118 FileUtils.copy(tempFile, outFile); 119 outFile.setReadable(true, false); // (readable, ownerOnly) 120 outFile.setWritable(true, false); // (writable, ownerOnly) 121 files.add(outFile); 122 Log.v(TAG, "Copied winscope trace file " + outFile.getCanonicalPath()); 123 } catch (Exception e) { 124 throw new RuntimeException(e); 125 } 126 } 127 128 deleteTempTraceFiles(); 129 130 return files; 131 } 132 isWindowTraceEnabled()133 private static boolean isWindowTraceEnabled() { 134 try { 135 return WindowManagerGlobal.getWindowManagerService().isWindowTraceEnabled(); 136 } catch (RemoteException e) { 137 Log.e(TAG, 138 "Could not get window trace status, defaulting to false." + e); 139 } 140 return false; 141 } 142 setWindowTraceEnabled(boolean toEnable)143 private static void setWindowTraceEnabled(boolean toEnable) { 144 try { 145 if (toEnable) { 146 WindowManagerGlobal.getWindowManagerService().startWindowTrace(); 147 Log.v(TAG, "Started window manager tracing"); 148 } else { 149 WindowManagerGlobal.getWindowManagerService().stopWindowTrace(); 150 Log.v(TAG, "Stopped window manager tracing"); 151 } 152 } catch (RemoteException e) { 153 Log.e(TAG, "Could not set window trace status." + e); 154 } 155 } 156 isImeTraceEnabled()157 private static boolean isImeTraceEnabled() { 158 return ImeTracing.getInstance().isEnabled(); 159 } 160 setImeTraceEnabled(boolean toEnable)161 private static void setImeTraceEnabled(boolean toEnable) { 162 if (toEnable) { 163 ImeTracing.getInstance().startImeTrace(); 164 Log.v(TAG, "Started IME tracing"); 165 } else { 166 ImeTracing.getInstance().stopImeTrace(); 167 Log.v(TAG, "Stopped IME tracing"); 168 } 169 } 170 isViewCaptureEnabled(ContentResolver contentResolver)171 private static boolean isViewCaptureEnabled(ContentResolver contentResolver) { 172 return Settings.Global.getInt(contentResolver, SETTINGS_VIEW_CAPTURE_ENABLED, 0) != 0; 173 } 174 setViewCaptureEnabled(Context context, boolean toEnable)175 private static void setViewCaptureEnabled(Context context, boolean toEnable) { 176 ContentResolver contentResolver = context.getContentResolver(); 177 if (toEnable) { 178 Settings.Global.putInt(contentResolver, SETTINGS_VIEW_CAPTURE_ENABLED, 1); 179 Log.v(TAG, "Started view capture tracing"); 180 } else { 181 LauncherApps launcherApps = context.getSystemService(LauncherApps.class); 182 if (launcherApps != null) { 183 // TODO: b/335894686 Remove after ViewCapture is migrated to new Perfetto API 184 launcherApps.saveViewCaptureData(); 185 } 186 Settings.Global.putInt(contentResolver, SETTINGS_VIEW_CAPTURE_ENABLED, 0); 187 Log.v(TAG, "Stopped view capture tracing"); 188 } 189 } 190 191 // Create an output file that combines Perfetto and Winscope trace file names. E.g.: 192 // Perfetto trace filename: "trace-oriole-MAIN-2023-08-16-17-30-38.perfetto-trace" 193 // Winscope trace filename: "wm_trace.winscope" 194 // Output filename: "trace-oriole-MAIN-2023-08-16-17-30-38-wm_trace.winscope" getOutputFile(String perfettoFilename, File tempFile)195 private static File getOutputFile(String perfettoFilename, File tempFile) { 196 String perfettoFilenameWithoutExtension = 197 perfettoFilename.substring(0, perfettoFilename.lastIndexOf('.')); 198 String filename = perfettoFilenameWithoutExtension + "-" + tempFile.getName(); 199 return TraceUtils.getOutputFile(filename); 200 } 201 deleteTempTraceFiles()202 private static void deleteTempTraceFiles() { 203 for (File file : getTraceFilesFromWmTraceDir()) { 204 if (!file.exists()) { 205 continue; 206 } 207 208 try { 209 Os.unlink(file.getAbsolutePath()); 210 Log.v(TAG, "Deleted winscope trace file " + file); 211 } catch (Exception e){ 212 Log.e(TAG, "Failed to delete winscope trace file " + file, e); 213 } 214 } 215 } 216 } 217