1 /*
2  * Copyright (C) 2021 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.car.test;
18 
19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DEBUGGING_CODE;
20 
21 import android.os.Handler;
22 import android.os.Looper;
23 import android.os.Message;
24 
25 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
26 
27 import org.mockito.Mockito;
28 
29 import java.util.ArrayList;
30 
31 /**
32  * A handler that allows control over when to dispatch messages and callbacks.
33  *
34  * <p>NOTE: Currently only supports {@link Runnable} messages. It doesn't dispatch regular messages.
35  *
36  * <p>Usage: Create an instance of {@link FakeHandlerWrapper}, and use {@link #getMockHandler()}
37  * in your test classes.
38  *
39  * <p>The implementation uses {@link Mockito} to bypass {@code final} keywords.
40  */
41 @ExcludeFromCodeCoverageGeneratedReport(reason = DEBUGGING_CODE)
42 public class FakeHandlerWrapper {
43     private Mode mMode;
44     private ArrayList<Message> mQueuedMessages = new ArrayList<>();
45 
46     private final Handler mMockHandler;
47 
FakeHandlerWrapper(Looper looper, Mode mode)48     public FakeHandlerWrapper(Looper looper, Mode mode) {
49         mMockHandler = Mockito.spy(new Handler(looper));
50         mMode = mode;
51         // Stubbing #sendMessageAtTime(Message, long).
52         Mockito.doAnswer(invocation -> {
53             Message msg = invocation.getArgument(0);
54             msg.when = invocation.getArgument(1);  // uptimeMillis
55             mQueuedMessages.add(msg);
56             if (mMode == Mode.IMMEDIATE) {
57                 dispatchQueuedMessages();
58             }
59             return true;
60         }).when(mMockHandler).sendMessageAtTime(Mockito.any(), Mockito.anyLong());
61         // Stubbing #removeCallbacks(Runnable).
62         Mockito.doAnswer(invocation -> {
63             Runnable callback = invocation.getArgument(0);
64             return mQueuedMessages.removeIf(msg -> msg.getCallback() == callback);
65         }).when(mMockHandler).removeCallbacks(Mockito.any());
66     }
67 
getMockHandler()68     public Handler getMockHandler() {
69         return mMockHandler;
70     }
71 
setMode(Mode mode)72     public void setMode(Mode mode) {
73         mMode = mode;
74     }
75 
76     /** Dispatch any messages that have been queued on the calling thread. */
dispatchQueuedMessages()77     public void dispatchQueuedMessages() {
78         ArrayList<Message> messages = new ArrayList<>(mQueuedMessages);
79         mQueuedMessages.clear();
80         for (Message msg : messages) {
81             Runnable callback = msg.getCallback();
82             if (callback != null) {
83                 callback.run();
84             }
85         }
86     }
87 
88     /** Returns the queued messages list. */
getQueuedMessages()89     public ArrayList<Message> getQueuedMessages() {
90         return new ArrayList<>(mQueuedMessages);
91     }
92 
93     public enum Mode {
94         /** Messages are dispatched immediately on the calling thread. */
95         IMMEDIATE,
96         /** Messages are queued until {@link #dispatchQueuedMessages()} is called. */
97         QUEUEING,
98     }
99 }
100