AutoFillUI.java revision b078212a1eb33d68f821b2aae0b18004e0c7c17e
1/* 2 * Copyright (C) 2016 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 */ 16package com.android.server.autofill.ui; 17 18import static com.android.server.autofill.Helper.sDebug; 19import static com.android.server.autofill.Helper.sVerbose; 20 21import android.annotation.NonNull; 22import android.annotation.Nullable; 23import android.content.Context; 24import android.content.IntentSender; 25import android.metrics.LogMaker; 26import android.os.Bundle; 27import android.os.Handler; 28import android.service.autofill.Dataset; 29import android.service.autofill.FillResponse; 30import android.service.autofill.SaveInfo; 31import android.service.autofill.ValueFinder; 32import android.text.TextUtils; 33import android.util.Slog; 34import android.view.autofill.AutofillId; 35import android.view.autofill.AutofillManager; 36import android.view.autofill.IAutofillWindowPresenter; 37import android.widget.Toast; 38 39import com.android.internal.logging.MetricsLogger; 40import com.android.internal.logging.nano.MetricsProto; 41import com.android.server.UiThread; 42 43import java.io.PrintWriter; 44 45/** 46 * Handles all autofill related UI tasks. The UI has two components: 47 * fill UI that shows a popup style window anchored at the focused 48 * input field for choosing a dataset to fill or trigger the response 49 * authentication flow; save UI that shows a toast style window for 50 * managing saving of user edits. 51 */ 52public final class AutoFillUI { 53 private static final String TAG = "AutofillUI"; 54 55 private final Handler mHandler = UiThread.getHandler(); 56 private final @NonNull Context mContext; 57 58 private @Nullable FillUi mFillUi; 59 private @Nullable SaveUi mSaveUi; 60 61 private @Nullable AutoFillUiCallback mCallback; 62 63 private final MetricsLogger mMetricsLogger = new MetricsLogger(); 64 65 private final @NonNull OverlayControl mOverlayControl; 66 67 public interface AutoFillUiCallback { 68 void authenticate(int requestId, int datasetIndex, @NonNull IntentSender intent, 69 @Nullable Bundle extras); 70 void fill(int requestId, int datasetIndex, @NonNull Dataset dataset); 71 void save(); 72 void cancelSave(); 73 void requestShowFillUi(AutofillId id, int width, int height, 74 IAutofillWindowPresenter presenter); 75 void requestHideFillUi(AutofillId id); 76 void startIntentSender(IntentSender intentSender); 77 } 78 79 public AutoFillUI(@NonNull Context context) { 80 mContext = context; 81 mOverlayControl = new OverlayControl(context); 82 } 83 84 public void setCallback(@NonNull AutoFillUiCallback callback) { 85 mHandler.post(() -> { 86 if (mCallback != callback) { 87 if (mCallback != null) { 88 hideAllUiThread(mCallback); 89 } 90 91 mCallback = callback; 92 } 93 }); 94 } 95 96 public void clearCallback(@NonNull AutoFillUiCallback callback) { 97 mHandler.post(() -> { 98 if (mCallback == callback) { 99 hideAllUiThread(callback); 100 mCallback = null; 101 } 102 }); 103 } 104 105 /** 106 * Displays an error message to the user. 107 */ 108 public void showError(int resId, @NonNull AutoFillUiCallback callback) { 109 showError(mContext.getString(resId), callback); 110 } 111 112 /** 113 * Displays an error message to the user. 114 */ 115 public void showError(@Nullable CharSequence message, @NonNull AutoFillUiCallback callback) { 116 Slog.w(TAG, "showError(): " + message); 117 118 mHandler.post(() -> { 119 if (mCallback != callback) { 120 return; 121 } 122 hideAllUiThread(callback); 123 if (!TextUtils.isEmpty(message)) { 124 Toast.makeText(mContext, message, Toast.LENGTH_LONG).show(); 125 } 126 }); 127 } 128 129 /** 130 * Hides the fill UI. 131 */ 132 public void hideFillUi(@NonNull AutoFillUiCallback callback) { 133 mHandler.post(() -> hideFillUiUiThread(callback)); 134 } 135 136 /** 137 * Filters the options in the fill UI. 138 * 139 * @param filterText The filter prefix. 140 */ 141 public void filterFillUi(@Nullable String filterText, @NonNull AutoFillUiCallback callback) { 142 mHandler.post(() -> { 143 if (callback != mCallback) { 144 return; 145 } 146 hideSaveUiUiThread(callback); 147 if (mFillUi != null) { 148 mFillUi.setFilterText(filterText); 149 } 150 }); 151 } 152 153 /** 154 * Shows the fill UI, removing the previous fill UI if the has changed. 155 * 156 * @param focusedId the currently focused field 157 * @param response the current fill response 158 * @param filterText text of the view to be filled 159 * @param packageName package name of the activity that is filled 160 * @param callback Identifier for the caller 161 */ 162 public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response, 163 @Nullable String filterText, @NonNull String packageName, 164 @NonNull AutoFillUiCallback callback) { 165 if (sDebug) { 166 final int size = filterText == null ? 0 : filterText.length(); 167 Slog.d(TAG, "showFillUi(): id=" + focusedId + ", filter=" + size + " chars"); 168 } 169 final LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_FILL_UI)) 170 .setPackageName(packageName) 171 .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN, 172 filterText == null ? 0 : filterText.length()) 173 .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, 174 response.getDatasets() == null ? 0 : response.getDatasets().size()); 175 176 mHandler.post(() -> { 177 if (callback != mCallback) { 178 return; 179 } 180 hideAllUiThread(callback); 181 mFillUi = new FillUi(mContext, response, focusedId, 182 filterText, mOverlayControl, new FillUi.Callback() { 183 @Override 184 public void onResponsePicked(FillResponse response) { 185 log.setType(MetricsProto.MetricsEvent.TYPE_DETAIL); 186 hideFillUiUiThread(callback); 187 if (mCallback != null) { 188 mCallback.authenticate(response.getRequestId(), 189 AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED, 190 response.getAuthentication(), response.getClientState()); 191 } 192 } 193 194 @Override 195 public void onDatasetPicked(Dataset dataset) { 196 log.setType(MetricsProto.MetricsEvent.TYPE_ACTION); 197 hideFillUiUiThread(callback); 198 if (mCallback != null) { 199 final int datasetIndex = response.getDatasets().indexOf(dataset); 200 mCallback.fill(response.getRequestId(), datasetIndex, dataset); 201 } 202 } 203 204 @Override 205 public void onCanceled() { 206 log.setType(MetricsProto.MetricsEvent.TYPE_DISMISS); 207 hideFillUiUiThread(callback); 208 } 209 210 @Override 211 public void onDestroy() { 212 if (log.getType() == MetricsProto.MetricsEvent.TYPE_UNKNOWN) { 213 log.setType(MetricsProto.MetricsEvent.TYPE_CLOSE); 214 } 215 mMetricsLogger.write(log); 216 } 217 218 @Override 219 public void requestShowFillUi(int width, int height, 220 IAutofillWindowPresenter windowPresenter) { 221 if (mCallback != null) { 222 mCallback.requestShowFillUi(focusedId, width, height, windowPresenter); 223 } 224 } 225 226 @Override 227 public void requestHideFillUi() { 228 if (mCallback != null) { 229 mCallback.requestHideFillUi(focusedId); 230 } 231 } 232 233 @Override 234 public void startIntentSender(IntentSender intentSender) { 235 if (mCallback != null) { 236 mCallback.startIntentSender(intentSender); 237 } 238 } 239 }); 240 }); 241 } 242 243 /** 244 * Shows the UI asking the user to save for autofill. 245 */ 246 public void showSaveUi(@NonNull CharSequence providerLabel, @NonNull SaveInfo info, 247 @NonNull ValueFinder valueFinder, @NonNull String packageName, 248 @NonNull AutoFillUiCallback callback) { 249 if (sVerbose) Slog.v(TAG, "showSaveUi() for " + packageName + ": " + info); 250 int numIds = 0; 251 numIds += info.getRequiredIds() == null ? 0 : info.getRequiredIds().length; 252 numIds += info.getOptionalIds() == null ? 0 : info.getOptionalIds().length; 253 254 LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_SAVE_UI)) 255 .setPackageName(packageName).addTaggedData( 256 MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_IDS, numIds); 257 258 mHandler.post(() -> { 259 if (callback != mCallback) { 260 return; 261 } 262 hideAllUiThread(callback); 263 mSaveUi = new SaveUi(mContext, providerLabel, info, valueFinder, mOverlayControl, 264 new SaveUi.OnSaveListener() { 265 @Override 266 public void onSave() { 267 log.setType(MetricsProto.MetricsEvent.TYPE_ACTION); 268 hideSaveUiUiThread(callback); 269 if (mCallback != null) { 270 mCallback.save(); 271 } 272 } 273 274 @Override 275 public void onCancel(IntentSender listener) { 276 log.setType(MetricsProto.MetricsEvent.TYPE_DISMISS); 277 hideSaveUiUiThread(callback); 278 if (listener != null) { 279 try { 280 listener.sendIntent(mContext, 0, null, null, null); 281 } catch (IntentSender.SendIntentException e) { 282 Slog.e(TAG, "Error starting negative action listener: " 283 + listener, e); 284 } 285 } 286 if (mCallback != null) { 287 mCallback.cancelSave(); 288 } 289 } 290 291 @Override 292 public void onDestroy() { 293 if (log.getType() == MetricsProto.MetricsEvent.TYPE_UNKNOWN) { 294 log.setType(MetricsProto.MetricsEvent.TYPE_CLOSE); 295 296 if (mCallback != null) { 297 mCallback.cancelSave(); 298 } 299 } 300 mMetricsLogger.write(log); 301 } 302 }); 303 }); 304 } 305 306 /** 307 * Hides all UI affordances. 308 */ 309 public void hideAll(@Nullable AutoFillUiCallback callback) { 310 mHandler.post(() -> hideAllUiThread(callback)); 311 } 312 313 public void dump(PrintWriter pw) { 314 pw.println("Autofill UI"); 315 final String prefix = " "; 316 final String prefix2 = " "; 317 if (mFillUi != null) { 318 pw.print(prefix); pw.println("showsFillUi: true"); 319 mFillUi.dump(pw, prefix2); 320 } else { 321 pw.print(prefix); pw.println("showsFillUi: false"); 322 } 323 if (mSaveUi != null) { 324 pw.print(prefix); pw.println("showsSaveUi: true"); 325 mSaveUi.dump(pw, prefix2); 326 } else { 327 pw.print(prefix); pw.println("showsSaveUi: false"); 328 } 329 } 330 331 @android.annotation.UiThread 332 private void hideFillUiUiThread(@Nullable AutoFillUiCallback callback) { 333 if (mFillUi != null && (callback == null || callback == mCallback)) { 334 mFillUi.destroy(); 335 mFillUi = null; 336 } 337 } 338 339 @android.annotation.UiThread 340 private void hideSaveUiUiThread(@Nullable AutoFillUiCallback callback) { 341 if (sVerbose) { 342 Slog.v(TAG, "hideSaveUiUiThread(): mSaveUi=" + mSaveUi + ", callback=" + callback 343 + ", mCallback=" + mCallback); 344 } 345 if (mSaveUi != null && (callback == null || callback == mCallback)) { 346 mSaveUi.destroy(); 347 mSaveUi = null; 348 } 349 } 350 351 @android.annotation.UiThread 352 private void hideAllUiThread(@Nullable AutoFillUiCallback callback) { 353 hideFillUiUiThread(callback); 354 hideSaveUiUiThread(callback); 355 } 356} 357