chromium/chrome/browser/image_descriptions/android/java/src/org/chromium/chrome/browser/image_descriptions/ImageDescriptionsController.java

// Copyright 2020 The Chromium Authors
// 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.image_descriptions;

import android.content.Context;

import org.jni_zero.NativeMethods;

import org.chromium.base.ResettersForTesting;
import org.chromium.base.shared_preferences.SharedPreferencesManager;
import org.chromium.chrome.browser.device.DeviceConditions;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.components.prefs.PrefService;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.content_public.browser.WebContents;
import org.chromium.net.ConnectionType;
import org.chromium.ui.accessibility.AccessibilityState;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.widget.Toast;

/**
 * Singleton class to control the Image Descriptions feature. This class can be used to initiate the
 * user flow, to turn the feature on/off and to update settings as needed.
 */
public class ImageDescriptionsController {
    // We display a "Don't ask again" choice if the user has selected the Just Once option 3 times.
    public static final int DONT_ASK_AGAIN_DISPLAY_LIMIT = 3;

    // Static instance of this singleton, lazily initialized during first getInstance() call.
    private static ImageDescriptionsController sInstance;

    private ImageDescriptionsControllerDelegate mDelegate;

    /**
     * Method to return the private instance of this singleton, lazily initialized.
     * @return      ImageDescriptionController instance
     */
    public static ImageDescriptionsController getInstance() {
        if (sInstance == null) {
            sInstance = new ImageDescriptionsController();
        }

        return sInstance;
    }

    /** Private constructor to prevent unwanted construction/initialization */
    private ImageDescriptionsController() {
        this.mDelegate = defaultDelegate();
    }

    /**
     * Creates a default ImageDescriptionsControllerDelegate implementation, used in production.
     * @return      Default ImageDescriptionsControllerDelegate delegate.
     */
    private ImageDescriptionsControllerDelegate defaultDelegate() {
        return new ImageDescriptionsControllerDelegate() {
            @Override
            public void enableImageDescriptions(Profile profile) {
                getPrefService(profile)
                        .setBoolean(Pref.ACCESSIBILITY_IMAGE_LABELS_ENABLED_ANDROID, true);
            }

            @Override
            public void disableImageDescriptions(Profile profile) {
                getPrefService(profile)
                        .setBoolean(Pref.ACCESSIBILITY_IMAGE_LABELS_ENABLED_ANDROID, false);
            }

            @Override
            public void setOnlyOnWifiRequirement(boolean onlyOnWifi, Profile profile) {
                getPrefService(profile)
                        .setBoolean(Pref.ACCESSIBILITY_IMAGE_LABELS_ONLY_ON_WIFI, onlyOnWifi);
            }

            @Override
            public void getImageDescriptionsJustOnce(
                    boolean dontAskAgain, WebContents webContents) {
                // User selected "Just Once", update counter and "Don't ask again" preference as
                // needed.
                getSharedPrefs()
                        .incrementInt(ChromePreferenceKeys.IMAGE_DESCRIPTIONS_JUST_ONCE_COUNT);
                getSharedPrefs()
                        .writeBoolean(
                                ChromePreferenceKeys.IMAGE_DESCRIPTIONS_DONT_ASK_AGAIN,
                                dontAskAgain);

                ImageDescriptionsControllerJni.get().getImageDescriptionsOnce(webContents);
            }
        };
    }

    /**
     * Set the ImageDescriptionsControllerDelegate delegate one time, used for testing purposes.
     * @param delegate      The new ImageDescriptionsControllerDelegate delegate to use.
     */
    public void setDelegateForTesting(ImageDescriptionsControllerDelegate delegate) {
        var oldValue = this.mDelegate;
        this.mDelegate = delegate;
        ResettersForTesting.register(() -> this.mDelegate = oldValue);
    }

    /**
     * Get the current ImageDescriptionsControllerDelegate in use for this instance.
     * @return mDelegate    The current ImageDescriptionsControllerDelegate in use.
     */
    public ImageDescriptionsControllerDelegate getDelegate() {
        return mDelegate;
    }

    /**
     * Handle user selecting menu item and the potential creation of the image descriptions dialog.
     */
    public void onImageDescriptionsMenuItemSelected(
            Context context, ModalDialogManager modalDialogManager, WebContents webContents) {
        Profile profile = Profile.fromWebContents(webContents).getOriginalProfile();
        boolean enabledBeforeMenuItemSelected = imageDescriptionsEnabled(profile);

        if (enabledBeforeMenuItemSelected) {
            // If descriptions are enabled, and the user has selected "only on wifi", and we
            // currently do not have a wifi connection, then do a "just once" fetch.
            if (onlyOnWifiEnabled(profile)
                    && DeviceConditions.getCurrentNetConnectionType(context)
                            != ConnectionType.CONNECTION_WIFI) {
                mDelegate.getImageDescriptionsJustOnce(false, webContents);
                Toast.makeText(
                                context,
                                R.string.image_descriptions_toast_just_once,
                                Toast.LENGTH_LONG)
                        .show();
            } else {
                // Otherwise, user has elected to stop descriptions.
                mDelegate.disableImageDescriptions(profile);
                Toast.makeText(context, R.string.image_descriptions_toast_off, Toast.LENGTH_LONG)
                        .show();
            }
        } else {
            // If descriptions are not enabled, and the user has selected "don't ask again", then do
            // a "just once" fetch. In all other cases, show the dialog to prompt the user.
            if (dontAskAgainEnabled()) {
                mDelegate.getImageDescriptionsJustOnce(true, webContents);
                Toast.makeText(
                                context,
                                R.string.image_descriptions_toast_just_once,
                                Toast.LENGTH_LONG)
                        .show();
            } else {
                ImageDescriptionsDialog prompt =
                        new ImageDescriptionsDialog(
                                context,
                                modalDialogManager,
                                getDelegate(),
                                shouldShowDontAskAgainOption(),
                                webContents);
                prompt.show();
            }
        }
    }

    protected boolean dontAskAgainEnabled() {
        return getSharedPrefs()
                .readBoolean(ChromePreferenceKeys.IMAGE_DESCRIPTIONS_DONT_ASK_AGAIN, false);
    }

    protected boolean shouldShowDontAskAgainOption() {
        return getSharedPrefs().readInt(ChromePreferenceKeys.IMAGE_DESCRIPTIONS_JUST_ONCE_COUNT)
                >= DONT_ASK_AGAIN_DISPLAY_LIMIT;
    }

    public boolean shouldShowImageDescriptionsMenuItem() {
        return AccessibilityState.isScreenReaderEnabled();
    }

    public boolean imageDescriptionsEnabled(Profile profile) {
        return getPrefService(profile).getBoolean(Pref.ACCESSIBILITY_IMAGE_LABELS_ENABLED_ANDROID);
    }

    public boolean onlyOnWifiEnabled(Profile profile) {
        return getPrefService(profile).getBoolean(Pref.ACCESSIBILITY_IMAGE_LABELS_ONLY_ON_WIFI);
    }

    /**
     * Helper method to return PrefService for last used regular profile.
     * @return PrefService
     */
    private PrefService getPrefService(Profile profile) {
        return UserPrefs.get(profile);
    }

    /**
     * Helper method to return SharedPreferencesManager instance.
     * @return ChromeSharedPreferences
     */
    private SharedPreferencesManager getSharedPrefs() {
        return ChromeSharedPreferences.getInstance();
    }

    @NativeMethods
    interface Natives {
        void getImageDescriptionsOnce(WebContents webContents);
    }
}