Loading core/java/android/view/ViewRootImpl.java +126 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; /** Loading Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 = Loading Loading @@ -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); Loading Loading @@ -2089,6 +2196,10 @@ public final class ViewRootImpl implements ViewParent, if (!mIsInTraversal) { scheduleTraversals(); } if (!mInsetsController.getState().isSourceOrDefaultVisible(ID_IME, Type.ime())) { notifyLeaveTypingEvent(); } } @Override Loading Loading @@ -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. */ Loading Loading @@ -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 Loading core/java/android/view/ViewRootRefreshRateController.java 0 → 100644 +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); } } } } core/java/android/view/inputmethod/RemoteInputConnectionImpl.java +30 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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(); } } } /** Loading Loading @@ -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. Loading Loading @@ -628,6 +643,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { return; } ic.commitText(text, newCursorPosition); notifyTypingHint(true /* isTyping */); }); } Loading Loading @@ -783,6 +799,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { return; } ic.setComposingText(text, newCursorPosition); notifyTypingHint(true /* isTyping */); }); } Loading Loading @@ -910,6 +927,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { return; } ic.deleteSurroundingText(beforeLength, afterLength); notifyTypingHint(true /* isTyping */); }); } Loading Loading @@ -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); } } } core/res/res/values/config.xml +3 −0 Original line number Diff line number Diff line Loading @@ -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. --> Loading core/res/res/values/symbols.xml +2 −0 Original line number Diff line number Diff line Loading @@ -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 Loading
core/java/android/view/ViewRootImpl.java +126 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; /** Loading Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 = Loading Loading @@ -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); Loading Loading @@ -2089,6 +2196,10 @@ public final class ViewRootImpl implements ViewParent, if (!mIsInTraversal) { scheduleTraversals(); } if (!mInsetsController.getState().isSourceOrDefaultVisible(ID_IME, Type.ime())) { notifyLeaveTypingEvent(); } } @Override Loading Loading @@ -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. */ Loading Loading @@ -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 Loading
core/java/android/view/ViewRootRefreshRateController.java 0 → 100644 +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); } } } }
core/java/android/view/inputmethod/RemoteInputConnectionImpl.java +30 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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(); } } } /** Loading Loading @@ -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. Loading Loading @@ -628,6 +643,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { return; } ic.commitText(text, newCursorPosition); notifyTypingHint(true /* isTyping */); }); } Loading Loading @@ -783,6 +799,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { return; } ic.setComposingText(text, newCursorPosition); notifyTypingHint(true /* isTyping */); }); } Loading Loading @@ -910,6 +927,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { return; } ic.deleteSurroundingText(beforeLength, afterLength); notifyTypingHint(true /* isTyping */); }); } Loading Loading @@ -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); } } }
core/res/res/values/config.xml +3 −0 Original line number Diff line number Diff line Loading @@ -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. --> Loading
core/res/res/values/symbols.xml +2 −0 Original line number Diff line number Diff line Loading @@ -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