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

Commit d9b817da authored by Ming-Shin Lu's avatar Ming-Shin Lu Committed by Android (Google) Code Review
Browse files

Merge "Introduce ViewRootRefreshRateController" into udc-qpr-dev

parents 7f052ad3 27b5f63f
Loading
Loading
Loading
Loading
+126 −0
Original line number Diff line number Diff line
@@ -50,6 +50,8 @@ import static android.view.ViewRootImplProto.VISIBLE_RECT;
import static android.view.ViewRootImplProto.WIDTH;
import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES;
import static android.view.ViewRootImplProto.WIN_FRAME;
import static android.view.ViewRootRefreshRateController.RefreshRatePref.LOWER;
import static android.view.ViewRootRefreshRateController.RefreshRatePref.RESTORE;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
@@ -96,6 +98,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
import android.annotation.UiContext;
import android.annotation.UiThread;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ICompatCameraControlCallback;
@@ -240,6 +243,7 @@ import java.util.OptionalInt;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;

/**
@@ -422,6 +426,74 @@ public final class ViewRootImpl implements ViewParent,
                ICompatCameraControlCallback callback);
    }

    /**
     * Used to notify if the user is typing or not.
     * @hide
     */
    public interface TypingHintNotifier {
        /**
         * Called when the typing hint is changed. This would be invoked by the
         * {@link android.view.inputmethod.RemoteInputConnectionImpl}
         * to hint if the user is typing when the it is {@link #isActive() active}.
         *
         * This can be only happened on the UI thread. The behavior won't be guaranteed if
         * invoking this on a non-UI thread.
         *
         * @param isTyping {@code true} if the user is typing.
         */
        @UiThread
        void onTypingHintChanged(boolean isTyping);

        /**
         * Indicates whether the notifier is currently in active state or not.
         *
         * @see #deactivate()
         */
        boolean isActive();

        /**
         * Deactivate the notifier when no longer in use. Mostly invoked when finishing the typing.
         */
        void deactivate();
    }

    /**
     * The {@link TypingHintNotifier} implementation used to handle
     * the refresh rate preference when the typing state is changed.
     */
    private static class TypingHintNotifierImpl implements TypingHintNotifier {

        private final AtomicReference<TypingHintNotifier> mActiveNotifier;

        @NonNull
        private final ViewRootRefreshRateController mController;

        TypingHintNotifierImpl(@NonNull AtomicReference<TypingHintNotifier> notifier,
                @NonNull ViewRootRefreshRateController controller) {
            mController = controller;
            mActiveNotifier = notifier;
        }

        @Override
        public void onTypingHintChanged(boolean isTyping) {
            if (!isActive()) {
                // No-op when the listener was deactivated.
                return;
            }
            mController.updateRefreshRatePreference(isTyping ? LOWER : RESTORE);
        }

        @Override
        public boolean isActive() {
            return mActiveNotifier.get() == this;
        }

        @Override
        public void deactivate() {
            mActiveNotifier.compareAndSet(this, null);
        }
    }

    /**
     * Callback used to notify corresponding activity about camera compat control changes, override
     * configuration change and make sure that all resources are set correctly before updating the
@@ -429,6 +501,32 @@ public final class ViewRootImpl implements ViewParent,
     */
    private ActivityConfigCallback mActivityConfigCallback;

    /**
     * The current active {@link TypingHintNotifier} to handle
     * typing hint change operations.
     */
    private final AtomicReference<TypingHintNotifier> mActiveTypingHintNotifier =
            new AtomicReference<>(null);

    /**
     * Create a {@link TypingHintNotifier} if the client support variable
     * refresh rate for typing. The {@link TypingHintNotifier} is created
     * and mapped to a new active input connection each time.
     *
     * @hide
     */
    @Nullable
    public TypingHintNotifier createTypingHintNotifierIfSupported() {
        if (mRefreshRateController == null) {
            return null;
        }
        final TypingHintNotifier newNotifier = new TypingHintNotifierImpl(mActiveTypingHintNotifier,
                mRefreshRateController);
        mActiveTypingHintNotifier.set(newNotifier);

        return newNotifier;
    }

    /**
     * Used when configuration change first updates the config of corresponding activity.
     * In that case we receive a call back from {@link ActivityThread} and this flag is used to
@@ -858,6 +956,8 @@ public final class ViewRootImpl implements ViewParent,
    private final InsetsController mInsetsController;
    private final ImeFocusController mImeFocusController;

    private ViewRootRefreshRateController mRefreshRateController;

    private boolean mIsSurfaceOpaque;

    private final BackgroundBlurDrawable.Aggregator mBlurRegionAggregator =
@@ -1048,6 +1148,13 @@ public final class ViewRootImpl implements ViewParent,
                mViewConfiguration,
                mContext.getSystemService(InputMethodManager.class));

        // Whether the variable refresh rate for typing is supported.
        boolean useVariableRefreshRateWhenTyping = context.getResources().getBoolean(
                R.bool.config_variableRefreshRateTypingSupported);
        if (useVariableRefreshRateWhenTyping) {
            mRefreshRateController = new ViewRootRefreshRateController(this);
        }

        mViewBoundsSandboxingEnabled = getViewBoundsSandboxingEnabled();
        mIsStylusPointerIconEnabled =
                InputSettings.isStylusPointerIconEnabled(mContext);
@@ -2089,6 +2196,10 @@ public final class ViewRootImpl implements ViewParent,
        if (!mIsInTraversal) {
            scheduleTraversals();
        }

        if (!mInsetsController.getState().isSourceOrDefaultVisible(ID_IME, Type.ime())) {
            notifyLeaveTypingEvent();
        }
    }

    @Override
@@ -6849,6 +6960,17 @@ public final class ViewRootImpl implements ViewParent,
        }
    }

    /**
     * Restores the refresh rate after leaving typing, the leaving typing cases like
     * the IME insets is invisible or the user interacts the screen outside keyboard.
     */
    @UiThread
    private void notifyLeaveTypingEvent() {
        if (mRefreshRateController != null && mActiveTypingHintNotifier.get() != null) {
            mRefreshRateController.updateRefreshRatePreference(RESTORE);
        }
    }

    /**
     * Delivers post-ime input events to the view hierarchy.
     */
