Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cf9e5123 authored by Anmol Gupta's avatar Anmol Gupta Committed by Ioana Stefan
Browse files

Add proto-based client side dumping for IME tracing

This CL implements a mechanism to dump IME related client states into
a proto file which can later be imported to winscope to allow easy
debugging. A new abstract class ImeTracing.java declares the methods
related to scheduling, collecting and dumping logs. Two child class
implement these methods for server and client separately.

The Design Doc for the IME tracing project is: go/ime-tracing

Bug: 154348613
Test: start trace by calling "adb shell ime tracing start"
      end trace by calling "adb shell ime tracing stop"
      pull trace using "adb pull /data/misc/wmtrace/ime_trace.pb ime_trace.pb"
Change-Id: Ia89f11d5ef8a220ea7746191b18769cea5a8359d
parent 7cbdbffe
Loading
Loading
Loading
Loading
+114 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.util.imetracing;

import android.app.ActivityThread;
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.ShellCommand;
import android.util.Log;
import android.util.proto.ProtoOutputStream;

import com.android.internal.view.IInputMethodManager;

/**
 *
 * An abstract class that declares the methods for ime trace related operations - enable trace,
 * schedule trace and add new trace to buffer. Both the client and server side classes can use
 * it by getting an implementation through {@link ImeTracing#getInstance()}.
 *
 * @hide
 */
public abstract class ImeTracing {

    static final String TAG = "imeTracing";
    public static final String PROTO_ARG = "--proto-com-android-imetracing";

    private static ImeTracing sInstance;
    static boolean sEnabled = false;
    IInputMethodManager mService;

    ImeTracing() throws ServiceNotFoundException {
        mService = IInputMethodManager.Stub.asInterface(
                ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE));
    }

    /**
     * Returns an instance of {@link ImeTracingServerImpl} when called from a server side class
     * and an instance of {@link ImeTracingClientImpl} when called from a client side class.
     * Useful to schedule a dump for next frame or save a dump when certain methods are called.
     *
     * @return Instance of one of the children classes of {@link ImeTracing}
     */
    public static ImeTracing getInstance() {
        if (sInstance == null) {
            try {
                sInstance = isSystemProcess()
                        ? new ImeTracingServerImpl() : new ImeTracingClientImpl();
            } catch (RemoteException | ServiceNotFoundException e) {
                Log.e(TAG, "Exception while creating ImeTracing instance", e);
            }
        }
        return sInstance;
    }

    /**
     * Sends request to start proto dump to {@link ImeTracingServerImpl} when called from a
     * server process and to {@link ImeTracingClientImpl} when called from a client process.
     */
    public abstract void triggerDump();

    /**
     * @param proto dump to be added to the buffer
     */
    public abstract void addToBuffer(ProtoOutputStream proto);

    /**
     * @param shell The shell command to process
     * @return {@code 0} if the command was successfully processed, {@code -1} otherwise
     */
    public abstract int onShellCommand(ShellCommand shell);

    /**
     * Sets whether ime tracing is enabled.
     *
     * @param enabled Tells whether ime tracing should be enabled or disabled.
     */
    public void setEnabled(boolean enabled) {
        sEnabled = enabled;
    }

    /**
     * @return {@code true} if dumping is enabled, {@code false} otherwise.
     */
    public boolean isEnabled() {
        return sEnabled;
    }

    /**
     * @return {@code true} if tracing is available, {@code false} otherwise.
     */
    public boolean isAvailable() {
        return mService != null;
    }

    private static boolean isSystemProcess() {
        return ActivityThread.isSystem();
    }
}
+71 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.util.imetracing;

import android.os.RemoteException;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.ShellCommand;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import android.view.inputmethod.InputMethodManager;

/**
 * @hide
 */
class ImeTracingClientImpl extends ImeTracing {

    private boolean mDumpInProgress;
    private final Object mDumpInProgressLock = new Object();

    ImeTracingClientImpl() throws ServiceNotFoundException, RemoteException {
        sEnabled = mService.isImeTraceEnabled();
    }

    @Override
    public void addToBuffer(ProtoOutputStream proto) {
    }

    @Override
    public int onShellCommand(ShellCommand shell) {
        return -1;
    }

    @Override
    public void triggerDump() {
        if (isAvailable() && isEnabled()) {
            boolean doDump = false;
            synchronized (mDumpInProgressLock) {
                if (!mDumpInProgress) {
                    mDumpInProgress = true;
                    doDump = true;
                }
            }

            if (doDump) {
                try {
                    ProtoOutputStream proto = new ProtoOutputStream();
                    InputMethodManager.dumpProto(proto);
                    mService.startProtoDump(proto.getBytes());
                } catch (RemoteException e) {
                    Log.e(TAG, "Exception while sending ime-related client dump to server", e);
                } finally {
                    mDumpInProgress = false;
                }
            }
        }
    }
}
+154 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.util.imetracing;

import static android.os.Build.IS_USER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorTraceFileProto.MAGIC_NUMBER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorTraceFileProto.MAGIC_NUMBER_H;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorTraceFileProto.MAGIC_NUMBER_L;

import android.os.RemoteException;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.ShellCommand;
import android.util.Log;
import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.TraceBuffer;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @hide
 */
class ImeTracingServerImpl extends ImeTracing {
    private static final String TRACE_FILENAME = "/data/misc/wmtrace/ime_trace.pb";
    private static final int BUFFER_CAPACITY = 4096 * 1024;

    // Needed for winscope to auto-detect the dump type. Explained further in
    // core.proto.android.view.inputmethod.inputmethodeditortrace.proto
    private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;

