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