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

Commit 425847b9 authored by Cosmin Băieș's avatar Cosmin Băieș Committed by Android (Google) Code Review
Browse files

Merge "Implement ImeTrackerService"

parents 4c6d93bc 89f36a77
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;
@@ -64,6 +65,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;
@@ -977,7 +979,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)
@@ -1054,7 +1063,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
@@ -1164,8 +1180,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());
@@ -1174,6 +1193,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
                    }
                }
            }
        }
        if (types == 0) {
            // nothing to animate.
            listener.onCancelled(null);
@@ -1182,6 +1202,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
            return;
        }
        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);

        cancelExistingControllers(types);
        if (DEBUG) Log.d(TAG, "controlAnimation types: " + types);
        mLastStartedAnimTypes |= types;
@@ -1189,7 +1211,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(
@@ -1288,7 +1310,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--) {
@@ -1301,7 +1326,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