diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java index ad5cc20754bde..0dd891cd42352 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java @@ -923,7 +923,7 @@ final class AutoFillManagerServiceImpl { // TODO(b/33197203): temporarily hack: show the save notification after autofilled, // since save is not automatically detected yet. - mUi.showSaveUI(mUserId, mId); + mUi.showSaveNotification(mUserId, mId); removeSelf = false; } catch (RemoteException e) { Slog.w(TAG, "Error auto-filling activity: " + e); diff --git a/services/autofill/java/com/android/server/autofill/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/AutoFillUI.java index 6618a27304a49..ba0601cd7c14a 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillUI.java @@ -29,12 +29,16 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Rect; +import android.graphics.PixelFormat; import android.os.Binder; import android.os.Bundle; import android.util.Slog; import android.view.autofill.AutoFillId; import android.view.autofill.Dataset; import android.view.autofill.FillResponse; +import android.view.Gravity; +import android.view.View; +import android.view.WindowManager; import android.widget.Toast; import com.android.internal.annotations.GuardedBy; @@ -53,8 +57,16 @@ final class AutoFillUI { private final Context mContext; + private final WindowManager mWm; + + /** + * Custom snackbar UI used for saving autofill or other informational messages. + */ + private View mSnackbar; + AutoFillUI(Context context, AutoFillManagerService service, Object lock) { mContext = context; + mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); mService = service; mLock = lock; @@ -130,7 +142,20 @@ final class AutoFillUI { * Shows the UI asking the user to save for auto-fill. */ void showSaveUI(int userId, int sessionId) { - showSaveNotification(userId, sessionId); + showSnackbar(new SavePrompt(mContext, new SavePrompt.OnSaveListener() { + @Override + public void onSaveClick() { + hideSnackbar(); + synchronized (mLock) { + final AutoFillManagerServiceImpl service = getServiceLocked(userId); + service.requestSaveLocked(sessionId); + } + } + @Override + public void onCancelClick() { + hideSnackbar(); + } + })); } /** @@ -158,10 +183,8 @@ final class AutoFillUI { } private void onSaveRequested(int userId, int sessionId) { - synchronized (mLock) { - final AutoFillManagerServiceImpl service = getServiceLocked(userId); - service.requestSaveLocked(sessionId); - } + // TODO(b/33197203): displays the snack bar, until save notification is refactored + showSaveUI(userId, sessionId); } private void onDatasetPicked(int userId, Dataset dataset, int sessionId) { @@ -191,6 +214,34 @@ final class AutoFillUI { } } + //similar to a snackbar, but can be a bit custom since it is more than just text. This will + //allow two buttons for saving or not saving the autofill for instance as well. + private void showSnackbar(View snackBar) { + WindowManager.LayoutParams params = new WindowManager.LayoutParams( + WindowManager.LayoutParams.FILL_PARENT, + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, // TODO(b/33197203) use TYPE_AUTO_FILL + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN, + PixelFormat.TRANSLUCENT); + + params.gravity = Gravity.BOTTOM | Gravity.LEFT; + + UiThread.getHandler().runWithScissors(() -> { + mSnackbar = snackBar; + mWm.addView(mSnackbar, params); + }, 0); + } + + private void hideSnackbar() { + UiThread.getHandler().runWithScissors(() -> { + if (mSnackbar != null) { + mWm.removeView(mSnackbar); + mSnackbar = null; + } + }, 0); + } + ///////////////////////////////////////////////////////////////////////////////// // 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. // @@ -215,9 +266,9 @@ final class AutoFillUI { private static final String TYPE_SAVE = "save"; private static final String TYPE_AUTH_RESPONSE = "auth_response"; - @GuardedBy("mLock") + @GuardedBy("mServiceLock") private BroadcastReceiver mNotificationReceiver; - @GuardedBy("mLock") + @GuardedBy("mServiceLock") private final AutoFillManagerService mService; private final Object mLock; @@ -403,8 +454,8 @@ final class AutoFillUI { final PendingIntent savePendingIntent = PendingIntent.getBroadcast(mContext, ++sResultCode, saveIntent, PendingIntent.FLAG_ONE_SHOT); - final String title = "AutoFill Save"; - final String subTitle = "Tap notification to ask provider to save fields."; + final String title = "AutoFill Save Emulation"; + final String subTitle = "Tap notification to launch the save snackbar."; final Notification notification = newNotificationBuilder() .setAutoCancel(true) diff --git a/services/autofill/java/com/android/server/autofill/SavePrompt.java b/services/autofill/java/com/android/server/autofill/SavePrompt.java new file mode 100644 index 0000000000000..022d646bec0b0 --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/SavePrompt.java @@ -0,0 +1,92 @@ +/* + * 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.graphics.Color; +import android.widget.RelativeLayout; +import android.widget.RelativeLayout.LayoutParams; +import android.widget.TextView; +import android.view.View; + +/** + * Autofill Save Prompt + */ +final class SavePrompt extends RelativeLayout { + public interface OnSaveListener { + void onSaveClick(); + + void onCancelClick(); + } + + private final TextView mTextView; + private final TextView mNoButton; + private final TextView mYesButton; + private final OnSaveListener mListener; + + SavePrompt(Context context, OnSaveListener listener) { + super(context); + mListener = listener; + setBackgroundColor(Color.YELLOW); + + // TODO(b/33197203): move layout to XML + mTextView = new TextView(context); + final LayoutParams textParams = new LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + textParams.setMargins(50, 25, 50, 0); + mTextView.setLayoutParams(textParams); + // TODO(b/33197203): use R.string once final wording is done + mTextView.setText("Save for autofill?"); + mTextView.setId(View.generateViewId()); + + mNoButton = new TextView(context); + // TODO(b/33197203): use R.string once final wording is done + mNoButton.setText("No thanks"); + mNoButton.setBackgroundColor(Color.TRANSPARENT); + mNoButton.setAllCaps(true); + mNoButton.setOnClickListener((v) -> { + mListener.onCancelClick(); + }); + + mYesButton = new TextView(context); + // TODO(b/33197203): use R.string once final wording is done + mYesButton.setText("Save"); + mYesButton.setBackgroundColor(Color.TRANSPARENT); + mYesButton.setId(View.generateViewId()); + mYesButton.setAllCaps(true); + mYesButton.setOnClickListener((v) -> { + mListener.onSaveClick(); + }); + + addView(mTextView); + addView(mNoButton); + addView(mYesButton); + + final LayoutParams yesLayoutParams = new LayoutParams( + LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + yesLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); + yesLayoutParams.addRule(RelativeLayout.BELOW, mTextView.getId()); + yesLayoutParams.setMargins(25, 25, 50, 25); + mYesButton.setLayoutParams(yesLayoutParams); + final LayoutParams noLayoutParams = new LayoutParams( + LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + noLayoutParams.addRule(RelativeLayout.LEFT_OF, mYesButton.getId()); + noLayoutParams.addRule(RelativeLayout.BELOW, mTextView.getId()); + noLayoutParams.setMargins(50, 25, 25, 25); + mNoButton.setLayoutParams(noLayoutParams); + } +}