1 /* 2 * Copyright (C) 2014 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 package com.android.systemui; 17 18 import static com.android.systemui.animation.FakeDialogLaunchAnimatorKt.fakeDialogLaunchAnimator; 19 20 import static org.mockito.Mockito.mock; 21 import static org.mockito.Mockito.spy; 22 import static org.mockito.Mockito.when; 23 24 import android.app.Instrumentation; 25 import android.os.Build; 26 import android.os.Handler; 27 import android.os.Looper; 28 import android.os.MessageQueue; 29 import android.os.ParcelFileDescriptor; 30 import android.testing.DexmakerShareClassLoaderRule; 31 import android.testing.LeakCheck; 32 import android.testing.TestWithLooperRule; 33 import android.testing.TestableLooper; 34 import android.util.Log; 35 36 import androidx.core.animation.AndroidXAnimatorIsolationRule; 37 import androidx.test.InstrumentationRegistry; 38 import androidx.test.uiautomator.UiDevice; 39 40 import com.android.keyguard.KeyguardUpdateMonitor; 41 import com.android.settingslib.bluetooth.LocalBluetoothManager; 42 import com.android.systemui.animation.DialogLaunchAnimator; 43 import com.android.systemui.broadcast.BroadcastDispatcher; 44 import com.android.systemui.broadcast.FakeBroadcastDispatcher; 45 import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger; 46 import com.android.systemui.classifier.FalsingManagerFake; 47 import com.android.systemui.dump.DumpManager; 48 import com.android.systemui.plugins.FalsingManager; 49 import com.android.systemui.settings.UserTracker; 50 import com.android.systemui.statusbar.SmartReplyController; 51 import com.android.systemui.statusbar.phone.SystemUIDialogManager; 52 53 import org.junit.After; 54 import org.junit.AfterClass; 55 import org.junit.Before; 56 import org.junit.ClassRule; 57 import org.junit.Rule; 58 import org.mockito.Mockito; 59 60 import java.io.FileInputStream; 61 import java.io.IOException; 62 import java.util.concurrent.ExecutionException; 63 import java.util.concurrent.Executor; 64 import java.util.concurrent.Future; 65 66 /** 67 * Base class that does System UI specific setup. 68 */ 69 public abstract class SysuiTestCase { 70 71 private static final String TAG = "SysuiTestCase"; 72 73 private Handler mHandler; 74 75 // set the lowest order so it's the outermost rule 76 @ClassRule(order = Integer.MIN_VALUE) 77 public static AndroidXAnimatorIsolationRule mAndroidXAnimatorIsolationRule = 78 new AndroidXAnimatorIsolationRule(); 79 80 @Rule 81 public SysuiTestableContext mContext = new SysuiTestableContext( 82 InstrumentationRegistry.getContext(), getLeakCheck()); 83 @Rule 84 public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = 85 new DexmakerShareClassLoaderRule(); 86 87 // set the highest order so it's the innermost rule 88 @Rule(order = Integer.MAX_VALUE) 89 public TestWithLooperRule mlooperRule = new TestWithLooperRule(); 90 91 public TestableDependency mDependency; 92 private Instrumentation mRealInstrumentation; 93 private FakeBroadcastDispatcher mFakeBroadcastDispatcher; 94 95 @Before SysuiSetup()96 public void SysuiSetup() throws Exception { 97 // Manually associate a Display to context for Robolectric test. Similar to b/214297409 98 if (isRobolectricTest()) { 99 mContext = mContext.createDefaultDisplayContext(); 100 } 101 SystemUIInitializer initializer = new SystemUIInitializerImpl(mContext); 102 initializer.init(true); 103 mDependency = new TestableDependency(initializer.getSysUIComponent().createDependency()); 104 Dependency.setInstance(mDependency); 105 mFakeBroadcastDispatcher = new FakeBroadcastDispatcher( 106 mContext, 107 mContext.getMainExecutor(), 108 mock(Looper.class), 109 mock(Executor.class), 110 mock(DumpManager.class), 111 mock(BroadcastDispatcherLogger.class), 112 mock(UserTracker.class), 113 shouldFailOnLeakedReceiver()); 114 115 mRealInstrumentation = InstrumentationRegistry.getInstrumentation(); 116 Instrumentation inst = spy(mRealInstrumentation); 117 when(inst.getContext()).thenAnswer(invocation -> { 118 throw new RuntimeException( 119 "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext"); 120 }); 121 when(inst.getTargetContext()).thenAnswer(invocation -> { 122 throw new RuntimeException( 123 "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext"); 124 }); 125 InstrumentationRegistry.registerInstance(inst, InstrumentationRegistry.getArguments()); 126 // Many tests end up creating a BroadcastDispatcher. Instead, give them a fake that will 127 // record receivers registered. They are not actually leaked as they are kept just as a weak 128 // reference and are never sent to the Context. This will also prevent a real 129 // BroadcastDispatcher from actually registering receivers. 130 mDependency.injectTestDependency(BroadcastDispatcher.class, mFakeBroadcastDispatcher); 131 // A lot of tests get the FalsingManager, often via several layers of indirection. 132 // None of them actually need it. 133 mDependency.injectTestDependency(FalsingManager.class, new FalsingManagerFake()); 134 mDependency.injectMockDependency(KeyguardUpdateMonitor.class); 135 136 // A lot of tests get the LocalBluetoothManager, often via several layers of indirection. 137 // None of them actually need it. 138 mDependency.injectMockDependency(LocalBluetoothManager.class); 139 140 // Notifications tests are injecting one of these, causing many classes (including 141 // KeyguardUpdateMonitor to be created (injected). 142 // TODO(b/1531701009) Clean up NotificationContentView creation to prevent this 143 mDependency.injectMockDependency(SmartReplyController.class); 144 145 // Make sure that all tests on any SystemUIDialog does not crash because this dependency 146 // is missing (constructing the actual one would throw). 147 // TODO(b/219008720): Remove this. 148 mDependency.injectMockDependency(SystemUIDialogManager.class); 149 mDependency.injectTestDependency(DialogLaunchAnimator.class, fakeDialogLaunchAnimator()); 150 } 151 shouldFailOnLeakedReceiver()152 protected boolean shouldFailOnLeakedReceiver() { 153 return false; 154 } 155 156 @After SysuiTeardown()157 public void SysuiTeardown() { 158 if (mRealInstrumentation != null) { 159 InstrumentationRegistry.registerInstance(mRealInstrumentation, 160 InstrumentationRegistry.getArguments()); 161 } 162 if (TestableLooper.get(this) != null) { 163 TestableLooper.get(this).processAllMessages(); 164 // Must remove static reference to this test object to prevent leak (b/261039202) 165 TestableLooper.remove(this); 166 } 167 disallowTestableLooperAsMainThread(); 168 mContext.cleanUpReceivers(this.getClass().getSimpleName()); 169 mFakeBroadcastDispatcher.cleanUpReceivers(this.getClass().getSimpleName()); 170 } 171 172 @AfterClass mockitoTearDown()173 public static void mockitoTearDown() { 174 Mockito.framework().clearInlineMocks(); 175 } 176 177 /** 178 * Tests are run on the TestableLooper; however, there are parts of SystemUI that assert that 179 * the code is run from the main looper. Therefore, we allow the TestableLooper to pass these 180 * assertions since in a test, the TestableLooper is essentially the MainLooper. 181 */ allowTestableLooperAsMainThread()182 protected void allowTestableLooperAsMainThread() { 183 com.android.systemui.util.Assert.setTestableLooper(TestableLooper.get(this).getLooper()); 184 } 185 disallowTestableLooperAsMainThread()186 protected void disallowTestableLooperAsMainThread() { 187 com.android.systemui.util.Assert.setTestableLooper(null); 188 } 189 getLeakCheck()190 protected LeakCheck getLeakCheck() { 191 return null; 192 } 193 getFakeBroadcastDispatcher()194 protected FakeBroadcastDispatcher getFakeBroadcastDispatcher() { 195 return mFakeBroadcastDispatcher; 196 } 197 getContext()198 public SysuiTestableContext getContext() { 199 return mContext; 200 } 201 getUiDevice()202 protected UiDevice getUiDevice() { 203 return UiDevice.getInstance(mRealInstrumentation); 204 } 205 runShellCommand(String command)206 protected void runShellCommand(String command) throws IOException { 207 ParcelFileDescriptor pfd = mRealInstrumentation.getUiAutomation() 208 .executeShellCommand(command); 209 210 // Read the input stream fully. 211 FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd); 212 while (fis.read() != -1); 213 fis.close(); 214 } 215 waitForIdleSync()216 protected void waitForIdleSync() { 217 if (mHandler == null) { 218 mHandler = new Handler(Looper.getMainLooper()); 219 } 220 waitForIdleSync(mHandler); 221 } 222 waitForUiOffloadThread()223 protected void waitForUiOffloadThread() { 224 Future<?> future = Dependency.get(UiOffloadThread.class).execute(() -> { }); 225 try { 226 future.get(); 227 } catch (InterruptedException | ExecutionException e) { 228 Log.e(TAG, "Failed to wait for ui offload thread.", e); 229 } 230 } 231 waitForIdleSync(Handler h)232 public static void waitForIdleSync(Handler h) { 233 validateThread(h.getLooper()); 234 Idler idler = new Idler(null); 235 h.getLooper().getQueue().addIdleHandler(idler); 236 // Ensure we are non-idle, so the idle handler can run. 237 h.post(new EmptyRunnable()); 238 idler.waitForIdle(); 239 } 240 isRobolectricTest()241 public static boolean isRobolectricTest() { 242 return Build.FINGERPRINT.contains("robolectric"); 243 } 244 validateThread(Looper l)245 private static final void validateThread(Looper l) { 246 if (Looper.myLooper() == l) { 247 throw new RuntimeException( 248 "This method can not be called from the looper being synced"); 249 } 250 } 251 252 /** Delegates to {@link android.testing.TestableResources#addOverride(int, Object)}. */ overrideResource(int resourceId, Object value)253 protected void overrideResource(int resourceId, Object value) { 254 mContext.getOrCreateTestableResources().addOverride(resourceId, value); 255 } 256 257 public static final class EmptyRunnable implements Runnable { run()258 public void run() { 259 } 260 } 261 262 public static final class Idler implements MessageQueue.IdleHandler { 263 private final Runnable mCallback; 264 private boolean mIdle; 265 Idler(Runnable callback)266 public Idler(Runnable callback) { 267 mCallback = callback; 268 mIdle = false; 269 } 270 271 @Override queueIdle()272 public boolean queueIdle() { 273 if (mCallback != null) { 274 mCallback.run(); 275 } 276 synchronized (this) { 277 mIdle = true; 278 notifyAll(); 279 } 280 return false; 281 } 282 waitForIdle()283 public void waitForIdle() { 284 synchronized (this) { 285 while (!mIdle) { 286 try { 287 wait(); 288 } catch (InterruptedException e) { 289 } 290 } 291 } 292 } 293 } 294 } 295