Loading services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java +33 −13 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading Loading @@ -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); } } /** Loading Loading @@ -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") Loading Loading @@ -731,7 +748,6 @@ final class AutoFillManagerServiceImpl { filterText = text.toString(); } } getUiForShowing().showFillUi(mActivityToken, viewState, response.getDatasets(), bounds, filterText); } Loading @@ -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) { Loading @@ -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; } Loading @@ -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) { Loading Loading @@ -817,6 +836,7 @@ final class AutoFillManagerServiceImpl { @Override public void onFailure(CharSequence message) { // TODO(b/33197203): call enableSessionLocked(false) getUiForShowing().showError(message); removeSelf(); } Loading @@ -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); Loading services/autofill/java/com/android/server/autofill/AutoFillUI.java +67 −157 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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); } Loading Loading @@ -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); Loading Loading @@ -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. // ///////////////////////////////////////// } }} services/autofill/java/com/android/server/autofill/SignInPrompt.java 0 → 100644 +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); } } Loading
services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java +33 −13 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading Loading @@ -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); } } /** Loading Loading @@ -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") Loading Loading @@ -731,7 +748,6 @@ final class AutoFillManagerServiceImpl { filterText = text.toString(); } } getUiForShowing().showFillUi(mActivityToken, viewState, response.getDatasets(), bounds, filterText); } Loading @@ -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) { Loading @@ -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; } Loading @@ -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) { Loading Loading @@ -817,6 +836,7 @@ final class AutoFillManagerServiceImpl { @Override public void onFailure(CharSequence message) { // TODO(b/33197203): call enableSessionLocked(false) getUiForShowing().showError(message); removeSelf(); } Loading @@ -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); Loading
services/autofill/java/com/android/server/autofill/AutoFillUI.java +67 −157 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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); } Loading Loading @@ -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); Loading Loading @@ -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. // ///////////////////////////////////////// } }}
services/autofill/java/com/android/server/autofill/SignInPrompt.java 0 → 100644 +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); } }