1 // Copyright 2016 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.offlinepages; 6 7 import static org.junit.Assert.assertEquals; 8 import static org.junit.Assert.assertFalse; 9 import static org.junit.Assert.assertTrue; 10 import static org.mockito.Mockito.any; 11 import static org.mockito.Mockito.anyBoolean; 12 import static org.mockito.Mockito.doReturn; 13 import static org.mockito.Mockito.eq; 14 import static org.mockito.Mockito.times; 15 import static org.mockito.Mockito.verify; 16 17 import android.app.Activity; 18 import android.content.Context; 19 import android.os.Bundle; 20 21 import org.junit.After; 22 import org.junit.Before; 23 import org.junit.Test; 24 import org.junit.runner.RunWith; 25 import org.mockito.ArgumentCaptor; 26 import org.mockito.ArgumentMatchers; 27 import org.mockito.Captor; 28 import org.mockito.Mock; 29 import org.mockito.MockitoAnnotations; 30 import org.robolectric.annotation.Config; 31 32 import org.chromium.base.ActivityState; 33 import org.chromium.base.ApplicationStatus; 34 import org.chromium.base.BaseSwitches; 35 import org.chromium.base.Callback; 36 import org.chromium.base.CommandLine; 37 import org.chromium.base.ContextUtils; 38 import org.chromium.base.SysUtils; 39 import org.chromium.base.test.BaseRobolectricTestRunner; 40 import org.chromium.base.test.util.Feature; 41 import org.chromium.chrome.browser.device.DeviceConditions; 42 import org.chromium.chrome.browser.device.ShadowDeviceConditions; 43 import org.chromium.components.background_task_scheduler.BackgroundTask; 44 import org.chromium.components.background_task_scheduler.BackgroundTaskScheduler; 45 import org.chromium.components.background_task_scheduler.BackgroundTaskSchedulerFactory; 46 import org.chromium.components.background_task_scheduler.NativeBackgroundTask; 47 import org.chromium.components.background_task_scheduler.TaskIds; 48 import org.chromium.components.background_task_scheduler.TaskInfo; 49 import org.chromium.components.background_task_scheduler.TaskParameters; 50 import org.chromium.net.ConnectionType; 51 52 /** 53 * Unit tests for OfflineBackgroundTask. 54 */ 55 @RunWith(BaseRobolectricTestRunner.class) 56 @Config(manifest = Config.NONE, shadows = {ShadowDeviceConditions.class}) 57 public class OfflineBackgroundTaskTest { 58 private static final boolean REQUIRE_POWER = true; 59 private static final boolean REQUIRE_UNMETERED = true; 60 private static final boolean POWER_CONNECTED = true; 61 private static final boolean POWER_SAVE_MODE_ON = true; 62 private static final boolean METERED = true; 63 private static final boolean SCREEN_ON_AND_UNLOCKED = true; 64 private static final int MINIMUM_BATTERY_LEVEL = 33; 65 private static final String IS_LOW_END_DEVICE_SWITCH = 66 "--" + BaseSwitches.ENABLE_LOW_END_DEVICE_MODE; 67 68 69 private Bundle mTaskExtras; 70 private long mTestTime; 71 private TriggerConditions mTriggerConditions = 72 new TriggerConditions(!REQUIRE_POWER, MINIMUM_BATTERY_LEVEL, REQUIRE_UNMETERED); 73 private DeviceConditions mDeviceConditions = new DeviceConditions(!POWER_CONNECTED, 74 MINIMUM_BATTERY_LEVEL + 5, ConnectionType.CONNECTION_3G, !POWER_SAVE_MODE_ON, !METERED, 75 SCREEN_ON_AND_UNLOCKED); 76 private Activity mTestActivity; 77 78 @Mock 79 private BackgroundSchedulerProcessor mBackgroundSchedulerProcessor; 80 81 @Mock 82 private BackgroundTaskScheduler mTaskScheduler; 83 @Mock 84 private BackgroundTask.TaskFinishedCallback mTaskFinishedCallback; 85 @Mock 86 private Callback<Boolean> mInternalBooleanCallback; 87 @Captor 88 private ArgumentCaptor<TaskInfo> mTaskInfo; 89 90 @Before setUp()91 public void setUp() { 92 MockitoAnnotations.initMocks(this); 93 BackgroundTaskSchedulerFactory.setSchedulerForTesting(mTaskScheduler); 94 doReturn(true) 95 .when(mTaskScheduler) 96 .schedule(eq(ContextUtils.getApplicationContext()), mTaskInfo.capture()); 97 98 ShadowDeviceConditions.setCurrentConditions(mDeviceConditions); 99 100 // Set up background scheduler processor mock. 101 BackgroundSchedulerProcessor.setInstanceForTesting(mBackgroundSchedulerProcessor); 102 103 // Build a bundle with trigger conditions. 104 mTaskExtras = new Bundle(); 105 TaskExtrasPacker.packTimeInBundle(mTaskExtras); 106 TaskExtrasPacker.packTriggerConditionsInBundle(mTaskExtras, mTriggerConditions); 107 108 // Run tests as a low-end device. 109 CommandLine.init(new String[] {"testcommand", IS_LOW_END_DEVICE_SWITCH}); 110 111 // Set up single, stopped Activity. 112 mTestActivity = new Activity(); 113 ApplicationStatus.onStateChangeForTesting(mTestActivity, ActivityState.CREATED); 114 ApplicationStatus.onStateChangeForTesting(mTestActivity, ActivityState.STOPPED); 115 } 116 117 @After tearDown()118 public void tearDown() { 119 // Clean up static state for subsequent Robolectric tests. 120 CommandLine.reset(); 121 SysUtils.resetForTesting(); 122 } 123 setupScheduledProcessingWithResult(boolean result)124 private void setupScheduledProcessingWithResult(boolean result) { 125 doReturn(result) 126 .when(mBackgroundSchedulerProcessor) 127 .startScheduledProcessing( 128 any(DeviceConditions.class), ArgumentMatchers.<Callback<Boolean>>any()); 129 } 130 131 @Test 132 @Feature({"OfflinePages"}) testCheckConditions_BatteryConditions_LowBattery_NoPower()133 public void testCheckConditions_BatteryConditions_LowBattery_NoPower() { 134 // Setup low battery conditions with no power connected. 135 DeviceConditions deviceConditionsLowBattery = new DeviceConditions(!POWER_CONNECTED, 136 MINIMUM_BATTERY_LEVEL - 1, ConnectionType.CONNECTION_WIFI, !POWER_SAVE_MODE_ON, 137 !METERED, SCREEN_ON_AND_UNLOCKED); 138 ShadowDeviceConditions.setCurrentConditions(deviceConditionsLowBattery); 139 140 // Verify that conditions for processing are not met. 141 assertFalse(OfflineBackgroundTask.checkConditions( 142 ContextUtils.getApplicationContext(), mTaskExtras)); 143 144 // Check impact on starting before native loaded. 145 TaskParameters params = TaskParameters.create(TaskIds.OFFLINE_PAGES_BACKGROUND_JOB_ID) 146 .addExtras(mTaskExtras) 147 .build(); 148 149 int result = new OfflineBackgroundTask().onStartTaskBeforeNativeLoaded( 150 ContextUtils.getApplicationContext(), params, mTaskFinishedCallback); 151 assertEquals(NativeBackgroundTask.StartBeforeNativeResult.RESCHEDULE, result); 152 // Task finished can only gets called from the native part, when async processing starts. 153 verify(mTaskFinishedCallback, times(0)).taskFinished(anyBoolean()); 154 } 155 156 @Test 157 @Feature({"OfflinePages"}) testCheckConditions_BatteryConditions_LowBattery_WithPower()158 public void testCheckConditions_BatteryConditions_LowBattery_WithPower() { 159 // Set battery percentage below minimum level, but connect power. 160 DeviceConditions deviceConditionsPowerConnected = new DeviceConditions(POWER_CONNECTED, 161 MINIMUM_BATTERY_LEVEL - 1, ConnectionType.CONNECTION_WIFI, !POWER_SAVE_MODE_ON, 162 !METERED, SCREEN_ON_AND_UNLOCKED); 163 ShadowDeviceConditions.setCurrentConditions(deviceConditionsPowerConnected); 164 165 // Now verify that same battery level, with power connected, will pass the conditions. 166 assertTrue(OfflineBackgroundTask.checkConditions( 167 ContextUtils.getApplicationContext(), mTaskExtras)); 168 169 // Check impact on starting before native loaded. 170 TaskParameters params = TaskParameters.create(TaskIds.OFFLINE_PAGES_BACKGROUND_JOB_ID) 171 .addExtras(mTaskExtras) 172 .build(); 173 174 int result = new OfflineBackgroundTask().onStartTaskBeforeNativeLoaded( 175 ContextUtils.getApplicationContext(), params, mTaskFinishedCallback); 176 assertEquals(NativeBackgroundTask.StartBeforeNativeResult.LOAD_NATIVE, result); 177 // Task finished can only gets called from the native part, when async processing starts. 178 verify(mTaskFinishedCallback, times(0)).taskFinished(anyBoolean()); 179 } 180 181 @Test 182 @Feature({"OfflinePages"}) testCheckConditions_OnLowEndDevice_ActivityStarted()183 public void testCheckConditions_OnLowEndDevice_ActivityStarted() { 184 // Transition the test Activity to a running state. 185 ApplicationStatus.onStateChangeForTesting(mTestActivity, ActivityState.STARTED); 186 187 // Verify that conditions for processing are not met. 188 assertFalse(OfflineBackgroundTask.checkConditions( 189 ContextUtils.getApplicationContext(), mTaskExtras)); 190 191 // Check impact on starting before native loaded. 192 TaskParameters params = TaskParameters.create(TaskIds.OFFLINE_PAGES_BACKGROUND_JOB_ID) 193 .addExtras(mTaskExtras) 194 .build(); 195 196 int result = new OfflineBackgroundTask().onStartTaskBeforeNativeLoaded( 197 ContextUtils.getApplicationContext(), params, mTaskFinishedCallback); 198 assertEquals(NativeBackgroundTask.StartBeforeNativeResult.RESCHEDULE, result); 199 // Task finished can only gets called from the native part, when async processing starts. 200 verify(mTaskFinishedCallback, times(0)).taskFinished(anyBoolean()); 201 } 202 203 @Test 204 @Feature({"OfflinePages"}) testCheckConditions_OnLowEndDevice_ActivityStopped()205 public void testCheckConditions_OnLowEndDevice_ActivityStopped() { 206 // Switch activity state to stopped. 207 ApplicationStatus.onStateChangeForTesting(mTestActivity, ActivityState.STOPPED); 208 209 // Now verify that condition check passes when Activity is stopped. 210 assertTrue(OfflineBackgroundTask.checkConditions( 211 ContextUtils.getApplicationContext(), mTaskExtras)); 212 213 // Check impact on starting before native loaded. 214 TaskParameters params = TaskParameters.create(TaskIds.OFFLINE_PAGES_BACKGROUND_JOB_ID) 215 .addExtras(mTaskExtras) 216 .build(); 217 218 int result = new OfflineBackgroundTask().onStartTaskBeforeNativeLoaded( 219 ContextUtils.getApplicationContext(), params, mTaskFinishedCallback); 220 assertEquals(NativeBackgroundTask.StartBeforeNativeResult.LOAD_NATIVE, result); 221 // Task finished can only gets called from the native part, when async processing starts. 222 verify(mTaskFinishedCallback, times(0)).taskFinished(anyBoolean()); 223 } 224 225 @Test 226 @Feature({"OfflinePages"}) testOnStartTaskWithNative_BackupScheduleIfExecutingTask()227 public void testOnStartTaskWithNative_BackupScheduleIfExecutingTask() { 228 setupScheduledProcessingWithResult(true); 229 230 TaskParameters params = TaskParameters.create(TaskIds.OFFLINE_PAGES_BACKGROUND_JOB_ID) 231 .addExtras(mTaskExtras) 232 .build(); 233 234 new OfflineBackgroundTask().onStartTaskWithNative( 235 ContextUtils.getApplicationContext(), params, mTaskFinishedCallback); 236 237 verify(mTaskScheduler, times(1)) 238 .schedule(eq(ContextUtils.getApplicationContext()), any(TaskInfo.class)); 239 // Task is running at this point, hence no callback issued. 240 verify(mTaskFinishedCallback, times(0)).taskFinished(anyBoolean()); 241 } 242 243 @Test 244 @Feature({"OfflinePages"}) testOnStartTaskWithNative_RescheduleThroughCallbackWhenRunning()245 public void testOnStartTaskWithNative_RescheduleThroughCallbackWhenRunning() { 246 setupScheduledProcessingWithResult(false); 247 248 TaskParameters params = TaskParameters.create(TaskIds.OFFLINE_PAGES_BACKGROUND_JOB_ID) 249 .addExtras(mTaskExtras) 250 .build(); 251 252 new OfflineBackgroundTask().onStartTaskWithNative( 253 ContextUtils.getApplicationContext(), params, mTaskFinishedCallback); 254 255 verify(mTaskScheduler, times(0)).schedule(any(Context.class), any(TaskInfo.class)); 256 // Task started async processing after native load, but processing refused to progress, 257 // hence task finished called with reschedule request. 258 verify(mTaskFinishedCallback, times(1)).taskFinished(eq(true)); 259 } 260 261 @Test 262 @Feature({"OfflinePages"}) testStartBackgroundRequests()263 public void testStartBackgroundRequests() { 264 setupScheduledProcessingWithResult(true); 265 266 assertTrue(OfflineBackgroundTask.startScheduledProcessing(mBackgroundSchedulerProcessor, 267 ContextUtils.getApplicationContext(), mTaskExtras, mInternalBooleanCallback)); 268 269 // Check with BackgroundSchedulerProcessor that processing started. 270 verify(mBackgroundSchedulerProcessor, times(1)) 271 .startScheduledProcessing(eq(mDeviceConditions), eq(mInternalBooleanCallback)); 272 } 273 274 @Test 275 @Feature({"OfflinePages"}) testStartBackgroundRequestsNotStarted()276 public void testStartBackgroundRequestsNotStarted() { 277 // Processing will not be started here. 278 setupScheduledProcessingWithResult(false); 279 280 assertFalse(OfflineBackgroundTask.startScheduledProcessing(mBackgroundSchedulerProcessor, 281 ContextUtils.getApplicationContext(), mTaskExtras, mInternalBooleanCallback)); 282 283 // Check with BackgroundSchedulerProcessor that it did not start. 284 verify(mBackgroundSchedulerProcessor, times(1)) 285 .startScheduledProcessing(eq(mDeviceConditions), eq(mInternalBooleanCallback)); 286 } 287 } 288