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

Commit 89f36a77 authored by Cosmin Băieș's avatar Cosmin Băieș
Browse files

Implement ImeTrackerService

Bug: 261716110
Test: atest android.view.inputmethod.cts.InputMethodStatsTest
Change-Id: I432bab2de58a9df2c421bb00946ab211de445660
parent ebf76779
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -3256,6 +3256,7 @@ package android.view.inputmethod {
    method public int getDisplayId();
    method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
    method public boolean hasActiveInputConnection(@Nullable android.view.View);
    method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean hasPendingImeVisibilityRequests();
    method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShown();
    method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void setStylusWindowIdleTimeoutForTest(long);
    field public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // 0xcc1a029L
+32 −7
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.view;

import static android.os.Trace.TRACE_TAG_VIEW;
import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER;
import static android.view.ImeInsetsSourceConsumerProto.IS_HIDE_ANIMATION_RUNNING;
import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL;
@@ -23,11 +24,15 @@ import static android.view.ImeInsetsSourceConsumerProto.IS_SHOW_REQUESTED_DURING

import android.annotation.Nullable;
import android.os.IBinder;
import android.os.Process;
import android.os.Trace;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl.Transaction;
import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodManager;

import com.android.internal.inputmethod.ImeTracing;
import com.android.internal.inputmethod.SoftInputShowHideReason;

import java.util.function.Supplier;

@@ -48,8 +53,8 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
    /**
     * Tracks whether {@link WindowInsetsController#show(int)} or
     * {@link InputMethodManager#showSoftInput(View, int)} is called during IME hide animation.
     * If it was called, we should not call {@link InputMethodManager#notifyImeHidden(IBinder)},
     * because the IME is being shown.
     * If it was called, we should not call {@link InputMethodManager#notifyImeHidden(IBinder,
     * ImeTracker.Token)}, because the IME is being shown.
     */
    private boolean mIsShowRequestedDuringHideAnimation;

@@ -76,7 +81,7 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
                // Remove IME surface as IME has finished hide animation, if there is no pending
                // show request.
                if (!mIsShowRequestedDuringHideAnimation) {
                    notifyHidden();
                    notifyHidden(null /* statsToken */);
                    removeSurface();
                }
            }
