summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoFontScaleListener.java
blob: ec53d2803a29fb63b22755fa433e4ce02686ee34 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.geckoview;

import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.provider.Settings;
import android.util.Log;
import androidx.annotation.UiThread;
import org.mozilla.gecko.util.ThreadUtils;

/**
 * A class that automatically adjusts font size settings for web content in Gecko in accordance with
 * the device's OS font scale setting.
 *
 * @see android.provider.Settings.System#FONT_SCALE
 */
/* package */ final class GeckoFontScaleListener extends ContentObserver {
  private static final String LOGTAG = "GeckoFontScaleListener";

  private static final float DEFAULT_FONT_SCALE = 1.0f;

  // We're referencing the *application* context, so this is in fact okay.
  @SuppressLint("StaticFieldLeak")
  private static final GeckoFontScaleListener sInstance = new GeckoFontScaleListener();

  private Context mApplicationContext;
  private GeckoRuntimeSettings mSettings;

  private boolean mAttached;
  private boolean mEnabled;
  private boolean mRunning;

  private float mPrevGeckoFontScale;

  public static GeckoFontScaleListener getInstance() {
    return sInstance;
  }

  private GeckoFontScaleListener() {
    // Ensure the ContentObserver callback runs on the UI thread.
    super(ThreadUtils.getUiHandler());
  }

  /**
   * Prepare the GeckoFontScaleListener for usage. If it has been previously enabled, it will now
   * start actively working.
   */
  public void attachToContext(final Context context, final GeckoRuntimeSettings settings) {
    ThreadUtils.assertOnUiThread();

    if (mAttached) {
      Log.w(LOGTAG, "Already attached!");
      return;
    }

    mAttached = true;
    mSettings = settings;
    mApplicationContext = context.getApplicationContext();
    onEnabledChange();
  }

  /**
   * Detaches the context and also stops the GeckoFontScaleListener if it was previously enabled.
   * This will also restore the previously used font size settings.
   */
  public void detachFromContext() {
    ThreadUtils.assertOnUiThread();

    if (!mAttached) {
      Log.w(LOGTAG, "Already detached!");
      return;
    }

    stop();
    mApplicationContext = null;
    mSettings = null;
    mAttached = false;
  }

  /**
   * Controls whether the GeckoFontScaleListener should automatically adjust font sizes for web
   * content in Gecko. When disabling, this will restore the previously used font size settings.
   *
   * <p>This method can be called at any time, but the GeckoFontScaleListener won't start actively
   * adjusting font sizes until it has been attached to a context.
   *
   * @param enabled True if automatic font size setting should be enabled.
   */
  public void setEnabled(final boolean enabled) {
    ThreadUtils.assertOnUiThread();
    mEnabled = enabled;
    onEnabledChange();
  }

  /**
   * Get whether the GeckoFontScaleListener is currently enabled.
   *
   * @return True if the GeckoFontScaleListener is currently enabled.
   */
  public boolean getEnabled() {
    return mEnabled;
  }

  private void onEnabledChange() {
    if (!mAttached) {
      return;
    }

    if (mEnabled) {
      start();
    } else {
      stop();
    }
  }

  private void start() {
    if (mRunning) {
      return;
    }

    mPrevGeckoFontScale = mSettings.getFontSizeFactor();
    final ContentResolver contentResolver = mApplicationContext.getContentResolver();
    final Uri fontSizeSetting = Settings.System.getUriFor(Settings.System.FONT_SCALE);
    contentResolver.registerContentObserver(fontSizeSetting, false, this);
    onSystemFontScaleChange(contentResolver, false);

    mRunning = true;
  }

  private void stop() {
    if (!mRunning) {
      return;
    }

    final ContentResolver contentResolver = mApplicationContext.getContentResolver();
    contentResolver.unregisterContentObserver(this);
    onSystemFontScaleChange(contentResolver, /*stopping*/ true);

    mRunning = false;
  }

  private void onSystemFontScaleChange(
      final ContentResolver contentResolver, final boolean stopping) {
    float fontScale;

    if (!stopping) { // Either we were enabled, or else the system font scale changed.
      fontScale =
          Settings.System.getFloat(contentResolver, Settings.System.FONT_SCALE, DEFAULT_FONT_SCALE);
      // Older Android versions don't sanitize the FONT_SCALE value. See Bug 1656078.
      if (fontScale < 0) {
        fontScale = DEFAULT_FONT_SCALE;
      }
    } else { // We were turned off.
      fontScale = mPrevGeckoFontScale;
    }

    mSettings.setFontSizeFactorInternal(fontScale);
  }

  @UiThread // See constructor.
  @Override
  public void onChange(final boolean selfChange) {
    onSystemFontScaleChange(mApplicationContext.getContentResolver(), false);
  }
}