@@ -7066,6 +7188,10 @@ public final class ViewRootImpl implements ViewParent,
                mLastClickToolType = event.getToolType(event.getActionIndex());
            }

            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
                notifyLeaveTypingEvent();
            }

            mAttachInfo.mUnbufferedDispatchRequested = false;
            mAttachInfo.mHandlingPointerEvent = true;
            // If the event was fully handled by the handwriting initiator, then don't dispatch it
+220 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.view;

import static android.os.Trace.TRACE_TAG_VIEW;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.os.Trace;
import android.util.Log;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Controller to request refresh rate preference operations to the {@link ViewRootImpl}.
 *
 * @hide
 */
public class ViewRootRefreshRateController {

    private static final String TAG = "VRRefreshRateController";

    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    private static final float TARGET_REFRESH_RATE_UPPER_BOUND = 60f;

    @NonNull
    private final ViewRootImpl mViewRootImpl;

    private final RefreshRateParams mRateParams;

    private final boolean mHasPreferredRefreshRate;

    private int mRefreshRatePref = RefreshRatePref.NONE;

    private boolean mMaxRefreshRateOverride = false;

    @IntDef(value = {
            RefreshRatePref.NONE,
            RefreshRatePref.LOWER,
            RefreshRatePref.RESTORE,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface RefreshRatePref {
        /**
         * Indicates that no refresh rate preference.
         */
        int NONE = 0;

        /**
         * Indicates that apply the lower refresh rate.
         */
        int LOWER = 1;

        /**
         * Indicates that restore to previous refresh rate.
         */
        int RESTORE = 2;
    }

    public ViewRootRefreshRateController(@NonNull ViewRootImpl viewRoot) {
        mViewRootImpl = viewRoot;
        mRateParams = new RefreshRateParams(getLowerSupportedRefreshRate());
        mHasPreferredRefreshRate = hasPreferredRefreshRate();
        if (mHasPreferredRefreshRate && DEBUG) {
            Log.d(TAG, "App has preferred refresh rate. name:" + viewRoot);
        }
    }

    /**
     * Updates the preference to {@link ViewRootRefreshRateController#mRefreshRatePref},
     * and check if it's needed to update the preferred refresh rate on demand. Like if the
     * user is typing, try to apply the {@link RefreshRateParams#mTargetRefreshRate}.
     *
     * @param refreshRatePref to indicate the refresh rate preference
     */
    public void updateRefreshRatePreference(@RefreshRatePref int refreshRatePref) {
        mRefreshRatePref = refreshRatePref;
        doRefreshRateCheck();
    }

    private void doRefreshRateCheck() {
        if (mRefreshRatePref == RefreshRatePref.NONE) {
            return;
        }
        if (mHasPreferredRefreshRate) {
            return;
        }
        if (DEBUG) {
            Log.d(TAG, "mMaxRefreshRateOverride:" + mMaxRefreshRateOverride
                    + ", mRefreshRatePref:" + refreshRatePrefToString(mRefreshRatePref));
        }

        switch (mRefreshRatePref) {
            case RefreshRatePref.LOWER :
                if (!mMaxRefreshRateOverride) {
                    // Save previous preferred rate before update
                    mRateParams.savePreviousRefreshRateParams(mViewRootImpl.mWindowAttributes);
                    updateMaxRefreshRate();
                } else if (mViewRootImpl.mDisplay.getRefreshRate()
                        > mRateParams.mTargetRefreshRate) {
                    // Boosted, try to update again.
                    updateMaxRefreshRate();
                }
                break;
            case RefreshRatePref.RESTORE :
                resetRefreshRate();
                break;
            default :
                throw new RuntimeException("Unexpected value: " + mRefreshRatePref);
        }
    }

    private void updateMaxRefreshRate() {
        Trace.traceBegin(TRACE_TAG_VIEW, "VRRC.updateMaxRefreshRate");
        WindowManager.LayoutParams params = mViewRootImpl.mWindowAttributes;
        params.preferredMaxDisplayRefreshRate = mRateParams.mTargetRefreshRate;
        mViewRootImpl.setLayoutParams(params, false);
        mMaxRefreshRateOverride = true;
        Trace.instant(TRACE_TAG_VIEW, "VRRC update preferredMax="
                + mRateParams.mTargetRefreshRate);
        Trace.traceEnd(TRACE_TAG_VIEW);
        if (DEBUG) {
            Log.d(TAG, "update max refresh rate to: " + params.preferredMaxDisplayRefreshRate);
        }
    }

    private void resetRefreshRate() {
        if (!mMaxRefreshRateOverride) {
            return;
        }
        Trace.traceBegin(TRACE_TAG_VIEW, "VRRC.resetRefreshRate");
        WindowManager.LayoutParams params = mViewRootImpl.mWindowAttributes;
        params.preferredMaxDisplayRefreshRate = mRateParams.mPreviousPreferredMaxRefreshRate;
        mViewRootImpl.setLayoutParams(params, false);
        mMaxRefreshRateOverride = false;
        Trace.instant(TRACE_TAG_VIEW, "VRRC restore previous="
                + mRateParams.mPreviousPreferredMaxRefreshRate);
        Trace.traceEnd(TRACE_TAG_VIEW);
        if (DEBUG) {
            Log.d(TAG, "reset max refresh rate to: " + params.preferredMaxDisplayRefreshRate);
        }
    }

    private boolean hasPreferredRefreshRate() {
        WindowManager.LayoutParams params = mViewRootImpl.mWindowAttributes;
        return params.preferredRefreshRate > 0
                || params.preferredMaxDisplayRefreshRate > 0
                || params.preferredMinDisplayRefreshRate > 0
                || params.preferredDisplayModeId > 0;
    }

    private float getLowerSupportedRefreshRate() {
        final Display display = mViewRootImpl.mDisplay;
        final Display.Mode defaultMode = display.getDefaultMode();
        float targetRefreshRate = defaultMode.getRefreshRate();
        for (Display.Mode mode : display.getSupportedModes()) {
            if (mode.getRefreshRate() < targetRefreshRate) {
                targetRefreshRate = mode.getRefreshRate();
            }
        }
        if (targetRefreshRate < TARGET_REFRESH_RATE_UPPER_BOUND) {
            targetRefreshRate = TARGET_REFRESH_RATE_UPPER_BOUND;
        }
        return targetRefreshRate;
    }

    private static String refreshRatePrefToString(@RefreshRatePref int pref) {
        switch (pref) {
            case RefreshRatePref.NONE:
                return "NONE";
            case RefreshRatePref.LOWER:
                return "LOWER";
            case RefreshRatePref.RESTORE:
                return "RESTORE";
            default:
                return "Unknown pref=" + pref;
        }
    }

    /**
     * A class for recording refresh rate parameters of the target view, including the target
     * refresh rate we want to apply when entering particular states, and the original preferred
     * refresh rate for restoring when leaving the state.
     */
    private static class RefreshRateParams {
        float mTargetRefreshRate;

        float mPreviousPreferredMaxRefreshRate = 0;

        RefreshRateParams(float targetRefreshRate) {
            mTargetRefreshRate = targetRefreshRate;
            if (DEBUG) {
                Log.d(TAG, "The target rate: " + targetRefreshRate);
            }
        }
        void savePreviousRefreshRateParams(WindowManager.LayoutParams param) {
            mPreviousPreferredMaxRefreshRate = param.preferredMaxDisplayRefreshRate;
            if (DEBUG) {
                Log.d(TAG, "Save previous params, preferred: " + param.preferredRefreshRate
                        + ", Max: " + param.preferredMaxDisplayRefreshRate);
            }
        }
    }
}
+30 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -182,6 +183,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {

    private CancellationSignalBeamer.Receiver mBeamer;

    private ViewRootImpl.TypingHintNotifier mTypingHintNotifier;

    RemoteInputConnectionImpl(@NonNull Looper looper,
            @NonNull InputConnection inputConnection,
            @NonNull InputMethodManager inputMethodManager, @Nullable View servedView) {
@@ -190,6 +193,12 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
        mH = new Handler(mLooper);
        mParentInputMethodManager = inputMethodManager;
        mServedView = new WeakReference<>(servedView);
        if (servedView != null) {
            final ViewRootImpl viewRoot = servedView.getViewRootImpl();
            if (viewRoot != null) {
                mTypingHintNotifier = viewRoot.createTypingHintNotifierIfSupported();
            }
        }
    }

    /**
@@ -364,6 +373,12 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
            return;
        }
        dispatch(() -> {
            notifyTypingHint(false /* isTyping */);
            // Deactivate the notifier when finishing typing.
            if (mTypingHintNotifier != null) {
                mTypingHintNotifier.deactivate();
            }

            // Note that we do not need to worry about race condition here, because 1) mFinished is
            // updated only inside this block, and 2) the code here is running on a Handler hence we
            // assume multiple closeConnection() tasks will not be handled at the same time.
@@ -628,6 +643,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
                return;
            }
            ic.commitText(text, newCursorPosition);
            notifyTypingHint(true /* isTyping */);
        });
    }

