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 android.util.imetracing;
18 
19 import static android.os.Build.IS_USER;
20 
21 import android.annotation.Nullable;
22 import android.inputmethodservice.AbstractInputMethodService;
23 import android.os.RemoteException;
24 import android.os.ServiceManager.ServiceNotFoundException;
25 import android.util.Log;
26 import android.util.proto.ProtoOutputStream;
27 import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceFileProto;
28 import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodManagerServiceTraceFileProto;
29 import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodServiceTraceFileProto;
30 import android.view.inputmethod.InputMethodManager;
31 
32 import com.android.internal.annotations.GuardedBy;
33 import com.android.internal.util.TraceBuffer;
34 
35 import java.io.File;
36 import java.io.IOException;
37 import java.io.PrintWriter;
38 
39 /**
40  * @hide
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() throws ServiceNotFoundException {
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, ProtoOutputStream icProto)109     public void triggerClientDump(String where, InputMethodManager immInstance,
110             ProtoOutputStream icProto) {
111         // Intentionally left empty, this is implemented in ImeTracingClientImpl
112     }
113 
114     @Override
triggerServiceDump(String where, AbstractInputMethodService service, ProtoOutputStream icProto)115     public void triggerServiceDump(String where, AbstractInputMethodService service,
116             ProtoOutputStream icProto) {
117         // Intentionally left empty, this is implemented in ImeTracingClientImpl
118     }
119 
120     @Override
triggerManagerServiceDump(String where)121     public void triggerManagerServiceDump(String where) {
122         if (!isEnabled() || !isAvailable()) {
123             return;
124         }
125 
126         synchronized (mDumpInProgressLock) {
127             if (mDumpInProgress) {
128                 return;
129             }
130             mDumpInProgress = true;
131         }
132 
133         try {
134             sendToService(null, IME_TRACING_FROM_IMMS, where);
135         } catch (RemoteException e) {
136             Log.e(TAG, "Exception while sending ime-related manager service dump to server", e);
137         } finally {
138             mDumpInProgress = false;
139         }
140     }
141 
writeTracesToFilesLocked()142     private void writeTracesToFilesLocked() {
143         try {
144             ProtoOutputStream clientsProto = new ProtoOutputStream();
145             clientsProto.write(InputMethodClientsTraceFileProto.MAGIC_NUMBER,
146                     MAGIC_NUMBER_CLIENTS_VALUE);
147             mBufferClients.writeTraceToFile(mTraceFileClients, clientsProto);
148 
149             ProtoOutputStream imsProto = new ProtoOutputStream();
150             imsProto.write(InputMethodServiceTraceFileProto.MAGIC_NUMBER, MAGIC_NUMBER_IMS_VALUE);
151             mBufferIms.writeTraceToFile(mTraceFileIms, imsProto);
152 
153             ProtoOutputStream immsProto = new ProtoOutputStream();
154             immsProto.write(InputMethodManagerServiceTraceFileProto.MAGIC_NUMBER,
155                     MAGIC_NUMBER_IMMS_VALUE);
156             mBufferImms.writeTraceToFile(mTraceFileImms, immsProto);
157 
158             resetBuffers();
159         } catch (IOException e) {
160             Log.e(TAG, "Unable to write buffer to file", e);
161         }
162     }
163 
164     @GuardedBy("mEnabledLock")
165     @Override
startTrace(@ullable PrintWriter pw)166     public void startTrace(@Nullable PrintWriter pw) {
167         if (IS_USER) {
168             Log.w(TAG, "Warn: Tracing is not supported on user builds.");
169             return;
170         }
171 
172         synchronized (mEnabledLock) {
173             if (isAvailable() && isEnabled()) {
174                 Log.w(TAG, "Warn: Tracing is already started.");
175                 return;
176             }
177 
178             logAndPrintln(pw, "Starting tracing in " + TRACE_DIRNAME + ": " + TRACE_FILENAME_CLIENTS
179                     + ", " + TRACE_FILENAME_IMS + ", " + TRACE_FILENAME_IMMS);
180             sEnabled = true;
181             resetBuffers();
182         }
183     }
184 
185     @Override
stopTrace(@ullable PrintWriter pw)186     public void stopTrace(@Nullable PrintWriter pw) {
187         if (IS_USER) {
188             Log.w(TAG, "Warn: Tracing is not supported on user builds.");
189             return;
190         }
191 
192         synchronized (mEnabledLock) {
193             if (!isAvailable() || !isEnabled()) {
194                 Log.w(TAG, "Warn: Tracing is not available or not started.");
195                 return;
196             }
197 
198             logAndPrintln(pw, "Stopping tracing and writing traces in " + TRACE_DIRNAME + ": "
199                     + TRACE_FILENAME_CLIENTS + ", " + TRACE_FILENAME_IMS + ", "
200                     + TRACE_FILENAME_IMMS);
201             sEnabled = false;
202             writeTracesToFilesLocked();
203         }
204     }
205 
206     /**
207      * {@inheritDoc}
208      */
209     @Override
saveForBugreport(@ullable PrintWriter pw)210     public void saveForBugreport(@Nullable PrintWriter pw) {
211         if (IS_USER) {
212             return;
213         }
214         synchronized (mEnabledLock) {
215             if (!isAvailable() || !isEnabled()) {
216                 return;
217             }
218             // Temporarily stop accepting logs from trace event providers.  There is a small chance
219             // that we may drop some trace events while writing the file, but we currently need to
220             // live with that.  Note that addToBuffer() also has a bug that it doesn't do
221             // read-acquire so flipping sEnabled here doesn't even guarantee that addToBuffer() will
222             // temporarily stop accepting incoming events...
223             // TODO(b/175761228): Implement atomic snapshot to avoid downtime.
224             // TODO(b/175761228): Fix synchronization around sEnabled.
225             sEnabled = false;
226             logAndPrintln(pw, "Writing traces in " + TRACE_DIRNAME + ": "
227                     + TRACE_FILENAME_CLIENTS + ", " + TRACE_FILENAME_IMS + ", "
228                     + TRACE_FILENAME_IMMS);
229             writeTracesToFilesLocked();
230             sEnabled = true;
231         }
232     }
233 
resetBuffers()234     private void resetBuffers() {
235         mBufferClients.resetBuffer();
236         mBufferIms.resetBuffer();
237         mBufferImms.resetBuffer();
238     }
239 }
240