Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5d21de69 authored by Eugene Susla's avatar Eugene Susla Committed by Android (Google) Code Review
Browse files

Merge "ServiceConnector impl + migration of RemoteFillService"

parents 8645b7d3 a081250c
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -24,7 +24,11 @@ import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;

/**
 * <p>Various utilities for debugging and logging.</p>
@@ -270,4 +274,20 @@ public class DebugUtils {
    private static String constNameWithoutPrefix(String prefix, Field field) {
        return field.getName().substring(prefix.length());
    }

    /**
     * Returns method names from current stack trace, where {@link StackTraceElement#getClass}
     * starts with the given classes name
     *
     * @hide
     */
    public static List<String> callersWithin(Class<?> cls, int offset) {
        List<String> result = Arrays.stream(Thread.currentThread().getStackTrace())
                .skip(offset + 3)
                .filter(st -> st.getClassName().startsWith(cls.getName()))
                .map(StackTraceElement::getMethodName)
                .collect(Collectors.toList());
        Collections.reverse(result);
        return result;
    }
}
+226 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.internal.infra;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Handler;
import android.os.Message;
import android.util.ExceptionUtils;
import android.util.Log;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.BiConsumer;
import java.util.function.Function;

/**
 * A customized {@link CompletableFuture} with focus on reducing the number of allocations involved
 * in a typical future usage scenario for Android.
 *
 * In particular this involves allocations optimizations in:
 * <ul>
 *     <li>{@link #thenCompose(Function)}</li>
 *     <li>{@link #orTimeout(long, TimeUnit)}</li>
 *     <li>{@link #whenComplete(BiConsumer)}</li>
 * </ul>
 *
 * @param <T> see {@link CompletableFuture}
 */
public class AndroidFuture<T> extends CompletableFuture<T> {

    private static final String LOG_TAG = AndroidFuture.class.getSimpleName();

    @GuardedBy("this")
    private @Nullable BiConsumer<? super T, ? super Throwable> mListener;
    private @NonNull Handler mTimeoutHandler = Handler.getMain();

    @Override
    public boolean complete(@Nullable T value) {
        boolean changed = super.complete(value);
        if (changed) {
            onCompleted(value, null);
        }
        return changed;
    }

    @Override
    public boolean completeExceptionally(@NonNull Throwable ex) {
        boolean changed = super.completeExceptionally(ex);
        if (changed) {
            onCompleted(null, ex);
        }
        return super.completeExceptionally(ex);
    }

    private void onCompleted(@Nullable T res, @Nullable Throwable err) {
        cancelTimeout();

        BiConsumer<? super T, ? super Throwable> listener;
        synchronized (this) {
            listener = mListener;
            mListener = null;
        }

        if (listener != null) {
            callListener(listener, res, err);
        }
    }

    @Override
    public AndroidFuture<T> whenComplete(
            @NonNull BiConsumer<? super T, ? super Throwable> action) {
        Preconditions.checkNotNull(action);
        synchronized (this) {
            if (!isDone()) {
                BiConsumer<? super T, ? super Throwable> oldListener = mListener;
                mListener = oldListener == null
                        ? action
                        : (res, err) -> {
                            callListener(oldListener, res, err);
                            callListener(action, res, err);
                        };
                return this;
            }
        }

        // isDone() == true at this point
        T res = null;
        Throwable err = null;
        try {
            res = get();
        } catch (ExecutionException e) {
            err = e.getCause();
        } catch (Throwable e) {
            err = e;
        }
        callListener(action, res, err);
        return this;
    }

    /**
     * Calls the provided listener, handling any exceptions that may arise.
     */
    // package-private to avoid synthetic method when called from lambda
    static <TT> void callListener(
            @NonNull BiConsumer<? super TT, ? super Throwable> listener,
            @Nullable TT res, @Nullable Throwable err) {
        try {
            try {
                listener.accept(res, err);
            } catch (Throwable t) {
                if (err == null) {
                    // listener happy-case threw, but exception case might not throw, so report the
                    // same exception thrown by listener's happy-path to it again
                    listener.accept(null, t);
                } else {
                    // listener exception-case threw
                    // give up on listener but preserve the original exception when throwing up
                    ExceptionUtils.getRootCause(t).initCause(err);
                    throw t;
                }
            }
        } catch (Throwable t2) {
            // give up on listener and log the result & exception to logcat
            Log.e(LOG_TAG, "Failed to call whenComplete listener. res = " + res, t2);
        }
    }

