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