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