    /** @inheritDoc */
    //@Override //TODO uncomment once java 9 APIs are exposed to frameworks
    public AndroidFuture<T> orTimeout(long timeout, @NonNull TimeUnit unit) {
        Message msg = PooledLambda.obtainMessage(AndroidFuture::triggerTimeout, this);
        msg.obj = this;
        mTimeoutHandler.sendMessageDelayed(msg, unit.toMillis(timeout));
        return this;
    }

    void triggerTimeout() {
        cancelTimeout();
        if (!isDone()) {
            completeExceptionally(new TimeoutException());
        }
    }

    protected void cancelTimeout() {
        mTimeoutHandler.removeCallbacksAndMessages(this);
    }

    /**
     * Specifies the handler on which timeout is to be triggered
     */
    public AndroidFuture<T> setTimeoutHandler(@NonNull Handler h) {
        cancelTimeout();
        mTimeoutHandler = Preconditions.checkNotNull(h);
        return this;
    }

    @Override
    public <U> AndroidFuture<U> thenCompose(
            @NonNull Function<? super T, ? extends CompletionStage<U>> fn) {
        return (AndroidFuture<U>) new ThenCompose<>(this, fn);
    }

    private static class ThenCompose<T, U> extends AndroidFuture<Object>
            implements BiConsumer<Object, Throwable> {
        private final AndroidFuture<T> mSource;
        private Function<? super T, ? extends CompletionStage<U>> mFn;

        ThenCompose(@NonNull AndroidFuture<T> source,
                @NonNull Function<? super T, ? extends CompletionStage<U>> fn) {
            mSource = source;
            mFn = Preconditions.checkNotNull(fn);
            // subscribe to first job completion
            source.whenComplete(this);
        }

        @Override
        public void accept(Object res, Throwable err) {
            Function<? super T, ? extends CompletionStage<U>> fn;
            synchronized (this) {
                fn = mFn;
                mFn = null;
            }
            if (fn != null) {
                // first job completed
                CompletionStage<U> secondJob;
                try {
                    secondJob = Preconditions.checkNotNull(fn.apply((T) res));
                } catch (Throwable t) {
                    completeExceptionally(t);
                    return;
                }
                // subscribe to second job completion
                secondJob.whenComplete(this);
            } else {
                // second job completed
                if (err != null) {
                    completeExceptionally(err);
                } else {
                    complete(res);
                }
            }
        }
    }
}
+699 −0

File added.

Preview size limit exceeded, changes collapsed.

+2 −2
Original line number Diff line number Diff line
@@ -1112,7 +1112,7 @@ final class AutofillManagerServiceImpl
                            final RemoteAugmentedAutofillService remoteService =
                                    mRemoteAugmentedAutofillService;
                            if (remoteService != null) {
                                remoteService.destroy();
                                remoteService.unbind();
                            }
                            mRemoteAugmentedAutofillService = null;
                        }
@@ -1135,7 +1135,7 @@ final class AutofillManagerServiceImpl
                    Slog.v(TAG, "updateRemoteAugmentedAutofillService(): "
                            + "destroying old remote service");
                }
                mRemoteAugmentedAutofillService.destroy();
                mRemoteAugmentedAutofillService.unbind();
                mRemoteAugmentedAutofillService = null;
                mRemoteAugmentedAutofillServiceInfo = null;
                resetAugmentedAutofillWhitelistLocked();
+106 −188
Original line number Diff line number Diff line
@@ -25,9 +25,11 @@ import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.RemoteException;
@@ -42,30 +44,39 @@ import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;

import com.android.internal.infra.AbstractSinglePendingRequestRemoteService;
import com.android.internal.infra.AbstractRemoteService;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.infra.ServiceConnector;
import com.android.internal.os.IResultReceiver;

