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