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 android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.RequiresPermission;
22 import android.app.ActivityThread;
23 import android.util.Log;
24 import android.util.proto.ProtoOutputStream;
25 import android.view.inputmethod.InputMethodManager;
26 import android.view.inputmethod.InputMethodManagerGlobal;
27 
28 import java.io.PrintWriter;
29 
30 /**
31  *
32  * An abstract class that declares the methods for ime trace related operations - enable trace,
33  * schedule trace and add new trace to buffer. Both the client and server side classes can use
34  * it by getting an implementation through {@link ImeTracing#getInstance()}.
35  */
36 public abstract class ImeTracing {
37 
38     static final String TAG = "imeTracing";
39     public static final String PROTO_ARG = "--proto-com-android-imetracing";
40 
41     /* Constants describing the component type that triggered a dump. */
42     public static final int IME_TRACING_FROM_CLIENT = 0;
43     public static final int IME_TRACING_FROM_IMS = 1;
44     public static final int IME_TRACING_FROM_IMMS = 2;
45 
46     private static ImeTracing sInstance;
47     static boolean sEnabled = false;
48 
49     private final boolean mIsAvailable = InputMethodManagerGlobal.isImeTraceAvailable();
50 
51     protected boolean mDumpInProgress;
52     protected final Object mDumpInProgressLock = new Object();
53 
54     /**
55      * Returns an instance of {@link ImeTracingServerImpl} when called from a server side class
56      * and an instance of {@link ImeTracingClientImpl} when called from a client side class.
57      * Useful to schedule a dump for next frame or save a dump when certain methods are called.
58      *
59      * @return Instance of one of the children classes of {@link ImeTracing}
60      */
getInstance()61     public static ImeTracing getInstance() {
62         if (sInstance == null) {
63             if (android.tracing.Flags.perfettoIme()) {
64                 sInstance = new ImeTracingPerfettoImpl();
65             } else if (isSystemProcess()) {
66                 sInstance = new ImeTracingServerImpl();
67             } else {
68                 sInstance = new ImeTracingClientImpl();
69             }
70         }
71         return sInstance;
72     }
73 
74     /**
75      * Transmits the information from client or InputMethodService side to the server, in order to
76      * be stored persistently to the current IME tracing dump.
77      *
78      * @param protoDump client or service side information to be stored by the server
79      * @param source where the information is coming from, refer to {@see #IME_TRACING_FROM_CLIENT}
80      * and {@see #IME_TRACING_FROM_IMS}
81      * @param where
82      */
sendToService(byte[] protoDump, int source, String where)83     protected void sendToService(byte[] protoDump, int source, String where) {
84         InputMethodManagerGlobal.startProtoDump(protoDump, source, where,
85                 e -> Log.e(TAG, "Exception while sending ime-related dump to server", e));
86     }
87 
88     /**
89      * Start IME trace.
90      */
91     @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
startImeTrace()92     public final void startImeTrace() {
93         InputMethodManagerGlobal.startImeTrace(e -> Log.e(TAG, "Could not start ime trace.", e));
94     }
95 
96     /**
97      * Stop IME trace.
98      */
99     @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
stopImeTrace()100     public final void stopImeTrace() {
101         InputMethodManagerGlobal.stopImeTrace(e -> Log.e(TAG, "Could not stop ime trace.", e));
102     }
103 
104     /**
105      * @param proto dump to be added to the buffer
106      */
addToBuffer(ProtoOutputStream proto, int source)107     public abstract void addToBuffer(ProtoOutputStream proto, int source);
108 
109     /**
110      * Starts a proto dump of the client side information.
111      *
112      * @param where Place where the trace was triggered.
113      * @param immInstance The {@link InputMethodManager} instance to dump.
114      * @param icProto {@link android.view.inputmethod.InputConnection} call data in proto format.
115      */
triggerClientDump(String where, InputMethodManager immInstance, @Nullable byte[] icProto)116     public abstract void triggerClientDump(String where, InputMethodManager immInstance,
117             @Nullable byte[] icProto);
118 
119     /**
120      * A delegate for {@link #triggerServiceDump(String, ServiceDumper, byte[])}.
121      */
122     @FunctionalInterface
123     public interface ServiceDumper {
124         /**
125          * Dumps internal data into {@link ProtoOutputStream}.
126          *
127          * @param proto {@link ProtoOutputStream} to be dumped into.
128          * @param icProto {@link android.view.inputmethod.InputConnection} call data in proto
129          *                format.
130          */
dumpToProto(ProtoOutputStream proto, @Nullable byte[] icProto)131         void dumpToProto(ProtoOutputStream proto, @Nullable byte[] icProto);
132     }
133 
134     /**
135      * Starts a proto dump of the currently connected InputMethodService information.
136      *
137      * @param where Place where the trace was triggered.
138      * @param dumper {@link ServiceDumper} to be used to dump
139      *               {@link android.inputmethodservice.InputMethodService}.
140      * @param icProto {@link android.view.inputmethod.InputConnection} call data in proto format.
141      */
triggerServiceDump(String where, ServiceDumper dumper, @Nullable byte[] icProto)142     public abstract void triggerServiceDump(String where, ServiceDumper dumper,
143             @Nullable byte[] icProto);
144 
145     /**
146      * Starts a proto dump of the InputMethodManagerService information.
147      *
148      * @param where Place where the trace was triggered.
149      */
triggerManagerServiceDump(String where, @NonNull ServiceDumper dumper)150     public abstract void triggerManagerServiceDump(String where, @NonNull ServiceDumper dumper);
151 
152     /**
153      * Being called while taking a bugreport so that tracing files can be included in the bugreport
154      * when the IME tracing is running.  Does nothing otherwise.
155      *
156      * @param pw Print writer
157      */
saveForBugreport(@ullable PrintWriter pw)158     public void saveForBugreport(@Nullable PrintWriter pw) {
159         // does nothing by default.
160     }
161 
162     /**
163      * Sets whether ime tracing is enabled.
164      *
165      * @param enabled Tells whether ime tracing should be enabled or disabled.
166      */
setEnabled(boolean enabled)167     public void setEnabled(boolean enabled) {
168         sEnabled = enabled;
169     }
170 
171     /**
172      * @return {@code true} if dumping is enabled, {@code false} otherwise.
173      */
isEnabled()174     public boolean isEnabled() {
175         return sEnabled;
176     }
177 
178     /**
179      * @return {@code true} if tracing is available, {@code false} otherwise.
180      */
isAvailable()181     public boolean isAvailable() {
182         return mIsAvailable;
183     }
184 
185     /**
186      * Starts a new IME trace if one is not already started.
187      *
188      * @param pw Print writer
189      */
startTrace(@ullable PrintWriter pw)190     public abstract void startTrace(@Nullable PrintWriter pw);
191 
192     /**
193      * Stops the IME trace if one was previously started and writes the current buffers to disk.
194      *
195      * @param pw Print writer
196      */
stopTrace(@ullable PrintWriter pw)197     public abstract void stopTrace(@Nullable PrintWriter pw);
198 
isSystemProcess()199     private static boolean isSystemProcess() {
200         return ActivityThread.isSystem();
201     }
202 
logAndPrintln(@ullable PrintWriter pw, String msg)203     protected void logAndPrintln(@Nullable PrintWriter pw, String msg) {
204         Log.i(TAG, msg);
205         if (pw != null) {
206             pw.println(msg);
207             pw.flush();
208         }
209     }
210 
211 }
212