@@ -120,7 +125,8 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
     * @return @see {@link android.view.InsetsSourceConsumer.ShowResult}.
     */
    @Override
    public @ShowResult int requestShow(boolean fromIme) {
    @ShowResult
    public int requestShow(boolean fromIme, @Nullable ImeTracker.Token statsToken) {
        if (fromIme) {
            ImeTracing.getInstance().triggerClientDump(
                    "ImeInsetsSourceConsumer#requestShow",
@@ -129,6 +135,9 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {

        // TODO: ResultReceiver for IME.
        // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag.
        ImeTracker.get().onProgress(statsToken,
                ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW);

        if (getControl() == null) {
            // If control is null, schedule to show IME when control is available.
            mIsRequestedVisibleAwaitingControl = true;
@@ -140,16 +149,32 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
            return ShowResult.SHOW_IMMEDIATELY;
        }

        return getImm().requestImeShow(mController.getHost().getWindowToken())
        return getImm().requestImeShow(mController.getHost().getWindowToken(), statsToken)
                ? ShowResult.IME_SHOW_DELAYED : ShowResult.IME_SHOW_FAILED;
    }

    /**
     * Notify {@link com.android.server.inputmethod.InputMethodManagerService} that
     * IME insets are hidden.
     *
     * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
     */
    private void notifyHidden() {
        getImm().notifyImeHidden(mController.getHost().getWindowToken());
    private void notifyHidden(@Nullable ImeTracker.Token statsToken) {
        // Create a new stats token to track the hide request when:
        //  - we do not already have one, or
        //  - we do already have one, but we have control and use the passed in token
        //      for the insets animation already.
        if (statsToken == null || getControl() != null) {
            statsToken = ImeTracker.get().onRequestHide(null /* component */, Process.myUid(),
                    ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
                    SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
        }

        ImeTracker.get().onProgress(statsToken,
                ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN);

        getImm().notifyImeHidden(mController.getHost().getWindowToken(), statsToken);
        Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0);
    }

    @Override
+37 −12
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import android.graphics.Rect;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
import android.os.Trace;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -65,6 +66,7 @@ import android.view.inputmethod.InputMethodManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.internal.inputmethod.ImeTracing;
import com.android.internal.inputmethod.SoftInputShowHideReason;

import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -978,7 +980,14 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation

    @Override
    public void show(@InsetsType int types) {
        show(types, false /* fromIme */, null /* statsToken */);
        ImeTracker.Token statsToken = null;
        if ((types & ime()) != 0) {
            statsToken = ImeTracker.get().onRequestShow(null /* component */,
                    Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
                    SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API);
        }

        show(types, false /* fromIme */, statsToken);
    }

    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@@ -1055,7 +1064,14 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation

    @Override
    public void hide(@InsetsType int types) {
        hide(types, false /* fromIme */, null /* statsToken */);
        ImeTracker.Token statsToken = null;
        if ((types & ime()) != 0) {
            statsToken = ImeTracker.get().onRequestHide(null /* component */,
                    Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
                    SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
        }

        hide(types, false /* fromIme */, statsToken);
    }

    @VisibleForTesting
@@ -1165,8 +1181,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
            if (DEBUG) Log.d(TAG, "user animation disabled types: " + disabledTypes);
            types &= ~mDisabledUserAnimationInsetsTypes;

            if (fromIme && (disabledTypes & ime()) != 0
                    && !mState.getSource(mImeSourceConsumer.getId()).isVisible()) {
            if ((disabledTypes & ime()) != 0) {
                ImeTracker.get().onFailed(statsToken,
                        ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);

                if (fromIme && !mState.getSource(mImeSourceConsumer.getId()).isVisible()) {
                    // We've requested IMM to show IME, but the IME is not controllable. We need to
                    // cancel the request.
                    setRequestedVisibleTypes(0 /* visibleTypes */, ime());
@@ -1175,12 +1194,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
                    }
                }
            }
        }
        if (types == 0) {
            // nothing to animate.
            listener.onCancelled(null);
            if (DEBUG) Log.d(TAG, "no types to animate in controlAnimationUnchecked");
            return;
        }
        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);

        cancelExistingControllers(types);
        if (DEBUG) Log.d(TAG, "controlAnimation types: " + types);
        mLastStartedAnimTypes |= types;
@@ -1188,7 +1210,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        final SparseArray<InsetsSourceControl> controls = new SparseArray<>();

        Pair<Integer, Boolean> typesReadyPair = collectSourceControls(
                fromIme, types, controls, animationType);
                fromIme, types, controls, animationType, statsToken);
        int typesReady = typesReadyPair.first;
        boolean imeReady = typesReadyPair.second;
        if (DEBUG) Log.d(TAG, String.format(
@@ -1279,7 +1301,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
     * @return Pair of (types ready to animate, IME ready to animate).
     */
    private Pair<Integer, Boolean> collectSourceControls(boolean fromIme, @InsetsType int types,
            SparseArray<InsetsSourceControl> controls, @AnimationType int animationType) {
            SparseArray<InsetsSourceControl> controls, @AnimationType int animationType,
            @Nullable ImeTracker.Token statsToken) {
        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS);

        int typesReady = 0;
        boolean imeReady = true;
        for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
@@ -1292,7 +1317,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
            boolean canRun = true;
            if (show) {
                // Show request
                switch(consumer.requestShow(fromIme)) {
                switch(consumer.requestShow(fromIme, statsToken)) {
                    case ShowResult.SHOW_IMMEDIATELY:
                        break;
                    case ShowResult.IME_SHOW_DELAYED:
+20 −7
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import android.util.Log;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets.Type.InsetsType;
import android.view.inputmethod.ImeTracker;

import com.android.internal.annotations.VisibleForTesting;

@@ -50,10 +51,14 @@ import java.util.function.Supplier;
public class InsetsSourceConsumer {

    @Retention(RetentionPolicy.SOURCE)
    @IntDef(value = {ShowResult.SHOW_IMMEDIATELY, ShowResult.IME_SHOW_DELAYED, ShowResult.IME_SHOW_FAILED})
    @IntDef(value = {
            ShowResult.SHOW_IMMEDIATELY,
            ShowResult.IME_SHOW_DELAYED,
            ShowResult.IME_SHOW_FAILED
    })
    @interface ShowResult {
        /**
         * Window type is ready to be shown, will be shown immidiately.
         * Window type is ready to be shown, will be shown immediately.
         */
        int SHOW_IMMEDIATELY = 0;
        /**
@@ -71,11 +76,13 @@ public class InsetsSourceConsumer {
    protected final InsetsController mController;
    protected final InsetsState mState;
    private int mId;
    private final @InsetsType int mType;
    @InsetsType
    private final int mType;

    private static final String TAG = "InsetsSourceConsumer";
    private final Supplier<Transaction> mTransactionSupplier;
    private @Nullable InsetsSourceControl mSourceControl;
    @Nullable
    private InsetsSourceControl mSourceControl;
    private boolean mHasWindowFocus;

    /**
@@ -180,7 +187,7 @@ public class InsetsSourceConsumer {
        return true;
    }

    @VisibleForTesting
    @VisibleForTesting(visibility = PACKAGE)
    public InsetsSourceControl getControl() {
        return mSourceControl;
    }
@@ -280,10 +287,16 @@ public class InsetsSourceConsumer {
     * @param fromController {@code true} if request is coming from controller.
     *                       (e.g. in IME case, controller is
     *                       {@link android.inputmethodservice.InputMethodService}).
     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
     *
     * @implNote The {@code statsToken} is ignored here, and only handled in
     * {@link ImeInsetsSourceConsumer} for IME animations only.
     *
     * @return @see {@link ShowResult}.
     */
    @VisibleForTesting
    public @ShowResult int requestShow(boolean fromController) {
    @VisibleForTesting(visibility = PACKAGE)
    @ShowResult
    public int requestShow(boolean fromController, @Nullable ImeTracker.Token statsToken) {
        return ShowResult.SHOW_IMMEDIATELY;
    }

+137 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import com.android.internal.inputmethod.InputBindResult;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.inputmethod.StartInputFlags;
import com.android.internal.inputmethod.StartInputReason;
import com.android.internal.view.IImeTracker;
import com.android.internal.view.IInputMethodManager;

import java.util.ArrayList;
@@ -61,6 +62,9 @@ final class IInputMethodManagerGlobalInvoker {
    @Nullable
    private static volatile IInputMethodManager sServiceCache = null;

    @Nullable
    private static volatile IImeTracker sTrackerServiceCache = null;

    /**
     * @return {@code true} if {@link IInputMethodManager} is available.
     */
@@ -527,4 +531,137 @@ final class IInputMethodManagerGlobalInvoker {
            throw e.rethrowFromSystemServer();
        }
    }

    @AnyThread
    @Nullable
    static IBinder onRequestShow(int uid, @ImeTracker.Origin int origin,
            @SoftInputShowHideReason int reason) {
        final IImeTracker service = getImeTrackerService();
        if (service == null) {
            return null;
        }
        try {
            return service.onRequestShow(uid, origin, reason);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @AnyThread
    @Nullable
    static IBinder onRequestHide(int uid, @ImeTracker.Origin int origin,
            @SoftInputShowHideReason int reason) {
        final IImeTracker service = getImeTrackerService();
        if (service == null) {
            return null;
        }
        try {
            return service.onRequestHide(uid, origin, reason);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @AnyThread
    static void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
        final IImeTracker service = getImeTrackerService();
        if (service == null) {
            return;
        }
        try {
            service.onProgress(statsToken, phase);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @AnyThread
    static void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
        final IImeTracker service = getImeTrackerService();
        if (service == null) {
            return;
        }
        try {
            service.onFailed(statsToken, phase);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @AnyThread
    static void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
        final IImeTracker service = getImeTrackerService();
        if (service == null) {
            return;
        }
        try {
            service.onCancelled(statsToken, phase);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @AnyThread
    static void onShown(@NonNull IBinder statsToken) {
        final IImeTracker service = getImeTrackerService();
        if (service == null) {
            return;
        }
        try {
            service.onShown(statsToken);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @AnyThread
    static void onHidden(@NonNull IBinder statsToken) {
        final IImeTracker service = getImeTrackerService();
        if (service == null) {
            return;
        }
        try {
            service.onHidden(statsToken);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @AnyThread
    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
    static boolean hasPendingImeVisibilityRequests() {
        final var service = getImeTrackerService();
        if (service == null) {
            return true;
        }
        try {
            return service.hasPendingImeVisibilityRequests();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @AnyThread
    @Nullable
    private static IImeTracker getImeTrackerService() {
        var trackerService = sTrackerServiceCache;
        if (trackerService == null) {
            final var service = getService();
            if (service == null) {
                return null;
            }

            try {
                trackerService = service.getImeTrackerService();
                if (trackerService == null) {
                    return null;
                }

                sTrackerServiceCache = trackerService;
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        return trackerService;
    }
}
Loading