diff options
| author | Xin Li <delphij@google.com> | 2020-09-10 17:22:01 +0000 |
|---|---|---|
| committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2020-09-10 17:22:01 +0000 |
| commit | 8ac6741e47c76bde065f868ea64d2f04541487b9 (patch) | |
| tree | 1a679458fdbd8d370692d56791e2bf83acee35b5 /core/java/android/inputmethodservice/InputMethodService.java | |
| parent | 3de940cc40b1e3fdf8224e18a8308a16768cbfa8 (diff) | |
| parent | c64112eb974e9aa7638aead998f07a868acfb5a7 (diff) | |
Merge "Merge Android R"
Diffstat (limited to 'core/java/android/inputmethodservice/InputMethodService.java')
| -rw-r--r-- | core/java/android/inputmethodservice/InputMethodService.java | 284 |
1 files changed, 265 insertions, 19 deletions
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 44825a8f1502..c5a11abe1136 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -16,9 +16,11 @@ package android.inputmethodservice; -import static android.view.Display.DEFAULT_DISPLAY; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; +import static android.view.WindowInsets.Type.navigationBars; +import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -34,6 +36,7 @@ import android.app.ActivityManager; import android.app.Dialog; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; @@ -41,6 +44,7 @@ import android.database.ContentObserver; import android.graphics.Rect; import android.graphics.Region; import android.net.Uri; +import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -62,8 +66,11 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.ViewRootImpl; import android.view.ViewTreeObserver; import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowInsets.Side; import android.view.WindowManager; import android.view.animation.AnimationUtils; import android.view.inputmethod.CompletionInfo; @@ -71,6 +78,8 @@ import android.view.inputmethod.CursorAnchorInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.InlineSuggestionsRequest; +import android.view.inputmethod.InlineSuggestionsResponse; import android.view.inputmethod.InputBinding; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputContentInfo; @@ -81,17 +90,21 @@ import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; +import android.window.WindowMetricsHelper; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.IInputContentUriToken; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; import com.android.internal.inputmethod.InputMethodPrivilegedOperations; import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry; +import com.android.internal.view.IInlineSuggestionsRequestCallback; +import com.android.internal.view.InlineSuggestionsRequestInfo; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collections; /** * InputMethodService provides a standard implementation of an InputMethod, @@ -431,7 +444,37 @@ public class InputMethodService extends AbstractInputMethodService { final Insets mTmpInsets = new Insets(); final int[] mTmpLocation = new int[2]; + private InlineSuggestionSessionController mInlineSuggestionSessionController; + + private boolean mAutomotiveHideNavBarForKeyboard; + private boolean mIsAutomotive; + + /** + * An opaque {@link Binder} token of window requesting {@link InputMethodImpl#showSoftInput} + * The original app window token is passed from client app window. + * {@link com.android.server.inputmethod.InputMethodManagerService} creates a unique dummy + * token to identify this window. + * This dummy token is only valid for a single call to {@link InputMethodImpl#showSoftInput}, + * after which it is set null until next call. + */ + private IBinder mCurShowInputToken; + + /** + * An opaque {@link Binder} token of window requesting {@link InputMethodImpl#hideSoftInput} + * The original app window token is passed from client app window. + * {@link com.android.server.inputmethod.InputMethodManagerService} creates a unique dummy + * token to identify this window. + * This dummy token is only valid for a single call to {@link InputMethodImpl#hideSoftInput}, + * after which it is set {@code null} until next call. + */ + private IBinder mCurHideInputToken; + final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> { + onComputeInsets(mTmpInsets); + if (!mViewsCreated) { + // The IME views are not ready, keep visible insets untouched. + mTmpInsets.visibleTopInsets = 0; + } if (isExtractViewShown()) { // In true fullscreen mode, we just say the window isn't covering // any content so we don't impact whatever is behind. @@ -440,12 +483,15 @@ public class InputMethodService extends AbstractInputMethodService { info.touchableRegion.setEmpty(); info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); } else { - onComputeInsets(mTmpInsets); info.contentInsets.top = mTmpInsets.contentTopInsets; info.visibleInsets.top = mTmpInsets.visibleTopInsets; info.touchableRegion.set(mTmpInsets.touchableRegion); info.setTouchableInsets(mTmpInsets.touchableInsets); } + + if (mInputFrame != null) { + setImeExclusionRect(mTmpInsets.visibleTopInsets); + } }; final View.OnClickListener mActionClickListener = v -> { @@ -466,6 +512,10 @@ public class InputMethodService extends AbstractInputMethodService { * all of the standard behavior for an input method. */ public class InputMethodImpl extends AbstractInputMethodImpl { + + private boolean mSystemCallingShowSoftInput; + private boolean mSystemCallingHideSoftInput; + /** * {@inheritDoc} * @hide @@ -486,6 +536,21 @@ public class InputMethodService extends AbstractInputMethodService { /** * {@inheritDoc} + * @hide + */ + @MainThread + @Override + public void onCreateInlineSuggestionsRequest( + @NonNull InlineSuggestionsRequestInfo requestInfo, + @NonNull IInlineSuggestionsRequestCallback cb) { + if (DEBUG) { + Log.d(TAG, "InputMethodService received onCreateInlineSuggestionsRequest()"); + } + mInlineSuggestionSessionController.onMakeInlineSuggestionsRequest(requestInfo, cb); + } + + /** + * {@inheritDoc} */ @MainThread @Override @@ -506,12 +571,10 @@ public class InputMethodService extends AbstractInputMethodService { @Override public void updateInputMethodDisplay(int displayId) { // Update display for adding IME window to the right display. - if (displayId != DEFAULT_DISPLAY) { - // TODO(b/111364446) Need to address context lifecycle issue if need to re-create - // for update resources & configuration correctly when show soft input - // in non-default display. - updateDisplay(displayId); - } + // TODO(b/111364446) Need to address context lifecycle issue if need to re-create + // for update resources & configuration correctly when show soft input + // in non-default display. + updateDisplay(displayId); } /** @@ -541,6 +604,7 @@ public class InputMethodService extends AbstractInputMethodService { public void unbindInput() { if (DEBUG) Log.v(TAG, "unbindInput(): binding=" + mInputBinding + " ic=" + mInputConnection); + // Unbind input is per process per display. onUnbindInput(); mInputBinding = null; mInputConnection = null; @@ -588,19 +652,40 @@ public class InputMethodService extends AbstractInputMethodService { /** * {@inheritDoc} + * @hide + */ + @MainThread + @Override + public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver, + IBinder hideInputToken) { + mSystemCallingHideSoftInput = true; + mCurHideInputToken = hideInputToken; + hideSoftInput(flags, resultReceiver); + mCurHideInputToken = null; + mSystemCallingHideSoftInput = false; + } + + /** + * {@inheritDoc} */ @MainThread @Override public void hideSoftInput(int flags, ResultReceiver resultReceiver) { if (DEBUG) Log.v(TAG, "hideSoftInput()"); + if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R + && !mSystemCallingHideSoftInput) { + Log.e(TAG, "IME shouldn't call hideSoftInput on itself." + + " Use requestHideSelf(int) itself"); + return; + } final boolean wasVisible = mIsPreRendered ? mDecorViewVisible && mWindowVisible : isInputViewShown(); + applyVisibilityInInsetsConsumerIfNecessary(false /* setVisible */); if (mIsPreRendered) { if (DEBUG) { Log.v(TAG, "Making IME window invisible"); } setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition); - applyVisibilityInInsetsConsumer(false /* setVisible */); onPreRenderedWindowVisibilityChanged(false /* setVisible */); } else { mShowInputFlags = 0; @@ -620,11 +705,33 @@ public class InputMethodService extends AbstractInputMethodService { /** * {@inheritDoc} + * @hide + */ + @MainThread + @Override + public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver, + IBinder showInputToken) { + mSystemCallingShowSoftInput = true; + mCurShowInputToken = showInputToken; + showSoftInput(flags, resultReceiver); + mCurShowInputToken = null; + mSystemCallingShowSoftInput = false; + } + + /** + * {@inheritDoc} */ @MainThread @Override public void showSoftInput(int flags, ResultReceiver resultReceiver) { if (DEBUG) Log.v(TAG, "showSoftInput()"); + // TODO(b/148086656): Disallow IME developers from calling InputMethodImpl methods. + if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R + && !mSystemCallingShowSoftInput) { + Log.e(TAG," IME shouldn't call showSoftInput on itself." + + " Use requestShowSelf(int) itself"); + return; + } final boolean wasVisible = mIsPreRendered ? mDecorViewVisible && mWindowVisible : isInputViewShown(); if (dispatchOnShowInputRequested(flags, false)) { @@ -632,11 +739,11 @@ public class InputMethodService extends AbstractInputMethodService { if (DEBUG) { Log.v(TAG, "Making IME window visible"); } - applyVisibilityInInsetsConsumer(true /* setVisible */); onPreRenderedWindowVisibilityChanged(true /* setVisible */); } else { showWindow(true); } + applyVisibilityInInsetsConsumerIfNecessary(true /* setVisible */); } // If user uses hard keyboard, IME button should always be shown. setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition); @@ -659,17 +766,96 @@ public class InputMethodService extends AbstractInputMethodService { public void changeInputMethodSubtype(InputMethodSubtype subtype) { dispatchOnCurrentInputMethodSubtypeChanged(subtype); } + + /** + * {@inheritDoc} + * @hide + */ + @Override + public void setCurrentShowInputToken(IBinder showInputToken) { + mCurShowInputToken = showInputToken; + } + + /** + * {@inheritDoc} + * @hide + */ + @Override + public void setCurrentHideInputToken(IBinder hideInputToken) { + mCurHideInputToken = hideInputToken; + } + } + + /** + * Called when Autofill is requesting an {@link InlineSuggestionsRequest} from the IME. + * + * <p>The Autofill Framework will first request the IME to create and send an + * {@link InlineSuggestionsRequest} back. Once Autofill Framework receives a valid request and + * also receives valid inline suggestions, they will be returned via + * {@link #onInlineSuggestionsResponse(InlineSuggestionsResponse)}.</p> + * + * <p>IME Lifecycle - The request will wait to be created after inputStarted</p> + * + * <p>If the IME wants to support displaying inline suggestions, they must set + * supportsInlineSuggestions in its XML and implement this method to return a valid + * {@link InlineSuggestionsRequest}.</p> + * + * @param uiExtras the extras that contain the UI renderer related information + * @return an {@link InlineSuggestionsRequest} to be sent to Autofill. + */ + @Nullable + public InlineSuggestionsRequest onCreateInlineSuggestionsRequest(@NonNull Bundle uiExtras) { + return null; + } + + /** + * Called when Autofill responds back with {@link InlineSuggestionsResponse} containing + * inline suggestions. + * + * <p>Should be implemented by subclasses.</p> + * + * @param response {@link InlineSuggestionsResponse} passed back by Autofill. + * @return Whether the IME will use and render the inline suggestions. + */ + public boolean onInlineSuggestionsResponse(@NonNull InlineSuggestionsResponse response) { + return false; + } + + /** + * Returns the {@link IBinder} input token from the host view root. + */ + @Nullable + private IBinder getHostInputToken() { + ViewRootImpl viewRoot = null; + if (mRootView != null) { + viewRoot = mRootView.getViewRootImpl(); + } + return viewRoot == null ? null : viewRoot.getInputToken(); } private void notifyImeHidden() { - setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition); - onPreRenderedWindowVisibilityChanged(false /* setVisible */); + requestHideSelf(0); + } + + private void removeImeSurface() { + if (!mShowInputRequested && !mWindowVisible) { + // hiding a window removes its surface. + mWindow.hide(); + } } private void setImeWindowStatus(int visibilityFlags, int backDisposition) { mPrivOps.setImeWindowStatus(visibilityFlags, backDisposition); } + /** Set region of the keyboard to be avoided from back gesture */ + private void setImeExclusionRect(int visibleTopInsets) { + View inputFrameRootView = mInputFrame.getRootView(); + Rect r = new Rect(0, visibleTopInsets, inputFrameRootView.getWidth(), + inputFrameRootView.getHeight()); + inputFrameRootView.setSystemGestureExclusionRects(Collections.singletonList(r)); + } + /** * Concrete implementation of * {@link AbstractInputMethodService.AbstractInputMethodSessionImpl} that provides @@ -776,6 +962,14 @@ public class InputMethodService extends AbstractInputMethodService { public final void notifyImeHidden() { InputMethodService.this.notifyImeHidden(); } + + /** + * Notify IME that surface can be now removed. + * @hide + */ + public final void removeImeSurface() { + InputMethodService.this.removeImeSurface(); + } } /** @@ -997,6 +1191,11 @@ public class InputMethodService extends AbstractInputMethodService { super.onCreate(); mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE); mSettingsObserver = SettingsObserver.createAndRegister(this); + + mIsAutomotive = isAutomotive(); + mAutomotiveHideNavBarForKeyboard = getApplicationContext().getResources().getBoolean( + com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard); + // TODO(b/111364446) Need to address context lifecycle issue if need to re-create // for update resources & configuration correctly when show soft input // in non-default display. @@ -1004,6 +1203,22 @@ public class InputMethodService extends AbstractInputMethodService { Context.LAYOUT_INFLATER_SERVICE); mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState, WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false); + mWindow.getWindow().getAttributes().setFitInsetsTypes(statusBars() | navigationBars()); + mWindow.getWindow().getAttributes().setFitInsetsSides(Side.all() & ~Side.BOTTOM); + mWindow.getWindow().getAttributes().setFitInsetsIgnoringVisibility(true); + + // IME layout should always be inset by navigation bar, no matter its current visibility, + // unless automotive requests it, since automotive may hide the navigation bar. + mWindow.getWindow().getDecorView().setOnApplyWindowInsetsListener( + (v, insets) -> v.onApplyWindowInsets( + new WindowInsets.Builder(insets).setInsets( + navigationBars(), + mIsAutomotive && mAutomotiveHideNavBarForKeyboard + ? android.graphics.Insets.NONE + : insets.getInsetsIgnoringVisibility(navigationBars()) + ) + .build())); + // For ColorView in DecorView to work, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS needs to be set // by default (but IME developers can opt this out later if they want a new behavior). mWindow.getWindow().setFlags( @@ -1011,6 +1226,10 @@ public class InputMethodService extends AbstractInputMethodService { initViews(); mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT); + + mInlineSuggestionSessionController = new InlineSuggestionSessionController( + this::onCreateInlineSuggestionsRequest, this::getHostInputToken, + this::onInlineSuggestionsResponse); } /** @@ -1220,8 +1439,9 @@ public class InputMethodService extends AbstractInputMethodService { * screen orientation changes. */ public int getMaxWidth() { - WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); - return wm.getDefaultDisplay().getWidth(); + final WindowManager windowManager = getSystemService(WindowManager.class); + return WindowMetricsHelper.getBoundsExcludingNavigationBarAndCutout( + windowManager.getCurrentWindowMetrics()).width(); } /** @@ -1853,6 +2073,7 @@ public class InputMethodService extends AbstractInputMethodService { */ private boolean dispatchOnShowInputRequested(int flags, boolean configChange) { final boolean result = onShowInputRequested(flags, configChange); + mInlineSuggestionSessionController.notifyOnShowInputRequested(result); if (result) { mShowInputFlags = flags; } else { @@ -1952,6 +2173,7 @@ public class InputMethodService extends AbstractInputMethodService { if (!mInputViewStarted) { if (DEBUG) Log.v(TAG, "CALL: onStartInputView"); mInputViewStarted = true; + mInlineSuggestionSessionController.notifyOnStartInputView(); onStartInputView(mInputEditorInfo, false); } } else if (!mCandidatesViewStarted) { @@ -1974,19 +2196,25 @@ public class InputMethodService extends AbstractInputMethodService { /** * Apply the IME visibility in {@link android.view.ImeInsetsSourceConsumer} when - * pre-rendering is enabled. + * {@link ViewRootImpl.sNewInsetsMode} is enabled. * @param setVisible {@code true} to make it visible, false to hide it. */ - private void applyVisibilityInInsetsConsumer(boolean setVisible) { - if (!mIsPreRendered) { + private void applyVisibilityInInsetsConsumerIfNecessary(boolean setVisible) { + if (!isVisibilityAppliedUsingInsetsConsumer()) { return; } - mPrivOps.applyImeVisibility(setVisible); + mPrivOps.applyImeVisibility(setVisible + ? mCurShowInputToken : mCurHideInputToken, setVisible); + } + + private boolean isVisibilityAppliedUsingInsetsConsumer() { + return ViewRootImpl.sNewInsetsMode > NEW_INSETS_MODE_NONE; } private void finishViews(boolean finishingInput) { if (mInputViewStarted) { if (DEBUG) Log.v(TAG, "CALL: onFinishInputView"); + mInlineSuggestionSessionController.notifyOnFinishInputView(); onFinishInputView(finishingInput); } else if (mCandidatesViewStarted) { if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView"); @@ -2007,7 +2235,15 @@ public class InputMethodService extends AbstractInputMethodService { mWindowVisible = false; finishViews(false /* finishingInput */); if (mDecorViewVisible) { - mWindow.hide(); + // When insets API is enabled, it is responsible for client and server side + // visibility of IME window. + if (isVisibilityAppliedUsingInsetsConsumer()) { + if (mInputView != null) { + mInputView.dispatchWindowVisibilityChanged(View.GONE); + } + } else { + mWindow.hide(); + } mDecorViewVisible = false; onWindowHidden(); mDecorViewWasVisible = false; @@ -2073,6 +2309,7 @@ public class InputMethodService extends AbstractInputMethodService { if (DEBUG) Log.v(TAG, "CALL: doFinishInput"); finishViews(true /* finishingInput */); if (mInputStarted) { + mInlineSuggestionSessionController.notifyOnFinishInput(); if (DEBUG) Log.v(TAG, "CALL: onFinishInput"); onFinishInput(); } @@ -2089,12 +2326,16 @@ public class InputMethodService extends AbstractInputMethodService { mStartedInputConnection = ic; mInputEditorInfo = attribute; initialize(); + mInlineSuggestionSessionController.notifyOnStartInput( + attribute == null ? null : attribute.packageName, + attribute == null ? null : attribute.autofillId); if (DEBUG) Log.v(TAG, "CALL: onStartInput"); onStartInput(attribute, restarting); if (mDecorViewVisible) { if (mShowInputRequested) { if (DEBUG) Log.v(TAG, "CALL: onStartInputView"); mInputViewStarted = true; + mInlineSuggestionSessionController.notifyOnStartInputView(); onStartInputView(mInputEditorInfo, restarting); startExtractingText(true); } else if (mCandidatesVisibility == View.VISIBLE) { @@ -3032,6 +3273,11 @@ public class InputMethodService extends AbstractInputMethodService { : IME_VISIBLE) : 0); } + private boolean isAutomotive() { + return getApplicationContext().getPackageManager().hasSystemFeature( + PackageManager.FEATURE_AUTOMOTIVE); + } + /** * Performs a dump of the InputMethodService's internal state. Override * to add your own information to the dump. |
