summaryrefslogtreecommitdiff
path: root/core/java/android/inputmethodservice/InputMethodService.java
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2020-09-10 17:22:01 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2020-09-10 17:22:01 +0000
commit8ac6741e47c76bde065f868ea64d2f04541487b9 (patch)
tree1a679458fdbd8d370692d56791e2bf83acee35b5 /core/java/android/inputmethodservice/InputMethodService.java
parent3de940cc40b1e3fdf8224e18a8308a16768cbfa8 (diff)
parentc64112eb974e9aa7638aead998f07a868acfb5a7 (diff)
Merge "Merge Android R"
Diffstat (limited to 'core/java/android/inputmethodservice/InputMethodService.java')
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java284
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.