Commit 8e0471f3 authored by Mark Schillaci's avatar Mark Schillaci Committed by Commit Bot

Accessibility Image Descriptions Android Port, Part II - Settings

This CL is the second part of an effort to port the existing Desktop
feature to Android. This feature enables a user to send an image to
Google to process to generate a descriptive alt text if a website
does not provide one.

Design Doc: go/2020-q1-android-image-descriptions
Slide Deck: go/clank-imageDescriptions
Launch Bug: 1057168

Original Desktop Design Doc for reference:
go/chrome-a11y-annotations-design

This is a conservative approach, we use a separate set of profile
Prefs for this feature rather than syncing with the Desktop Prefs.
This can be updated in time as needed. We have also hidden the
entire feature behind a feature flag, so this CL is not visible
to an average user.

----------

This CL adds the following:

- Settings sub page for image descriptions
- Preference options for image descriptions
- Associated strings etc for above

----------

AX-Relnotes: Adds feature to enable generating alt image descriptions
Bug: 1057169
Change-Id: Ie968e8e831b20df7bfbfe27f0ffad1282fd835c3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2268299
Commit-Queue: Mark Schillaci <mschillaci@google.com>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Reviewed-by: default avatarMark Schillaci <mschillaci@google.com>
Reviewed-by: default avatarNatalie Chouinard <chouinard@chromium.org>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#818672}
parent 074455f8
......@@ -29,4 +29,9 @@
android:key="captions"
android:title="@string/accessibility_captions_title"/>
<Preference
android:fragment="org.chromium.chrome.browser.image_descriptions.ImageDescriptionsSettings"
android:key="image_descriptions"
android:title="@string/image_descriptions_settings_title" />
</PreferenceScreen>
......@@ -14,6 +14,7 @@ import androidx.preference.PreferenceFragmentCompat;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.accessibility.FontSizePrefs;
import org.chromium.chrome.browser.accessibility.FontSizePrefs.FontSizePrefsObserver;
import org.chromium.chrome.browser.image_descriptions.ImageDescriptionsController;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
......@@ -32,6 +33,7 @@ public class AccessibilitySettings
static final String PREF_FORCE_ENABLE_ZOOM = "force_enable_zoom";
static final String PREF_READER_FOR_ACCESSIBILITY = "reader_for_accessibility";
static final String PREF_CAPTIONS = "captions";
static final String PREF_IMAGE_DESCRIPTIONS = "image_descriptions";
private TextScalePreference mTextScalePref;
private ChromeBaseCheckBoxPreference mForceEnableZoomPref;
......@@ -100,6 +102,10 @@ public class AccessibilitySettings
return true;
});
Preference imageDescriptionsPreference = findPreference(PREF_IMAGE_DESCRIPTIONS);
imageDescriptionsPreference.setVisible(
ImageDescriptionsController.getInstance().shouldShowImageDescriptionsMenuItem());
}
@Override
......
......@@ -30,6 +30,8 @@ import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeBaseAppCompatActivity;
import org.chromium.chrome.browser.feedback.FragmentHelpAndFeedbackLauncher;
import org.chromium.chrome.browser.feedback.HelpAndFeedbackLauncherImpl;
import org.chromium.chrome.browser.image_descriptions.ImageDescriptionsController;
import org.chromium.chrome.browser.image_descriptions.ImageDescriptionsSettings;
import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
import org.chromium.chrome.browser.password_check.PasswordCheckComponentUiFactory;
import org.chromium.chrome.browser.password_check.PasswordCheckEditFragmentView;
......@@ -306,6 +308,17 @@ public class SettingsActivity extends ChromeBaseAppCompatActivity
PasswordCheckEditFragmentView editFragment = (PasswordCheckEditFragmentView) fragment;
editFragment.setCheckProvider(PasswordCheckFactory::getOrCreate);
}
if (fragment instanceof ImageDescriptionsSettings) {
ImageDescriptionsSettings imageFragment = (ImageDescriptionsSettings) fragment;
Bundle extras = imageFragment.getArguments();
if (extras != null) {
extras.putBoolean(ImageDescriptionsSettings.IMAGE_DESCRIPTIONS,
ImageDescriptionsController.getInstance().imageDescriptionsEnabled());
extras.putBoolean(ImageDescriptionsSettings.IMAGE_DESCRIPTIONS_DATA_POLICY,
ImageDescriptionsController.getInstance().onlyOnWifiEnabled());
}
imageFragment.setDelegate(ImageDescriptionsController.getInstance().getDelegate());
}
}
private void ensureActivityNotExported() {
......
......@@ -6,18 +6,21 @@ package org.chromium.chrome.browser.accessibility.settings;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.swipeUp;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import android.app.Instrumentation;
import android.content.Intent;
import android.content.IntentFilter;
import android.provider.Settings;
import android.support.test.InstrumentationRegistry;
import androidx.preference.Preference;
import androidx.test.espresso.matcher.ViewMatchers;
import androidx.test.filters.SmallTest;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
......@@ -27,8 +30,11 @@ import org.chromium.base.task.PostTask;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.accessibility.FontSizePrefs;
import org.chromium.chrome.browser.settings.SettingsActivityTestRule;
import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.util.browser.Features;
import org.chromium.components.browser_ui.settings.ChromeBaseCheckBoxPreference;
import org.chromium.content_public.browser.ContentFeatureList;
import org.chromium.content_public.browser.UiThreadTaskTraits;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
......@@ -45,9 +51,10 @@ public class AccessibilitySettingsTest {
public SettingsActivityTestRule<AccessibilitySettings> mSettingsActivityTestRule =
new SettingsActivityTestRule<>(AccessibilitySettings.class);
@Before
public void setUp() {
mSettingsActivityTestRule.startSettingsActivity();
@After
public void tearDown() {
TestThreadUtils.runOnUiThreadBlocking(
() -> ChromeAccessibilityUtil.get().setAccessibilityEnabledForTesting(false));
}
/**
......@@ -58,7 +65,9 @@ public class AccessibilitySettingsTest {
@SmallTest
@Feature({"Accessibility"})
public void testAccessibilitySettings() throws Exception {
mSettingsActivityTestRule.startSettingsActivity();
AccessibilitySettings accessibilitySettings = mSettingsActivityTestRule.getFragment();
TextScalePreference textScalePref =
(TextScalePreference) accessibilitySettings.findPreference(
AccessibilitySettings.PREF_TEXT_SCALE);
......@@ -105,7 +114,9 @@ public class AccessibilitySettingsTest {
@SmallTest
@Feature({"Accessibility"})
public void testChangedFontPrefSavedOnStop() {
mSettingsActivityTestRule.startSettingsActivity();
AccessibilitySettings accessibilitySettings = mSettingsActivityTestRule.getFragment();
TextScalePreference textScalePref =
accessibilitySettings.findPreference(AccessibilitySettings.PREF_TEXT_SCALE);
......@@ -132,7 +143,9 @@ public class AccessibilitySettingsTest {
@SmallTest
@Feature({"Accessibility"})
public void testUnchangedFontPrefNotSavedOnStop() {
mSettingsActivityTestRule.startSettingsActivity();
AccessibilitySettings accessibilitySettings = mSettingsActivityTestRule.getFragment();
// Simulate activity stopping.
TestThreadUtils.runOnUiThreadBlocking(() -> accessibilitySettings.onStop());
Assert.assertEquals("Histogram should not have been recorded.", 0,
......@@ -144,7 +157,9 @@ public class AccessibilitySettingsTest {
@SmallTest
@Feature({"Accessibility"})
public void testCaptionPreferences() {
mSettingsActivityTestRule.startSettingsActivity();
AccessibilitySettings accessibilitySettings = mSettingsActivityTestRule.getFragment();
Preference captionsPref =
accessibilitySettings.findPreference(AccessibilitySettings.PREF_CAPTIONS);
Assert.assertNotNull(captionsPref);
......@@ -161,6 +176,56 @@ public class AccessibilitySettingsTest {
InstrumentationRegistry.getInstrumentation().removeMonitor(monitor);
}
@Test
@SmallTest
@Feature({"Accessibility"})
@Features.DisableFeatures({ContentFeatureList.EXPERIMENTAL_ACCESSIBILITY_LABELS})
public void testImageDescriptionsPreferences_Disabled() {
mSettingsActivityTestRule.startSettingsActivity();
AccessibilitySettings accessibilitySettings = mSettingsActivityTestRule.getFragment();
Preference imageDescriptionsPref =
accessibilitySettings.findPreference(AccessibilitySettings.PREF_IMAGE_DESCRIPTIONS);
Assert.assertNotNull(imageDescriptionsPref);
Assert.assertFalse(
"Image Descriptions should not be visible", imageDescriptionsPref.isVisible());
}
@Test
@SmallTest
@Feature({"Accessibility"})
@Features.EnableFeatures({ContentFeatureList.EXPERIMENTAL_ACCESSIBILITY_LABELS})
public void testImageDescriptionsPreferences_Enabled() {
// Enable accessibility services to display settings option.
TestThreadUtils.runOnUiThreadBlocking(
() -> ChromeAccessibilityUtil.get().setAccessibilityEnabledForTesting(true));
mSettingsActivityTestRule.startSettingsActivity();
AccessibilitySettings accessibilitySettings = mSettingsActivityTestRule.getFragment();
Preference imageDescriptionsPref =
accessibilitySettings.findPreference(AccessibilitySettings.PREF_IMAGE_DESCRIPTIONS);
Assert.assertNotNull(imageDescriptionsPref);
Assert.assertTrue(
"Image Descriptions option should be visible", imageDescriptionsPref.isVisible());
Instrumentation.ActivityMonitor monitor =
InstrumentationRegistry.getInstrumentation().addMonitor(
new IntentFilter(Intent.ACTION_MAIN), null, false);
// First scroll to bottom of the page, then click.
onView(ViewMatchers.isRoot()).perform(swipeUp());
onView(withText(org.chromium.chrome.R.string.image_descriptions_settings_title))
.perform(click());
monitor.waitForActivityWithTimeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL);
Assert.assertEquals(
"Clicking image descriptions should open subpage", 1, monitor.getHits());
InstrumentationRegistry.getInstrumentation().removeMonitor(monitor);
}
private void assertFontSizePrefs(
final boolean expectedForceEnableZoom, final float expectedFontScale) {
TestThreadUtils.runOnUiThreadBlocking(() -> {
......
......@@ -29,6 +29,7 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.chromium.base.ContextUtils;
import org.chromium.base.FeatureList;
import org.chromium.base.ThreadUtils;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.supplier.OneshotSupplierImpl;
......@@ -143,6 +144,7 @@ public class AppMenuPropertiesDelegateUnitTest {
mJniMocker.mock(UserPrefsJni.TEST_HOOKS, mUserPrefsJniMock);
Profile.setLastUsedProfileForTesting(mProfile);
Mockito.when(mUserPrefsJniMock.get(mProfile)).thenReturn(mPrefService);
FeatureList.setTestCanUseDefaultsForTesting();
mAppMenuPropertiesDelegate = Mockito.spy(new AppMenuPropertiesDelegateImpl(
ContextUtils.getApplicationContext(), mActivityTabProvider,
......
......@@ -26,6 +26,7 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.chromium.base.ContextUtils;
import org.chromium.base.FeatureList;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.supplier.OneshotSupplierImpl;
import org.chromium.base.test.BaseRobolectricTestRunner;
......@@ -128,6 +129,7 @@ public class TabbedAppMenuPropertiesDelegateUnitTest {
when(mContentFeatureListJniMock.isEnabled(
ContentFeatureList.EXPERIMENTAL_ACCESSIBILITY_LABELS))
.thenReturn(false);
FeatureList.setTestCanUseDefaultsForTesting();
mTabbedAppMenuPropertiesDelegate = Mockito.spy(
new TabbedAppMenuPropertiesDelegate(ContextUtils.getApplicationContext(),
......
......@@ -7,7 +7,10 @@ import("//build/config/android/rules.gni")
android_library("java") {
sources = [
"android/java/src/org/chromium/chrome/browser/image_descriptions/ImageDescriptionsController.java",
"android/java/src/org/chromium/chrome/browser/image_descriptions/ImageDescriptionsControllerDelegate.java",
"android/java/src/org/chromium/chrome/browser/image_descriptions/ImageDescriptionsDialog.java",
"android/java/src/org/chromium/chrome/browser/image_descriptions/ImageDescriptionsSettings.java",
"android/java/src/org/chromium/chrome/browser/image_descriptions/RadioButtonGroupAccessibilityPreference.java",
]
deps = [
......@@ -15,11 +18,14 @@ android_library("java") {
"//chrome/browser/preferences:java",
"//chrome/browser/profiles/android:java",
"//chrome/browser/util:java",
"//components/browser_ui/settings/android:java",
"//components/browser_ui/widget/android:java",
"//components/prefs/android:java",
"//components/user_prefs/android:java",
"//content/public/android:content_java",
"//third_party/android_deps:androidx_annotation_annotation_java",
"//third_party/android_deps:androidx_fragment_fragment_java",
"//third_party/android_deps:androidx_preference_preference_java",
"//ui/android:ui_full_java",
]
......@@ -27,7 +33,11 @@ android_library("java") {
}
android_resources("java_resources") {
sources = [ "android/java/res/layout/image_descriptions_dialog.xml" ]
sources = [
"android/java/res/layout/image_descriptions_dialog.xml",
"android/java/res/layout/radio_button_group_accessibility_preference.xml",
"android/java/res/xml/image_descriptions_preference.xml",
]
deps = [
"//chrome/browser/ui/android/strings:ui_strings_grd",
......@@ -42,6 +52,7 @@ android_library("javatests") {
sources = [
"android/java/src/org/chromium/chrome/browser/image_descriptions/ImageDescriptionsControllerTest.java",
"android/java/src/org/chromium/chrome/browser/image_descriptions/ImageDescriptionsDialogTest.java",
"android/java/src/org/chromium/chrome/browser/image_descriptions/ImageDescriptionsSettingsTest.java",
]
deps = [
......@@ -51,12 +62,15 @@ android_library("javatests") {
"//chrome/browser/flags:java",
"//chrome/browser/preferences:java",
"//chrome/browser/profiles/android:java",
"//chrome/browser/settings:test_support_java",
"//chrome/test/android:chrome_java_test_support",
"//components/browser_ui/modaldialog/android:java",
"//components/browser_ui/settings/android:java",
"//components/browser_ui/widget/android:java",
"//components/prefs/android:java",
"//components/user_prefs/android:java",
"//content/public/test/android:content_java_test_support",
"//third_party/android_deps:androidx_preference_preference_java",
"//third_party/android_deps:androidx_test_runner_java",
"//third_party/android_deps:espresso_java",
"//third_party/junit",
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 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. -->
<org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/image_descriptions_settings_radio_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="false">
<org.chromium.components.browser_ui.widget.RadioButtonWithDescription
android:id="@+id/image_descriptions_settings_only_on_wifi_radio_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/min_touch_target_size"
app:primaryText="@string/image_descriptions_dialog_option_only_on_wifi" />
<org.chromium.components.browser_ui.widget.RadioButtonWithDescription
android:id="@+id/image_descriptions_settings_mobile_data_radio_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/min_touch_target_size"
app:primaryText="@string/image_descriptions_settings_use_mobile_data_title"
app:descriptionText="@string/image_descriptions_settings_use_mobile_data_content" />
</org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 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. -->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<org.chromium.components.browser_ui.settings.ChromeSwitchPreference
android:key="image_descriptions_switch"
android:persistent="false"
android:title="@string/image_descriptions_settings_toggle_title"
android:summary="@string/image_descriptions_settings_toggle_content" />
<org.chromium.chrome.browser.image_descriptions.RadioButtonGroupAccessibilityPreference
android:key="image_descriptions_data_policy"
android:persistent="false" />
</PreferenceScreen>
......@@ -29,7 +29,7 @@ public class ImageDescriptionsController {
// Static instance of this singleton, lazily initialized during first getInstance() call.
private static ImageDescriptionsController sInstance;
private ImageDescriptionsDialog.Delegate mDelegate;
private ImageDescriptionsControllerDelegate mDelegate;
/**
* Method to return the private instance of this singleton, lazily initialized.
......@@ -51,17 +51,28 @@ public class ImageDescriptionsController {
}
/**
* Creates a default ImageDescriptionsDialog.Delegate implementation, used in production.
* @return Default ImageDescriptionsDialog.Delegate delegate.
* Creates a default ImageDescriptionsControllerDelegate implementation, used in production.
* @return Default ImageDescriptionsControllerDelegate delegate.
*/
private ImageDescriptionsDialog.Delegate defaultDelegate() {
return new ImageDescriptionsDialog.Delegate() {
private ImageDescriptionsControllerDelegate defaultDelegate() {
return new ImageDescriptionsControllerDelegate() {
@Override
public void enableImageDescriptions(boolean onlyOnWifi) {
public void enableImageDescriptions() {
getPrefService().setBoolean(Pref.ACCESSIBILITY_IMAGE_LABELS_ENABLED_ANDROID, true);
// TODO(mschillaci): Use JNI to enable descriptions in native code with AXMode
}
@Override
public void disableImageDescriptions() {
getPrefService().setBoolean(Pref.ACCESSIBILITY_IMAGE_LABELS_ENABLED_ANDROID, false);
// TODO(mschillaci): Potentially remove AXMode through JNI to turn off
// descriptions?
}
@Override
public void setOnlyOnWifiRequirement(boolean onlyOnWifi) {
getPrefService().setBoolean(
Pref.ACCESSIBILITY_IMAGE_LABELS_ONLY_ON_WIFI, onlyOnWifi);
// TODO (mschillaci@) - Use JNI to enable descriptions in native code with AXMode
}
@Override
......@@ -73,21 +84,29 @@ public class ImageDescriptionsController {
getSharedPrefs().writeBoolean(
ChromePreferenceKeys.IMAGE_DESCRIPTIONS_DONT_ASK_AGAIN, dontAskAgain);
// TODO (mschillaci@) - Use JNI to enable descriptions once with AXActionData. Will
// need a Tab so that we can get web_contents.
// TODO(mschillaci): Use JNI to enable descriptions once with AXActionData. Will
// need a Tab so that we can get web_contents.
}
};
}
/**
* Set the ImageDescriptionsDialog.Delegate delegate one time, used for testing purposes.
* @param delegate The new ImageDescriptionsDialog.Delegate delegate to use.
* Set the ImageDescriptionsControllerDelegate delegate one time, used for testing purposes.
* @param delegate The new ImageDescriptionsControllerDelegate delegate to use.
*/
@VisibleForTesting
public void setDelegateForTesting(ImageDescriptionsDialog.Delegate delegate) {
public void setDelegateForTesting(ImageDescriptionsControllerDelegate delegate) {
this.mDelegate = delegate;
}
/**
* 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.
*/
......@@ -102,16 +121,11 @@ public class ImageDescriptionsController {
getImageDescriptionsJustOnce(true);
} else {
ImageDescriptionsDialog prompt = new ImageDescriptionsDialog(
context, modalDialogManager, mDelegate, shouldShowDontAskAgainOption());
context, modalDialogManager, getDelegate(), shouldShowDontAskAgainOption());
prompt.show();
}
}
protected void disableImageDescriptions() {
getPrefService().setBoolean(Pref.ACCESSIBILITY_IMAGE_LABELS_ENABLED_ANDROID, false);
// TODO (mschillaci@) - Potentially remove AXMode through JNI to turn off descriptions?
}
protected boolean dontAskAgainEnabled() {
return getSharedPrefs().readBoolean(
ChromePreferenceKeys.IMAGE_DESCRIPTIONS_DONT_ASK_AGAIN, false);
......@@ -123,7 +137,7 @@ public class ImageDescriptionsController {
}
public boolean shouldShowImageDescriptionsMenuItem() {
// TODO (mschillaci@) - Expand this to check touch exploration rather than accessibility
// TODO(mschillaci): Expand this to check touch exploration rather than accessibility
return ContentFeatureList.isEnabled(ContentFeatureList.EXPERIMENTAL_ACCESSIBILITY_LABELS)
&& ChromeAccessibilityUtil.get().isAccessibilityEnabled();
}
......@@ -138,8 +152,16 @@ public class ImageDescriptionsController {
// Pass-through methods to our delegate.
protected void enableImageDescriptions(boolean onlyOnWifi) {
mDelegate.enableImageDescriptions(onlyOnWifi);
protected void enableImageDescriptions() {
mDelegate.enableImageDescriptions();
}
protected void disableImageDescriptions() {
mDelegate.disableImageDescriptions();
}
protected void setOnlyOnWifiRequirement(boolean onlyOnWifi) {
mDelegate.setOnlyOnWifiRequirement(onlyOnWifi);
}
protected void getImageDescriptionsJustOnce(boolean dontAskAgain) {
......@@ -151,7 +173,7 @@ public class ImageDescriptionsController {
* @return PrefService
*/
private PrefService getPrefService() {
// TODO (mschillaci@) - Use the correct profile here for Incognito mode etc.
// TODO(mschillaci): Use the correct profile here for Incognito mode etc.
return UserPrefs.get(Profile.getLastUsedRegularProfile());
}
......
// Copyright 2020 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.image_descriptions;
/**
* A delegate to {@link ImageDescriptionsController} to allow UIs to control its state.
*/
public interface ImageDescriptionsControllerDelegate {
/**
* Enable image descriptions. Calling this method will enable the image descriptions feature
* for the current profile. Any currently opened tabs will receive image descriptions, as will
* any future pages.
*/
void enableImageDescriptions();
/**
* Disable image descriptions. Calling this method will disable the image descriptions feature
* for the current profile. Any existing image descriptions will persist, but no new image
* descriptions will be generated.
*/
void disableImageDescriptions();
/**
* Set "Only on Wi-Fi" requirement. Calling this method will set the only on wifi user
* preference for the current profile. If set to true, the image descriptions feature will not
* be run while on mobile data but instead only on wifi.
*
* @param onlyOnWifi Boolean - whether or not to require wifi for image descriptions.
*/
void setOnlyOnWifiRequirement(boolean onlyOnWifi);
/**
* Get image descriptions once. Calling this method will fetch image descriptions one time for
* the currently opened tab. It will not save any settings to the current profile and is
* considered a one-off use of the feature. The method allows for setting the "Don't ask again"
* option in shared prefs so users can easily fetch one-off descriptions bypassing the dialog.
*
* @param dontAskAgain Boolean - whether or not to ask again before next one-off use.
*/
void getImageDescriptionsJustOnce(boolean dontAskAgain);
}
......@@ -48,7 +48,7 @@ public class ImageDescriptionsControllerTest extends DummyUiActivityTestCase {
public JniMocker mJniMocker = new JniMocker();
@Mock
private ImageDescriptionsDialog.Delegate mDelegate;
private ImageDescriptionsControllerDelegate mDelegate;
@Mock
private UserPrefs.Natives mUserPrefsJniMock;
......@@ -129,13 +129,14 @@ public class ImageDescriptionsControllerTest extends DummyUiActivityTestCase {
Assert.assertFalse("Image descriptions should be disabled by default",
mController.imageDescriptionsEnabled());
mController.enableImageDescriptions(false);
mController.enableImageDescriptions();
mController.setOnlyOnWifiRequirement(false);
verify(mPrefService, times(1))
.setBoolean(Pref.ACCESSIBILITY_IMAGE_LABELS_ENABLED_ANDROID, true);
verify(mPrefService, times(1))
.setBoolean(Pref.ACCESSIBILITY_IMAGE_LABELS_ONLY_ON_WIFI, false);
mController.enableImageDescriptions(true);
mController.setOnlyOnWifiRequirement(true);
verify(mPrefService, times(1))
.setBoolean(Pref.ACCESSIBILITY_IMAGE_LABELS_ONLY_ON_WIFI, true);
}
......
......@@ -24,15 +24,7 @@ import org.chromium.ui.modelutil.PropertyModel;
*/
public class ImageDescriptionsDialog
implements ModalDialogProperties.Controller, RadioGroup.OnCheckedChangeListener {
/**
* A delegate to respond to actions taken in the dialog.
*/
public interface Delegate {
void enableImageDescriptions(boolean onlyOnWifi);
void getImageDescriptionsJustOnce(boolean dontAskAgain);
}
private ImageDescriptionsDialog.Delegate mControllerDelegate;
private ImageDescriptionsControllerDelegate mControllerDelegate;
private ModalDialogManager mModalDialogManager;
private PropertyModel mPropertyModel;
......@@ -47,7 +39,7 @@ public class ImageDescriptionsDialog
private boolean mDontAskAgainState;
protected ImageDescriptionsDialog(Context context, ModalDialogManager modalDialogManager,
ImageDescriptionsDialog.Delegate delegate, boolean shouldShowDontAskAgainOption) {
ImageDescriptionsControllerDelegate delegate, boolean shouldShowDontAskAgainOption) {
mModalDialogManager = modalDialogManager;
mControllerDelegate = delegate;
......@@ -123,7 +115,8 @@ public class ImageDescriptionsDialog
if (buttonType == ModalDialogProperties.ButtonType.POSITIVE) {
// Determine desired level of descriptions and default to just once
if (mOptionAlwaysRadioButton.isChecked()) {
mControllerDelegate.enableImageDescriptions(mOnlyOnWifiState);
mControllerDelegate.enableImageDescriptions();
mControllerDelegate.setOnlyOnWifiRequirement(mOnlyOnWifiState);
} else if (mOptionJustOnceRadioButton.isChecked()) {
mControllerDelegate.getImageDescriptionsJustOnce(mDontAskAgainState);
}
......
......@@ -15,6 +15,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.view.View;
import android.widget.CheckBox;
......@@ -27,13 +28,13 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
......@@ -56,7 +57,7 @@ public class ImageDescriptionsDialogTest extends DummyUiActivityTestCase {
public JniMocker mJniMocker = new JniMocker();
@Mock
private ImageDescriptionsDialog.Delegate mDelegate;
private ImageDescriptionsControllerDelegate mDelegate;
@Mock
private UserPrefs.Natives mUserPrefsJniMock;
......@@ -80,7 +81,7 @@ public class ImageDescriptionsDialogTest extends DummyUiActivityTestCase {
mJniMocker.mock(UserPrefsJni.TEST_HOOKS, mUserPrefsJniMock);
Profile.setLastUsedProfileForTesting(mProfile);
Mockito.when(mUserPrefsJniMock.get(mProfile)).thenReturn(mPrefService);
when(mUserPrefsJniMock.get(mProfile)).thenReturn(mPrefService);
mAppModalPresenter = new AppModalPresenter(getActivity());
mModalDialogManager =
......@@ -95,7 +96,8 @@ public class ImageDescriptionsDialogTest extends DummyUiActivityTestCase {
private void showDialog() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
mController.disableImageDescriptions();
when(mPrefService.getBoolean(Pref.ACCESSIBILITY_IMAGE_LABELS_ENABLED_ANDROID))
.thenReturn(false);
mManager.writeInt(ChromePreferenceKeys.IMAGE_DESCRIPTIONS_JUST_ONCE_COUNT, 0);
mManager.writeBoolean(ChromePreferenceKeys.IMAGE_DESCRIPTIONS_DONT_ASK_AGAIN, false);
mController.onImageDescriptionsMenuItemSelected(getActivity(), mModalDialogManager);
......@@ -104,7 +106,8 @@ public class ImageDescriptionsDialogTest extends DummyUiActivityTestCase {
private void showDialogWithDontAskAgainVisible() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
mController.disableImageDescriptions();
when(mPrefService.getBoolean(Pref.ACCESSIBILITY_IMAGE_LABELS_ENABLED_ANDROID))
.thenReturn(false);
mManager.writeInt(ChromePreferenceKeys.IMAGE_DESCRIPTIONS_JUST_ONCE_COUNT, 5);
mController.onImageDescriptionsMenuItemSelected(getActivity(), mModalDialogManager);
});
......@@ -279,7 +282,9 @@ public class ImageDescriptionsDialogTest extends DummyUiActivityTestCase {
// User clicks "No thanks", dialog should dismiss with no action taken.
clickNegativeButton();
verify(mDelegate, never()).enableImageDescriptions(anyBoolean());
verify(mDelegate, never()).enableImageDescriptions();
verify(mDelegate, never()).disableImageDescriptions();
verify(mDelegate, never()).setOnlyOnWifiRequirement(anyBoolean());
verify(mDelegate, never()).getImageDescriptionsJustOnce(anyBoolean());
}
......@@ -297,7 +302,9 @@ public class ImageDescriptionsDialogTest extends DummyUiActivityTestCase {
// User clicks "Get descriptions"
clickPositiveButton();
verify(mDelegate, never()).enableImageDescriptions(anyBoolean());
verify(mDelegate, never()).enableImageDescriptions();
verify(mDelegate, never()).disableImageDescriptions();
verify(mDelegate, never()).setOnlyOnWifiRequirement(anyBoolean());
verify(mDelegate, times(1)).getImageDescriptionsJustOnce(false);
}
......@@ -319,7 +326,9 @@ public class ImageDescriptionsDialogTest extends DummyUiActivityTestCase {
// User clicks "Get descriptions"
clickPositiveButton();
verify(mDelegate, never()).enableImageDescriptions(anyBoolean());
verify(mDelegate, never()).enableImageDescriptions();
verify(mDelegate, never()).disableImageDescriptions();
verify(mDelegate, never()).setOnlyOnWifiRequirement(anyBoolean());
verify(mDelegate, times(1)).getImageDescriptionsJustOnce(true);
}
......@@ -345,7 +354,9 @@ public class ImageDescriptionsDialogTest extends DummyUiActivityTestCase {
// User clicks "Get descriptions"
clickPositiveButton();
verify(mDelegate, times(1)).enableImageDescriptions(false);
verify(mDelegate, times(1)).enableImageDescriptions();
verify(mDelegate, times(1)).setOnlyOnWifiRequirement(false);
verify(mDelegate, never()).disableImageDescriptions();
verify(mDelegate, never()).getImageDescriptionsJustOnce(anyBoolean());
}
......@@ -360,7 +371,9 @@ public class ImageDescriptionsDialogTest extends DummyUiActivityTestCase {
// User clicks "Get descriptions"
clickPositiveButton();
verify(mDelegate, times(1)).enableImageDescriptions(true);
verify(mDelegate, times(1)).enableImageDescriptions();
verify(mDelegate, times(1)).setOnlyOnWifiRequirement(true);
verify(mDelegate, never()).disableImageDescriptions();
verify(mDelegate, never()).getImageDescriptionsJustOnce(anyBoolean());
}
}
// Copyright 2020 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.image_descriptions;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import org.chromium.components.browser_ui.settings.ChromeSwitchPreference;
import org.chromium.components.browser_ui.settings.SettingsUtils;
/**
* Fragment for the "Image Descriptions" settings sub-page under Settings > Accessibility. This page
* allows a user to control whether or not the feature is on, and whether or not it is allowed to
* run on mobile data or requires a Wi-Fi connection.
*/
public class ImageDescriptionsSettings
extends PreferenceFragmentCompat implements Preference.OnPreferenceChangeListener {
public static final String IMAGE_DESCRIPTIONS = "image_descriptions_switch";
public static final String IMAGE_DESCRIPTIONS_DATA_POLICY = "image_descriptions_data_policy";
private ChromeSwitchPreference mGetImageDescriptionsSwitch;
private RadioButtonGroupAccessibilityPreference mRadioButtonGroupAccessibilityPreference;
private ImageDescriptionsControllerDelegate mDelegate;
private boolean mIsEnabled;
private boolean mOnlyOnWifi;
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getActivity().setTitle(R.string.image_descriptions_settings_title);
setDivider(null);
}
@Override
public void onCreatePreferences(Bundle bundle, String s) {
SettingsUtils.addPreferencesFromResource(this, R.xml.image_descriptions_preference);
Bundle extras = getArguments();
if (extras != null) {
mIsEnabled = extras.getBoolean(IMAGE_DESCRIPTIONS);
mOnlyOnWifi = extras.getBoolean(IMAGE_DESCRIPTIONS_DATA_POLICY);
}
mGetImageDescriptionsSwitch = (ChromeSwitchPreference) findPreference(IMAGE_DESCRIPTIONS);
mGetImageDescriptionsSwitch.setOnPreferenceChangeListener(this);
mGetImageDescriptionsSwitch.setChecked(mIsEnabled);
mRadioButtonGroupAccessibilityPreference =
(RadioButtonGroupAccessibilityPreference) findPreference(
IMAGE_DESCRIPTIONS_DATA_POLICY);
mRadioButtonGroupAccessibilityPreference.setOnPreferenceChangeListener(this);
mRadioButtonGroupAccessibilityPreference.setEnabled(mIsEnabled);
mRadioButtonGroupAccessibilityPreference.initialize(mOnlyOnWifi);
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference.getKey().equals(IMAGE_DESCRIPTIONS)) {
boolean userHasEnabled = (boolean) newValue;
// If userChoice is true, user has turned on descriptions, enable and set Wi-Fi pref.
if (userHasEnabled) {
mDelegate.enableImageDescriptions();
mDelegate.setOnlyOnWifiRequirement(
mRadioButtonGroupAccessibilityPreference.getOnlyOnWifiValue());
// We enable the radio button group if get image descriptions is enabled.
mRadioButtonGroupAccessibilityPreference.setEnabled(true);
} else {
mDelegate.disableImageDescriptions();
mRadioButtonGroupAccessibilityPreference.setEnabled(false);
}
} else if (preference.getKey().equals(IMAGE_DESCRIPTIONS_DATA_POLICY)) {
mDelegate.setOnlyOnWifiRequirement((boolean) newValue);
}
return true;
}
public void setDelegate(ImageDescriptionsControllerDelegate delegate) {
mDelegate = delegate;
}
}
// Copyright 2020 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.image_descriptions;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.RadioGroup;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import org.chromium.components.browser_ui.widget.RadioButtonWithDescription;
import org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout;
/**
* A radio button group used for accessibility preference. This allows the user to toggle between
* allowing the get image descriptions feature on mobile data, or requiring it need Wi-Fi to run.
*/
public class RadioButtonGroupAccessibilityPreference
extends Preference implements RadioGroup.OnCheckedChangeListener {
private RadioButtonWithDescriptionLayout mButtonGroup;
private RadioButtonWithDescription mOnlyOnWifi;
private RadioButtonWithDescription mUseMobileData;
private boolean mOnlyOnWifiValue;
public RadioButtonGroupAccessibilityPreference(Context context, AttributeSet attrs) {
super(context, attrs);
setLayoutResource(R.layout.radio_button_group_accessibility_preference);
}
public void initialize(boolean onlyOnWifi) {
mOnlyOnWifiValue = onlyOnWifi;
}
public boolean getOnlyOnWifiValue() {
return mOnlyOnWifiValue;
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
mOnlyOnWifi = (RadioButtonWithDescription) holder.findViewById(
R.id.image_descriptions_settings_only_on_wifi_radio_button);
mUseMobileData = (RadioButtonWithDescription) holder.findViewById(
R.id.image_descriptions_settings_mobile_data_radio_button);
mButtonGroup = (RadioButtonWithDescriptionLayout) mOnlyOnWifi.getParent();
mButtonGroup.setOnCheckedChangeListener(this);
if (mOnlyOnWifiValue) {
mOnlyOnWifi.setChecked(true);
} else {
mUseMobileData.setChecked(true);
}
}
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
mOnlyOnWifiValue = mOnlyOnWifi.isChecked();
callChangeListener(mOnlyOnWifiValue);
}
}
......@@ -4250,6 +4250,21 @@ Data from your Incognito session will only be cleared from Chrome when you <ph n
<message name="IDS_IMAGE_DESCRIPTIONS_DIALOG_GET_DESCRIPTIONS_BUTTON" desc="Button for dialog to enable option to get image descriptions.">
Get descriptions
</message>
<message name="IDS_IMAGE_DESCRIPTIONS_SETTINGS_TITLE" desc="Title of the preference that allows user to update accessibility image descriptions settings. [CHAR-LIMIT=32]">
Image descriptions
</message>
<message name="IDS_IMAGE_DESCRIPTIONS_SETTINGS_TOGGLE_TITLE" desc="Title of the toggle to turn preference on and off for accessibility image descriptions.">
Get image descriptions
</message>
<message name="IDS_IMAGE_DESCRIPTIONS_SETTINGS_TOGGLE_CONTENT" desc="Content of the toggle to turn preference on and off for accessibility image descriptions.">
Some images are sent to Google to improve descriptions for you
</message>
<message name="IDS_IMAGE_DESCRIPTIONS_SETTINGS_USE_MOBILE_DATA_TITLE" desc="Title of radio button to allow user to specify getting accessibility image descriptions while on mobile data.">
Use mobile data
</message>
<message name="IDS_IMAGE_DESCRIPTIONS_SETTINGS_USE_MOBILE_DATA_CONTENT" desc="Content of radio button to allow user to specify getting accessibility image descriptions while on mobile data.">
Wi-Fi is used when available
</message>
</messages>
</release>
</grit>
......@@ -4,6 +4,7 @@
package org.chromium.content_public.browser;
import org.chromium.base.FeatureList;
import org.chromium.content.browser.ContentFeatureListImpl;
/**
......@@ -19,6 +20,8 @@ public class ContentFeatureList {
* @return Whether the feature is enabled or not.
*/
public static boolean isEnabled(String featureName) {
Boolean testValue = FeatureList.getTestValueForFeature(featureName);
if (testValue != null) return testValue;
return ContentFeatureListImpl.isEnabled(featureName);
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment