From 1e4dff2fa4be19b790d6be3e42c1442800fc4b16 Mon Sep 17 00:00:00 2001 From: Svetoslav Ganov Date: Tue, 14 Feb 2017 05:22:29 +0000 Subject: [PATCH] I will reimplement this correctly later - it conflicts with the CL with API changes and the deadline is tomorrow and I would like to avoid staying until midnight again. Change-Id: I1ec11eee2bfbca5e833140136ca22768130c6c43 --- .../autofill/AutoFillManagerServiceImpl.java | 46 +--- .../android/server/autofill/AutoFillUI.java | 222 ++++++++++++------ .../android/server/autofill/SignInPrompt.java | 37 --- 3 files changed, 169 insertions(+), 136 deletions(-) delete mode 100644 services/autofill/java/com/android/server/autofill/SignInPrompt.java diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java index ff567391058cc..9e0b31a76a6ee 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java @@ -353,19 +353,13 @@ final class AutoFillManagerServiceImpl { final AutoFillId mId; private final Listener mListener; - // TODO(b/33197203): would not need a reference to response if it was an inner class of - // Session... - FillResponse mResponse; - - Intent mAuthIntent; - - ComponentName mServiceComponent; + // // TODO(b/33197203): does it really need a reference to the session's response? + private FillResponse mResponse; private AutoFillValue mAutoFillValue; private Rect mBounds; private boolean mValueUpdated; - ViewState(AutoFillId id, Listener listener) { mId = id; mListener = listener; @@ -379,16 +373,6 @@ 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. @@ -426,9 +410,8 @@ 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); } + } /** @@ -461,7 +444,7 @@ final class AutoFillManagerServiceImpl { private final IAutoFillAppCallback mAppCallback; @GuardedBy("mLock") - private RemoteFillService mRemoteFillService; + RemoteFillService mRemoteFillService; // TODO(b/33197203): Get a response per view instead of per activity. @GuardedBy("mLock") @@ -741,6 +724,7 @@ final class AutoFillManagerServiceImpl { filterText = text.toString(); } } + getUiForShowing().showFillUi(mActivityToken, viewState, response.getDatasets(), bounds, filterText); } @@ -751,12 +735,6 @@ 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) { @@ -777,13 +755,14 @@ final class AutoFillManagerServiceImpl { @Override public void onFailure(CharSequence message) { - // TODO(b/33197203): call enableSessionLocked(false) getUiForShowing().showError(message); removeSelf(); } })); - mCurrentViewState.setResponse(mCurrentResponse, mComponent, fillInIntent); - return; + + getUiForShowing().showFillResponseAuthRequest( + mCurrentResponse.getAuthentication(), fillInIntent); + return; } final ArraySet savableIds = mCurrentResponse.getSavableIds(); @@ -797,7 +776,9 @@ final class AutoFillManagerServiceImpl { } // TODO(b/33197203): Consider using mCurrentResponse, depends on partitioning design - mCurrentViewState.setResponse(mCurrentResponse); + if (mCurrentViewState != null) { + mCurrentViewState.setResponse(mCurrentResponse); + } } void autoFill(Dataset dataset) { @@ -829,7 +810,6 @@ final class AutoFillManagerServiceImpl { @Override public void onFailure(CharSequence message) { - // TODO(b/33197203): call enableSessionLocked(false) getUiForShowing().showError(message); removeSelf(); } @@ -841,7 +821,7 @@ final class AutoFillManagerServiceImpl { private Intent createAuthFillInIntent(String itemId, AssistStructure structure, Bundle extras, FillCallback fillCallback) { - final Intent fillInIntent = new Intent(); + 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); diff --git a/services/autofill/java/com/android/server/autofill/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/AutoFillUI.java index f7e27b9b14d59..0763c745f1215 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillUI.java @@ -18,22 +18,29 @@ 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; @@ -51,15 +58,19 @@ 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 View mFillView; + private DatasetPicker mFillView; private ViewState mViewState; private AutoFillUiCallback mCallback; @@ -147,85 +158,62 @@ final class AutoFillUI { UiThread.getHandler().runWithScissors(() -> { hideSnackbarUiThread(); + hideFillResponseAuthUiUiThread(); }, 0); - if (datasets == null && viewState.mAuthIntent == null) { + if (datasets == 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; - 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 DatasetPicker(mContext, datasets, + (dataset) -> { + final AutoFillUiCallback callback; + synchronized (mLock) { + callback = mCallback; + } + if (callback != null) { + callback.fill(dataset); + } else { + Slog.w(TAG, "null callback on showFillUi() for " + viewState.mId); + } + hideFillUi(); + }); - 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) { - callback = mCallback; - } - if (callback != null) { - callback.fill(dataset); - } else { - // TODO(b/33197203): need to figure out why it's null sometimes - Slog.w(TAG, "no callback on showFillUi() for " + viewState.mId); - } - hideFillUiUiThread(); - }); - } mFillWindow = new AnchoredWindow(mWm, appToken, mFillView); - if (DEBUG) Slog.d(TAG, "showFillUi(): view changed for: " + viewState.mId); + if (DEBUG) Slog.d(TAG, "showFillUi(): view changed"); } - if (datasetPicker != null) { - datasetPicker.update(filterText); - } + if (DEBUG) Slog.d(TAG, "showFillUi(): bounds=" + bounds + ", filterText=" + filterText); + mFillView.update(filterText); mFillWindow.show(bounds); + }, 0); + } + /** + * Shows an UI affordance indicating that user action is required before a {@link FillResponse} + * can be used. + * + *

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); } @@ -263,12 +251,14 @@ 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); @@ -320,4 +310,104 @@ 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. // + ///////////////////////////////////////// +} diff --git a/services/autofill/java/com/android/server/autofill/SignInPrompt.java b/services/autofill/java/com/android/server/autofill/SignInPrompt.java deleted file mode 100644 index caf519c50ecb9..0000000000000 --- a/services/autofill/java/com/android/server/autofill/SignInPrompt.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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); - } -}