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