summaryrefslogtreecommitdiff
path: root/core/java/android
diff options
context:
space:
mode:
authorFeng Cao <fengcao@google.com>2020-05-07 05:38:36 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2020-05-07 05:38:36 +0000
commit6fbdd1173af93beb322d70d8d6a6bb3bf6bab351 (patch)
tree572a1dc6622644b194a276a66315868dd318cbcf /core/java/android
parent8985523aa5fbb5f7e97deed8edac00332370c026 (diff)
parentf9748230fdc9872a366fbb270849700869fa054a (diff)
Merge "Support re-attaching the inline suggestion view to window" into rvc-dev am: 254545cae5 am: f9748230fd
Change-Id: I037ddac754e8e7a69b7fd2b76aa8f768b523ae39
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/service/autofill/IInlineSuggestionUi.aidl29
-rw-r--r--core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl6
-rw-r--r--core/java/android/service/autofill/ISurfacePackageResultCallback.aidl28
-rw-r--r--core/java/android/service/autofill/InlineSuggestionRenderService.java121
-rw-r--r--core/java/android/view/inputmethod/InlineSuggestion.java270
-rw-r--r--core/java/android/widget/inline/InlineContentView.java137
6 files changed, 478 insertions, 113 deletions
diff --git a/core/java/android/service/autofill/IInlineSuggestionUi.aidl b/core/java/android/service/autofill/IInlineSuggestionUi.aidl
new file mode 100644
index 000000000000..7289853064f8
--- /dev/null
+++ b/core/java/android/service/autofill/IInlineSuggestionUi.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 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.service.autofill;
+
+import android.service.autofill.ISurfacePackageResultCallback;
+
+/**
+ * Interface to interact with a remote inline suggestion UI.
+ *
+ * @hide
+ */
+oneway interface IInlineSuggestionUi {
+ void getSurfacePackage(ISurfacePackageResultCallback callback);
+ void releaseSurfaceControlViewHost();
+}
diff --git a/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl b/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl
index 172cfef9fee2..97eb790b9acc 100644
--- a/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl
+++ b/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl
@@ -18,17 +18,19 @@ package android.service.autofill;
import android.content.IntentSender;
import android.os.IBinder;
+import android.service.autofill.IInlineSuggestionUi;
import android.view.SurfaceControlViewHost;
/**
- * Interface to receive events from inline suggestions.
+ * Interface to receive events from a remote inline suggestion UI.
*
* @hide
*/
oneway interface IInlineSuggestionUiCallback {
void onClick();
void onLongClick();
- void onContent(in SurfaceControlViewHost.SurfacePackage surface, int width, int height);
+ void onContent(in IInlineSuggestionUi content, in SurfaceControlViewHost.SurfacePackage surface,
+ int width, int height);
void onError();
void onTransferTouchFocusToImeWindow(in IBinder sourceInputToken, int displayId);
void onStartIntentSender(in IntentSender intentSender);
diff --git a/core/java/android/service/autofill/ISurfacePackageResultCallback.aidl b/core/java/android/service/autofill/ISurfacePackageResultCallback.aidl
new file mode 100644
index 000000000000..0c2c624952eb
--- /dev/null
+++ b/core/java/android/service/autofill/ISurfacePackageResultCallback.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 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.service.autofill;
+
+import android.view.SurfaceControlViewHost;
+
+/**
+ * Interface to receive a SurfaceControlViewHost.SurfacePackage.
+ *
+ * @hide
+ */
+oneway interface ISurfacePackageResultCallback {
+ void onResult(in SurfaceControlViewHost.SurfacePackage result);
+}
diff --git a/core/java/android/service/autofill/InlineSuggestionRenderService.java b/core/java/android/service/autofill/InlineSuggestionRenderService.java
index 6c22b1936d74..3ea443bab3f8 100644
--- a/core/java/android/service/autofill/InlineSuggestionRenderService.java
+++ b/core/java/android/service/autofill/InlineSuggestionRenderService.java
@@ -33,6 +33,7 @@ import android.os.Looper;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.util.Log;
+import android.util.LruCache;
import android.util.Size;
import android.view.Display;
import android.view.SurfaceControlViewHost;
@@ -40,6 +41,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
+import java.lang.ref.WeakReference;
+
/**
* A service that renders an inline presentation view given the {@link InlinePresentation}.
*
@@ -65,6 +68,27 @@ public abstract class InlineSuggestionRenderService extends Service {
private IInlineSuggestionUiCallback mCallback;
+
+ /**
+ * A local LRU cache keeping references to the inflated {@link SurfaceControlViewHost}s, so
+ * they can be released properly when no longer used. Each view needs to be tracked separately,
+ * therefore for simplicity we use the hash code of the value object as key in the cache.
+ */
+ private final LruCache<InlineSuggestionUiImpl, Boolean> mActiveInlineSuggestions =
+ new LruCache<InlineSuggestionUiImpl, Boolean>(30) {
+ @Override
+ public void entryRemoved(boolean evicted, InlineSuggestionUiImpl key,
+ Boolean oldValue,
+ Boolean newValue) {
+ if (evicted) {
+ Log.w(TAG,
+ "Hit max=100 entries in the cache. Releasing oldest one to make "
+ + "space.");
+ key.releaseSurfaceControlViewHost();
+ }
+ }
+ };
+
/**
* If the specified {@code width}/{@code height} is an exact value, then it will be returned as
* is, otherwise the method tries to measure a size that is just large enough to fit the view
@@ -169,8 +193,14 @@ public abstract class InlineSuggestionRenderService extends Service {
return true;
});
- sendResult(callback, host.getSurfacePackage(), measuredSize.getWidth(),
- measuredSize.getHeight());
+ try {
+ InlineSuggestionUiImpl uiImpl = new InlineSuggestionUiImpl(host, mHandler);
+ mActiveInlineSuggestions.put(uiImpl, true);
+ callback.onContent(new InlineSuggestionUiWrapper(uiImpl), host.getSurfacePackage(),
+ measuredSize.getWidth(), measuredSize.getHeight());
+ } catch (RemoteException e) {
+ Log.w(TAG, "RemoteException calling onContent()");
+ }
} finally {
updateDisplay(Display.DEFAULT_DISPLAY);
}
@@ -181,12 +211,87 @@ public abstract class InlineSuggestionRenderService extends Service {
callback.sendResult(rendererInfo);
}
- private void sendResult(@NonNull IInlineSuggestionUiCallback callback,
- @Nullable SurfaceControlViewHost.SurfacePackage surface, int width, int height) {
- try {
- callback.onContent(surface, width, height);
- } catch (RemoteException e) {
- Log.w(TAG, "RemoteException calling onContent(" + surface + ")");
+ /**
+ * A wrapper class around the {@link InlineSuggestionUiImpl} to ensure it's not strongly
+ * reference by the remote system server process.
+ */
+ private static final class InlineSuggestionUiWrapper extends
+ android.service.autofill.IInlineSuggestionUi.Stub {
+
+ private final WeakReference<InlineSuggestionUiImpl> mUiImpl;
+
+ InlineSuggestionUiWrapper(InlineSuggestionUiImpl uiImpl) {
+ mUiImpl = new WeakReference<>(uiImpl);
+ }
+
+ @Override
+ public void releaseSurfaceControlViewHost() {
+ final InlineSuggestionUiImpl uiImpl = mUiImpl.get();
+ if (uiImpl != null) {
+ uiImpl.releaseSurfaceControlViewHost();
+ }
+ }
+
+ @Override
+ public void getSurfacePackage(ISurfacePackageResultCallback callback) {
+ final InlineSuggestionUiImpl uiImpl = mUiImpl.get();
+ if (uiImpl != null) {
+ uiImpl.getSurfacePackage(callback);
+ }
+ }
+ }
+
+ /**
+ * Keeps track of a SurfaceControlViewHost to ensure it's released when its lifecycle ends.
+ *
+ * <p>This class is thread safe, because all the outside calls are piped into a single
+ * handler thread to be processed.
+ */
+ private final class InlineSuggestionUiImpl {
+
+ @Nullable
+ private SurfaceControlViewHost mViewHost;
+ @NonNull
+ private final Handler mHandler;
+
+ InlineSuggestionUiImpl(SurfaceControlViewHost viewHost, Handler handler) {
+ this.mViewHost = viewHost;
+ this.mHandler = handler;
+ }
+
+ /**
+ * Call {@link SurfaceControlViewHost#release()} to release it. After this, this view is
+ * not usable, and any further calls to the
+ * {@link #getSurfacePackage(ISurfacePackageResultCallback)} will get {@code null} result.
+ */
+ public void releaseSurfaceControlViewHost() {
+ mHandler.post(() -> {
+ if (mViewHost == null) {
+ return;
+ }
+ Log.v(TAG, "Releasing inline suggestion view host");
+ mViewHost.release();
+ mViewHost = null;
+ InlineSuggestionRenderService.this.mActiveInlineSuggestions.remove(
+ InlineSuggestionUiImpl.this);
+ Log.v(TAG, "Removed the inline suggestion from the cache, current size="
+ + InlineSuggestionRenderService.this.mActiveInlineSuggestions.size());
+ });
+ }
+
+ /**
+ * Sends back a new {@link android.view.SurfaceControlViewHost.SurfacePackage} if the view
+ * is not released, {@code null} otherwise.
+ */
+ public void getSurfacePackage(ISurfacePackageResultCallback callback) {
+ Log.d(TAG, "getSurfacePackage");
+ mHandler.post(() -> {
+ try {
+ callback.onResult(mViewHost == null ? null : mViewHost.getSurfacePackage());
+ } catch (RemoteException e) {
+ Log.w(TAG, "RemoteException calling onSurfacePackage");
+ }
+ });
}
}
diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java
index 6b1a480986c8..4c72474435a4 100644
--- a/core/java/android/view/inputmethod/InlineSuggestion.java
+++ b/core/java/android/view/inputmethod/InlineSuggestion.java
@@ -18,11 +18,13 @@ package android.view.inputmethod;
import android.annotation.BinderThread;
import android.annotation.CallbackExecutor;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.content.Context;
-import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
@@ -42,26 +44,26 @@ import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
- * This class represents an inline suggestion which is made by one app
- * and can be embedded into the UI of another. Suggestions may contain
- * sensitive information not known to the host app which needs to be
- * protected from spoofing. To address that the suggestion view inflated
- * on demand for embedding is created in such a way that the hosting app
- * cannot introspect its content and cannot interact with it.
+ * This class represents an inline suggestion which is made by one app and can be embedded into the
+ * UI of another. Suggestions may contain sensitive information not known to the host app which
+ * needs to be protected from spoofing. To address that the suggestion view inflated on demand for
+ * embedding is created in such a way that the hosting app cannot introspect its content and cannot
+ * interact with it.
*/
-@DataClass(
- genEqualsHashCode = true,
- genToString = true,
- genHiddenConstDefs = true,
+@DataClass(genEqualsHashCode = true, genToString = true, genHiddenConstDefs = true,
genHiddenConstructor = true)
-@DataClass.Suppress({"getContentProvider"})
public final class InlineSuggestion implements Parcelable {
private static final String TAG = "InlineSuggestion";
- private final @NonNull InlineSuggestionInfo mInfo;
+ @NonNull
+ private final InlineSuggestionInfo mInfo;
- private final @Nullable IInlineContentProvider mContentProvider;
+ /**
+ * @hide
+ */
+ @Nullable
+ private final IInlineContentProvider mContentProvider;
/**
* Used to keep a strong reference to the callback so it doesn't get garbage collected.
@@ -69,7 +71,8 @@ public final class InlineSuggestion implements Parcelable {
* @hide
*/
@DataClass.ParcelWith(InlineContentCallbackImplParceling.class)
- private @Nullable InlineContentCallbackImpl mInlineContentCallback;
+ @Nullable
+ private InlineContentCallbackImpl mInlineContentCallback;
/**
* Creates a new {@link InlineSuggestion}, for testing purpose.
@@ -87,8 +90,7 @@ public final class InlineSuggestion implements Parcelable {
*
* @hide
*/
- public InlineSuggestion(
- @NonNull InlineSuggestionInfo info,
+ public InlineSuggestion(@NonNull InlineSuggestionInfo info,
@Nullable IInlineContentProvider contentProvider) {
this(info, contentProvider, /* inlineContentCallback */ null);
}
@@ -96,25 +98,30 @@ public final class InlineSuggestion implements Parcelable {
/**
* Inflates a view with the content of this suggestion at a specific size.
*
- * <p> The size must be either 1) between the
- * {@link android.widget.inline.InlinePresentationSpec#getMinSize() min size} and the
- * {@link android.widget.inline.InlinePresentationSpec#getMaxSize() max size} of the
- * presentation spec returned by {@link InlineSuggestionInfo#getInlinePresentationSpec()},
- * or 2) {@link ViewGroup.LayoutParams#WRAP_CONTENT}. If the size is set to
- * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, then the size of the inflated view will be just
- * large enough to fit the content, while still conforming to the min / max size specified by
- * the {@link android.widget.inline.InlinePresentationSpec}.
+ * <p> Each dimension of the size must satisfy one of the following conditions:
+ *
+ * <ol>
+ * <li>between {@link android.widget.inline.InlinePresentationSpec#getMinSize()} and
+ * {@link android.widget.inline.InlinePresentationSpec#getMaxSize()} of the presentation spec
+ * from {@code mInfo}
+ * <li>{@link ViewGroup.LayoutParams#WRAP_CONTENT}
+ * </ol>
+ *
+ * If the size is set to {@link
+ * ViewGroup.LayoutParams#WRAP_CONTENT}, then the size of the inflated view will be just large
+ * enough to fit the content, while still conforming to the min / max size specified by the
+ * {@link android.widget.inline.InlinePresentationSpec}.
*
* <p> The caller can attach an {@link android.view.View.OnClickListener} and/or an
- * {@link android.view.View.OnLongClickListener} to the view in the
- * {@code callback} to receive click and long click events on the view.
+ * {@link android.view.View.OnLongClickListener} to the view in the {@code callback} to receive
+ * click and long click events on the view.
*
* @param context Context in which to inflate the view.
- * @param size The size at which to inflate the suggestion. For each dimension, it maybe
- * an exact value or {@link ViewGroup.LayoutParams#WRAP_CONTENT}.
- * @param callback Callback for receiving the inflated view, where the
- * {@link ViewGroup.LayoutParams} of the view is set as the actual size of
- * the underlying remote view.
+ * @param size The size at which to inflate the suggestion. For each dimension, it maybe an
+ * exact value or {@link ViewGroup.LayoutParams#WRAP_CONTENT}.
+ * @param callback Callback for receiving the inflated view, where the {@link
+ * ViewGroup.LayoutParams} of the view is set as the actual size of the
+ * underlying remote view.
* @throws IllegalArgumentException If an invalid argument is passed.
* @throws IllegalStateException If this method is already called.
*/
@@ -130,19 +137,17 @@ public final class InlineSuggestion implements Parcelable {
+ ", nor wrap_content");
}
mInlineContentCallback = getInlineContentCallback(context, callbackExecutor, callback);
- AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
- if (mContentProvider == null) {
- callback.accept(/* view */ null);
- return;
- }
- try {
- mContentProvider.provideContent(size.getWidth(), size.getHeight(),
- new InlineContentCallbackWrapper(mInlineContentCallback));
- } catch (RemoteException e) {
- Slog.w(TAG, "Error creating suggestion content surface: " + e);
- callback.accept(/* view */ null);
- }
- });
+ if (mContentProvider == null) {
+ callbackExecutor.execute(() -> callback.accept(/* view */ null));
+ return;
+ }
+ try {
+ mContentProvider.provideContent(size.getWidth(), size.getHeight(),
+ new InlineContentCallbackWrapper(mInlineContentCallback));
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error creating suggestion content surface: " + e);
+ callbackExecutor.execute(() -> callback.accept(/* view */ null));
+ }
}
/**
@@ -161,9 +166,14 @@ public final class InlineSuggestion implements Parcelable {
if (mInlineContentCallback != null) {
throw new IllegalStateException("Already called #inflate()");
}
- return new InlineContentCallbackImpl(context, callbackExecutor, callback);
+ return new InlineContentCallbackImpl(context, mContentProvider, callbackExecutor,
+ callback);
}
+ /**
+ * A wrapper class around the {@link InlineContentCallbackImpl} to ensure it's not strongly
+ * reference by the remote system server process.
+ */
private static final class InlineContentCallbackWrapper extends IInlineContentCallback.Stub {
private final WeakReference<InlineContentCallbackImpl> mCallbackImpl;
@@ -201,17 +211,68 @@ public final class InlineSuggestion implements Parcelable {
}
}
+ /**
+ * Handles the communication between the inline suggestion view in current (IME) process and
+ * the remote view provided from the system server.
+ *
+ * <p>This class is thread safe, because all the outside calls are piped into a single
+ * handler thread to be processed.
+ */
private static final class InlineContentCallbackImpl {
- private final @NonNull Context mContext;
- private final @NonNull Executor mCallbackExecutor;
- private final @NonNull Consumer<InlineContentView> mCallback;
- private @Nullable InlineContentView mView;
+ @NonNull
+ private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+
+ @NonNull
+ private final Context mContext;
+ @Nullable
+ private final IInlineContentProvider mInlineContentProvider;
+ @NonNull
+ private final Executor mCallbackExecutor;
+
+ /**
+ * Callback from the client (IME) that will receive the inflated suggestion view. It'll
+ * only be called once when the view SurfacePackage is first sent back to the client. Any
+ * updates to the view due to attach to window and detach from window events will be
+ * handled under the hood, transparent from the client.
+ */
+ @NonNull
+ private final Consumer<InlineContentView> mCallback;
+
+ /**
+ * Indicates whether the first content has been received or not.
+ */
+ private boolean mFirstContentReceived = false;
+
+ /**
+ * The client (IME) side view which internally wraps a remote view. It'll be set when
+ * {@link #onContent(SurfaceControlViewHost.SurfacePackage, int, int)} is called, which
+ * should only happen once in the lifecycle of this inline suggestion instance.
+ */
+ @Nullable
+ private InlineContentView mView;
+
+ /**
+ * The SurfacePackage pointing to the remote view. It's cached here to be sent to the next
+ * available consumer.
+ */
+ @Nullable
+ private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
+
+ /**
+ * The callback (from the {@link InlineContentView}) which consumes the surface package.
+ * It's cached here to be called when the SurfacePackage is returned from the remote
+ * view owning process.
+ */
+ @Nullable
+ private Consumer<SurfaceControlViewHost.SurfacePackage> mSurfacePackageConsumer;
InlineContentCallbackImpl(@NonNull Context context,
+ @Nullable IInlineContentProvider inlineContentProvider,
@NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull Consumer<InlineContentView> callback) {
mContext = context;
+ mInlineContentProvider = inlineContentProvider;
mCallbackExecutor = callbackExecutor;
mCallback = callback;
}
@@ -219,28 +280,110 @@ public final class InlineSuggestion implements Parcelable {
@BinderThread
public void onContent(SurfaceControlViewHost.SurfacePackage content, int width,
int height) {
- if (content == null) {
+ mMainHandler.post(() -> handleOnContent(content, width, height));
+ }
+
+ @MainThread
+ private void handleOnContent(SurfaceControlViewHost.SurfacePackage content, int width,
+ int height) {
+ if (!mFirstContentReceived) {
+ handleOnFirstContentReceived(content, width, height);
+ mFirstContentReceived = true;
+ } else {
+ handleOnSurfacePackage(content);
+ }
+ }
+
+ /**
+ * Called when the view content is returned for the first time.
+ */
+ @MainThread
+ private void handleOnFirstContentReceived(SurfaceControlViewHost.SurfacePackage content,
+ int width, int height) {
+ mSurfacePackage = content;
+ if (mSurfacePackage == null) {
mCallbackExecutor.execute(() -> mCallback.accept(/* view */null));
} else {
mView = new InlineContentView(mContext);
mView.setLayoutParams(new ViewGroup.LayoutParams(width, height));
- mView.setChildSurfacePackage(content);
+ mView.setChildSurfacePackageUpdater(getSurfacePackageUpdater());
mCallbackExecutor.execute(() -> mCallback.accept(mView));
}
}
+ /**
+ * Called when any subsequent SurfacePackage is returned from the remote view owning
+ * process.
+ */
+ @MainThread
+ private void handleOnSurfacePackage(SurfaceControlViewHost.SurfacePackage surfacePackage) {
+ mSurfacePackage = surfacePackage;
+ if (mSurfacePackage != null && mSurfacePackageConsumer != null) {
+ mSurfacePackageConsumer.accept(mSurfacePackage);
+ mSurfacePackageConsumer = null;
+ }
+ }
+
+ @MainThread
+ private void handleOnSurfacePackageReleased() {
+ mSurfacePackage = null;
+ try {
+ mInlineContentProvider.onSurfacePackageReleased();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error calling onSurfacePackageReleased(): " + e);
+ }
+ }
+
+ @MainThread
+ private void handleGetSurfacePackage(
+ Consumer<SurfaceControlViewHost.SurfacePackage> consumer) {
+ if (mSurfacePackage != null) {
+ consumer.accept(mSurfacePackage);
+ } else {
+ mSurfacePackageConsumer = consumer;
+ try {
+ mInlineContentProvider.requestSurfacePackage();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error calling getSurfacePackage(): " + e);
+ consumer.accept(null);
+ mSurfacePackageConsumer = null;
+ }
+ }
+ }
+
+ private InlineContentView.SurfacePackageUpdater getSurfacePackageUpdater() {
+ return new InlineContentView.SurfacePackageUpdater() {
+ @Override
+ public void onSurfacePackageReleased() {
+ mMainHandler.post(
+ () -> InlineContentCallbackImpl.this.handleOnSurfacePackageReleased());
+ }
+
+ @Override
+ public void getSurfacePackage(
+ Consumer<SurfaceControlViewHost.SurfacePackage> consumer) {
+ mMainHandler.post(
+ () -> InlineContentCallbackImpl.this.handleGetSurfacePackage(consumer));
+ }
+ };
+ }
+
@BinderThread
public void onClick() {
- if (mView != null && mView.hasOnClickListeners()) {
- mView.callOnClick();
- }
+ mMainHandler.post(() -> {
+ if (mView != null && mView.hasOnClickListeners()) {
+ mView.callOnClick();
+ }
+ });
}
@BinderThread
public void onLongClick() {
- if (mView != null && mView.hasOnLongClickListeners()) {
- mView.performLongClick();
- }
+ mMainHandler.post(() -> {
+ if (mView != null && mView.hasOnLongClickListeners()) {
+ mView.performLongClick();
+ }
+ });
}
}
@@ -262,6 +405,7 @@ public final class InlineSuggestion implements Parcelable {
+
// Code below generated by codegen v1.0.15.
//
// DO NOT MODIFY!
@@ -302,6 +446,14 @@ public final class InlineSuggestion implements Parcelable {
}
/**
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @Nullable IInlineContentProvider getContentProvider() {
+ return mContentProvider;
+ }
+
+ /**
* Used to keep a strong reference to the callback so it doesn't get garbage collected.
*
* @hide
@@ -421,7 +573,7 @@ public final class InlineSuggestion implements Parcelable {
};
@DataClass.Generated(
- time = 1587771173367L,
+ time = 1588308946517L,
codegenVersion = "1.0.15",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java",
inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\nprivate @com.android.internal.util.DataClass.ParcelWith(android.view.inputmethod.InlineSuggestion.InlineContentCallbackImplParceling.class) @android.annotation.Nullable android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl mInlineContentCallback\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestion newInlineSuggestion(android.view.inputmethod.InlineSuggestionInfo)\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.widget.inline.InlineContentView>)\nprivate static boolean isValid(int,int,int)\nprivate synchronized android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl getInlineContentCallback(android.content.Context,java.util.concurrent.Executor,java.util.function.Consumer<android.widget.inline.InlineContentView>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
diff --git a/core/java/android/widget/inline/InlineContentView.java b/core/java/android/widget/inline/InlineContentView.java
index 4f2af63626cf..8657e828a3f6 100644
--- a/core/java/android/widget/inline/InlineContentView.java
+++ b/core/java/android/widget/inline/InlineContentView.java
@@ -21,40 +21,45 @@ import android.annotation.Nullable;
import android.content.Context;
import android.graphics.PixelFormat;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup;
+import java.util.function.Consumer;
+
/**
- * This class represents a view that holds opaque content from another app that
- * you can inline in your UI.
+ * This class represents a view that holds opaque content from another app that you can inline in
+ * your UI.
*
* <p>Since the content presented by this view is from another security domain,it is
- * shown on a remote surface preventing the host application from accessing that content.
- * Also the host application cannot interact with the inlined content by injecting touch
- * events or clicking programmatically.
+ * shown on a remote surface preventing the host application from accessing that content. Also the
+ * host application cannot interact with the inlined content by injecting touch events or clicking
+ * programmatically.
*
* <p>This view can be overlaid by other windows, i.e. redressed, but if this is the case
- * the inined UI would not be interactive. Sometimes this is desirable, e.g. animating
- * transitions.
+ * the inlined UI would not be interactive. Sometimes this is desirable, e.g. animating transitions.
*
* <p>By default the surface backing this view is shown on top of the hosting window such
- * that the inlined content is interactive. However, you can temporarily move the surface
- * under the hosting window which could be useful in some cases, e.g. animating transitions.
- * At this point the inlined content will not be interactive and the touch events would
- * be delivered to your app.
- * <p>
- * Instances of this class are created by the platform and can be programmatically attached
- * to your UI. Once you attach and detach this view it can not longer be reused and you
- * should obtain a new view from the platform via the dedicated APIs.
+ * that the inlined content is interactive. However, you can temporarily move the surface under the
+ * hosting window which could be useful in some cases, e.g. animating transitions. At this point the
+ * inlined content will not be interactive and the touch events would be delivered to your app.
+ *
+ * <p> Instances of this class are created by the platform and can be programmatically attached to
+ * your UI. Once the view is attached to the window, you may detach and reattach it to the window.
+ * It should work seamlessly from the hosting process's point of view.
*/
public class InlineContentView extends ViewGroup {
+ private static final String TAG = "InlineContentView";
+
+ private static final boolean DEBUG = false;
+
/**
- * Callback for observing the lifecycle of the surface control
- * that manipulates the backing secure embedded UI surface.
+ * Callback for observing the lifecycle of the surface control that manipulates the backing
+ * secure embedded UI surface.
*/
public interface SurfaceControlCallback {
/**
@@ -72,15 +77,41 @@ public class InlineContentView extends ViewGroup {
void onDestroyed(@NonNull SurfaceControl surfaceControl);
}
- private final @NonNull SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
+ /**
+ * Callback for sending an updated surface package in case the previous one is released
+ * from the detached from window event, and for getting notified of such event.
+ *
+ * This is expected to be provided to the {@link InlineContentView} so it can get updates
+ * from and send updates to the remote content (i.e. surface package) provider.
+ *
+ * @hide
+ */
+ public interface SurfacePackageUpdater {
+
+ /**
+ * Called when the previous surface package is released due to view being detached
+ * from the window.
+ */
+ void onSurfacePackageReleased();
+
+ /**
+ * Called to request an updated surface package.
+ *
+ * @param consumer consumes the updated surface package.
+ */
+ void getSurfacePackage(Consumer<SurfaceControlViewHost.SurfacePackage> consumer);
+ }
+
+ @NonNull
+ private final SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
mSurfaceControlCallback.onCreated(mSurfaceView.getSurfaceControl());
}
@Override
- public void surfaceChanged(@NonNull SurfaceHolder holder,
- int format, int width, int height) {
+ public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
+ int height) {
/* do nothing */
}
@@ -90,13 +121,17 @@ public class InlineContentView extends ViewGroup {
}
};
- private final @NonNull SurfaceView mSurfaceView;
+ @NonNull
+ private final SurfaceView mSurfaceView;
+
+ @Nullable
+ private SurfaceControlCallback mSurfaceControlCallback;
- private @Nullable SurfaceControlCallback mSurfaceControlCallback;
+ @Nullable
+ private SurfacePackageUpdater mSurfacePackageUpdater;
/**
* @inheritDoc
- *
* @hide
*/
public InlineContentView(@NonNull Context context) {
@@ -105,7 +140,6 @@ public class InlineContentView extends ViewGroup {
/**
* @inheritDoc
- *
* @hide
*/
public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs) {
@@ -114,7 +148,6 @@ public class InlineContentView extends ViewGroup {
/**
* @inheritDoc
- *
* @hide
*/
public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs,
@@ -123,20 +156,18 @@ public class InlineContentView extends ViewGroup {
}
/**
- * Gets the surface control. If the surface is not created this method
- * returns {@code null}.
+ * Gets the surface control. If the surface is not created this method returns {@code null}.
*
* @return The surface control.
- *
* @see #setSurfaceControlCallback(SurfaceControlCallback)
*/
- public @Nullable SurfaceControl getSurfaceControl() {
+ @Nullable
+ public SurfaceControl getSurfaceControl() {
return mSurfaceView.getSurfaceControl();
}
/**
* @inheritDoc
- *
* @hide
*/
public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs,
@@ -149,14 +180,35 @@ public class InlineContentView extends ViewGroup {
}
/**
- * Sets the embedded UI.
- * @param surfacePackage The embedded UI.
+ * Sets the embedded UI provider.
*
* @hide
*/
- public void setChildSurfacePackage(
- @Nullable SurfaceControlViewHost.SurfacePackage surfacePackage) {
- mSurfaceView.setChildSurfacePackage(surfacePackage);
+ public void setChildSurfacePackageUpdater(
+ @Nullable SurfacePackageUpdater surfacePackageUpdater) {
+ mSurfacePackageUpdater = surfacePackageUpdater;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ if (DEBUG) Log.v(TAG, "onAttachedToWindow");
+ super.onAttachedToWindow();
+ if (mSurfacePackageUpdater != null) {
+ mSurfacePackageUpdater.getSurfacePackage(
+ sp -> {
+ if (DEBUG) Log.v(TAG, "Received new SurfacePackage");
+ mSurfaceView.setChildSurfacePackage(sp);
+ });
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ if (DEBUG) Log.v(TAG, "onDetachedFromWindow");
+ super.onDetachedFromWindow();
+ if (mSurfacePackageUpdater != null) {
+ mSurfacePackageUpdater.onSurfacePackageReleased();
+ }
}
@Override
@@ -165,8 +217,8 @@ public class InlineContentView extends ViewGroup {
}
/**
- * Sets a callback to observe the lifecycle of the surface control for
- * managing the backing surface.
+ * Sets a callback to observe the lifecycle of the surface control for managing the backing
+ * surface.
*
* @param callback The callback to set or {@code null} to clear.
*/
@@ -182,7 +234,6 @@ public class InlineContentView extends ViewGroup {
/**
* @return Whether the surface backing this view appears on top of its parent.
- *
* @see #setZOrderedOnTop(boolean)
*/
public boolean isZOrderedOnTop() {
@@ -190,17 +241,15 @@ public class InlineContentView extends ViewGroup {
}
/**
- * Controls whether the backing surface is placed on top of this view's window.
- * Normally, it is placed on top of the window, to allow interaction
- * with the inlined UI. Via this method, you can place the surface below the
- * window. This means that all of the contents of the window this view is in
- * will be visible on top of its surface.
+ * Controls whether the backing surface is placed on top of this view's window. Normally, it is
+ * placed on top of the window, to allow interaction with the inlined UI. Via this method, you
+ * can place the surface below the window. This means that all of the contents of the window
+ * this view is in will be visible on top of its surface.
*
* <p> The Z ordering can be changed dynamically if the backing surface is
* created, otherwise the ordering would be applied at surface construction time.
*
* @param onTop Whether to show the surface on top of this view's window.
- *
* @see #isZOrderedOnTop()
*/
public boolean setZOrderedOnTop(boolean onTop) {