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
17package com.android.server;
18
19import static junit.framework.Assert.*;
20import static org.mockito.Mockito.*;
21
22import android.hardware.health.V2_0.IHealth;
23import android.hidl.manager.V1_0.IServiceManager;
24import android.hidl.manager.V1_0.IServiceNotification;
25import android.os.RemoteException;
26import android.support.test.filters.SmallTest;
27import android.test.AndroidTestCase;
28import android.util.Slog;
29
30import java.util.Arrays;
31import java.util.Collection;
32import java.util.NoSuchElementException;
33
34import org.junit.runner.RunWith;
35import org.mockito.ArgumentMatcher;
36import org.mockito.Mock;
37import org.mockito.MockitoAnnotations;
38import org.mockito.Spy;
39import org.mockito.invocation.InvocationOnMock;
40
41
42public class BatteryServiceTest extends AndroidTestCase {
43
44    @Mock IServiceManager mMockedManager;
45    @Mock IHealth mMockedHal;
46    @Mock IHealth mMockedHal2;
47
48    @Mock BatteryService.HealthServiceWrapper.Callback mCallback;
49    @Mock BatteryService.HealthServiceWrapper.IServiceManagerSupplier mManagerSupplier;
50    @Mock BatteryService.HealthServiceWrapper.IHealthSupplier mHealthServiceSupplier;
51    BatteryService.HealthServiceWrapper mWrapper;
52
53    private static final String HEALTHD = BatteryService.HealthServiceWrapper.INSTANCE_HEALTHD;
54    private static final String VENDOR = BatteryService.HealthServiceWrapper.INSTANCE_VENDOR;
55
56    @Override
57    public void setUp() {
58        MockitoAnnotations.initMocks(this);
59    }
60
61    @Override
62    public void tearDown() {
63        if (mWrapper != null)
64            mWrapper.getHandlerThread().quitSafely();
65    }
66
67    public static <T> ArgumentMatcher<T> isOneOf(Collection<T> collection) {
68        return new ArgumentMatcher<T>() {
69            @Override public boolean matches(T e) {
70                return collection.contains(e);
71            }
72            @Override public String toString() {
73                return collection.toString();
74            }
75        };
76    }
77
78    private void initForInstances(String... instanceNamesArr) throws Exception {
79        final Collection<String> instanceNames = Arrays.asList(instanceNamesArr);
80        doAnswer((invocation) -> {
81                // technically, preexisting is ignored by
82                // BatteryService.HealthServiceWrapper.Notification, but still call it correctly.
83                sendNotification(invocation, true);
84                sendNotification(invocation, true);
85                sendNotification(invocation, false);
86                return null;
87            }).when(mMockedManager).registerForNotifications(
88                eq(IHealth.kInterfaceName),
89                argThat(isOneOf(instanceNames)),
90                any(IServiceNotification.class));
91
92        doReturn(mMockedManager).when(mManagerSupplier).get();
93        doReturn(mMockedHal)        // init calls this
94            .doReturn(mMockedHal)   // notification 1
95            .doReturn(mMockedHal)   // notification 2
96            .doReturn(mMockedHal2)  // notification 3
97            .doThrow(new RuntimeException("Should not call getService for more than 4 times"))
98            .when(mHealthServiceSupplier).get(argThat(isOneOf(instanceNames)));
99
100        mWrapper = new BatteryService.HealthServiceWrapper();
101    }
102
103    private void waitHandlerThreadFinish() throws Exception {
104        for (int i = 0; i < 5; i++) {
105            if (!mWrapper.getHandlerThread().getThreadHandler().hasMessagesOrCallbacks()) {
106                return;
107            }
108            Thread.sleep(300);
109        }
110        assertFalse(mWrapper.getHandlerThread().getThreadHandler().hasMessagesOrCallbacks());
111    }
112
113    private static void sendNotification(InvocationOnMock invocation, boolean preexisting)
114            throws Exception {
115        ((IServiceNotification)invocation.getArguments()[2]).onRegistration(
116                IHealth.kInterfaceName,
117                (String)invocation.getArguments()[1],
118                preexisting);
119    }
120
121    @SmallTest
122    public void testWrapPreferVendor() throws Exception {
123        initForInstances(VENDOR, HEALTHD);
124        mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier);
125        waitHandlerThreadFinish();
126        verify(mCallback, times(1)).onRegistration(same(null), same(mMockedHal), eq(VENDOR));
127        verify(mCallback, never()).onRegistration(same(mMockedHal), same(mMockedHal), anyString());
128        verify(mCallback, times(1)).onRegistration(same(mMockedHal), same(mMockedHal2), eq(VENDOR));
129    }
130
131    @SmallTest
132    public void testUseHealthd() throws Exception {
133        initForInstances(HEALTHD);
134        mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier);
135        waitHandlerThreadFinish();
136        verify(mCallback, times(1)).onRegistration(same(null), same(mMockedHal), eq(HEALTHD));
137        verify(mCallback, never()).onRegistration(same(mMockedHal), same(mMockedHal), anyString());
138        verify(mCallback, times(1)).onRegistration(same(mMockedHal), same(mMockedHal2), eq(HEALTHD));
139    }
140
141    @SmallTest
142    public void testNoService() throws Exception {
143        initForInstances("unrelated");
144        try {
145            mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier);
146            fail("Expect NoSuchElementException");
147        } catch (NoSuchElementException ex) {
148            // expected
149        }
150    }
151}
152