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