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