SaveUi.java revision 56d86dd775b7239db9b2289bf1994b3dc2924d61
1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.autofill.ui; 18 19import static com.android.server.autofill.ui.Helper.DEBUG; 20 21import android.annotation.NonNull; 22import android.app.Dialog; 23import android.content.Context; 24import android.content.IntentSender; 25import android.os.Handler; 26import android.service.autofill.SaveInfo; 27import android.util.ArraySet; 28import android.util.Slog; 29import android.view.Gravity; 30import android.view.Window; 31import android.view.WindowManager; 32import android.widget.TextView; 33import android.view.LayoutInflater; 34import android.view.View; 35 36import com.android.internal.R; 37import com.android.server.UiThread; 38 39/** 40 * Autofill Save Prompt 41 */ 42final class SaveUi { 43 44 private static final String TAG = "SaveUi"; 45 46 public interface OnSaveListener { 47 void onSave(); 48 void onCancel(IntentSender listener); 49 void onDestroy(); 50 } 51 52 private class OneTimeListener implements OnSaveListener { 53 54 private final OnSaveListener mRealListener; 55 private boolean mDone; 56 57 OneTimeListener(OnSaveListener realListener) { 58 mRealListener = realListener; 59 } 60 61 @Override 62 public void onSave() { 63 if (DEBUG) Slog.d(TAG, "onSave(): " + mDone); 64 if (mDone) { 65 return; 66 } 67 mDone = true; 68 mRealListener.onSave(); 69 } 70 71 @Override 72 public void onCancel(IntentSender listener) { 73 if (DEBUG) Slog.d(TAG, "onCancel(): " + mDone); 74 if (mDone) { 75 return; 76 } 77 mDone = true; 78 mRealListener.onCancel(listener); 79 } 80 81 @Override 82 public void onDestroy() { 83 if (DEBUG) Slog.d(TAG, "onDestroy(): " + mDone); 84 if (mDone) { 85 return; 86 } 87 mDone = true; 88 mRealListener.onDestroy(); 89 } 90 } 91 92 private final Handler mHandler = UiThread.getHandler(); 93 94 private final @NonNull Dialog mDialog; 95 96 private final @NonNull OneTimeListener mListener; 97 98 private boolean mDestroyed; 99 100 SaveUi(@NonNull Context context, @NonNull CharSequence providerLabel, @NonNull SaveInfo info, 101 @NonNull OnSaveListener listener) { 102 mListener = new OneTimeListener(listener); 103 104 final LayoutInflater inflater = LayoutInflater.from(context); 105 final View view = inflater.inflate(R.layout.autofill_save, null); 106 107 final TextView titleView = (TextView) view.findViewById(R.id.autofill_save_title); 108 109 final ArraySet<String> types = new ArraySet<>(3); 110 final int type = info.getType(); 111 112 if ((type & SaveInfo.SAVE_DATA_TYPE_PASSWORD) != 0) { 113 types.add(context.getString(R.string.autofill_save_type_password)); 114 } 115 if ((type & SaveInfo.SAVE_DATA_TYPE_ADDRESS) != 0) { 116 types.add(context.getString(R.string.autofill_save_type_address)); 117 } 118 if ((type & SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD) != 0) { 119 types.add(context.getString(R.string.autofill_save_type_credit_card)); 120 } 121 if ((type & SaveInfo.SAVE_DATA_TYPE_USERNAME) != 0) { 122 types.add(context.getString(R.string.autofill_save_type_username)); 123 } 124 if ((type & SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS) != 0) { 125 types.add(context.getString(R.string.autofill_save_type_email_address)); 126 } 127 128 final String title; 129 switch (types.size()) { 130 case 1: 131 title = context.getString(R.string.autofill_save_title_with_type, 132 types.valueAt(0), providerLabel); 133 break; 134 case 2: 135 title = context.getString(R.string.autofill_save_title_with_2types, 136 types.valueAt(0), types.valueAt(1), providerLabel); 137 break; 138 case 3: 139 title = context.getString(R.string.autofill_save_title_with_3types, 140 types.valueAt(0), types.valueAt(1), types.valueAt(2), providerLabel); 141 break; 142 default: 143 // Use generic if more than 3 or invalid type (size 0). 144 title = context.getString(R.string.autofill_save_title, providerLabel); 145 } 146 147 titleView.setText(title); 148 final CharSequence subTitle = info.getDescription(); 149 if (subTitle != null) { 150 final TextView subTitleView = (TextView) view.findViewById(R.id.autofill_save_subtitle); 151 subTitleView.setText(subTitle); 152 subTitleView.setVisibility(View.VISIBLE); 153 } 154 155 if (DEBUG) { 156 Slog.d(TAG, "Title: " + title + " SubTitle: " + subTitle); 157 } 158 159 final TextView noButton = view.findViewById(R.id.autofill_save_no); 160 if (info.getNegativeActionTitle() != null) { 161 noButton.setText(info.getNegativeActionTitle()); 162 noButton.setOnClickListener((v) -> mListener.onCancel( 163 info.getNegativeActionListener())); 164 } else { 165 noButton.setOnClickListener((v) -> mListener.onCancel(null)); 166 } 167 168 final View yesButton = view.findViewById(R.id.autofill_save_yes); 169 yesButton.setOnClickListener((v) -> mListener.onSave()); 170 171 final View closeButton = view.findViewById(R.id.autofill_save_close); 172 closeButton.setOnClickListener((v) -> mListener.onCancel(null)); 173 174 mDialog = new Dialog(context, R.style.Theme_Material_Panel); 175 mDialog.setContentView(view); 176 177 final Window window = mDialog.getWindow(); 178 window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); 179 window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 180 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 181 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 182 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH); 183 window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); 184 window.setGravity(Gravity.BOTTOM | Gravity.CENTER); 185 window.setCloseOnTouchOutside(true); 186 final WindowManager.LayoutParams params = window.getAttributes(); 187 params.width = WindowManager.LayoutParams.MATCH_PARENT; 188 params.accessibilityTitle = context.getString(R.string.autofill_save_accessibility_title); 189 190 mDialog.show(); 191 } 192 193 void destroy() { 194 throwIfDestroyed(); 195 mListener.onDestroy(); 196 mHandler.removeCallbacksAndMessages(mListener); 197 mDialog.dismiss(); 198 mDestroyed = true; 199 } 200 201 private void throwIfDestroyed() { 202 if (mDestroyed) { 203 throw new IllegalStateException("cannot interact with a destroyed instance"); 204 } 205 } 206} 207