• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1  /*
2   * Copyright (C) 2019 The Android Open Source Project
3   *
4   * Licensed under the Apache License, Version 2.0 (the "License");
5   * you may not use this file except in compliance with the License.
6   * You may obtain a copy of the License at
7   *
8   *      http://www.apache.org/licenses/LICENSE-2.0
9   *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package android.view;
18  
19  import android.annotation.AnyThread;
20  import android.annotation.NonNull;
21  import android.annotation.UiThread;
22  import android.util.Log;
23  import android.view.inputmethod.InputMethodManager;
24  
25  import com.android.internal.inputmethod.InputMethodDebug;
26  import com.android.internal.inputmethod.StartInputFlags;
27  import com.android.internal.inputmethod.StartInputReason;
28  
29  /**
30   * Responsible for IME focus handling inside {@link ViewRootImpl}.
31   * @hide
32   */
33  public final class ImeFocusController {
34      private static final boolean DEBUG = false;
35      private static final String TAG = "ImeFocusController";
36  
37      private final ViewRootImpl mViewRootImpl;
38      private boolean mHasImeFocus = false;
39      private View mServedView;
40      private View mNextServedView;
41      private InputMethodManagerDelegate mDelegate;
42  
43      @UiThread
ImeFocusController(@onNull ViewRootImpl viewRootImpl)44      ImeFocusController(@NonNull ViewRootImpl viewRootImpl) {
45          mViewRootImpl = viewRootImpl;
46      }
47  
48      @NonNull
getImmDelegate()49      private InputMethodManagerDelegate getImmDelegate() {
50          InputMethodManagerDelegate delegate = mDelegate;
51          if (delegate != null) {
52              return delegate;
53          }
54          delegate = mViewRootImpl.mContext.getSystemService(InputMethodManager.class).getDelegate();
55          mDelegate = delegate;
56          return delegate;
57      }
58  
59      /** Called when the view root is moved to a different display. */
60      @UiThread
onMovedToDisplay()61      void onMovedToDisplay() {
62          // InputMethodManager managed its instances for different displays. So if the associated
63          // display is changed, the delegate also needs to be refreshed (by getImmDelegate).
64          // See the comment in {@link android.app.SystemServiceRegistry} for InputMethodManager
65          // and {@link android.view.inputmethod.InputMethodManager#forContext}.
66          mDelegate = null;
67      }
68  
69      @UiThread
onTraversal(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute)70      void onTraversal(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) {
71          final boolean hasImeFocus = updateImeFocusable(windowAttribute, false /* force */);
72          if (!hasWindowFocus || isInLocalFocusMode(windowAttribute)) {
73              return;
74          }
75          if (hasImeFocus == mHasImeFocus) {
76              return;
77          }
78          mHasImeFocus = hasImeFocus;
79          if (mHasImeFocus) {
80              onPreWindowFocus(true /* hasWindowFocus */, windowAttribute);
81              onPostWindowFocus(mViewRootImpl.mView.findFocus(), true /* hasWindowFocus */,
82                      windowAttribute);
83          }
84      }
85  
86      @UiThread
onPreWindowFocus(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute)87      void onPreWindowFocus(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) {
88          if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
89              return;
90          }
91          if (hasWindowFocus) {
92              getImmDelegate().setCurrentRootView(mViewRootImpl);
93          }
94      }
95  
96      @UiThread
updateImeFocusable(WindowManager.LayoutParams windowAttribute, boolean force)97      boolean updateImeFocusable(WindowManager.LayoutParams windowAttribute, boolean force) {
98          final boolean hasImeFocus = WindowManager.LayoutParams.mayUseInputMethod(
99                  windowAttribute.flags);
100          if (force) {
101              mHasImeFocus = hasImeFocus;
102          }
103          return hasImeFocus;
104      }
105  
106      @UiThread
onPostWindowFocus(View focusedView, boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute)107      void onPostWindowFocus(View focusedView, boolean hasWindowFocus,
108              WindowManager.LayoutParams windowAttribute) {
109          if (!hasWindowFocus || !mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
110              return;
111          }
112          if (DEBUG) {
113              Log.v(TAG, "onWindowFocus: " + focusedView
114                      + " softInputMode=" + InputMethodDebug.softInputModeToString(
115                      windowAttribute.softInputMode));
116          }
117  
118          boolean forceFocus = false;
119          final InputMethodManagerDelegate immDelegate = getImmDelegate();
120          if (immDelegate.isRestartOnNextWindowFocus(true /* reset */)) {
121              if (DEBUG) Log.v(TAG, "Restarting due to isRestartOnNextWindowFocus as true");
122              forceFocus = true;
123          }
124          // Update mNextServedView when focusedView changed.
125          final View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView;
126          onViewFocusChanged(viewForWindowFocus, true);
127  
128          // Starting new input when the next focused view is same as served view but the currently
129          // active connection (if any) is not associated with it.
130          final boolean nextFocusIsServedView = mServedView == viewForWindowFocus;
131          if (nextFocusIsServedView && !immDelegate.hasActiveConnection(viewForWindowFocus)) {
132              forceFocus = true;
133          }
134  
135          immDelegate.startInputAsyncOnWindowFocusGain(viewForWindowFocus,
136                  windowAttribute.softInputMode, windowAttribute.flags, forceFocus);
137      }
138  
checkFocus(boolean forceNewFocus, boolean startInput)139      public boolean checkFocus(boolean forceNewFocus, boolean startInput) {
140          final InputMethodManagerDelegate immDelegate = getImmDelegate();
141          if (!immDelegate.isCurrentRootView(mViewRootImpl)
142                  || (mServedView == mNextServedView && !forceNewFocus)) {
143              return false;
144          }
145          if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView
146                  + " next=" + mNextServedView
147                  + " force=" + forceNewFocus
148                  + " package="
149                  + (mServedView != null ? mServedView.getContext().getPackageName() : "<none>"));
150  
151          // Close the connection when no next served view coming.
152          if (mNextServedView == null) {
153              immDelegate.finishInput();
154              immDelegate.closeCurrentIme();
155              return false;
156          }
157          mServedView = mNextServedView;
158          immDelegate.finishComposingText();
159  
160          if (startInput) {
161              immDelegate.startInput(StartInputReason.CHECK_FOCUS, null /* focusedView */,
162                      0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */);
163          }
164          return true;
165      }
166  
167      @UiThread
onViewFocusChanged(View view, boolean hasFocus)168      void onViewFocusChanged(View view, boolean hasFocus) {
169          if (view == null || view.isTemporarilyDetached()) {
170              return;
171          }
172          if (!getImmDelegate().isCurrentRootView(view.getViewRootImpl())) {
173              return;
174          }
175          if (!view.hasImeFocus() || !view.hasWindowFocus()) {
176              return;
177          }
178          if (DEBUG) Log.d(TAG, "onViewFocusChanged, view=" + view + ", mServedView=" + mServedView);
179  
180          // We don't need to track the next served view when the view lost focus here because:
181          // 1) The current view focus may be cleared temporary when in touch mode, closing input
182          //    at this moment isn't the right way.
183          // 2) We only care about the served view change when it focused, since changing input
184          //    connection when the focus target changed is reasonable.
185          // 3) Setting the next served view as null when no more served view should be handled in
186          //    other special events (e.g. view detached from window or the window dismissed).
187          if (hasFocus) {
188              mNextServedView = view;
189          }
190          mViewRootImpl.dispatchCheckFocus();
191      }
192  
193      @UiThread
onViewDetachedFromWindow(View view)194      void onViewDetachedFromWindow(View view) {
195          if (!getImmDelegate().isCurrentRootView(view.getViewRootImpl())) {
196              return;
197          }
198          if (mServedView == view) {
199              mNextServedView = null;
200              mViewRootImpl.dispatchCheckFocus();
201          }
202      }
203  
204      @UiThread
onWindowDismissed()205      void onWindowDismissed() {
206          final InputMethodManagerDelegate immDelegate = getImmDelegate();
207          if (!immDelegate.isCurrentRootView(mViewRootImpl)) {
208              return;
209          }
210          if (mServedView != null) {
211              immDelegate.finishInput();
212          }
213          immDelegate.setCurrentRootView(null);
214          mHasImeFocus = false;
215      }
216  
217      /**
218       * @param windowAttribute {@link WindowManager.LayoutParams} to be checked.
219       * @return Whether the window is in local focus mode or not.
220       */
221      @AnyThread
isInLocalFocusMode(WindowManager.LayoutParams windowAttribute)222      private static boolean isInLocalFocusMode(WindowManager.LayoutParams windowAttribute) {
223          return (windowAttribute.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
224      }
225  
onProcessImeInputStage(Object token, InputEvent event, WindowManager.LayoutParams windowAttribute, InputMethodManager.FinishedInputEventCallback callback)226      int onProcessImeInputStage(Object token, InputEvent event,
227              WindowManager.LayoutParams windowAttribute,
228              InputMethodManager.FinishedInputEventCallback callback) {
229          if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
230              return InputMethodManager.DISPATCH_NOT_HANDLED;
231          }
232          final InputMethodManager imm =
233                  mViewRootImpl.mContext.getSystemService(InputMethodManager.class);
234          if (imm == null) {
235              return InputMethodManager.DISPATCH_NOT_HANDLED;
236          }
237          return imm.dispatchInputEvent(event, token, callback, mViewRootImpl.mHandler);
238      }
239  
240      /**
241       * A delegate implementing some basic {@link InputMethodManager} APIs.
242       * @hide
243       */
244      public interface InputMethodManagerDelegate {
startInput(@tartInputReason int startInputReason, View focusedView, @StartInputFlags int startInputFlags, @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags)245          boolean startInput(@StartInputReason int startInputReason, View focusedView,
246                  @StartInputFlags int startInputFlags,
247                  @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags);
startInputAsyncOnWindowFocusGain(View rootView, @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags, boolean forceNewFocus)248          void startInputAsyncOnWindowFocusGain(View rootView,
249                  @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags,
250                  boolean forceNewFocus);
finishInput()251          void finishInput();
closeCurrentIme()252          void closeCurrentIme();
finishComposingText()253          void finishComposingText();
setCurrentRootView(ViewRootImpl rootView)254          void setCurrentRootView(ViewRootImpl rootView);
isCurrentRootView(ViewRootImpl rootView)255          boolean isCurrentRootView(ViewRootImpl rootView);
isRestartOnNextWindowFocus(boolean reset)256          boolean isRestartOnNextWindowFocus(boolean reset);
hasActiveConnection(View view)257          boolean hasActiveConnection(View view);
258      }
259  
getServedView()260      public View getServedView() {
261          return mServedView;
262      }
263  
getNextServedView()264      public View getNextServedView() {
265          return mNextServedView;
266      }
267  
setServedView(View view)268      public void setServedView(View view) {
269          mServedView = view;
270      }
271  
setNextServedView(View view)272      public void setNextServedView(View view) {
273          mNextServedView = view;
274      }
275  
276      /**
277       * Indicates whether the view's window has IME focused.
278       */
279      @UiThread
hasImeFocus()280      boolean hasImeFocus() {
281          return mHasImeFocus;
282      }
283  }
284