Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 package org.mozilla.gecko;
8 import org.mozilla.gecko.db.BrowserContract;
9 import org.mozilla.gecko.db.BrowserDB;
10 import org.mozilla.gecko.favicons.Favicons;
11 import org.mozilla.gecko.util.ThreadUtils;
13 import android.content.BroadcastReceiver;
14 import android.content.ComponentCallbacks2;
15 import android.content.Context;
16 import android.content.Intent;
17 import android.content.IntentFilter;
18 import android.os.Build;
19 import android.util.Log;
21 /**
22 * This is a utility class to keep track of how much memory and disk-space pressure
23 * the system is under. It receives input from GeckoActivity via the onLowMemory() and
24 * onTrimMemory() functions, and also listens for some system intents related to
25 * disk-space notifications. Internally it will track how much memory and disk pressure
26 * the system is under, and perform various actions to help alleviate the pressure.
27 *
28 * Note that since there is no notification for when the system has lots of free memory
29 * again, this class also assumes that, over time, the system will free up memory. This
30 * assumption is implemented using a timer that slowly lowers the internal memory
31 * pressure state if no new low-memory notifications are received.
32 *
33 * Synchronization note: MemoryMonitor contains an inner class PressureDecrementer. Both
34 * of these classes may be accessed from various threads, and have both been designed to
35 * be thread-safe. In terms of lock ordering, code holding the PressureDecrementer lock
36 * is allowed to pick up the MemoryMonitor lock, but not vice-versa.
37 */
38 class MemoryMonitor extends BroadcastReceiver {
39 private static final String LOGTAG = "GeckoMemoryMonitor";
40 private static final String ACTION_MEMORY_DUMP = "org.mozilla.gecko.MEMORY_DUMP";
41 private static final String ACTION_FORCE_PRESSURE = "org.mozilla.gecko.FORCE_MEMORY_PRESSURE";
43 // Memory pressue levels, keep in sync with those in AndroidJavaWrappers.h
44 private static final int MEMORY_PRESSURE_NONE = 0;
45 private static final int MEMORY_PRESSURE_CLEANUP = 1;
46 private static final int MEMORY_PRESSURE_LOW = 2;
47 private static final int MEMORY_PRESSURE_MEDIUM = 3;
48 private static final int MEMORY_PRESSURE_HIGH = 4;
50 private static MemoryMonitor sInstance = new MemoryMonitor();
52 static MemoryMonitor getInstance() {
53 return sInstance;
54 }
56 private final PressureDecrementer mPressureDecrementer;
57 private int mMemoryPressure;
58 private boolean mStoragePressure;
59 private boolean mInited;
61 private MemoryMonitor() {
62 mPressureDecrementer = new PressureDecrementer();
63 mMemoryPressure = MEMORY_PRESSURE_NONE;
64 mStoragePressure = false;
65 }
67 public void init(final Context context) {
68 if (mInited) {
69 return;
70 }
72 IntentFilter filter = new IntentFilter();
73 filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
74 filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
75 filter.addAction(ACTION_MEMORY_DUMP);
76 filter.addAction(ACTION_FORCE_PRESSURE);
77 context.getApplicationContext().registerReceiver(this, filter);
78 mInited = true;
79 }
81 public void onLowMemory() {
82 Log.d(LOGTAG, "onLowMemory() notification received");
83 if (increaseMemoryPressure(MEMORY_PRESSURE_HIGH)) {
84 // We need to wait on Gecko here, because if we haven't reduced
85 // memory usage enough when we return from this, Android will kill us.
86 GeckoAppShell.sendEventToGeckoSync(GeckoEvent.createNoOpEvent());
87 }
88 }
90 public void onTrimMemory(int level) {
91 Log.d(LOGTAG, "onTrimMemory() notification received with level " + level);
92 if (Build.VERSION.SDK_INT < 14) {
93 // this won't even get called pre-ICS
94 return;
95 }
97 if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
98 increaseMemoryPressure(MEMORY_PRESSURE_HIGH);
99 } else if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
100 increaseMemoryPressure(MEMORY_PRESSURE_MEDIUM);
101 } else if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
102 // includes TRIM_MEMORY_BACKGROUND
103 increaseMemoryPressure(MEMORY_PRESSURE_CLEANUP);
104 } else {
105 // levels down here mean gecko is the foreground process so we
106 // should be less aggressive with wiping memory as it may impact
107 // user experience.
108 increaseMemoryPressure(MEMORY_PRESSURE_LOW);
109 }
110 }
112 @Override
113 public void onReceive(Context context, Intent intent) {
114 if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(intent.getAction())) {
115 Log.d(LOGTAG, "Device storage is low");
116 mStoragePressure = true;
117 ThreadUtils.postToBackgroundThread(new StorageReducer(context));
118 } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(intent.getAction())) {
119 Log.d(LOGTAG, "Device storage is ok");
120 mStoragePressure = false;
121 } else if (ACTION_MEMORY_DUMP.equals(intent.getAction())) {
122 String label = intent.getStringExtra("label");
123 if (label == null) {
124 label = "default";
125 }
126 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Memory:Dump", label));
127 } else if (ACTION_FORCE_PRESSURE.equals(intent.getAction())) {
128 increaseMemoryPressure(MEMORY_PRESSURE_HIGH);
129 }
130 }
132 private boolean increaseMemoryPressure(int level) {
133 int oldLevel;
134 synchronized (this) {
135 // bump up our level if we're not already higher
136 if (mMemoryPressure > level) {
137 return false;
138 }
139 oldLevel = mMemoryPressure;
140 mMemoryPressure = level;
141 }
143 // since we don't get notifications for when memory pressure is off,
144 // we schedule our own timer to slowly back off the memory pressure level.
145 // note that this will reset the time to next decrement if the decrementer
146 // is already running, which is the desired behaviour because we just got
147 // a new low-mem notification.
148 mPressureDecrementer.start();
150 if (oldLevel == level) {
151 // if we're not going to a higher level we probably don't
152 // need to run another round of the same memory reductions
153 // we did on the last memory pressure increase.
154 return false;
155 }
157 // TODO hook in memory-reduction stuff for different levels here
158 if (level >= MEMORY_PRESSURE_MEDIUM) {
159 //Only send medium or higher events because that's all that is used right now
160 if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
161 GeckoAppShell.dispatchMemoryPressure();
162 }
164 Favicons.clearMemCache();
165 }
166 return true;
167 }
169 private boolean decreaseMemoryPressure() {
170 int newLevel;
171 synchronized (this) {
172 if (mMemoryPressure <= 0) {
173 return false;
174 }
176 newLevel = --mMemoryPressure;
177 }
178 Log.d(LOGTAG, "Decreased memory pressure to " + newLevel);
180 return true;
181 }
183 class PressureDecrementer implements Runnable {
184 private static final int DECREMENT_DELAY = 5 * 60 * 1000; // 5 minutes
186 private boolean mPosted;
188 synchronized void start() {
189 if (mPosted) {
190 // cancel the old one before scheduling a new one
191 ThreadUtils.getBackgroundHandler().removeCallbacks(this);
192 }
193 ThreadUtils.getBackgroundHandler().postDelayed(this, DECREMENT_DELAY);
194 mPosted = true;
195 }
197 @Override
198 public synchronized void run() {
199 if (!decreaseMemoryPressure()) {
200 // done decrementing, bail out
201 mPosted = false;
202 return;
203 }
205 // need to keep decrementing
206 ThreadUtils.getBackgroundHandler().postDelayed(this, DECREMENT_DELAY);
207 }
208 }
210 class StorageReducer implements Runnable {
211 private final Context mContext;
212 public StorageReducer(final Context context) {
213 this.mContext = context;
214 }
216 @Override
217 public void run() {
218 // this might get run right on startup, if so wait 10 seconds and try again
219 if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
220 ThreadUtils.getBackgroundHandler().postDelayed(this, 10000);
221 return;
222 }
224 if (!mStoragePressure) {
225 // pressure is off, so we can abort
226 return;
227 }
229 BrowserDB.expireHistory(mContext.getContentResolver(),
230 BrowserContract.ExpirePriority.AGGRESSIVE);
231 BrowserDB.removeThumbnails(mContext.getContentResolver());
232 // TODO: drop or shrink disk caches
233 }
234 }
235 }