| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.chrome.browser.precache; |
| 6 |
| 7 import android.app.AlarmManager; |
| 8 import android.app.PendingIntent; |
| 9 import android.content.BroadcastReceiver; |
| 10 import android.content.Context; |
| 11 import android.content.Intent; |
| 12 import android.content.SharedPreferences; |
| 13 import android.content.SharedPreferences.Editor; |
| 14 import android.os.PowerManager; |
| 15 import android.os.PowerManager.WakeLock; |
| 16 import android.os.SystemClock; |
| 17 import android.preference.PreferenceManager; |
| 18 |
| 19 import org.chromium.base.ThreadUtils; |
| 20 import org.chromium.base.VisibleForTesting; |
| 21 import org.chromium.components.precache.DeviceState; |
| 22 |
| 23 /** |
| 24 * BroadcastReceiver that determines when conditions are right for precaching, a
nd starts the |
| 25 * {@link PrecacheService} if they are. Conditions are right for precaching when
the device is |
| 26 * connected to power, Wi-Fi, interactivity (e.g., the screen) is off, and at le
ast |
| 27 * |WAIT_UNTIL_NEXT_PRECACHE_MS| have passed since the last time precaching was
done. |
| 28 */ |
| 29 public class PrecacheServiceLauncher extends BroadcastReceiver { |
| 30 private static final String TAG = "PrecacheServiceLauncher"; |
| 31 |
| 32 @VisibleForTesting |
| 33 static final String PREF_IS_PRECACHING_ENABLED = "precache.is_precaching_ena
bled"; |
| 34 |
| 35 @VisibleForTesting |
| 36 static final String PREF_PRECACHE_LAST_TIME = "precache.last_time"; |
| 37 |
| 38 @VisibleForTesting |
| 39 static final String ACTION_ALARM = |
| 40 "org.chromium.chrome.browser.precache.PrecacheServiceLauncher.ALARM"
; |
| 41 |
| 42 private static final int INTERACTIVE_STATE_POLLING_PERIOD_MS = 15 * 60 * 100
0; // 15 minutes. |
| 43 static final int WAIT_UNTIL_NEXT_PRECACHE_MS = 4 * 60 * 60 * 1000; // 4 hou
rs. |
| 44 |
| 45 private static WakeLock sWakeLock = null; |
| 46 |
| 47 private DeviceState mDeviceState = DeviceState.getInstance(); |
| 48 |
| 49 @VisibleForTesting |
| 50 void setDeviceState(DeviceState deviceState) { |
| 51 mDeviceState = deviceState; |
| 52 } |
| 53 |
| 54 /** |
| 55 * Set whether or not precaching is enabled. If precaching is enabled, this
receiver will start |
| 56 * the PrecacheService when it receives an intent. If precaching is disabled
, any running |
| 57 * PrecacheService will be stopped, and this receiver will do nothing when i
t receives an |
| 58 * intent. |
| 59 * |
| 60 * @param context The Context to use. |
| 61 * @param enabled Whether or not precaching is enabled. |
| 62 */ |
| 63 public static void setIsPrecachingEnabled(Context context, boolean enabled)
{ |
| 64 Editor editor = PreferenceManager.getDefaultSharedPreferences(context).e
dit(); |
| 65 editor.putBoolean(PREF_IS_PRECACHING_ENABLED, enabled); |
| 66 editor.apply(); |
| 67 |
| 68 if (!enabled) { |
| 69 // Stop any running PrecacheService. If PrecacheService is not runni
ng, then this does |
| 70 // nothing. |
| 71 Intent serviceIntent = new Intent(null, null, context, PrecacheServi
ce.class); |
| 72 context.stopService(serviceIntent); |
| 73 } |
| 74 } |
| 75 |
| 76 |
| 77 @Override |
| 78 public void onReceive(Context context, Intent intent) { |
| 79 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
context); |
| 80 boolean isPrecachingEnabled = prefs.getBoolean(PREF_IS_PRECACHING_ENABLE
D, false); |
| 81 long lastPrecacheTimeMs = prefs.getLong(PREF_PRECACHE_LAST_TIME, 0L); |
| 82 if (lastPrecacheTimeMs > getElapsedRealtimeOnSystem()) { |
| 83 // System.elapsedRealtime() counts milliseconds since boot, so if th
e device has been |
| 84 // rebooted since the last time precaching was performed, reset last
PrecacheTimeMs to 0. |
| 85 lastPrecacheTimeMs = 0L; |
| 86 } |
| 87 |
| 88 // Do nothing if precaching is disabled. |
| 89 if (!isPrecachingEnabled) return; |
| 90 |
| 91 boolean isPowerConnected = mDeviceState.isPowerConnected(context); |
| 92 boolean isWifiAvailable = mDeviceState.isWifiAvailable(context); |
| 93 boolean isInteractive = mDeviceState.isInteractive(context); |
| 94 boolean areConditionsGoodForPrecaching = |
| 95 isPowerConnected && isWifiAvailable && !isInteractive; |
| 96 long timeSinceLastPrecacheMs = getElapsedRealtimeOnSystem() - lastPrecac
heTimeMs; |
| 97 boolean hasEnoughTimePassedSinceLastPrecache = |
| 98 timeSinceLastPrecacheMs >= WAIT_UNTIL_NEXT_PRECACHE_MS; |
| 99 |
| 100 // Only start precaching when an alarm action is received. This is to pr
event situations |
| 101 // such as power being connected, precaching starting, then precaching b
eing immediately |
| 102 // canceled because the screen turns on in response to power being conne
cted. |
| 103 if (ACTION_ALARM.equals(intent.getAction()) |
| 104 && areConditionsGoodForPrecaching |
| 105 && hasEnoughTimePassedSinceLastPrecache) { |
| 106 // Store a pref indicating that precaching is starting now. |
| 107 Editor editor = prefs.edit(); |
| 108 editor.putLong(PREF_PRECACHE_LAST_TIME, getElapsedRealtimeOnSystem()
); |
| 109 editor.apply(); |
| 110 |
| 111 setAlarm(context, Math.max( |
| 112 INTERACTIVE_STATE_POLLING_PERIOD_MS, WAIT_UNTIL_NEXT_PRECACH
E_MS)); |
| 113 |
| 114 acquireWakeLockAndStartService(context); |
| 115 } else { |
| 116 if (isPowerConnected && isWifiAvailable) { |
| 117 // If we're just waiting for non-interactivity (e.g., the screen
to be off), or for |
| 118 // enough time to pass after Wi-Fi or power has been connected,
then set an alarm |
| 119 // for the next time to check the device state. We can't receive
SCREEN_ON/OFF |
| 120 // intents as is done for detecting changes in power and connect
ivity, because |
| 121 // SCREEN_ON/OFF intents are only delivered to BroadcastReceiver
s that are |
| 122 // registered dynamically in code, but the PrecacheServiceLaunch
er is registered in |
| 123 // the Android manifest. |
| 124 setAlarm(context, Math.max(INTERACTIVE_STATE_POLLING_PERIOD_MS, |
| 125 WAIT_UNTIL_NEXT_PRECACHE_MS - timeSinceLastPrecacheMs)); |
| 126 } else { |
| 127 // If the device doesn't have connected power or doesn't have Wi
-Fi, then there's no |
| 128 // point in setting an alarm. |
| 129 cancelAlarm(context); |
| 130 } |
| 131 } |
| 132 } |
| 133 |
| 134 /** Release the wakelock if it is held. */ |
| 135 @VisibleForTesting |
| 136 protected static void releaseWakeLock() { |
| 137 ThreadUtils.assertOnUiThread(); |
| 138 if (sWakeLock != null && sWakeLock.isHeld()) sWakeLock.release(); |
| 139 } |
| 140 |
| 141 /** |
| 142 * Acquire the wakelock and start the PrecacheService. |
| 143 * |
| 144 * @param context The Context to use to start the service. |
| 145 */ |
| 146 private void acquireWakeLockAndStartService(Context context) { |
| 147 acquireWakeLock(context); |
| 148 startPrecacheService(context); |
| 149 } |
| 150 |
| 151 @VisibleForTesting |
| 152 protected void startPrecacheService(Context context) { |
| 153 Intent serviceIntent = new Intent( |
| 154 PrecacheService.ACTION_START_PRECACHE, null, context, PrecacheSe
rvice.class); |
| 155 context.startService(serviceIntent); |
| 156 } |
| 157 |
| 158 @VisibleForTesting |
| 159 protected void acquireWakeLock(Context context) { |
| 160 ThreadUtils.assertOnUiThread(); |
| 161 if (sWakeLock == null) { |
| 162 PowerManager pm = (PowerManager) context.getSystemService(Context.PO
WER_SERVICE); |
| 163 sWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); |
| 164 } |
| 165 sWakeLock.acquire(); |
| 166 } |
| 167 |
| 168 /** |
| 169 * Get a PendingIntent for setting an alarm to notify the PrecacheServiceLau
ncher. |
| 170 * |
| 171 * @param context The Context to use for the PendingIntent. |
| 172 * @return The PendingIntent. |
| 173 */ |
| 174 private static PendingIntent getPendingAlarmIntent(Context context) { |
| 175 return PendingIntent.getBroadcast(context, 0, |
| 176 new Intent(ACTION_ALARM, null, context, PrecacheServiceLauncher.
class), |
| 177 PendingIntent.FLAG_UPDATE_CURRENT); |
| 178 } |
| 179 |
| 180 /** |
| 181 * Set an alarm to notify the PrecacheServiceLauncher in the future. |
| 182 * |
| 183 * @param context The Context to use. |
| 184 * @param delayMs Delay in milliseconds before the alarm goes off. |
| 185 */ |
| 186 private void setAlarm(Context context, long delayMs) { |
| 187 setAlarmOnSystem(context, AlarmManager.ELAPSED_REALTIME_WAKEUP, |
| 188 getElapsedRealtimeOnSystem() + delayMs, getPendingAlarmIntent(co
ntext)); |
| 189 } |
| 190 |
| 191 /** |
| 192 * Set the alarm on the system using the given parameters. This method can b
e overridden in |
| 193 * tests. |
| 194 */ |
| 195 @VisibleForTesting |
| 196 protected void setAlarmOnSystem(Context context, int type, long triggerAtMil
lis, |
| 197 PendingIntent operation) { |
| 198 AlarmManager alarmManager = (AlarmManager) context.getSystemService(Cont
ext.ALARM_SERVICE); |
| 199 alarmManager.set(type, triggerAtMillis, operation); |
| 200 } |
| 201 |
| 202 /** |
| 203 * Cancel a previously set alarm, if there is one. This method can be overri
dden in tests. |
| 204 * |
| 205 * @param context The Context to use. |
| 206 */ |
| 207 private void cancelAlarm(Context context) { |
| 208 cancelAlarmOnSystem(context, getPendingAlarmIntent(context)); |
| 209 } |
| 210 |
| 211 /** Cancel a previously set alarm on the system. This method can be overridd
en in tests. */ |
| 212 @VisibleForTesting |
| 213 protected void cancelAlarmOnSystem(Context context, PendingIntent operation)
{ |
| 214 AlarmManager alarmManager = (AlarmManager) context.getSystemService(Cont
ext.ALARM_SERVICE); |
| 215 alarmManager.cancel(operation); |
| 216 } |
| 217 |
| 218 @VisibleForTesting |
| 219 protected long getElapsedRealtimeOnSystem() { |
| 220 return SystemClock.elapsedRealtime(); |
| 221 } |
| 222 } |
| 223 |
| OLD | NEW |