// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser.externalauth; import android.app.Activity; import android.app.Dialog; import android.content.Context; import com.google.android.gms.common.GoogleApiAvailability; import org.chromium.base.ThreadUtils; import org.chromium.chrome.browser.metrics.LaunchMetrics.EnumeratedHistogramSample; import java.util.concurrent.atomic.AtomicBoolean; /** * Handles situations where Google Play Services encounters a user-recoverable * error. This is typically because Google Play Services requires an upgrade. * Three "canned" handlers are provided, with suggested use cases as described * below. * <br> * <strong>Silent</strong>: use this handler if the dependency is purely for * convenience and a (potentially suboptimal) fallback is available. * <br> * <strong>SystemNotification</strong>: use this handler if the dependency is * for a feature that the user isn't actively trying to access interactively. * To avoid excessively nagging the user, only one notification will ever be * shown during the lifetime of the process. * <br> * <strong>ModalDialog</strong>: use this handler if the dependency is * for a feature that the user is actively trying to access interactively where * the feature cannot function (or would be severely impaired) unless the * dependency is satisfied. The dialog will be presented as many times as the * user tries to access the feature. * <br> * If none of these behaviors is suitable, a new behavior can be defined by * subclassing this class. */ public abstract class UserRecoverableErrorHandler { private static final String ERROR_HANDLER_ACTION_HISTOGRAM_NAME = "GooglePlayServices.ErrorHandlerAction"; // Never remove or reorder histogram values. It is safe to append new values to the end. private static final int ERROR_HANDLER_ACTION_SILENT = 0; private static final int ERROR_HANDLER_ACTION_SYSTEM_NOTIFICATION = 1; private static final int ERROR_HANDLER_ACTION_MODAL_DIALOG = 2; private static final int ERROR_HANDLER_ACTION_IGNORED_AS_REDUNDANT = 3; private static final int ERROR_HANDLER_ACTION_HISTOGRAM_BOUNDARY = 4; private static final EnumeratedHistogramSample sErrorHandlerActionHistogramSample = new EnumeratedHistogramSample(ERROR_HANDLER_ACTION_HISTOGRAM_NAME, ERROR_HANDLER_ACTION_HISTOGRAM_BOUNDARY); /** * Handles the specified error code from Google Play Services. * This method must only be called on the UI thread. * This method asserts that it is being called on the UI thread, then calls * {@link #handle(Context, int)}. * @param context the context in which the error was encountered * @param errorCode the error code from Google Play Services */ public final void handleError(final Context context, final int errorCode) { ThreadUtils.assertOnUiThread(); handle(context, errorCode); } /** * This method is invoked by {@link #handleError(Context, int)} to do the * work appropriate for the subclass on the UI thread. * @param context the context in which the error was encountered * @param errorCode the error code from Google Play Services */ protected abstract void handle(final Context context, final int errorCode); /** * A handler that does nothing. */ public static final class Silent extends UserRecoverableErrorHandler { @Override protected final void handle(final Context context, final int errorCode) { sErrorHandlerActionHistogramSample.record(ERROR_HANDLER_ACTION_SILENT); } } /** * A handler that displays a System Notification. To avoid repeatedly * nagging the user, this is done at most one time per application * lifecycle. The system notification is shown by calling * {@link GoogleApiAvailability#showErrorNotification(int, Context)}. * @see GoogleApiAvailability#showErrorNotification(Context, int) */ public static final class SystemNotification extends UserRecoverableErrorHandler { /** * Tracks whether the notification has yet been shown, used to ensure * that the notification is shown at most one time per application * lifecycle. */ private static final AtomicBoolean sNotificationShown = new AtomicBoolean(false); @Override protected void handle(final Context context, final int errorCode) { if (!sNotificationShown.getAndSet(true)) { sErrorHandlerActionHistogramSample .record(ERROR_HANDLER_ACTION_IGNORED_AS_REDUNDANT); return; } GoogleApiAvailability.getInstance().showErrorNotification(context, errorCode); sErrorHandlerActionHistogramSample.record(ERROR_HANDLER_ACTION_SYSTEM_NOTIFICATION); } } /** * A handler that displays a modal dialog. Unlike * {@link SystemNotification}, this handler will take action every time it * is invoked. * @see GoogleApiAvailability#getErrorDialog(Activity, int, int, * android.content.DialogInterface.OnCancelListener) */ public static class ModalDialog extends UserRecoverableErrorHandler { private static final int NO_RESPONSE_REQUIRED = -1; /** * The activity from which to start the dialog and any subsequent * actions, and the activity which will receive the response from those * actions. */ private final Activity mActivity; /** * Create a new Modal Dialog handler for the specified activity and error code. The * specified activity may be used to launch the dialog via * {@link Activity#startActivityForResult(android.content.Intent, int)} and also to receive * the result via Activity's protected onActivityResult method. * * @param activity the activity to use */ public ModalDialog(Activity activity) { mActivity = activity; } /** * Displays the dialog in a modal manner using * {@link GoogleApiAvailability#showErrorDialog(int, Activity, int)}. * @param context the context in which the error was encountered * @param errorCode the error code from Google Play Services */ @Override protected final void handle(final Context context, final int errorCode) { final Dialog dialog = GoogleApiAvailability.getInstance().getErrorDialog( mActivity, errorCode, NO_RESPONSE_REQUIRED); if (dialog != null) { // This can happen if |errorCode| is ConnectionResult.SERVICE_INVALID. dialog.show(); } sErrorHandlerActionHistogramSample.record(ERROR_HANDLER_ACTION_MODAL_DIALOG); } } }