// Copyright 2014 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.accessibility; import android.content.Context; import android.content.SharedPreferences; import org.chromium.base.ContextUtils; import org.chromium.base.ObserverList; import org.chromium.base.ThreadUtils; import org.chromium.base.VisibleForTesting; import org.chromium.base.annotations.CalledByNative; import org.chromium.chrome.browser.util.MathUtils; /** * Singleton class for accessing these font size-related preferences: * - User Font Scale Factor: the font scale value that the user sees and can set. This is a value * between 50% and 200% (i.e. 0.5 and 2). * - Font Scale Factor: the font scale factor applied to webpage text during font boosting. This * equals the user font scale factor times the Android system font scale factor, which * reflects the font size indicated in Android settings > Display > Font size. * - Force Enable Zoom: whether force enable zoom is on or off * - User Set Force Enable Zoom: whether the user has manually set the force enable zoom button */ public class FontSizePrefs { /** * The font scale threshold beyond which force enable zoom is automatically turned on. It * is chosen such that force enable zoom will be activated when the accessibility large text * setting is on (i.e. this value should be the same as or lesser than the font size scale used * by accessiblity large text). */ public static final float FORCE_ENABLE_ZOOM_THRESHOLD_MULTIPLIER = 1.3f; private static final float EPSILON = 0.001f; static final String PREF_USER_SET_FORCE_ENABLE_ZOOM = "user_set_force_enable_zoom"; static final String PREF_USER_FONT_SCALE_FACTOR = "user_font_scale_factor"; private static FontSizePrefs sFontSizePrefs; private final long mFontSizePrefsAndroidPtr; private final Context mApplicationContext; private final SharedPreferences mSharedPreferences; private final ObserverList<FontSizePrefsObserver> mObserverList; private Float mSystemFontScaleForTests = null; /** * Interface for observing changes in font size-related preferences. */ public interface FontSizePrefsObserver { void onFontScaleFactorChanged(float fontScaleFactor, float userFontScaleFactor); void onForceEnableZoomChanged(boolean enabled); } private FontSizePrefs(Context context) { mFontSizePrefsAndroidPtr = nativeInit(); mApplicationContext = context.getApplicationContext(); mSharedPreferences = ContextUtils.getAppSharedPreferences(); mObserverList = new ObserverList<FontSizePrefsObserver>(); } /** * Returns the singleton FontSizePrefs, constructing it if it doesn't already exist. */ public static FontSizePrefs getInstance(Context context) { ThreadUtils.assertOnUiThread(); if (sFontSizePrefs == null) { sFontSizePrefs = new FontSizePrefs(context); } return sFontSizePrefs; } /** * Adds an observer to listen for changes to font scale-related preferences. */ public void addObserver(FontSizePrefsObserver observer) { mObserverList.addObserver(observer); } /** * Removes an observer so it will no longer receive updates for changes to font scale-related * preferences. */ public void removeObserver(FontSizePrefsObserver observer) { mObserverList.removeObserver(observer); } /** * Updates the fontScaleFactor based on the userFontScaleFactor and the system-wide font scale. * * This should be called during application start-up and whenever the system font size changes. */ public void onSystemFontScaleChanged() { float userFontScaleFactor = getUserFontScaleFactor(); if (userFontScaleFactor != 0f) { setFontScaleFactor(userFontScaleFactor * getSystemFontScale()); } } /** * Sets the userFontScaleFactor. This should be a value between .5 and 2. */ public void setUserFontScaleFactor(float userFontScaleFactor) { SharedPreferences.Editor sharedPreferencesEditor = mSharedPreferences.edit(); sharedPreferencesEditor.putFloat(PREF_USER_FONT_SCALE_FACTOR, userFontScaleFactor); sharedPreferencesEditor.apply(); setFontScaleFactor(userFontScaleFactor * getSystemFontScale()); } /** * Returns the userFontScaleFactor. This is the value that should be displayed to the user. */ public float getUserFontScaleFactor() { float userFontScaleFactor = mSharedPreferences.getFloat(PREF_USER_FONT_SCALE_FACTOR, 0f); if (userFontScaleFactor == 0f) { float fontScaleFactor = getFontScaleFactor(); if (Math.abs(fontScaleFactor - 1f) <= EPSILON) { // If the font scale factor is 1, assume that the user hasn't customized their font // scale and/or wants the default value userFontScaleFactor = 1f; } else { // Initialize userFontScaleFactor based on fontScaleFactor, since // userFontScaleFactor was added long after fontScaleFactor. userFontScaleFactor = MathUtils.clamp(fontScaleFactor / getSystemFontScale(), 0.5f, 2f); } SharedPreferences.Editor sharedPreferencesEditor = mSharedPreferences.edit(); sharedPreferencesEditor.putFloat(PREF_USER_FONT_SCALE_FACTOR, userFontScaleFactor); sharedPreferencesEditor.apply(); } return userFontScaleFactor; } /** * Returns the fontScaleFactor. This is the product of the userFontScaleFactor and the system * font scale, and is the amount by which webpage text will be scaled during font boosting. */ public float getFontScaleFactor() { return nativeGetFontScaleFactor(mFontSizePrefsAndroidPtr); } /** * Sets forceEnableZoom due to a user request (e.g. checking a checkbox). This implicitly sets * userSetForceEnableZoom. */ public void setForceEnableZoomFromUser(boolean enabled) { setForceEnableZoom(enabled, true); } /** * Returns whether forceEnableZoom is enabled. */ public boolean getForceEnableZoom() { return nativeGetForceEnableZoom(mFontSizePrefsAndroidPtr); } /** * Sets a mock value for the system-wide font scale. Use only in tests. */ @VisibleForTesting void setSystemFontScaleForTest(float fontScale) { mSystemFontScaleForTests = fontScale; } private float getSystemFontScale() { if (mSystemFontScaleForTests != null) return mSystemFontScaleForTests; return mApplicationContext.getResources().getConfiguration().fontScale; } private void setForceEnableZoom(boolean enabled, boolean fromUser) { SharedPreferences.Editor sharedPreferencesEditor = mSharedPreferences.edit(); sharedPreferencesEditor.putBoolean(PREF_USER_SET_FORCE_ENABLE_ZOOM, fromUser); sharedPreferencesEditor.apply(); nativeSetForceEnableZoom(mFontSizePrefsAndroidPtr, enabled); } private boolean getUserSetForceEnableZoom() { return mSharedPreferences.getBoolean(PREF_USER_SET_FORCE_ENABLE_ZOOM, false); } private void setFontScaleFactor(float fontScaleFactor) { float previousFontScaleFactor = getFontScaleFactor(); nativeSetFontScaleFactor(mFontSizePrefsAndroidPtr, fontScaleFactor); if (previousFontScaleFactor < FORCE_ENABLE_ZOOM_THRESHOLD_MULTIPLIER && fontScaleFactor >= FORCE_ENABLE_ZOOM_THRESHOLD_MULTIPLIER && !getForceEnableZoom()) { // If the font scale factor just crossed above the threshold, set force enable zoom even // if the user has previously unset it. setForceEnableZoom(true, false); } else if (previousFontScaleFactor >= FORCE_ENABLE_ZOOM_THRESHOLD_MULTIPLIER && fontScaleFactor < FORCE_ENABLE_ZOOM_THRESHOLD_MULTIPLIER && !getUserSetForceEnableZoom()) { // If the font scale factor just crossed below the threshold and the user didn't set // force enable zoom manually, then unset force enable zoom. setForceEnableZoom(false, false); } } @CalledByNative private void onFontScaleFactorChanged(float fontScaleFactor) { float userFontScaleFactor = getUserFontScaleFactor(); for (FontSizePrefsObserver observer : mObserverList) { observer.onFontScaleFactorChanged(fontScaleFactor, userFontScaleFactor); } } @CalledByNative private void onForceEnableZoomChanged(boolean enabled) { for (FontSizePrefsObserver observer : mObserverList) { observer.onForceEnableZoomChanged(enabled); } } private native long nativeInit(); private native void nativeSetFontScaleFactor(long nativeFontSizePrefsAndroid, float fontScaleFactor); private native float nativeGetFontScaleFactor(long nativeFontSizePrefsAndroid); private native boolean nativeGetForceEnableZoom(long nativeFontSizePrefsAndroid); private native void nativeSetForceEnableZoom(long nativeFontSizePrefsAndroid, boolean enabled); }