@@ -783,6 +799,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
                return;
            }
            ic.setComposingText(text, newCursorPosition);
            notifyTypingHint(true /* isTyping */);
        });
    }

@@ -910,6 +927,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
                return;
            }
            ic.deleteSurroundingText(beforeLength, afterLength);
            notifyTypingHint(true /* isTyping */);
        });
    }

@@ -1473,4 +1491,16 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
    private static boolean useImeTracing() {
        return ImeTracing.getInstance().isEnabled();
    }

    /**
     * Dispatch the typing hint to {@link ViewRootImpl.TypingHintNotifier}.
     * The input connection indicates that the user is typing when {@link #commitText} or
     * {@link #setComposingText)} and the user finish typing when {@link #deactivate()}.
     */
    @UiThread
    private void notifyTypingHint(boolean isTyping) {
        if (mTypingHintNotifier != null) {
            mTypingHintNotifier.onTypingHintChanged(isTyping);
        }
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -6562,6 +6562,9 @@
         device. -->
    <bool name="config_enableAppCloningBuildingBlocks">true</bool>

    <!-- Whether the variable refresh rate when typing feature is enabled for the device. -->
    <bool name="config_variableRefreshRateTypingSupported">false</bool>

    <!-- Enables or disables support for repair mode. The feature creates a secure
         environment to protect the user's privacy when the device is being repaired.
         Off by default, since OEMs may have had a similar feature on their devices. -->
+2 −0
Original line number Diff line number Diff line
@@ -4942,6 +4942,8 @@

  <java-symbol type="bool" name="config_repairModeSupported" />

  <java-symbol type="bool" name="config_variableRefreshRateTypingSupported" />

  <java-symbol type="string" name="config_devicePolicyManagementUpdater" />

  <java-symbol type="string" name="config_deviceSpecificDeviceStatePolicyProvider" />
Loading