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