1 /* <lambda>null2 * Copyright (C) 2023 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 com.android.systemui.util.concurrency 18 19 import android.os.Handler 20 import java.util.concurrent.ConcurrentHashMap 21 import java.util.concurrent.CopyOnWriteArrayList 22 import java.util.concurrent.Executor 23 import org.mockito.ArgumentMatchers.any 24 import org.mockito.ArgumentMatchers.anyLong 25 import org.mockito.Mockito 26 import org.mockito.Mockito.doAnswer 27 import org.mockito.invocation.InvocationOnMock 28 import org.mockito.stubbing.Answer 29 30 /** 31 * Wrap an [Executor] in a mock [Handler] that execute when [Handler.post] is called, and throws an 32 * exception otherwise. This is useful when a class requires a Handler only because Handlers are 33 * used by ContentObserver, and no other methods are used. 34 * 35 * If the [executor] is a [DelayableExecutor], it also supports: 36 * * [Handler.postDelayed] with a Runnable parameter 37 * * [Handler.postAtTime] with a RunnableParameter 38 */ 39 fun mockExecutorHandler(executor: Executor): Handler { 40 val handlerMock = Mockito.mock(Handler::class.java, RuntimeExceptionAnswer()) 41 val cancellations = ConcurrentHashMap<Runnable, MutableList<Cancellation>>() 42 doAnswer { invocation: InvocationOnMock -> 43 executor.execute(invocation.getArgument(0)) 44 true 45 } 46 .`when`(handlerMock) 47 .post(any()) 48 if (executor is DelayableExecutor) { 49 doAnswer { invocation: InvocationOnMock -> 50 val runnable = invocation.getArgument<Runnable>(0) 51 val uptimeMillis = invocation.getArgument<Long>(1) 52 val token = Any() 53 val canceller = 54 executor.executeAtTime( 55 { 56 cancellations.get(runnable)?.removeIf { it.token == token } 57 cancellations.remove(runnable, emptyList()) 58 runnable.run() 59 }, 60 uptimeMillis 61 ) 62 cancellations 63 .getOrPut(runnable) { CopyOnWriteArrayList() } 64 .add(Cancellation(token, canceller)) 65 true 66 } 67 .`when`(handlerMock) 68 .postAtTime(any(), anyLong()) 69 doAnswer { invocation: InvocationOnMock -> 70 val runnable = invocation.getArgument<Runnable>(0) 71 val delayInMillis = invocation.getArgument<Long>(1) 72 val token = Any() 73 val canceller = 74 executor.executeDelayed( 75 { 76 cancellations.get(runnable)?.removeIf { it.token == token } 77 cancellations.remove(runnable, emptyList()) 78 runnable.run() 79 }, 80 delayInMillis 81 ) 82 cancellations 83 .getOrPut(runnable) { CopyOnWriteArrayList() } 84 .add(Cancellation(token, canceller)) 85 true 86 } 87 .`when`(handlerMock) 88 .postDelayed(any(), anyLong()) 89 doAnswer { invocation: InvocationOnMock -> 90 val runnable = invocation.getArgument<Runnable>(0) 91 cancellations.remove(runnable)?.forEach(Runnable::run) 92 Unit 93 } 94 .`when`(handlerMock) 95 .removeCallbacks(any()) 96 } 97 return handlerMock 98 } 99 100 private class Cancellation(val token: Any, canceller: Runnable) : Runnable by canceller 101 102 private class RuntimeExceptionAnswer : Answer<Any> { answernull103 override fun answer(invocation: InvocationOnMock): Any { 104 throw RuntimeException(invocation.method.name + " is not stubbed") 105 } 106 } 107