1 /* 2 * Copyright (C) 2023 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 17 package com.android.devicelockcontroller.receivers; 18 19 import static com.android.devicelockcontroller.WorkManagerExceptionHandler.ALARM_REASON; 20 21 import static org.mockito.ArgumentMatchers.any; 22 import static org.mockito.ArgumentMatchers.eq; 23 import static org.mockito.Mockito.verify; 24 import static org.mockito.Mockito.verifyNoInteractions; 25 import static org.mockito.Mockito.verifyNoMoreInteractions; 26 import static org.mockito.Mockito.when; 27 import static org.robolectric.Shadows.shadowOf; 28 29 import android.content.BroadcastReceiver; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.HandlerThread; 35 import android.os.Looper; 36 37 import androidx.test.core.app.ApplicationProvider; 38 39 import com.android.devicelockcontroller.TestDeviceLockControllerApplication; 40 import com.android.devicelockcontroller.WorkManagerExceptionHandler; 41 import com.android.devicelockcontroller.WorkManagerExceptionHandler.AlarmReason; 42 import com.android.devicelockcontroller.schedule.DeviceLockControllerScheduler; 43 import com.android.devicelockcontroller.storage.GlobalParametersClient; 44 45 import com.google.common.util.concurrent.Futures; 46 import com.google.common.util.concurrent.ListenableFuture; 47 import com.google.common.util.concurrent.SettableFuture; 48 49 import org.junit.Before; 50 import org.junit.Test; 51 import org.junit.runner.RunWith; 52 import org.robolectric.RobolectricTestRunner; 53 54 import java.time.Duration; 55 import java.util.concurrent.TimeUnit; 56 57 @RunWith(RobolectricTestRunner.class) 58 public final class WorkFailureAlarmReceiverTest { 59 private static final long TIMEOUT_MILLIS = 1000; 60 61 private DeviceLockControllerScheduler mScheduler; 62 private TestDeviceLockControllerApplication mTestApp; 63 private Handler mHandler; 64 65 @Before setUp()66 public void setUp() { 67 final HandlerThread handlerThread = new HandlerThread("BroadcastHandlerThread"); 68 handlerThread.start(); 69 mHandler = handlerThread.getThreadHandler(); 70 71 mTestApp = ApplicationProvider.getApplicationContext(); 72 mScheduler = mTestApp.getDeviceLockControllerScheduler(); 73 } 74 75 @Test onReceive_reasonInitialization_shouldCallMaybeScheduleInitialCheckIn()76 public void onReceive_reasonInitialization_shouldCallMaybeScheduleInitialCheckIn() 77 throws Exception { 78 GlobalParametersClient.getInstance().setProvisionReady(false).get(); 79 80 when(mScheduler.maybeScheduleInitialCheckIn()).thenReturn(Futures.immediateVoidFuture()); 81 82 final ListenableFuture<Void> broadcastComplete = 83 sendBroadcastToWorkFailureAlarmReceiver(AlarmReason.INITIALIZATION); 84 85 shadowOf(Looper.getMainLooper()).idle(); 86 87 broadcastComplete.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 88 89 verify(mScheduler).maybeScheduleInitialCheckIn(); 90 verifyNoMoreInteractions(mScheduler); 91 } 92 93 @Test onReceive_reasonInitialCheckIn_shouldCallMaybeScheduleInitialCheckIn()94 public void onReceive_reasonInitialCheckIn_shouldCallMaybeScheduleInitialCheckIn() 95 throws Exception { 96 GlobalParametersClient.getInstance().setProvisionReady(false).get(); 97 98 when(mScheduler.maybeScheduleInitialCheckIn()).thenReturn(Futures.immediateVoidFuture()); 99 100 final ListenableFuture<Void> broadcastComplete = 101 sendBroadcastToWorkFailureAlarmReceiver(AlarmReason.INITIAL_CHECK_IN); 102 103 shadowOf(Looper.getMainLooper()).idle(); 104 105 broadcastComplete.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 106 107 verify(mScheduler).maybeScheduleInitialCheckIn(); 108 verifyNoMoreInteractions(mScheduler); 109 } 110 111 @Test onReceive_reasonRetryCheckIn_shouldCallScheduleRetryCheckInWork()112 public void onReceive_reasonRetryCheckIn_shouldCallScheduleRetryCheckInWork() throws Exception { 113 GlobalParametersClient.getInstance().setProvisionReady(false).get(); 114 115 when(mScheduler.scheduleRetryCheckInWork(any(Duration.class))) 116 .thenReturn(Futures.immediateVoidFuture()); 117 118 final ListenableFuture<Void> broadcastComplete = 119 sendBroadcastToWorkFailureAlarmReceiver(AlarmReason.RETRY_CHECK_IN); 120 121 shadowOf(Looper.getMainLooper()).idle(); 122 123 broadcastComplete.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 124 125 verify(mScheduler).scheduleRetryCheckInWork(eq(Duration.ZERO)); 126 verifyNoMoreInteractions(mScheduler); 127 } 128 129 @Test onReceive_reasonRescheduleCheckIn_shouldCallNotifyNeedRescheduleCheckIn()130 public void onReceive_reasonRescheduleCheckIn_shouldCallNotifyNeedRescheduleCheckIn() 131 throws Exception { 132 GlobalParametersClient.getInstance().setProvisionReady(false).get(); 133 134 when(mScheduler.notifyNeedRescheduleCheckIn()).thenReturn(Futures.immediateVoidFuture()); 135 136 final ListenableFuture<Void> broadcastComplete = 137 sendBroadcastToWorkFailureAlarmReceiver(AlarmReason.RESCHEDULE_CHECK_IN); 138 139 shadowOf(Looper.getMainLooper()).idle(); 140 141 broadcastComplete.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 142 143 verify(mScheduler).notifyNeedRescheduleCheckIn(); 144 verifyNoMoreInteractions(mScheduler); 145 } 146 147 @Test onReceive_whenProvisionReady_doesNotSchedule()148 public void onReceive_whenProvisionReady_doesNotSchedule() throws Exception { 149 GlobalParametersClient.getInstance().setProvisionReady(true).get(); 150 151 when(mScheduler.maybeScheduleInitialCheckIn()).thenReturn(Futures.immediateVoidFuture()); 152 153 final ListenableFuture<Void> broadcastComplete = 154 sendBroadcastToWorkFailureAlarmReceiver(AlarmReason.INITIALIZATION); 155 156 shadowOf(Looper.getMainLooper()).idle(); 157 158 broadcastComplete.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 159 160 verifyNoInteractions(mScheduler); 161 } 162 getIntentForAlarmReason(@larmReason int alarmReason)163 private Intent getIntentForAlarmReason(@AlarmReason int alarmReason) { 164 final Intent intent = new Intent(mTestApp, 165 WorkManagerExceptionHandler.WorkFailureAlarmReceiver.class); 166 final Bundle bundle = new Bundle(); 167 bundle.putInt(ALARM_REASON, alarmReason); 168 intent.putExtras(bundle); 169 return intent; 170 } 171 sendBroadcastToWorkFailureAlarmReceiver( @larmReason int alarmReason)172 private ListenableFuture<Void> sendBroadcastToWorkFailureAlarmReceiver( 173 @AlarmReason int alarmReason) { 174 final SettableFuture<Void> broadcastComplete = SettableFuture.create(); 175 176 mTestApp.sendOrderedBroadcast( 177 getIntentForAlarmReason(alarmReason), 178 null /* receiverPermission */, 179 new BroadcastReceiver() { 180 @Override 181 public void onReceive(Context context, Intent intent) { 182 broadcastComplete.set(null); 183 } 184 }, 185 mHandler /* scheduler */, 186 0 /* initialCode */, 187 null /* initialData */, 188 null /* initialExtras */); 189 190 return broadcastComplete; 191 } 192 } 193