blob: 7fbf4b9c590ca79d26045f68bc2dd8e601ad315b [file] [log] [blame]
/*
* 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 com.android.server.autofill.ui;
import static com.android.server.autofill.Helper.sVerbose;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Handler;
import android.util.Slog;
import com.android.internal.view.inline.IInlineContentCallback;
import com.android.internal.view.inline.IInlineContentProvider;
import com.android.server.FgThread;
/**
* We create one instance of this class for each {@link android.view.inputmethod.InlineSuggestion}
* instance. Each inline suggestion instance will only be sent to the remote IME process once. In
* case of filtering and resending the suggestion when keyboard state changes between hide and
* show, a new instance of this class will be created using {@link #copy()}, with the same backing
* {@link RemoteInlineSuggestionUi}. When the
* {@link #provideContent(int, int, IInlineContentCallback)} is called the first time (it's only
* allowed to be called at most once), the passed in width/height is used to determine whether
* the existing {@link RemoteInlineSuggestionUi} provided in the constructor can be reused, or a
* new one should be created to suit the new size requirement for the view. In normal cases,
* we should not expect the size requirement to change, although in theory the public API allows
* the IME to do that.
*
* <p>This design is to enable us to be able to reuse the backing remote view while still keeping
* the callbacks relatively well aligned. For example, if we allow multiple remote IME binder
* callbacks to call into one instance of this class, then binder A may call in with width/height
* X for which we create a view (i.e. {@link RemoteInlineSuggestionUi}) for it,
*
* See also {@link RemoteInlineSuggestionUi} for relevant information.
*/
final class InlineContentProviderImpl extends IInlineContentProvider.Stub {
// TODO(b/153615023): consider not holding strong reference to heavy objects in this stub, to
// avoid memory leak in case the client app is holding the remote reference for a longer
// time than expected. Essentially we need strong reference in the system process to
// the member variables, but weak reference to them in the IInlineContentProvider.Stub.
private static final String TAG = InlineContentProviderImpl.class.getSimpleName();
private final Handler mHandler = FgThread.getHandler();;
@NonNull
private final RemoteInlineSuggestionViewConnector mRemoteInlineSuggestionViewConnector;
@Nullable
private RemoteInlineSuggestionUi mRemoteInlineSuggestionUi;
private boolean mProvideContentCalled = false;
InlineContentProviderImpl(
@NonNull RemoteInlineSuggestionViewConnector remoteInlineSuggestionViewConnector,
@Nullable RemoteInlineSuggestionUi remoteInlineSuggestionUi) {
mRemoteInlineSuggestionViewConnector = remoteInlineSuggestionViewConnector;
mRemoteInlineSuggestionUi = remoteInlineSuggestionUi;
}
/**
* Returns a new instance of this class, with the same {@code mInlineSuggestionRenderer} and
* {@code mRemoteInlineSuggestionUi}. The latter may or may not be reusable depending on the
* size information provided when the client calls {@link #provideContent(int, int,
* IInlineContentCallback)}.
*/
@NonNull
public InlineContentProviderImpl copy() {
return new InlineContentProviderImpl(mRemoteInlineSuggestionViewConnector,
mRemoteInlineSuggestionUi);
}
/**
* Provides a SurfacePackage associated with the inline suggestion view to the IME. If such
* view doesn't exit, then create a new one. This method should be called once per lifecycle
* of this object. Any further calls to the method will be ignored.
*/
@Override
public void provideContent(int width, int height, IInlineContentCallback callback) {
mHandler.post(() -> handleProvideContent(width, height, callback));
}
@Override
public void requestSurfacePackage() {
mHandler.post(this::handleGetSurfacePackage);
}
@Override
public void onSurfacePackageReleased() {
mHandler.post(this::handleOnSurfacePackageReleased);
}
private void handleProvideContent(int width, int height, IInlineContentCallback callback) {
if (sVerbose) Slog.v(TAG, "handleProvideContent");
if (mProvideContentCalled) {
// This method should only be called once.
return;
}
mProvideContentCalled = true;
if (mRemoteInlineSuggestionUi == null || !mRemoteInlineSuggestionUi.match(width, height)) {
mRemoteInlineSuggestionUi = new RemoteInlineSuggestionUi(
mRemoteInlineSuggestionViewConnector,
width, height, mHandler);
}
mRemoteInlineSuggestionUi.setInlineContentCallback(callback);
mRemoteInlineSuggestionUi.requestSurfacePackage();
}
private void handleGetSurfacePackage() {
if (sVerbose) Slog.v(TAG, "handleGetSurfacePackage");
if (!mProvideContentCalled || mRemoteInlineSuggestionUi == null) {
// provideContent should be called first, and remote UI should not be null.
return;
}
mRemoteInlineSuggestionUi.requestSurfacePackage();
}
private void handleOnSurfacePackageReleased() {
if (sVerbose) Slog.v(TAG, "handleOnSurfacePackageReleased");
if (!mProvideContentCalled || mRemoteInlineSuggestionUi == null) {
// provideContent should be called first, and remote UI should not be null.
return;
}
mRemoteInlineSuggestionUi.surfacePackageReleased();
}
}