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

Commit 88cefeb2 authored by Ioana Stefan's avatar Ioana Stefan Committed by Android (Google) Code Review
Browse files

Merge "Add proto-based client side dumping for IME tracing"

parents 3fb43953 cf9e5123
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