    private final TraceBuffer mBuffer;
    private final File mTraceFile;
    private final Object mEnabledLock = new Object();

    ImeTracingServerImpl() throws ServiceNotFoundException {
        mBuffer = new TraceBuffer<>(BUFFER_CAPACITY);
        mTraceFile = new File(TRACE_FILENAME);
    }

    /**
     * The provided dump is added to the current dump buffer {@link ImeTracingServerImpl#mBuffer}.
     *
     * @param proto dump to be added to the buffer
     */
    @Override
    public void addToBuffer(ProtoOutputStream proto) {
        if (isAvailable() && isEnabled()) {
            mBuffer.add(proto);
        }
    }

    /**
     * Responds to a shell command of the format "adb shell cmd input_method ime tracing <command>"
     *
     * @param shell The shell command to process
     * @return {@code 0} if the command was valid and successfully processed, {@code -1} otherwise
     */
    @Override
    public int onShellCommand(ShellCommand shell) {
        PrintWriter pw = shell.getOutPrintWriter();
        String cmd = shell.getNextArgRequired();
        switch (cmd) {
            case "start":
                startTrace(pw);
                return 0;
            case "stop":
                stopTrace(pw);
                return 0;
            default:
                pw.println("Unknown command: " + cmd);
                pw.println("Input method trace options:");
                pw.println("  start: Start tracing");
                pw.println("  stop: Stop tracing");
                return -1;
        }
    }

    @Override
    public void triggerDump() {
        if (isAvailable() && isEnabled()) {
            try {
                mService.startProtoDump(null);
            } catch (RemoteException e) {
                Log.e(TAG, "Exception while triggering proto dump", e);
            }
        }
    }

    private void writeTraceToFileLocked() {
        try {
            ProtoOutputStream proto = new ProtoOutputStream();
            proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
            mBuffer.writeTraceToFile(mTraceFile, proto);
        } catch (IOException e) {
            Log.e(TAG, "Unable to write buffer to file", e);
        }
    }

    @GuardedBy("mEnabledLock")
    private void startTrace(PrintWriter pw) {
        if (IS_USER) {
            Log.w(TAG, "Warn: Tracing is not supported on user builds.");
            return;
        }

        synchronized (mEnabledLock) {
            if (isAvailable() && isEnabled()) {
                Log.w(TAG, "Warn: Tracing is already started.");
                return;
            }

            pw.println("Starting tracing to " + mTraceFile + ".");
            sEnabled = true;
            mBuffer.resetBuffer();
        }
    }

    @GuardedBy("mEnabledLock")
    private void stopTrace(PrintWriter pw) {
        if (IS_USER) {
            Log.w(TAG, "Warn: Tracing is not supported on user builds.");
            return;
        }

        synchronized (mEnabledLock) {
            if (!isAvailable() || !isEnabled()) {
                Log.w(TAG, "Warn: Tracing is not available or not started.");
                return;
            }

            pw.println("Stopping tracing and writing traces to " + mTraceFile + ".");
            sEnabled = false;
            writeTraceToFileLocked();
            mBuffer.resetBuffer();
        }
    }
}
+15 −0
Original line number Diff line number Diff line
@@ -16,16 +16,23 @@

package android.view;

import static android.view.ImeFocusControllerProto.HAS_IME_FOCUS;
import static android.view.ImeFocusControllerProto.NEXT_SERVED_VIEW;
import static android.view.ImeFocusControllerProto.SERVED_VIEW;

import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.UiThread;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import android.view.inputmethod.InputMethodManager;

import com.android.internal.inputmethod.InputMethodDebug;
import com.android.internal.inputmethod.StartInputFlags;
import com.android.internal.inputmethod.StartInputReason;

import java.util.Objects;

/**
 * Responsible for IME focus handling inside {@link ViewRootImpl}.
 * @hide
@@ -280,4 +287,12 @@ public final class ImeFocusController {
    boolean hasImeFocus() {
        return mHasImeFocus;
    }

    void dumpDebug(ProtoOutputStream proto, long fieldId) {
        final long token = proto.start(fieldId);
        proto.write(HAS_IME_FOCUS, mHasImeFocus);
        proto.write(SERVED_VIEW, Objects.toString(mServedView));
        proto.write(NEXT_SERVED_VIEW, Objects.toString(mNextServedView));
        proto.end(token);
    }
}
+15 −1
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package android.view;

import static android.view.ImeInsetsSourceConsumerProto.FOCUSED_EDITOR;
import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER;
import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL;
import static android.view.InsetsController.AnimationType;
import static android.view.InsetsState.ITYPE_IME;

@@ -24,6 +27,7 @@ import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
import android.os.Parcel;
import android.text.TextUtils;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl.Transaction;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
@@ -111,7 +115,6 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
    public @ShowResult int requestShow(boolean fromIme) {
        // TODO: ResultReceiver for IME.
        // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag.

        if (getControl() == null) {
            // If control is null, schedule to show IME when control is available.
            mIsRequestedVisibleAwaitingControl = true;
@@ -227,6 +230,17 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
        return Arrays.equals(parcel1.createByteArray(), parcel2.createByteArray());
    }

    @Override
    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
        final long token = proto.start(fieldId);
        super.dumpDebug(proto, INSETS_SOURCE_CONSUMER);
        if (mFocusedEditor != null) {
            mFocusedEditor.dumpDebug(proto, FOCUSED_EDITOR);
        }
        proto.write(IS_REQUESTED_VISIBLE_AWAITING_CONTROL, mIsRequestedVisibleAwaitingControl);
        proto.end(token);
    }

    private InputMethodManager getImm() {
        return mController.getHost().getInputMethodManager();
    }
Loading