1 /*
2  * Copyright (C) 2017 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.server.health;
18 
19 import static junit.framework.Assert.assertFalse;
20 import static junit.framework.Assert.fail;
21 
22 import static org.mockito.AdditionalMatchers.not;
23 import static org.mockito.Mockito.*;
24 
25 import android.hidl.manager.V1_0.IServiceManager;
26 import android.hidl.manager.V1_0.IServiceNotification;
27 import android.os.IServiceCallback;
28 import android.os.RemoteException;
29 
30 import androidx.test.filters.SmallTest;
31 import androidx.test.runner.AndroidJUnit4;
32 
33 import org.junit.After;
34 import org.junit.Before;
35 import org.junit.Test;
36 import org.junit.runner.RunWith;
37 import org.mockito.ArgumentMatcher;
38 import org.mockito.Mock;
39 import org.mockito.MockitoAnnotations;
40 import org.mockito.invocation.InvocationOnMock;
41 
42 import java.util.Arrays;
43 import java.util.Collection;
44 import java.util.NoSuchElementException;
45 
46 @RunWith(AndroidJUnit4.class)
47 public class HealthServiceWrapperTest {
48     @Mock IServiceManager mMockedManager;
49     @Mock android.hardware.health.V2_0.IHealth mMockedHal;
50     @Mock android.hardware.health.V2_0.IHealth mMockedHal2;
51 
52     @Mock HealthServiceWrapperHidl.Callback mCallback;
53     @Mock HealthServiceWrapperHidl.IServiceManagerSupplier mManagerSupplier;
54     @Mock HealthServiceWrapperHidl.IHealthSupplier mHealthServiceSupplier;
55 
56     @Mock android.hardware.health.IHealth.Stub mMockedAidlHal;
57     @Mock android.hardware.health.IHealth.Stub mMockedAidlHal2;
58     @Mock HealthServiceWrapperAidl.ServiceManagerStub mMockedAidlManager;
59     @Mock HealthRegCallbackAidl mRegCallbackAidl;
60 
61     HealthServiceWrapper mWrapper;
62 
63     private static final String VENDOR = HealthServiceWrapperHidl.INSTANCE_VENDOR;
64     private static final String AIDL_SERVICE_NAME = HealthServiceWrapperAidl.SERVICE_NAME;
65 
66     @Before
setUp()67     public void setUp() {
68         MockitoAnnotations.initMocks(this);
69 
70         // Mocks the conversion between IHealth and IBinder.
71         when(mMockedAidlHal.asBinder()).thenCallRealMethod(); // returns mMockedAidlHal
72         when(mMockedAidlHal2.asBinder()).thenCallRealMethod(); // returns mMockedAidlHal2
73         when(mMockedAidlHal.queryLocalInterface(android.hardware.health.IHealth.DESCRIPTOR))
74                 .thenReturn(mMockedAidlHal);
75         when(mMockedAidlHal2.queryLocalInterface(android.hardware.health.IHealth.DESCRIPTOR))
76                 .thenReturn(mMockedAidlHal2);
77     }
78 
79     @After
tearDown()80     public void tearDown() {
81         validateMockitoUsage();
82         if (mWrapper != null) mWrapper.getHandlerThread().quitSafely();
83     }
84 
isOneOf(T[] collection)85     public static <T> ArgumentMatcher<T> isOneOf(T[] collection) {
86         return isOneOf(Arrays.asList(collection));
87     }
88 
isOneOf(Collection<T> collection)89     public static <T> ArgumentMatcher<T> isOneOf(Collection<T> collection) {
90         return new ArgumentMatcher<T>() {
91             @Override
92             public boolean matches(T e) {
93                 return collection.contains(e);
94             }
95 
96             @Override
97             public String toString() {
98                 return "is one of " + collection.toString();
99             }
100         };
101     }
102 
103     /**
104      * Set up mock objects to pretend that the given AIDL and HIDL instances exists.
105      *
106      * <p>Also, when registering service notifications, the mocked service managers immediately
107      * sends 3 registration notifications, including 2 referring to the original HAL and 1 referring
108      * to the new HAL.
109      *
110      * @param aidlInstances e.g. {"android.hardware.health.IHealth/default"}
111      * @param hidlInstances e.g. {"default", "backup"}
112      * @throws Exception
113      */
114     private void initForInstances(String[] aidlInstances, String[] hidlInstances) throws Exception {
115         doAnswer(
116                 (invocation) -> {
117                     sendAidlRegCallback(invocation, mMockedAidlHal);
118                     sendAidlRegCallback(invocation, mMockedAidlHal);
119                     sendAidlRegCallback(invocation, mMockedAidlHal2);
120                     return null;
121                 })
122                 .when(mMockedAidlManager)
123                 .registerForNotifications(
124                         argThat(isOneOf(aidlInstances)), any(IServiceCallback.class));
125         when(mMockedAidlManager.waitForDeclaredService(argThat(isOneOf(aidlInstances))))
126                 .thenReturn(mMockedAidlHal)
127                 .thenThrow(new RuntimeException("waitForDeclaredService called more than once"));
128         when(mMockedAidlManager.waitForDeclaredService(not(argThat(isOneOf(aidlInstances)))))
129                 .thenReturn(null);
130 
131         doAnswer(
132                 (invocation) -> {
133                     // technically, preexisting is ignored by
134                     // HealthServiceWrapperHidl.Notification, but still call it correctly.
135                     sendNotification(invocation, true);
136                     sendNotification(invocation, true);
137                     sendNotification(invocation, false);
138                     return null;
139                 })
140                 .when(mMockedManager)
141                 .registerForNotifications(
142                         eq(android.hardware.health.V2_0.IHealth.kInterfaceName),
143                         argThat(isOneOf(hidlInstances)),
144                         any(IServiceNotification.class));
145 
146         doReturn(mMockedManager).when(mManagerSupplier).get();
147         doReturn(mMockedHal) // init calls this
148                 .doReturn(mMockedHal) // notification 1
149                 .doReturn(mMockedHal) // notification 2
150                 .doReturn(mMockedHal2) // notification 3
151                 .doThrow(new RuntimeException("Should not call getService for more than 4 times"))
152                 .when(mHealthServiceSupplier)
153                 .get(argThat(isOneOf(hidlInstances)));
154     }
155 
156     private void waitHandlerThreadFinish() throws Exception {
157         for (int i = 0; i < 5; i++) {
158             if (!mWrapper.getHandlerThread().getThreadHandler().hasMessagesOrCallbacks()) {
159                 return;
160             }
161             Thread.sleep(300);
162         }
163         assertFalse(mWrapper.getHandlerThread().getThreadHandler().hasMessagesOrCallbacks());
164     }
165 
166     private static void sendNotification(InvocationOnMock invocation, boolean preexisting)
167             throws Exception {
168         ((IServiceNotification) invocation.getArguments()[2])
169                 .onRegistration(
170                         android.hardware.health.V2_0.IHealth.kInterfaceName,
171                         (String) invocation.getArguments()[1],
172                         preexisting);
173     }
174 
175     private static void sendAidlRegCallback(
176             InvocationOnMock invocation, android.hardware.health.IHealth service) throws Exception {
177         ((IServiceCallback) invocation.getArguments()[1])
178                 .onRegistration((String) invocation.getArguments()[0], service.asBinder());
179     }
180 
181     private void createWrapper() throws RemoteException {
182         mWrapper =
183                 HealthServiceWrapper.create(
184                         mRegCallbackAidl,
185                         mMockedAidlManager,
186                         mCallback,
187                         mManagerSupplier,
188                         mHealthServiceSupplier);
189     }
190 
191     @SmallTest
192     @Test
193     public void testWrapAidlOnly() throws Exception {
194         initForInstances(new String[] {AIDL_SERVICE_NAME}, new String[0]);
195         createWrapper();
196         waitHandlerThreadFinish();
197         verify(mRegCallbackAidl, times(1)).onRegistration(same(null), same(mMockedAidlHal));
198         verify(mRegCallbackAidl, never())
199                 .onRegistration(same(mMockedAidlHal), same(mMockedAidlHal));
200         verify(mRegCallbackAidl, times(1))
201                 .onRegistration(same(mMockedAidlHal), same(mMockedAidlHal2));
202         verify(mCallback, never()).onRegistration(any(), any(), anyString());
203     }
204 
205     @SmallTest
206     @Test
207     public void testWrapPreferAidl() throws Exception {
208         initForInstances(new String[] {AIDL_SERVICE_NAME}, new String[] {VENDOR});
209         createWrapper();
210         waitHandlerThreadFinish();
211         verify(mRegCallbackAidl, times(1)).onRegistration(same(null), same(mMockedAidlHal));
212         verify(mRegCallbackAidl, never())
213                 .onRegistration(same(mMockedAidlHal), same(mMockedAidlHal));
214         verify(mRegCallbackAidl, times(1))
215                 .onRegistration(same(mMockedAidlHal), same(mMockedAidlHal2));
216         verify(mCallback, never()).onRegistration(any(), any(), anyString());
217     }
218 
219     @SmallTest
220     @Test
221     public void testWrapFallbackHidl() throws Exception {
222         initForInstances(new String[0], new String[] {VENDOR});
223         createWrapper();
224         waitHandlerThreadFinish();
225         verify(mRegCallbackAidl, never()).onRegistration(any(), any());
226         verify(mCallback, times(1)).onRegistration(same(null), same(mMockedHal), eq(VENDOR));
227         verify(mCallback, never()).onRegistration(same(mMockedHal), same(mMockedHal), anyString());
228         verify(mCallback, times(1)).onRegistration(same(mMockedHal), same(mMockedHal2), eq(VENDOR));
229     }
230 
231     @SmallTest
232     @Test
233     public void testNoService() throws Exception {
234         initForInstances(new String[0], new String[] {"unrelated"});
235         try {
236             createWrapper();
237             fail("Expect NoSuchElementException");
238         } catch (NoSuchElementException ex) {
239             // expected
240         }
241     }
242 }
243