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

Commit f4de7400 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Use floating bar to show authentication sign-in request."

parents 858a5d99 b06621b7
Loading
Loading
Loading
Loading
+33 −13
Original line number Diff line number Diff line
@@ -360,13 +360,19 @@ final class AutoFillManagerServiceImpl {

        final AutoFillId mId;
        private final Listener mListener;
        // // TODO(b/33197203): does it really need a reference to the session's response?
        private FillResponse mResponse;
        // TODO(b/33197203): would not need a reference to response if it was an inner class of
        // Session...
        FillResponse mResponse;

        Intent mAuthIntent;

        ComponentName mServiceComponent;
        private AutoFillValue mAutoFillValue;
        private Rect mBounds;

        private boolean mValueUpdated;


        ViewState(AutoFillId id, Listener listener) {
            mId = id;
            mListener = listener;
@@ -380,6 +386,16 @@ final class AutoFillManagerServiceImpl {
            maybeCallOnFillReady();
        }

        /**
         * Used when a {@link FillResponse} requires authentication to be unlocked.
         */
        void setResponse(FillResponse response, ComponentName serviceComponent, Intent authIntent) {
            mAuthIntent = authIntent;
            mServiceComponent = serviceComponent;
            setResponse(response);
        }


        // TODO(b/33197203): need to refactor / rename / document this method to make it clear that
        // it can change  the value and update the UI; similarly, should replace code that
        // directly sets mAutoFilLValue to use encapsulation.
@@ -417,8 +433,9 @@ final class AutoFillManagerServiceImpl {
            pw.print(prefix); pw.print("value:" ); pw.println(mAutoFillValue);
            pw.print(prefix); pw.print("updated:" ); pw.println(mValueUpdated);
            pw.print(prefix); pw.print("bounds:" ); pw.println(mBounds);
            pw.print(prefix); pw.print("authIntent:" ); pw.println(mAuthIntent);
            pw.print(prefix); pw.print("serviceComponent:" ); pw.println(mServiceComponent);
        }

    }

    /**
@@ -451,7 +468,7 @@ final class AutoFillManagerServiceImpl {
        private final IAutoFillAppCallback mAppCallback;

        @GuardedBy("mLock")
        RemoteFillService mRemoteFillService;
        private RemoteFillService mRemoteFillService;

        // TODO(b/33197203): Get a response per view instead of per activity.
        @GuardedBy("mLock")
@@ -731,7 +748,6 @@ final class AutoFillManagerServiceImpl {
                    filterText = text.toString();
                }
            }

            getUiForShowing().showFillUi(mActivityToken, viewState, response.getDatasets(),
                    bounds, filterText);
        }
@@ -742,6 +758,12 @@ final class AutoFillManagerServiceImpl {

            // TODO(b/33197203): add MetricsLogger calls

            if (mCurrentViewState == null) {
                // TODO(b/33197203): temporary sanity check; should never happen
                Slog.w(TAG, "processResponseLocked(): mCurrentResponse is null");
                return;
            }

            mCurrentResponse = response;

            if (mCurrentResponse.getAuthentication() != null) {
@@ -762,13 +784,12 @@ final class AutoFillManagerServiceImpl {

                            @Override
                            public void onFailure(CharSequence message) {
                                // TODO(b/33197203): call enableSessionLocked(false)
                                getUiForShowing().showError(message);
                                removeSelf();
                            }
                        }));

                 getUiForShowing().showFillResponseAuthRequest(
                         mCurrentResponse.getAuthentication(), fillInIntent);
                mCurrentViewState.setResponse(mCurrentResponse, mComponent, fillInIntent);
                return;
            }

@@ -783,10 +804,8 @@ final class AutoFillManagerServiceImpl {
            }

            // TODO(b/33197203): Consider using mCurrentResponse, depends on partitioning design
            if (mCurrentViewState != null) {
            mCurrentViewState.setResponse(mCurrentResponse);
        }
        }

        void autoFill(Dataset dataset) {
            synchronized (mLock) {
@@ -817,6 +836,7 @@ final class AutoFillManagerServiceImpl {

                    @Override
                    public void onFailure(CharSequence message) {
                        // TODO(b/33197203): call enableSessionLocked(false)
                        getUiForShowing().showError(message);
                        removeSelf();
                    }
@@ -828,7 +848,7 @@ final class AutoFillManagerServiceImpl {

        private Intent createAuthFillInIntent(String itemId, AssistStructure structure,
                Bundle extras, FillCallback fillCallback) {
            Intent fillInIntent = new Intent();
            final Intent fillInIntent = new Intent();
            fillInIntent.putExtra(Intent.EXTRA_AUTO_FILL_ITEM_ID, itemId);
            fillInIntent.putExtra(Intent.EXTRA_AUTO_FILL_ASSIST_STRUCTURE, structure);
            fillInIntent.putExtra(Intent.EXTRA_AUTO_FILL_EXTRAS, extras);
+67 −157
Original line number Diff line number Diff line
@@ -18,29 +18,22 @@ package com.android.server.autofill;
import static com.android.server.autofill.Helper.DEBUG;

import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.util.ArraySet;
import android.os.Looper;
import android.text.format.DateUtils;
import android.util.ArraySet;
import android.util.Slog;
import android.view.autofill.Dataset;
import android.view.autofill.FillResponse;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.autofill.Dataset;
import android.widget.Toast;

import com.android.internal.os.HandlerCaller;
@@ -58,19 +51,15 @@ final class AutoFillUI {
    private static final long SNACK_BAR_LIFETIME_MS = 30 * DateUtils.SECOND_IN_MILLIS;
    private static final int MSG_HIDE_SNACK_BAR = 1;

    private static final String EXTRA_AUTH_INTENT_SENDER =
            "com.android.server.autofill.extra.AUTH_INTENT_SENDER";
    private static final String EXTRA_AUTH_FILL_IN_INTENT =
            "com.android.server.autofill.extra.AUTH_FILL_IN_INTENT";

    private final Context mContext;
    private final WindowManager mWm;

    // TODO(b/33197203) Fix locking - some state requires lock and some not - requires refactoring
    private final Object mLock = new Object();

    // Fill UI variables
    private AnchoredWindow mFillWindow;
    private DatasetPicker mFillView;
    private View mFillView;
    private ViewState mViewState;

    private AutoFillUiCallback mCallback;
@@ -158,23 +147,61 @@ final class AutoFillUI {

        UiThread.getHandler().runWithScissors(() -> {
            hideSnackbarUiThread();
            hideFillResponseAuthUiUiThread();
        }, 0);

        if (datasets == null) {
        if (datasets == null && viewState.mAuthIntent == null) {
            // TODO(b/33197203): shouldn't be called, but keeping the WTF for a while just to be
            // safe, otherwise it would crash system server...
            Slog.wtf(TAG, "showFillUI(): no dataset");
            return;
        }

        // TODO(b/33197203): should not display UI after we launched an authentication intent, since
        // we have no warranty the provider will call onFailure() if the authentication failed or
        // user dismissed the auth window
        // because if the service does not handle calling the callback,

        UiThread.getHandler().runWithScissors(() -> {
            // The dataset picker is only shown when authentication is not required...
            DatasetPicker datasetPicker = null;

            if (mViewState == null || !mViewState.mId.equals(viewState.mId)) {
                hideFillUiUiThread();

                mViewState = viewState;

                mFillView = new DatasetPicker(mContext, datasets,
                if (viewState.mAuthIntent != null) {
                    final String packageName = viewState.mServiceComponent.getPackageName();
                    CharSequence serviceName = null;
                    try {
                        final PackageManager pm = mContext.getPackageManager();
                        final ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
                        serviceName = pm.getApplicationLabel(info);
                    } catch (Exception e) {
                        Slog.w(TAG, "Could not get label for " + packageName + ": " + e);
                        serviceName = packageName;
                    }

                    mFillView = new SignInPrompt(mContext, serviceName, (e) -> {
                        final IntentSender intentSender = viewState.mResponse.getAuthentication();
                        final AutoFillUiCallback callback;
                        final Intent authIntent;
                        synchronized (mLock) {
                            callback = mCallback;
                            authIntent = viewState.mAuthIntent;
                            // Must reset the authentication intent so UI display the datasets after
                            // the user authenticated.
                            viewState.mAuthIntent = null;
                        }
                        if (callback != null) {
                            callback.authenticate(intentSender, authIntent);
                        } else {
                            // TODO(b/33197203): need to figure out why it's null sometimes
                            Slog.w(TAG, "no callback on showFillUi().auth for " + viewState.mId);
                        }
                    });

                } else {
                    mFillView = datasetPicker = new DatasetPicker(mContext, datasets,
                            (dataset) -> {
                                final AutoFillUiCallback callback;
                                synchronized (mLock) {
@@ -183,37 +210,22 @@ final class AutoFillUI {
                                if (callback != null) {
                                    callback.fill(dataset);
                                } else {
                                Slog.w(TAG, "null callback on showFillUi() for " + viewState.mId);
                                    // TODO(b/33197203): need to figure out why it's null sometimes
                                    Slog.w(TAG, "no callback on showFillUi() for " + viewState.mId);
                                }
                            hideFillUi();
                                hideFillUiUiThread();
                            });

                }
                mFillWindow = new AnchoredWindow(mWm, appToken, mFillView);

                if (DEBUG) Slog.d(TAG, "showFillUi(): view changed");
                if (DEBUG) Slog.d(TAG, "showFillUi(): view changed for: " + viewState.mId);
            }

            if (DEBUG) Slog.d(TAG, "showFillUi(): bounds=" + bounds + ", filterText=" + filterText);
            mFillView.update(filterText);
            mFillWindow.show(bounds);
        }, 0);
            if (datasetPicker != null) {
                datasetPicker.update(filterText);
            }
            mFillWindow.show(bounds);

    /**
     * Shows an UI affordance indicating that user action is required before a {@link FillResponse}
     * can be used.
     *
     * <p>It typically replaces the auto-fill bar with a message saying "Press fingerprint or tap to
     * autofill" or "Tap to autofill", depending on the value of {@code usesFingerprint}.
     */
    void showFillResponseAuthRequest(IntentSender intent, Intent fillInIntent) {
        if (!hasCallback()) {
            return;
        }
        hideAll();
        UiThread.getHandler().runWithScissors(() -> {
            // TODO(b/33197203): proper implementation
            showFillResponseAuthUiUiThread(intent, fillInIntent);
        }, 0);
    }

@@ -251,14 +263,12 @@ final class AutoFillUI {
        UiThread.getHandler().runWithScissors(() -> {
            hideSnackbarUiThread();
            hideFillUiUiThread();
            hideFillResponseAuthUiUiThread();
        }, 0);
    }

    void dump(PrintWriter pw) {
        pw.println("AufoFill UI");
        final String prefix = "  ";
        pw.print(prefix); pw.print("sResultCode: "); pw.println(sResultCode);
        pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
        pw.print(prefix); pw.print("mSnackBar: "); pw.println(mSnackbar);
        pw.print(prefix); pw.print("mViewState: "); pw.println(mViewState);
@@ -310,104 +320,4 @@ final class AutoFillUI {
        void authenticate(IntentSender intent, Intent fillInIntent);
        void fill(Dataset dataset);
        void save();
    }

    /////////////////////////////////////////////////////////////////////////////////
    // TODO(b/33197203): temporary code using a notification to request auto-fill. //
    // Will be removed once UX decide the right way to present it to the user.     //
    /////////////////////////////////////////////////////////////////////////////////

    // TODO(b/33197203): remove from frameworks/base/core/res/AndroidManifest.xml once not used
    private static final String NOTIFICATION_AUTO_FILL_INTENT =
            "com.android.internal.autofill.action.REQUEST_AUTOFILL";

    private BroadcastReceiver mNotificationReceiver;
    private final Object mLock = new Object();

    // Hack used to generate unique pending intents
    static int sResultCode = 0;

    private void ensureNotificationListener() {
        synchronized (mLock) {
            if (mNotificationReceiver == null) {
                mNotificationReceiver = new NotificationReceiver();
                mContext.registerReceiver(mNotificationReceiver,
                        new IntentFilter(NOTIFICATION_AUTO_FILL_INTENT));
            }
        }
    }

    final class NotificationReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            final AutoFillUiCallback callback;
            synchronized (mLock) {
                callback = mCallback;
            }
            if (callback != null) {
                IntentSender intentSender = intent.getParcelableExtra(EXTRA_AUTH_INTENT_SENDER);
                Intent fillInIntent = intent.getParcelableExtra(EXTRA_AUTH_FILL_IN_INTENT);
                callback.authenticate(intentSender, fillInIntent);
            }
            collapseStatusBar();
        }
    }

    @android.annotation.UiThread
    private void showFillResponseAuthUiUiThread(IntentSender intent, Intent fillInIntent) {
        final String title = "AutoFill Authentication";
        final StringBuilder subTitle = new StringBuilder("Provider require user authentication.\n");

        final Intent authIntent = new Intent(NOTIFICATION_AUTO_FILL_INTENT);
        authIntent.putExtra(EXTRA_AUTH_INTENT_SENDER, intent);
        authIntent.putExtra(EXTRA_AUTH_FILL_IN_INTENT, fillInIntent);

        final PendingIntent authPendingIntent = PendingIntent.getBroadcast(
                mContext, ++sResultCode, authIntent, PendingIntent.FLAG_ONE_SHOT);

        subTitle.append("Tap notification to launch its authentication UI.");

        final Notification.Builder notification = newNotificationBuilder()
                .setAutoCancel(true)
                .setOngoing(false)
                .setContentTitle(title)
                .setStyle(new Notification.BigTextStyle().bigText(subTitle.toString()))
                .setContentIntent(authPendingIntent);

        ensureNotificationListener();

        final long identity = Binder.clearCallingIdentity();
        try {
            NotificationManager.from(mContext).notify(0, notification.build());
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    @android.annotation.UiThread
    private void hideFillResponseAuthUiUiThread() {
        final long identity = Binder.clearCallingIdentity();
        try {
            NotificationManager.from(mContext).cancel(0);
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    private Notification.Builder newNotificationBuilder() {
        return new Notification.Builder(mContext)
                .setCategory(Notification.CATEGORY_SYSTEM)
                .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
                .setLocalOnly(true)
                .setColor(mContext.getColor(
                        com.android.internal.R.color.system_notification_accent_color));
    }

    private void collapseStatusBar() {
        final StatusBarManager sbm = (StatusBarManager) mContext.getSystemService("statusbar");
        sbm.collapsePanels();
    }
    /////////////////////////////////////////
    // End of temporary notification code. //
    /////////////////////////////////////////
}
    }}
+37 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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;

import android.content.Context;
import android.view.View;
import android.widget.Button;

/**
 * A view displaying the sign-in prompt for an auto-fill service.
 */
final class SignInPrompt extends Button {

    SignInPrompt(Context context, CharSequence serviceName, View.OnClickListener listener) {
        super(context);
        // TODO(b/33197203): use strings.xml
        final String text = "Sign in to " + serviceName + " to autofill";

        // TODO(b/33197203): polish UI / use better altenative than a button...

        setText(text);
        setOnClickListener(listener);
    }
}