1 /* 2 * Copyright (C) 2021 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 android.hardware.devicestate.cts; 18 19 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 20 21 import android.hardware.devicestate.DeviceStateManager; 22 import android.hardware.devicestate.DeviceStateRequest; 23 import android.server.wm.ActivityManagerTestBase; 24 25 import androidx.annotation.CallSuper; 26 import androidx.annotation.NonNull; 27 import androidx.annotation.Nullable; 28 import androidx.test.ext.junit.runners.AndroidJUnit4; 29 30 import org.junit.Before; 31 import org.junit.runner.RunWith; 32 33 import java.util.concurrent.CountDownLatch; 34 import java.util.concurrent.TimeUnit; 35 36 import javax.annotation.concurrent.GuardedBy; 37 38 /** 39 * Abstract base class for {@link DeviceStateManager} CTS tests. 40 */ 41 @RunWith(AndroidJUnit4.class) 42 public abstract class DeviceStateManagerTestBase extends ActivityManagerTestBase { 43 static final int CALLBACK_TIMEOUT_MS = 1000; 44 45 private DeviceStateManager mDeviceStateManager; 46 47 @CallSuper 48 @Before setup()49 public void setup() { 50 mDeviceStateManager = getInstrumentation().getTargetContext() 51 .getSystemService(DeviceStateManager.class); 52 } 53 54 /** Returns an instance of {@link DeviceStateManager} for use in tests. */ 55 @NonNull getDeviceStateManager()56 DeviceStateManager getDeviceStateManager() { 57 if (mDeviceStateManager == null) { 58 // called before setup(); 59 throw new IllegalStateException(); 60 } 61 return mDeviceStateManager; 62 } 63 64 /** 65 * Runs the supplied {@code Runnable} ensuring the {@code request} is active during execution. 66 * If the request becomes suspended or canceled before or during runnable execution a 67 * {@link java.lang.InterruptedException} will be thrown. 68 */ runWithRequestActive(@onNull DeviceStateRequest request, boolean isBaseStateRequest, @NonNull Runnable runnable)69 protected final void runWithRequestActive(@NonNull DeviceStateRequest request, 70 boolean isBaseStateRequest, 71 @NonNull Runnable runnable) throws Throwable { 72 final UncaughtExceptionHandler exceptionHandler = new UncaughtExceptionHandler(); 73 final RequestAwareThread thread = new RequestAwareThread(request, runnable); 74 thread.setUncaughtExceptionHandler(exceptionHandler); 75 try (DeviceStateRequestSession session = 76 new DeviceStateRequestSession(mDeviceStateManager, request, 77 isBaseStateRequest, thread)) { 78 // Set the exception handler to get the exception and rethrow. 79 thread.start(); 80 // Wait for the request aware thread to finish executing the runnable. If the request 81 // is suspended or canceled this method will throw an InterruptedException. 82 thread.join(); 83 } 84 85 // Rethrow any exceptions from the runnable. 86 final Throwable t = exceptionHandler.getThrowable(); 87 if (t != null) { 88 throw t; 89 } 90 } 91 92 /** 93 * An implementation of {@link Thread} that listens to changes in a request state and 94 * automatically interrupts if the request is suspended or canceled while the thread 95 * is running. 96 */ 97 private static final class RequestAwareThread extends Thread 98 implements DeviceStateRequest.Callback { 99 private final Object mLock = new Object(); 100 101 private final CountDownLatch mActiveLatch = new CountDownLatch(1); 102 103 @NonNull 104 private final DeviceStateRequest mRequest; 105 @NonNull 106 private final Runnable mRunnable; 107 108 @GuardedBy("mLock") 109 private boolean mIsRunning; 110 @GuardedBy("mLock") 111 private boolean mWasSuspendedOrCanceled; 112 RequestAwareThread(@onNull DeviceStateRequest request, @NonNull Runnable runnable)113 private RequestAwareThread(@NonNull DeviceStateRequest request, 114 @NonNull Runnable runnable) { 115 mRequest = request; 116 mRunnable = runnable; 117 } 118 119 @Override run()120 public void run() { 121 // Wait for the request to be active. 122 boolean success; 123 try { 124 success = mActiveLatch.await(CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS); 125 } catch (InterruptedException e) { 126 // This thread was interrupted while waiting for the callback. 127 success = false; 128 } 129 if (!success) { 130 throw new RuntimeException("Timed out waiting for " + toString(mRequest) 131 + " to become active."); 132 } 133 synchronized (mLock) { 134 if (mWasSuspendedOrCanceled) { 135 interrupt(); 136 return; 137 } 138 mIsRunning = true; 139 } 140 try { 141 mRunnable.run(); 142 } finally { 143 synchronized (mLock) { 144 mIsRunning = false; 145 } 146 } 147 } 148 149 @Override onRequestActivated(@onNull DeviceStateRequest request)150 public void onRequestActivated(@NonNull DeviceStateRequest request) { 151 if (!request.equals(mRequest)) { 152 return; 153 } 154 155 mActiveLatch.countDown(); 156 } 157 158 @Override onRequestSuspended(@onNull DeviceStateRequest request)159 public void onRequestSuspended(@NonNull DeviceStateRequest request) { 160 if (!request.equals(mRequest)) { 161 return; 162 } 163 164 synchronized (mLock) { 165 mWasSuspendedOrCanceled = true; 166 interruptIfRunningLocked(); 167 } 168 } 169 170 @Override onRequestCanceled(@onNull DeviceStateRequest request)171 public void onRequestCanceled(@NonNull DeviceStateRequest request) { 172 if (!request.equals(mRequest)) { 173 return; 174 } 175 176 synchronized (mLock) { 177 mWasSuspendedOrCanceled = true; 178 interruptIfRunningLocked(); 179 } 180 } 181 interruptIfRunningLocked()182 private void interruptIfRunningLocked() { 183 if (mIsRunning) { 184 // Interrupt this thread if the runnable is still running and the request was 185 // cancelled or suspended. 186 interrupt(); 187 } 188 } 189 toString(@onNull DeviceStateRequest request)190 private static String toString(@NonNull DeviceStateRequest request) { 191 return "DeviceStateRequest{state=" + request.getState() + ", flags=" 192 + request.getFlags() + "}"; 193 } 194 } 195 196 /** 197 * An implementation of {@link Thread.UncaughtExceptionHandler} that simply stores the latest 198 * notified uncaught exception. 199 */ 200 private static final class UncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { 201 @Nullable 202 private Throwable mThrowable; 203 204 @Override uncaughtException(Thread t, Throwable e)205 public void uncaughtException(Thread t, Throwable e) { 206 mThrowable = e; 207 } 208 209 @Nullable getThrowable()210 public Throwable getThrowable() { 211 return mThrowable; 212 } 213 } 214 } 215