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