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