1 /*
2  * Copyright (C) 2020 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.internal.inputmethod;
18 
19 import static android.os.Build.IS_USER;
20 
21 import android.annotation.Nullable;
22 import android.os.SystemClock;
23 import android.util.Log;
24 import android.util.proto.ProtoOutputStream;
25 import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceFileProto;
26 import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodManagerServiceTraceFileProto;
27 import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodServiceTraceFileProto;
28 import android.view.inputmethod.InputMethodManager;
29 
30 import com.android.internal.annotations.GuardedBy;
31 import com.android.internal.util.TraceBuffer;
32 
33 import java.io.File;
34 import java.io.IOException;
35 import java.io.PrintWriter;
36 import java.util.concurrent.TimeUnit;
37 
38 /**
39  * An implementation of {@link ImeTracing} for the system_server process.
40  */
41 class ImeTracingServerImpl extends ImeTracing {
42     private static final String TRACE_DIRNAME = "/data/misc/wmtrace/";
43     private static final String TRACE_FILENAME_CLIENTS = "ime_trace_clients.winscope";
44     private static final String TRACE_FILENAME_IMS = "ime_trace_service.winscope";
45     private static final String TRACE_FILENAME_IMMS = "ime_trace_managerservice.winscope";
46     private static final int BUFFER_CAPACITY = 4096 * 1024;
47 
48     // Needed for winscope to auto-detect the dump type. Explained further in
49     // core.proto.android.view.inputmethod.inputmethodeditortrace.proto.
50     // This magic number corresponds to InputMethodClientsTraceFileProto.
51     private static final long MAGIC_NUMBER_CLIENTS_VALUE =
52             ((long) InputMethodClientsTraceFileProto.MAGIC_NUMBER_H << 32)
53                 | InputMethodClientsTraceFileProto.MAGIC_NUMBER_L;
54     // This magic number corresponds to InputMethodServiceTraceFileProto.
55     private static final long MAGIC_NUMBER_IMS_VALUE =
56             ((long) InputMethodServiceTraceFileProto.MAGIC_NUMBER_H << 32)
57                 | InputMethodServiceTraceFileProto.MAGIC_NUMBER_L;
58     // This magic number corresponds to InputMethodManagerServiceTraceFileProto.
59     private static final long MAGIC_NUMBER_IMMS_VALUE =
60             ((long) InputMethodManagerServiceTraceFileProto.MAGIC_NUMBER_H << 32)
61                 | InputMethodManagerServiceTraceFileProto.MAGIC_NUMBER_L;
62 
63     private final TraceBuffer mBufferClients;
64     private final File mTraceFileClients;
65     private final TraceBuffer mBufferIms;
66     private final File mTraceFileIms;
67     private final TraceBuffer mBufferImms;
68     private final File mTraceFileImms;
69 
70     private final Object mEnabledLock = new Object();
71 
ImeTracingServerImpl()72     ImeTracingServerImpl() {
73         mBufferClients = new TraceBuffer<>(BUFFER_CAPACITY);
74         mTraceFileClients = new File(TRACE_DIRNAME + TRACE_FILENAME_CLIENTS);
75         mBufferIms = new TraceBuffer<>(BUFFER_CAPACITY);
76         mTraceFileIms = new File(TRACE_DIRNAME + TRACE_FILENAME_IMS);
77         mBufferImms = new TraceBuffer<>(BUFFER_CAPACITY);
78         mTraceFileImms = new File(TRACE_DIRNAME + TRACE_FILENAME_IMMS);
79     }
80 
81     /**
82      * The provided dump is added to the corresponding dump buffer:
83      * {@link ImeTracingServerImpl#mBufferClients} or {@link ImeTracingServerImpl#mBufferIms}.
84      *
85      * @param proto dump to be added to the buffer
86      */
87     @Override
addToBuffer(ProtoOutputStream proto, int source)88     public void addToBuffer(ProtoOutputStream proto, int source) {
89         if (isAvailable() && isEnabled()) {
90             switch (source) {
91                 case IME_TRACING_FROM_CLIENT:
92                     mBufferClients.add(proto);
93                     return;
94                 case IME_TRACING_FROM_IMS:
95                     mBufferIms.add(proto);
96                     return;
97                 case IME_TRACING_FROM_IMMS:
98                     mBufferImms.add(proto);
99                     return;
100                 default:
101                     // Source not recognised.
102                     Log.w(TAG, "Request to add to buffer, but source not recognised.");
103             }
104         }
105     }
106 
107     @Override
triggerClientDump(String where, InputMethodManager immInstance, @Nullable byte[] icProto)108     public void triggerClientDump(String where, InputMethodManager immInstance,
109             @Nullable byte[] icProto) {
110         // Intentionally left empty, this is implemented in ImeTracingClientImpl
111     }
112 
113     @Override
triggerServiceDump(String where, ServiceDumper dumper, @Nullable byte[] icProto)114     public void triggerServiceDump(String where, ServiceDumper dumper, @Nullable byte[] icProto) {
115         // Intentionally left empty, this is implemented in ImeTracingClientImpl
116     }
117 
118     @Override
triggerManagerServiceDump(String where)119     public void triggerManagerServiceDump(String where) {
120         if (!isEnabled() || !isAvailable()) {
121             return;
122         }
123 
124         synchronized (mDumpInProgressLock) {
125             if (mDumpInProgress) {
126                 return;
127             }
128             mDumpInProgress = true;
129         }
130 
131         try {
132             sendToService(null, IME_TRACING_FROM_IMMS, where);
133         } finally {
134             mDumpInProgress = false;
135         }
136     }
137 
writeTracesToFilesLocked()138     private void writeTracesToFilesLocked() {
139         try {
140             long timeOffsetNs =
141                     TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
142                     - SystemClock.elapsedRealtimeNanos();
143 
144             ProtoOutputStream clientsProto = new ProtoOutputStream();
145             clientsProto.write(InputMethodClientsTraceFileProto.MAGIC_NUMBER,
146                     MAGIC_NUMBER_CLIENTS_VALUE);
147             clientsProto.write(InputMethodClientsTraceFileProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS,
148                     timeOffsetNs);
149             mBufferClients.writeTraceToFile(mTraceFileClients, clientsProto);
150 
151             ProtoOutputStream imsProto = new ProtoOutputStream();
152             imsProto.write(InputMethodServiceTraceFileProto.MAGIC_NUMBER,
153                     MAGIC_NUMBER_IMS_VALUE);
154             imsProto.write(InputMethodServiceTraceFileProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS,
155                     timeOffsetNs);
156             mBufferIms.writeTraceToFile(mTraceFileIms, imsProto);
157 
158             ProtoOutputStream immsProto = new ProtoOutputStream();
159             immsProto.write(InputMethodManagerServiceTraceFileProto.MAGIC_NUMBER,
160                     MAGIC_NUMBER_IMMS_VALUE);
161             immsProto.write(
162                     InputMethodManagerServiceTraceFileProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS,
163                     timeOffsetNs);
164             mBufferImms.writeTraceToFile(mTraceFileImms, immsProto);
165 
166             resetBuffers();
167         } catch (IOException e) {
168             Log.e(TAG, "Unable to write buffer to file", e);
169         }
170     }
171 
172     @GuardedBy("mEnabledLock")
173     @Override
startTrace(@ullable PrintWriter pw)174     public void startTrace(@Nullable PrintWriter pw) {
175         if (IS_USER) {
176             Log.w(TAG, "Warn: Tracing is not supported on user builds.");
177             return;
178         }
179 
180         synchronized (mEnabledLock) {
181             if (isAvailable() && isEnabled()) {
182                 Log.w(TAG, "Warn: Tracing is already started.");
183                 return;
184             }
185 
186             logAndPrintln(pw, "Starting tracing in " + TRACE_DIRNAME + ": " + TRACE_FILENAME_CLIENTS
187                     + ", " + TRACE_FILENAME_IMS + ", " + TRACE_FILENAME_IMMS);
188             sEnabled = true;
189             resetBuffers();
190         }
191     }
192 
193     @Override
stopTrace(@ullable PrintWriter pw)194     public void stopTrace(@Nullable PrintWriter pw) {
195         if (IS_USER) {
196             Log.w(TAG, "Warn: Tracing is not supported on user builds.");
197             return;
198         }
199 
200         synchronized (mEnabledLock) {
201             if (!isAvailable() || !isEnabled()) {
202                 Log.w(TAG, "Warn: Tracing is not available or not started.");
203                 return;
204             }
205 
206             logAndPrintln(pw, "Stopping tracing and writing traces in " + TRACE_DIRNAME + ": "
207                     + TRACE_FILENAME_CLIENTS + ", " + TRACE_FILENAME_IMS + ", "
208                     + TRACE_FILENAME_IMMS);
209             sEnabled = false;
210             writeTracesToFilesLocked();
211         }
212     }
213 
214     /**
215      * {@inheritDoc}
216      */
217     @Override
saveForBugreport(@ullable PrintWriter pw)218     public void saveForBugreport(@Nullable PrintWriter pw) {
219         if (IS_USER) {
220             return;
221         }
222         synchronized (mEnabledLock) {
223             if (!isAvailable() || !isEnabled()) {
224                 return;
225             }
226             // Temporarily stop accepting logs from trace event providers.  There is a small chance
227             // that we may drop some trace events while writing the file, but we currently need to
228             // live with that.  Note that addToBuffer() also has a bug that it doesn't do
229             // read-acquire so flipping sEnabled here doesn't even guarantee that addToBuffer() will
230             // temporarily stop accepting incoming events...
231             // TODO(b/175761228): Implement atomic snapshot to avoid downtime.
232             // TODO(b/175761228): Fix synchronization around sEnabled.
233             sEnabled = false;
234             logAndPrintln(pw, "Writing traces in " + TRACE_DIRNAME + ": "
235                     + TRACE_FILENAME_CLIENTS + ", " + TRACE_FILENAME_IMS + ", "
236                     + TRACE_FILENAME_IMMS);
237             writeTracesToFilesLocked();
238             sEnabled = true;
239         }
240     }
241 
resetBuffers()242     private void resetBuffers() {
243         mBufferClients.resetBuffer();
244         mBufferIms.resetBuffer();
245         mBufferImms.resetBuffer();
246     }
247 }
248