• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * 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 android.service.dreams;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.Mockito.atLeastOnce;
22 import static org.mockito.Mockito.never;
23 import static org.mockito.Mockito.times;
24 import static org.mockito.Mockito.verify;
25 
26 import android.content.Context;
27 import android.content.Intent;
28 import android.os.Handler;
29 import android.os.RemoteException;
30 import android.os.test.TestLooper;
31 
32 import androidx.test.ext.junit.runners.AndroidJUnit4;
33 import androidx.test.filters.SmallTest;
34 
35 import com.android.internal.util.ObservableServiceConnection;
36 import com.android.internal.util.PersistentServiceConnection;
37 
38 import org.junit.Before;
39 import org.junit.Test;
40 import org.junit.runner.RunWith;
41 import org.mockito.ArgumentCaptor;
42 import org.mockito.Mock;
43 import org.mockito.Mockito;
44 import org.mockito.MockitoAnnotations;
45 
46 import java.util.concurrent.atomic.AtomicInteger;
47 import java.util.function.Consumer;
48 
49 @SmallTest
50 @RunWith(AndroidJUnit4.class)
51 public class DreamOverlayConnectionHandlerTest {
52     @Mock
53     private Context mContext;
54     @Mock
55     private PersistentServiceConnection<IDreamOverlay> mConnection;
56     @Mock
57     private Intent mServiceIntent;
58     @Mock
59     private IDreamOverlay mOverlayService;
60     @Mock
61     private IDreamOverlayClient mOverlayClient;
62     @Mock
63     private Runnable mOnDisconnectRunnable;
64 
65     private TestLooper mTestLooper;
66     private DreamOverlayConnectionHandler mDreamOverlayConnectionHandler;
67 
68     @Before
setUp()69     public void setUp() {
70         MockitoAnnotations.initMocks(this);
71         mTestLooper = new TestLooper();
72         mDreamOverlayConnectionHandler = new DreamOverlayConnectionHandler(
73                 mContext,
74                 mTestLooper.getLooper(),
75                 mServiceIntent,
76                 mOnDisconnectRunnable,
77                 new TestInjector(mConnection));
78     }
79 
80     @Test
consumerShouldRunImmediatelyWhenClientAvailable()81     public void consumerShouldRunImmediatelyWhenClientAvailable() throws RemoteException {
82         mDreamOverlayConnectionHandler.bind();
83         connectService();
84         provideClient();
85 
86         final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class);
87         mDreamOverlayConnectionHandler.addConsumer(consumer);
88         mTestLooper.dispatchAll();
89         verify(consumer).accept(mOverlayClient);
90     }
91 
92     @Test
consumerShouldRunAfterClientAvailable()93     public void consumerShouldRunAfterClientAvailable() throws RemoteException {
94         mDreamOverlayConnectionHandler.bind();
95         connectService();
96 
97         final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class);
98         mDreamOverlayConnectionHandler.addConsumer(consumer);
99         mTestLooper.dispatchAll();
100         // No client yet, so we shouldn't have executed
101         verify(consumer, never()).accept(mOverlayClient);
102 
103         provideClient();
104         mTestLooper.dispatchAll();
105         verify(consumer).accept(mOverlayClient);
106     }
107 
108     @Test
consumerShouldNeverRunIfClientConnectsAndDisconnects()109     public void consumerShouldNeverRunIfClientConnectsAndDisconnects() throws RemoteException {
110         mDreamOverlayConnectionHandler.bind();
111         connectService();
112 
113         final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class);
114         mDreamOverlayConnectionHandler.addConsumer(consumer);
115         mTestLooper.dispatchAll();
116         // No client yet, so we shouldn't have executed
117         verify(consumer, never()).accept(mOverlayClient);
118         verify(mOnDisconnectRunnable, never()).run();
119 
120         provideClient();
121         // Service disconnected before looper could handle the message.
122         disconnectService();
123         mTestLooper.dispatchAll();
124         verify(consumer, never()).accept(mOverlayClient);
125         verify(mOnDisconnectRunnable).run();
126     }
127 
128     @Test
consumerShouldNeverRunIfUnbindCalled()129     public void consumerShouldNeverRunIfUnbindCalled() throws RemoteException {
130         mDreamOverlayConnectionHandler.bind();
131         connectService();
132         provideClient();
133 
134         final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class);
135         mDreamOverlayConnectionHandler.addConsumer(consumer);
136         mDreamOverlayConnectionHandler.unbind();
137         mTestLooper.dispatchAll();
138         // We unbinded immediately after adding consumer, so should never have run.
139         verify(consumer, never()).accept(mOverlayClient);
140     }
141 
142     @Test
consumersOnlyRunOnceIfUnbound()143     public void consumersOnlyRunOnceIfUnbound() throws RemoteException {
144         mDreamOverlayConnectionHandler.bind();
145         connectService();
146         provideClient();
147 
148         AtomicInteger counter = new AtomicInteger();
149         // Add 10 consumers in a row which call unbind within the consumer.
150         for (int i = 0; i < 10; i++) {
151             mDreamOverlayConnectionHandler.addConsumer(client -> {
152                 counter.getAndIncrement();
153                 mDreamOverlayConnectionHandler.unbind();
154             });
155         }
156         mTestLooper.dispatchAll();
157         // Only the first consumer should have run, since we unbinded.
158         assertThat(counter.get()).isEqualTo(1);
159     }
160 
161     @Test
consumerShouldRunAgainAfterReconnect()162     public void consumerShouldRunAgainAfterReconnect() throws RemoteException {
163         mDreamOverlayConnectionHandler.bind();
164         connectService();
165         provideClient();
166 
167         final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class);
168         mDreamOverlayConnectionHandler.addConsumer(consumer);
169         mTestLooper.dispatchAll();
170         verify(consumer, times(1)).accept(mOverlayClient);
171 
172         disconnectService();
173         mTestLooper.dispatchAll();
174         // No new calls should happen when service disconnected.
175         verify(consumer, times(1)).accept(mOverlayClient);
176 
177         connectService();
178         provideClient();
179         mTestLooper.dispatchAll();
180         // We should trigger the consumer again once the server reconnects.
181         verify(consumer, times(2)).accept(mOverlayClient);
182     }
183 
184     @Test
consumerShouldNeverRunIfRemovedImmediately()185     public void consumerShouldNeverRunIfRemovedImmediately() throws RemoteException {
186         mDreamOverlayConnectionHandler.bind();
187         connectService();
188         provideClient();
189 
190         final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class);
191         mDreamOverlayConnectionHandler.addConsumer(consumer);
192         mDreamOverlayConnectionHandler.removeConsumer(consumer);
193         mTestLooper.dispatchAll();
194         verify(consumer, never()).accept(mOverlayClient);
195     }
196 
connectService()197     private void connectService() {
198         final ObservableServiceConnection.Callback<IDreamOverlay> callback =
199                 captureConnectionCallback();
200         callback.onConnected(mConnection, mOverlayService);
201     }
202 
disconnectService()203     private void disconnectService() {
204         final ObservableServiceConnection.Callback<IDreamOverlay> callback =
205                 captureConnectionCallback();
206         callback.onDisconnected(mConnection, /* reason= */ 0);
207     }
208 
provideClient()209     private void provideClient() throws RemoteException {
210         final IDreamOverlayClientCallback callback = captureClientCallback();
211         callback.onDreamOverlayClient(mOverlayClient);
212     }
213 
captureConnectionCallback()214     private ObservableServiceConnection.Callback<IDreamOverlay> captureConnectionCallback() {
215         ArgumentCaptor<ObservableServiceConnection.Callback<IDreamOverlay>>
216                 callbackCaptor =
217                 ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class);
218         verify(mConnection).addCallback(callbackCaptor.capture());
219         return callbackCaptor.getValue();
220     }
221 
captureClientCallback()222     private IDreamOverlayClientCallback captureClientCallback() throws RemoteException {
223         ArgumentCaptor<IDreamOverlayClientCallback> callbackCaptor =
224                 ArgumentCaptor.forClass(IDreamOverlayClientCallback.class);
225         verify(mOverlayService, atLeastOnce()).getClient(callbackCaptor.capture());
226         return callbackCaptor.getValue();
227     }
228 
229     static class TestInjector extends DreamOverlayConnectionHandler.Injector {
230         private final PersistentServiceConnection<IDreamOverlay> mConnection;
231 
TestInjector(PersistentServiceConnection<IDreamOverlay> connection)232         TestInjector(PersistentServiceConnection<IDreamOverlay> connection) {
233             mConnection = connection;
234         }
235 
236         @Override
buildConnection(Context context, Handler handler, Intent serviceIntent)237         public PersistentServiceConnection<IDreamOverlay> buildConnection(Context context,
238                 Handler handler, Intent serviceIntent) {
239             return mConnection;
240         }
241     }
242 }
243