| /* |
| * Copyright (C) 2019 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.view.ImeFocusControllerProto.HAS_IME_FOCUS; |
| |
| import android.annotation.AnyThread; |
| import android.annotation.NonNull; |
| import android.annotation.UiThread; |
| import android.util.Log; |
| import android.util.proto.ProtoOutputStream; |
| import android.view.inputmethod.InputMethodManager; |
| |
| import com.android.internal.inputmethod.InputMethodDebug; |
| |
| /** |
| * Responsible for IME focus handling inside {@link ViewRootImpl}. |
| * @hide |
| */ |
| public final class ImeFocusController { |
| private static final boolean DEBUG = false; |
| private static final String TAG = "ImeFocusController"; |
| |
| private final ViewRootImpl mViewRootImpl; |
| private boolean mHasImeFocus = false; |
| private InputMethodManagerDelegate mDelegate; |
| |
| @UiThread |
| ImeFocusController(@NonNull ViewRootImpl viewRootImpl) { |
| mViewRootImpl = viewRootImpl; |
| } |
| |
| @NonNull |
| private InputMethodManagerDelegate getImmDelegate() { |
| if (mDelegate == null) { |
| mDelegate = mViewRootImpl.mContext.getSystemService( |
| InputMethodManager.class).getDelegate(); |
| } |
| return mDelegate; |
| } |
| |
| /** Called when the view root is moved to a different display. */ |
| @UiThread |
| void onMovedToDisplay() { |
| // InputMethodManager managed its instances for different displays. So if the associated |
| // display is changed, the delegate also needs to be refreshed (by getImmDelegate). |
| // See the comment in {@link android.app.SystemServiceRegistry} for InputMethodManager |
| // and {@link android.view.inputmethod.InputMethodManager#forContext}. |
| mDelegate = null; |
| } |
| |
| @UiThread |
| void onTraversal(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) { |
| final boolean hasImeFocus = WindowManager.LayoutParams.mayUseInputMethod( |
| windowAttribute.flags); |
| if (!hasWindowFocus || isInLocalFocusMode(windowAttribute)) { |
| return; |
| } |
| if (hasImeFocus == mHasImeFocus) { |
| return; |
| } |
| mHasImeFocus = hasImeFocus; |
| if (mHasImeFocus) { |
| getImmDelegate().onPreWindowGainedFocus(mViewRootImpl); |
| final View focusedView = mViewRootImpl.mView.findFocus(); |
| View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView; |
| getImmDelegate().onPostWindowGainedFocus(viewForWindowFocus, windowAttribute); |
| } |
| } |
| |
| @UiThread |
| void onPreWindowFocus(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) { |
| mHasImeFocus = WindowManager.LayoutParams.mayUseInputMethod(windowAttribute.flags); |
| if (!hasWindowFocus || !mHasImeFocus || isInLocalFocusMode(windowAttribute)) { |
| if (!hasWindowFocus) { |
| getImmDelegate().onWindowLostFocus(mViewRootImpl); |
| } |
| } else { |
| getImmDelegate().onPreWindowGainedFocus(mViewRootImpl); |
| } |
| } |
| |
| @UiThread |
| void onPostWindowFocus(View focusedView, boolean hasWindowFocus, |
| WindowManager.LayoutParams windowAttribute) { |
| if (!hasWindowFocus || !mHasImeFocus || isInLocalFocusMode(windowAttribute)) { |
| return; |
| } |
| View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView; |
| if (DEBUG) { |
| Log.v(TAG, "onWindowFocus: " + viewForWindowFocus |
| + " softInputMode=" + InputMethodDebug.softInputModeToString( |
| windowAttribute.softInputMode)); |
| } |
| |
| getImmDelegate().onPostWindowGainedFocus(viewForWindowFocus, windowAttribute); |
| } |
| |
| /** |
| * @see ViewRootImpl#dispatchCheckFocus() |
| */ |
| @UiThread |
| void onScheduledCheckFocus() { |
| getImmDelegate().onScheduledCheckFocus(mViewRootImpl); |
| } |
| |
| @UiThread |
| void onViewFocusChanged(View view, boolean hasFocus) { |
| getImmDelegate().onViewFocusChanged(view, hasFocus); |
| } |
| |
| @UiThread |
| void onViewDetachedFromWindow(View view) { |
| getImmDelegate().onViewDetachedFromWindow(view, mViewRootImpl); |
| |
| } |
| |
| @UiThread |
| void onWindowDismissed() { |
| getImmDelegate().onWindowDismissed(mViewRootImpl); |
| mHasImeFocus = false; |
| } |
| |
| /** |
| * @param windowAttribute {@link WindowManager.LayoutParams} to be checked. |
| * @return Whether the window is in local focus mode or not. |
| */ |
| @AnyThread |
| private static boolean isInLocalFocusMode(WindowManager.LayoutParams windowAttribute) { |
| return (windowAttribute.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0; |
| } |
| |
| int onProcessImeInputStage(Object token, InputEvent event, |
| WindowManager.LayoutParams windowAttribute, |
| InputMethodManager.FinishedInputEventCallback callback) { |
| if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) { |
| return InputMethodManager.DISPATCH_NOT_HANDLED; |
| } |
| final InputMethodManager imm = |
| mViewRootImpl.mContext.getSystemService(InputMethodManager.class); |
| if (imm == null) { |
| return InputMethodManager.DISPATCH_NOT_HANDLED; |
| } |
| return imm.dispatchInputEvent(event, token, callback, mViewRootImpl.mHandler); |
| } |
| |
| /** |
| * A delegate implementing some basic {@link InputMethodManager} APIs. |
| * @hide |
| */ |
| public interface InputMethodManagerDelegate { |
| void onPreWindowGainedFocus(ViewRootImpl viewRootImpl); |
| void onPostWindowGainedFocus(View viewForWindowFocus, |
| @NonNull WindowManager.LayoutParams windowAttribute); |
| void onWindowLostFocus(@NonNull ViewRootImpl viewRootImpl); |
| void onViewFocusChanged(@NonNull View view, boolean hasFocus); |
| void onScheduledCheckFocus(@NonNull ViewRootImpl viewRootImpl); |
| void onViewDetachedFromWindow(View view, ViewRootImpl viewRootImpl); |
| void onWindowDismissed(ViewRootImpl viewRootImpl); |
| } |
| |
| /** |
| * Indicates whether the view's window has IME focused. |
| */ |
| @UiThread |
| boolean hasImeFocus() { |
| return mHasImeFocus; |
| } |
| |
| void dumpDebug(ProtoOutputStream proto, long fieldId) { |
| final long token = proto.start(fieldId); |
| proto.write(HAS_IME_FOCUS, mHasImeFocus); |
| proto.end(token); |
| } |
| } |