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

Commit 1125f7d1 authored by Felix Stern's avatar Felix Stern Committed by Android (Google) Code Review
Browse files

Merge "Make ImeTrackerService pending check asynchronous" into main

parents dfd73669 a484baa8
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -4256,19 +4256,19 @@ package android.view.inputmethod {

  public final class InputMethodManager {
    method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void addVirtualStylusIdForTestSession();
    method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void finishTrackingPendingImeVisibilityRequests();
    method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void finishTrackingPendingRequests();
    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> getEnabledInputMethodListAsUser(@NonNull android.os.UserHandle);
    method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(@NonNull String, boolean, @NonNull android.os.UserHandle);
    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 void hideSoftInputFromServerForTest();
    method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isCurrentRootView(@NonNull android.view.View);
    method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShown();
    method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public boolean isStylusHandwritingAvailableAsUser(@NonNull android.os.UserHandle);
    method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void setStylusWindowIdleTimeoutForTest(long);
    method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean shouldShowImeSwitcherButtonForTest();
    method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void waitUntilNoPendingRequests(long);
  }

  public final class InsertModeGesture extends android.view.inputmethod.CancellableHandwritingGesture implements android.os.Parcelable {
+12 −8
Original line number Diff line number Diff line
@@ -712,32 +712,36 @@ final class IInputMethodManagerGlobalInvoker {
        }
    }

    /** @see com.android.server.inputmethod.ImeTrackerService#hasPendingImeVisibilityRequests */
    /** @see com.android.server.inputmethod.ImeTrackerService#waitUntilNoPendingRequests */
    @AnyThread
    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
    static boolean hasPendingImeVisibilityRequests() {
    static void waitUntilNoPendingRequests(long timeoutMs) {
        final var service = getImeTrackerService();
        if (service == null) {
            return true;
            return;
        }
        try {
            return service.hasPendingImeVisibilityRequests();
            final var future = new AndroidFuture<Void>();
            service.waitUntilNoPendingRequests(future, timeoutMs);
            future.get(timeoutMs, TimeUnit.MILLISECONDS);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        } catch (Exception e) {
            throw ExceptionUtils.propagate(e);
        }
    }

    @AnyThread
    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
    static void finishTrackingPendingImeVisibilityRequests() {
    static void finishTrackingPendingRequests() {
        final var service = getImeTrackerService();
        if (service == null) {
            return;
        }
        try {
            final var completionSignal = new AndroidFuture<Void>();
            service.finishTrackingPendingImeVisibilityRequests(completionSignal);
            completionSignal.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
            final var future = new AndroidFuture<Void>();
            service.finishTrackingPendingRequests(future);
            future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        } catch (Exception e) {
+10 −6
Original line number Diff line number Diff line
@@ -4611,15 +4611,19 @@ public final class InputMethodManager {
    }

    /**
     * A test API for CTS to check whether there are any pending IME visibility requests.
     * A test API for CTS to wait until there are no more pending IME visibility requests, up to the
     * given timeout. This will throw a {@link java.util.concurrent.TimeoutException} if the wait
     * times out.
     *
     * @param timeoutMs the timeout in milliseconds.
     *
     * @return {@code true} iff there are pending IME visibility requests.
     * @hide
     */
    @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
    @TestApi
    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
    public boolean hasPendingImeVisibilityRequests() {
        return IInputMethodManagerGlobalInvoker.hasPendingImeVisibilityRequests();
    public void waitUntilNoPendingRequests(long timeoutMs) {
        IInputMethodManagerGlobalInvoker.waitUntilNoPendingRequests(timeoutMs);
    }

    /**
@@ -4631,8 +4635,8 @@ public final class InputMethodManager {
    @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
    @TestApi
    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
    public void finishTrackingPendingImeVisibilityRequests() {
        IInputMethodManagerGlobalInvoker.finishTrackingPendingImeVisibilityRequests();
    public void finishTrackingPendingRequests() {
        IInputMethodManagerGlobalInvoker.finishTrackingPendingRequests();
    }

    /**
+17 −16
Original line number Diff line number Diff line
@@ -24,7 +24,7 @@ import com.android.internal.infra.AndroidFuture;
 * Interface to the global IME tracker service, used by all client applications.
 * @hide
 */
interface IImeTracker {
oneway interface IImeTracker {

    /**
     * Called when an IME request is started.
@@ -37,7 +37,7 @@ interface IImeTracker {
     * @param fromUser whether this request was created directly from user interaction.
     * @param startTime the time in milliseconds when the request was started.
     */
    oneway void onStart(in ImeTracker.Token statsToken, int uid, int type, int origin, int reason,
    void onStart(in ImeTracker.Token statsToken, int uid, int type, int origin, int reason,
        boolean fromUser, long startTime);

    /**
@@ -46,7 +46,7 @@ interface IImeTracker {
     * @param statsToken the token tracking the request.
     * @param phase the new phase the request reached.
     */
    oneway void onProgress(in ImeTracker.Token statsToken, int phase);
    void onProgress(in ImeTracker.Token statsToken, int phase);

    /**
     * Called when the IME request fails.
@@ -54,7 +54,7 @@ interface IImeTracker {
     * @param statsToken the token tracking the request.
     * @param phase the phase the request failed at.
     */
    oneway void onFailed(in ImeTracker.Token statsToken, int phase);
    void onFailed(in ImeTracker.Token statsToken, int phase);

    /**
     * Called when the IME request is cancelled.
@@ -62,21 +62,21 @@ interface IImeTracker {
     * @param statsToken the token tracking the request.
     * @param phase the phase the request was cancelled at.
     */
    oneway void onCancelled(in ImeTracker.Token statsToken, int phase);
    void onCancelled(in ImeTracker.Token statsToken, int phase);

    /**
     * Called when the show IME request is successful.
     *
     * @param statsToken the token tracking the request.
     */
    oneway void onShown(in ImeTracker.Token statsToken);
    void onShown(in ImeTracker.Token statsToken);

    /**
     * Called when the hide IME request is successful.
     *
     * @param statsToken the token tracking the request.
     */
    oneway void onHidden(in ImeTracker.Token statsToken);
    void onHidden(in ImeTracker.Token statsToken);

    /**
     * Called when the user-controlled IME request was dispatched to the requesting app. The
@@ -84,27 +84,28 @@ interface IImeTracker {
     *
     * @param statsToken the token tracking the request.
     */
    oneway void onDispatched(in ImeTracker.Token statsToken);
    void onDispatched(in ImeTracker.Token statsToken);

    /**
     * Checks whether there are any pending IME visibility requests.
     * Waits until there are no more pending IME visibility requests, up to a given timeout, and
     * notifies the given future.
     *
     * @return {@code true} iff there are pending IME visibility requests.
     * @param future    the future to notify.
     * @param timeoutMs the timeout in milliseconds.
     */
    @EnforcePermission("TEST_INPUT_METHOD")
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
            + "android.Manifest.permission.TEST_INPUT_METHOD)")
    boolean hasPendingImeVisibilityRequests();
    void waitUntilNoPendingRequests(in AndroidFuture<void> future, long timeoutMs);

    /**
     * Finishes the tracking of any pending IME visibility requests. This won't stop the actual
     * requests, but allows resetting the state when starting up test runs.
     * Finishes the tracking of any pending IME visibility requests and notifies the given future.
     * This won't stop the actual requests, but allows resetting the state when starting test runs.
     *
     * @param completionSignal used to signal when the tracking has been finished.
     * @param future the future to notify.
     */
    @EnforcePermission("TEST_INPUT_METHOD")
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
            + "android.Manifest.permission.TEST_INPUT_METHOD)")
    oneway void finishTrackingPendingImeVisibilityRequests(
        in AndroidFuture completionSignal /* T=Void */);
    void finishTrackingPendingRequests(in AndroidFuture<void> future);
}
+29 −10
Original line number Diff line number Diff line
@@ -41,11 +41,13 @@ import java.io.PrintWriter;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;

/**
@@ -77,6 +79,10 @@ public final class ImeTrackerService extends IImeTracker.Stub {
    /** Recorder for metrics data from entries. */
    private final MetricsRecorder mMetricsRecorder;

    /** Collection of listeners waiting until there are no more pending requests. */
    @GuardedBy("mLock")
    private final ArrayList<AndroidFuture<Void>> mPendingRequestsListeners = new ArrayList<>();

    /** Interface for recording metrics data from entries. */
    @FunctionalInterface
    interface MetricsRecorder {
@@ -334,27 +340,32 @@ public final class ImeTrackerService extends IImeTracker.Stub {

    @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
    @Override
    public boolean hasPendingImeVisibilityRequests() {
        super.hasPendingImeVisibilityRequests_enforcePermission();
    public void waitUntilNoPendingRequests(@NonNull AndroidFuture<Void> future, long timeoutMs) {
        super.waitUntilNoPendingRequests_enforcePermission();
        synchronized (mLock) {
            return !mHistory.activeEntries().isEmpty();
            if (mHistory.activeEntries().isEmpty()) {
                future.complete(null);
            } else {
                mPendingRequestsListeners.add(future);
                mHandler.postDelayed(() -> {
                    future.completeExceptionally(new TimeoutException());
                    mPendingRequestsListeners.remove(future);
                }, future, timeoutMs);
            }
        }
    }

    @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
    @Override
    public void finishTrackingPendingImeVisibilityRequests(
            @NonNull AndroidFuture completionSignal /* T=Void */) {
        super.finishTrackingPendingImeVisibilityRequests_enforcePermission();
        @SuppressWarnings("unchecked") final AndroidFuture<Void> typedCompletionSignal =
                completionSignal;
    public void finishTrackingPendingRequests(@NonNull AndroidFuture<Void> future) {
        super.finishTrackingPendingRequests_enforcePermission();
        try {
            synchronized (mLock) {
                mHistory.activeEntries().clear();
            }
            typedCompletionSignal.complete(null);
            future.complete(null);
        } catch (Throwable e) {
            typedCompletionSignal.completeExceptionally(e);
            future.completeExceptionally(e);
        }
    }

@@ -371,6 +382,14 @@ public final class ImeTrackerService extends IImeTracker.Stub {
        mHandler.removeCallbacksAndMessages(entry /* token */);
        mHistory.complete(id, entry);
        mMetricsRecorder.record(entry);
        if (!mPendingRequestsListeners.isEmpty() && mHistory.mActiveEntries.isEmpty()) {
            for (int i = 0; i < mPendingRequestsListeners.size(); i++) {
                final var listener = mPendingRequestsListeners.get(i);
                listener.complete(null);
                mHandler.removeCallbacksAndMessages(listener);
            }
            mPendingRequestsListeners.clear();
        }
    }

    /**