1/*
2 * Copyright 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package androidx.work.impl.workers;
18
19import static org.hamcrest.CoreMatchers.is;
20import static org.hamcrest.MatcherAssert.assertThat;
21import static org.mockito.Mockito.mock;
22import static org.mockito.Mockito.spy;
23import static org.mockito.Mockito.when;
24
25import android.content.Context;
26import android.os.Handler;
27import android.os.Looper;
28import android.support.annotation.NonNull;
29import android.support.test.InstrumentationRegistry;
30import android.support.test.filters.SdkSuppress;
31import android.support.test.filters.SmallTest;
32import android.support.test.runner.AndroidJUnit4;
33
34import androidx.work.Configuration;
35import androidx.work.Constraints;
36import androidx.work.Data;
37import androidx.work.DatabaseTest;
38import androidx.work.OneTimeWorkRequest;
39import androidx.work.State;
40import androidx.work.impl.ExecutionListener;
41import androidx.work.impl.Extras;
42import androidx.work.impl.Scheduler;
43import androidx.work.impl.WorkManagerImpl;
44import androidx.work.impl.WorkerWrapper;
45import androidx.work.impl.constraints.trackers.BatteryChargingTracker;
46import androidx.work.impl.constraints.trackers.BatteryNotLowTracker;
47import androidx.work.impl.constraints.trackers.NetworkStateTracker;
48import androidx.work.impl.constraints.trackers.StorageNotLowTracker;
49import androidx.work.impl.constraints.trackers.Trackers;
50import androidx.work.impl.model.WorkSpec;
51import androidx.work.worker.EchoingWorker;
52import androidx.work.worker.SleepTestWorker;
53import androidx.work.worker.TestWorker;
54
55import org.junit.After;
56import org.junit.Before;
57import org.junit.Test;
58import org.junit.runner.RunWith;
59
60import java.util.Collections;
61import java.util.concurrent.CountDownLatch;
62import java.util.concurrent.ExecutorService;
63import java.util.concurrent.Executors;
64import java.util.concurrent.TimeUnit;
65
66@RunWith(AndroidJUnit4.class)
67@SmallTest
68public class ConstraintTrackingWorkerTest extends DatabaseTest implements ExecutionListener {
69
70    private static final long DELAY_IN_MILLIS = 100;
71    private static final long TEST_TIMEOUT_IN_SECONDS = 6;
72    private static final String TEST_ARGUMENT_NAME = "test";
73
74    private Context mContext;
75    private Handler mHandler;
76    private CountDownLatch mLatch;
77    private ExecutorService mExecutorService;
78
79    private WorkManagerImpl mWorkManagerImpl;
80    private Configuration mConfiguration;
81    private Scheduler mScheduler;
82    private Trackers mTracker;
83    private BatteryChargingTracker mBatteryChargingTracker;
84    private BatteryNotLowTracker mBatteryNotLowTracker;
85    private NetworkStateTracker mNetworkStateTracker;
86    private StorageNotLowTracker mStorageNotLowTracker;
87
88    @Before
89    public void setUp() {
90        mContext = InstrumentationRegistry.getTargetContext().getApplicationContext();
91        mHandler = new Handler(Looper.getMainLooper());
92        mExecutorService = Executors.newSingleThreadScheduledExecutor();
93        mLatch = new CountDownLatch(1);
94        mConfiguration = new Configuration.Builder().build();
95
96        mWorkManagerImpl = mock(WorkManagerImpl.class);
97        mScheduler = mock(Scheduler.class);
98        when(mWorkManagerImpl.getWorkDatabase()).thenReturn(mDatabase);
99        when(mWorkManagerImpl.getConfiguration()).thenReturn(mConfiguration);
100
101        mBatteryChargingTracker = spy(new BatteryChargingTracker(mContext));
102        mBatteryNotLowTracker = spy(new BatteryNotLowTracker(mContext));
103        // Requires API 24+ types.
104        mNetworkStateTracker = mock(NetworkStateTracker.class);
105        mStorageNotLowTracker = spy(new StorageNotLowTracker(mContext));
106        mTracker = mock(Trackers.class);
107
108        when(mTracker.getBatteryChargingTracker()).thenReturn(mBatteryChargingTracker);
109        when(mTracker.getBatteryNotLowTracker()).thenReturn(mBatteryNotLowTracker);
110        when(mTracker.getNetworkStateTracker()).thenReturn(mNetworkStateTracker);
111        when(mTracker.getStorageNotLowTracker()).thenReturn(mStorageNotLowTracker);
112
113        // Override Trackers being used by WorkConstraintsProxy
114        Trackers.setInstance(mTracker);
115    }
116
117    @After
118    public void tearDown() {
119        mExecutorService.shutdownNow();
120    }
121
122    @Test
123    @SdkSuppress(minSdkVersion = 23, maxSdkVersion = 25)
124    public void testConstraintTrackingWorker_onConstraintsMet() throws InterruptedException {
125        when(mBatteryNotLowTracker.getInitialState()).thenReturn(true);
126        Constraints constraints = new Constraints.Builder()
127                .setRequiresBatteryNotLow(true)
128                .build();
129
130        String delegateName = EchoingWorker.class.getName();
131
132        Data input = new Data.Builder()
133                .putString(ConstraintTrackingWorker.ARGUMENT_CLASS_NAME, delegateName)
134                .putBoolean(TEST_ARGUMENT_NAME, true)
135                .build();
136
137        final OneTimeWorkRequest work =
138                new OneTimeWorkRequest.Builder(ConstraintTrackingWorker.class)
139                    .setInputData(input)
140                    .setConstraints(constraints)
141                    .build();
142
143        insertWork(work);
144        String workSpecId = work.getStringId();
145
146        ConstraintTrackingWorker worker =
147                (ConstraintTrackingWorker) WorkerWrapper.workerFromClassName(
148                        mContext,
149                        ConstraintTrackingWorker.class.getName(),
150                        work.getId(),
151                        new Extras(input, Collections.<String>emptyList(), null, 1));
152
153        ConstraintTrackingWorker spyWorker = spy(worker);
154        when(spyWorker.getWorkDatabase()).thenReturn(mDatabase);
155
156        WorkerWrapper.Builder builder =
157                new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, workSpecId);
158        builder.withWorker(spyWorker)
159                .withListener(this)
160                .withSchedulers(Collections.singletonList(mScheduler));
161
162        mExecutorService.submit(builder.build());
163        mLatch.await(TEST_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
164        WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(workSpecId);
165        assertThat(mLatch.getCount(), is(0L));
166        assertThat(workSpec.state, is(State.SUCCEEDED));
167        Data output = workSpec.output;
168        assertThat(output.getBoolean(TEST_ARGUMENT_NAME, false), is(true));
169    }
170
171    @Test
172    @SdkSuppress(minSdkVersion = 23, maxSdkVersion = 25)
173    public void testConstraintTrackingWorker_onConstraintsNotMet() throws InterruptedException {
174        when(mBatteryNotLowTracker.getInitialState()).thenReturn(false);
175        Constraints constraints = new Constraints.Builder()
176                .setRequiresBatteryNotLow(true)
177                .build();
178
179        String delegateName = TestWorker.class.getName();
180        Data input = new Data.Builder()
181                .putString(ConstraintTrackingWorker.ARGUMENT_CLASS_NAME, delegateName)
182                .build();
183
184        final OneTimeWorkRequest work =
185                new OneTimeWorkRequest.Builder(ConstraintTrackingWorker.class)
186                    .setConstraints(constraints)
187                    .build();
188
189        insertWork(work);
190        String workSpecId = work.getStringId();
191
192        ConstraintTrackingWorker worker =
193                (ConstraintTrackingWorker) WorkerWrapper.workerFromClassName(
194                        mContext,
195                        ConstraintTrackingWorker.class.getName(),
196                        work.getId(),
197                        new Extras(input, Collections.<String>emptyList(), null, 1));
198
199        ConstraintTrackingWorker spyWorker = spy(worker);
200        when(spyWorker.getWorkDatabase()).thenReturn(mDatabase);
201
202        WorkerWrapper.Builder builder =
203                new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, workSpecId);
204        builder.withWorker(spyWorker)
205                .withListener(this)
206                .withSchedulers(Collections.singletonList(mScheduler));
207
208        mExecutorService.submit(builder.build());
209        mLatch.await(TEST_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
210        WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(workSpecId);
211        assertThat(mLatch.getCount(), is(0L));
212        assertThat(workSpec.state, is(State.ENQUEUED));
213    }
214
215    @Test
216    @SdkSuppress(minSdkVersion = 23, maxSdkVersion = 25)
217    public void testConstraintTrackingWorker_onConstraintsChanged() throws InterruptedException {
218        when(mBatteryNotLowTracker.getInitialState()).thenReturn(true);
219        Constraints constraints = new Constraints.Builder()
220                .setRequiresBatteryNotLow(true)
221                .build();
222
223        String delegateName = SleepTestWorker.class.getName();
224        Data input = new Data.Builder()
225                .putString(ConstraintTrackingWorker.ARGUMENT_CLASS_NAME, delegateName)
226                .build();
227
228        final OneTimeWorkRequest work =
229                new OneTimeWorkRequest.Builder(ConstraintTrackingWorker.class)
230                    .setConstraints(constraints)
231                    .build();
232
233        insertWork(work);
234
235        String workSpecId = work.getStringId();
236
237        ConstraintTrackingWorker worker =
238                (ConstraintTrackingWorker) WorkerWrapper.workerFromClassName(
239                        mContext,
240                        ConstraintTrackingWorker.class.getName(),
241                        work.getId(),
242                        new Extras(input, Collections.<String>emptyList(), null, 1));
243
244        ConstraintTrackingWorker spyWorker = spy(worker);
245        when(spyWorker.getWorkDatabase()).thenReturn(mDatabase);
246        WorkerWrapper.Builder builder =
247                new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, workSpecId);
248        builder.withWorker(spyWorker)
249                .withListener(this)
250                .withSchedulers(Collections.singletonList(mScheduler));
251
252        mExecutorService.submit(builder.build());
253        mHandler.postDelayed(new Runnable() {
254            @Override
255            public void run() {
256                mBatteryNotLowTracker.setState(false);
257            }
258        }, DELAY_IN_MILLIS);
259
260        mLatch.await(TEST_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
261        WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(workSpecId);
262        assertThat(mLatch.getCount(), is(0L));
263        assertThat(workSpec.state, is(State.ENQUEUED));
264    }
265
266    @Test
267    @SdkSuppress(minSdkVersion = 23, maxSdkVersion = 25)
268    public void testConstraintTrackingWorker_onConstraintsChangedTwice()
269            throws InterruptedException {
270        when(mBatteryNotLowTracker.getInitialState()).thenReturn(true);
271        Constraints constraints = new Constraints.Builder()
272                .setRequiresBatteryNotLow(true)
273                .build();
274
275        String delegateName = SleepTestWorker.class.getName();
276        Data input = new Data.Builder()
277                .putString(ConstraintTrackingWorker.ARGUMENT_CLASS_NAME, delegateName)
278                .build();
279
280        final OneTimeWorkRequest work =
281                new OneTimeWorkRequest.Builder(ConstraintTrackingWorker.class)
282                    .setConstraints(constraints)
283                    .build();
284
285        insertWork(work);
286
287        String workSpecId = work.getStringId();
288
289        ConstraintTrackingWorker worker =
290                (ConstraintTrackingWorker) WorkerWrapper.workerFromClassName(
291                        mContext,
292                        ConstraintTrackingWorker.class.getName(),
293                        work.getId(),
294                        new Extras(input, Collections.<String>emptyList(), null, 1));
295
296        ConstraintTrackingWorker spyWorker = spy(worker);
297        when(spyWorker.getWorkDatabase()).thenReturn(mDatabase);
298
299        WorkerWrapper.Builder builder =
300                new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, workSpecId);
301        builder.withWorker(spyWorker)
302                .withListener(this)
303                .withSchedulers(Collections.singletonList(mScheduler));
304
305        mExecutorService.submit(builder.build());
306
307        mHandler.postDelayed(new Runnable() {
308            @Override
309            public void run() {
310                mBatteryNotLowTracker.setState(false);
311            }
312        }, DELAY_IN_MILLIS);
313
314        mHandler.postDelayed(new Runnable() {
315            @Override
316            public void run() {
317                mBatteryNotLowTracker.setState(true);
318            }
319        }, DELAY_IN_MILLIS);
320
321        mLatch.await(TEST_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
322        WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(workSpecId);
323        assertThat(mLatch.getCount(), is(0L));
324        assertThat(workSpec.state, is(State.ENQUEUED));
325    }
326
327    @Override
328    public void onExecuted(
329            @NonNull String workSpecId,
330            boolean isSuccessful,
331            boolean needsReschedule) {
332        mLatch.countDown();
333    }
334}
335