import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;

final class RemoteAugmentedAutofillService
        extends AbstractSinglePendingRequestRemoteService<RemoteAugmentedAutofillService,
            IAugmentedAutofillService> {
        extends ServiceConnector.Impl<IAugmentedAutofillService> {

    private static final String TAG = RemoteAugmentedAutofillService.class.getSimpleName();

    private final int mIdleUnbindTimeoutMs;
    private final int mRequestTimeoutMs;
    private final ComponentName mComponentName;

    RemoteAugmentedAutofillService(Context context, ComponentName serviceName,
            int userId, RemoteAugmentedAutofillServiceCallbacks callbacks,
            boolean bindInstantServiceAllowed, boolean verbose, int idleUnbindTimeoutMs,
            int requestTimeoutMs) {
        super(context, AugmentedAutofillService.SERVICE_INTERFACE, serviceName, userId, callbacks,
                context.getMainThreadHandler(),
                bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0, verbose);
        super(context,
                new Intent(AugmentedAutofillService.SERVICE_INTERFACE).setComponent(serviceName),
                bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0,
                userId, IAugmentedAutofillService.Stub::asInterface);
        mIdleUnbindTimeoutMs = idleUnbindTimeoutMs;
        mRequestTimeoutMs = requestTimeoutMs;
        mComponentName = serviceName;

        // Bind right away.
        scheduleBind();
        connect();
    }

    @Nullable
@@ -93,219 +104,126 @@ final class RemoteAugmentedAutofillService
        return new Pair<>(serviceInfo, serviceComponent);
    }

    @Override // from RemoteService
    protected void handleOnConnectedStateChanged(boolean state) {
        if (state && getTimeoutIdleBindMillis() != PERMANENT_BOUND_TIMEOUT_MS) {
            scheduleUnbind();
    public ComponentName getComponentName() {
        return mComponentName;
    }

    @Override // from ServiceConnector.Impl
    protected void onServiceConnectionStatusChanged(
            IAugmentedAutofillService service, boolean connected) {
        try {
            if (state) {
                mService.onConnected(sDebug, sVerbose);
            if (connected) {
                service.onConnected(sDebug, sVerbose);
            } else {
                mService.onDisconnected();
                service.onDisconnected();
            }
        } catch (Exception e) {
            Slog.w(mTag, "Exception calling onConnectedStateChanged(" + state + "): " + e);
        }
            Slog.w(TAG,
                    "Exception calling onServiceConnectionStatusChanged(" + connected + "): ", e);
        }

    @Override // from AbstractRemoteService
    protected IAugmentedAutofillService getServiceInterface(IBinder service) {
        return IAugmentedAutofillService.Stub.asInterface(service);
    }

    @Override // from AbstractRemoteService
    protected long getTimeoutIdleBindMillis() {
    protected long getAutoDisconnectTimeoutMs() {
        return mIdleUnbindTimeoutMs;
    }

    @Override // from AbstractRemoteService
    protected long getRemoteRequestMillis() {
        return mRequestTimeoutMs;
    }

    /**
     * Called by {@link Session} to request augmented autofill.
     */
    public void onRequestAutofillLocked(int sessionId, @NonNull IAutoFillManagerClient client,
            int taskId, @NonNull ComponentName activityComponent, @NonNull AutofillId focusedId,
            @Nullable AutofillValue focusedValue) {
        scheduleRequest(new PendingAutofillRequest(this, sessionId, client, taskId,
                activityComponent, focusedId, focusedValue));
    }
        long requestTime = SystemClock.elapsedRealtime();
        AtomicReference<ICancellationSignal> cancellationRef = new AtomicReference<>();

        postAsync(service -> {
            AndroidFuture<Void> requestAutofill = new AndroidFuture<>();
            // TODO(b/122728762): set cancellation signal, timeout (from both client and service),
            // cache IAugmentedAutofillManagerClient reference, etc...
            client.getAugmentedAutofillClient(new IResultReceiver.Stub() {
                @Override
    public String toString() {
        return "RemoteAugmentedAutofillService["
                + ComponentName.flattenToShortString(getComponentName()) + "]";
    }

    /**
     * Called by {@link Session} when it's time to destroy all augmented autofill requests.
     */
    public void onDestroyAutofillWindowsRequest() {
        scheduleAsyncRequest((s) -> s.onDestroyAllFillWindowsRequest());
    }

    private void dispatchOnFillTimeout(@NonNull ICancellationSignal cancellation) {
        mHandler.post(() -> {
            try {
                cancellation.cancel();
            } catch (RemoteException e) {
                Slog.w(mTag, "Error calling cancellation signal: " + e);
            }
        });
    }

    // TODO(b/123100811): inline into PendingAutofillRequest if it doesn't have any other subclass
    private abstract static class MyPendingRequest
            extends PendingRequest<RemoteAugmentedAutofillService, IAugmentedAutofillService> {
        protected final int mSessionId;

        private MyPendingRequest(@NonNull RemoteAugmentedAutofillService service, int sessionId) {
            super(service);
            mSessionId = sessionId;
        }
    }

    private static final class PendingAutofillRequest extends MyPendingRequest {
        private final @NonNull AutofillId mFocusedId;
        private final @Nullable AutofillValue mFocusedValue;
        private final @NonNull IAutoFillManagerClient mClient;
        private final @NonNull ComponentName mActivityComponent;
        private final int mTaskId;
        private final long mRequestTime = SystemClock.elapsedRealtime();
        private final @NonNull IFillCallback mCallback;
        private ICancellationSignal mCancellation;

        protected PendingAutofillRequest(@NonNull RemoteAugmentedAutofillService service,
                int sessionId, @NonNull IAutoFillManagerClient client, int taskId,
                @NonNull ComponentName activityComponent, @NonNull AutofillId focusedId,
                @Nullable AutofillValue focusedValue) {
            super(service, sessionId);
            mClient = client;
            mTaskId = taskId;
            mActivityComponent = activityComponent;
            mFocusedId = focusedId;
            mFocusedValue = focusedValue;
            mCallback = new IFillCallback.Stub() {
                public void send(int resultCode, Bundle resultData) throws RemoteException {
                    final IBinder realClient = resultData
                            .getBinder(AutofillManager.EXTRA_AUGMENTED_AUTOFILL_CLIENT);
                    service.onFillRequest(sessionId, realClient, taskId, activityComponent,
                            focusedId, focusedValue, requestTime, new IFillCallback.Stub() {
                                @Override
                                public void onSuccess() {
                    if (!finish()) return;
                    // NOTE: so far we don't need notify RemoteAugmentedAutofillServiceCallbacks
                }

                @Override
                public void onCancellable(ICancellationSignal cancellation) {
                    synchronized (mLock) {
                        final boolean cancelled;
                        synchronized (mLock) {
                            mCancellation = cancellation;
                            cancelled = isCancelledLocked();
                        }
                        if (cancelled) {
                            try {
                                cancellation.cancel();
                            } catch (RemoteException e) {
                                Slog.e(mTag, "Error requesting a cancellation", e);
                            }
                        }
                    }
                                    requestAutofill.complete(null);
                                }

                                @Override
                                public boolean isCompleted() {
                    return isRequestCompleted();
                                    return requestAutofill.isDone()
                                            && !requestAutofill.isCancelled();
                                }

                                @Override
                public void cancel() {
                    synchronized (mLock) {
                        final boolean cancelled = isCancelledLocked();
                        final ICancellationSignal cancellation = mCancellation;
                        if (!cancelled) {
                            try {
                                cancellation.cancel();
                            } catch (RemoteException e) {
                                Slog.e(mTag, "Error requesting a cancellation", e);
                            }
                        }
                    }
                                public void onCancellable(ICancellationSignal cancellation) {
                                    if (requestAutofill.isCancelled()) {
                                        dispatchCancellation(cancellation);
                                    } else {
                                        cancellationRef.set(cancellation);
                                    }
            };
                                }

                                @Override
        public void run() {
            synchronized (mLock) {
                if (isCancelledLocked()) {
                    if (sDebug) Slog.d(mTag, "run() called after canceled");
                    return;
                }
                                public void cancel() {
                                    requestAutofill.cancel(true);
                                }
            final RemoteAugmentedAutofillService remoteService = getService();
            if (remoteService == null) return;

            final IResultReceiver receiver = new IResultReceiver.Stub() {

                @Override
                public void send(int resultCode, Bundle resultData) throws RemoteException {
                    final IBinder realClient = resultData
                            .getBinder(AutofillManager.EXTRA_AUGMENTED_AUTOFILL_CLIENT);
                    remoteService.mService.onFillRequest(mSessionId, realClient, mTaskId,
                            mActivityComponent, mFocusedId, mFocusedValue, mRequestTime, mCallback);
                            });
                }
            };

            // TODO(b/122728762): set cancellation signal, timeout (from both mClient and service),
            // cache IAugmentedAutofillManagerClient reference, etc...
            try {
                mClient.getAugmentedAutofillClient(receiver);
            } catch (RemoteException e) {
            });
            return requestAutofill;
        }).orTimeout(mRequestTimeoutMs, TimeUnit.MILLISECONDS)
                .whenComplete((res, err) -> {
                    if (err instanceof CancellationException) {
                        dispatchCancellation(cancellationRef.get());
                    } else if (err instanceof TimeoutException) {
                        // TODO(b/122858578): must update the logged AUTOFILL_AUGMENTED_REQUEST with
                        // the timeout
                        Slog.w(TAG, "PendingAutofillRequest timed out (" + mRequestTimeoutMs
                                + "ms) for " + RemoteAugmentedAutofillService.this);
                        // NOTE: so far we don't need notify RemoteAugmentedAutofillServiceCallbacks
                        dispatchCancellation(cancellationRef.get());
                    } else if (err != null) {
                        Slog.e(TAG, "exception handling getAugmentedAutofillClient() for "
                        + mSessionId + ": " + e);
                finish();
            }
        }

        @Override
        protected void onTimeout(RemoteAugmentedAutofillService remoteService) {
            // TODO(b/122858578): must update the logged AUTOFILL_AUGMENTED_REQUEST with the
            // timeout
            Slog.w(TAG, "PendingAutofillRequest timed out (" + remoteService.mRequestTimeoutMs
                    + "ms) for " + remoteService);
                                + sessionId + ": ", err);
                    } else {
                        // NOTE: so far we don't need notify RemoteAugmentedAutofillServiceCallbacks
            final ICancellationSignal cancellation;
            synchronized (mLock) {
                cancellation = mCancellation;
            }
            if (cancellation != null) {
                remoteService.dispatchOnFillTimeout(cancellation);
                    }
            finish();
                });
    }

        @Override
        public boolean cancel() {
            if (!super.cancel()) return false;

            final ICancellationSignal cancellation;
            synchronized (mLock) {
                cancellation = mCancellation;
    void dispatchCancellation(@Nullable ICancellationSignal cancellation) {
        if (cancellation == null) {
            return;
        }
            if (cancellation != null) {
        Handler.getMain().post(() -> {
            try {
                cancellation.cancel();
            } catch (RemoteException e) {
                    Slog.e(mTag, "Error cancelling a fill request", e);
                Slog.e(TAG, "Error requesting a cancellation", e);
            }
        });
    }
            return true;

    @Override
    public String toString() {
        return "RemoteAugmentedAutofillService["
                + ComponentName.flattenToShortString(mComponentName) + "]";
    }

    /**
     * Called by {@link Session} when it's time to destroy all augmented autofill requests.
     */
    public void onDestroyAutofillWindowsRequest() {
        fireAndForget((s) -> s.onDestroyAllFillWindowsRequest());
    }

    public interface RemoteAugmentedAutofillServiceCallbacks
            extends VultureCallback<RemoteAugmentedAutofillService> {
            extends AbstractRemoteService.VultureCallback<RemoteAugmentedAutofillService> {
        // NOTE: so far we don't need to notify the callback implementation (an inner class on
        // AutofillManagerServiceImpl) of the request results (success, timeouts, etc..), so this
        // callback interface is empty.
Loading