blob: dbe5c313886fa5d8e1870c3a24c22092bbeaa38a [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.clockwork.displayoffload;
import static com.google.android.clockwork.ambient.offload.IDisplayOffloadCallbacks.ERROR_HAL_REMOTE_EXCEPTION;
import static com.google.android.clockwork.ambient.offload.IDisplayOffloadCallbacks.ERROR_HAL_STATUS_NOT_OK;
import static com.google.android.clockwork.ambient.offload.IDisplayOffloadCallbacks.ERROR_LAYOUT_CONTAINS_CYCLE;
import static com.google.android.clockwork.ambient.offload.IDisplayOffloadCallbacks.ERROR_LAYOUT_INVALID_RESOURCE;
import static com.google.android.clockwork.ambient.offload.IDisplayOffloadService.RESOURCE_ID_VIRTUAL_ROOT;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.os.RemoteException;
import android.util.Log;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.util.IndentingPrintWriter;
import com.google.android.clockwork.ambient.offload.IDisplayOffloadService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import vendor.google_clockwork.displayoffload.V1_0.Status;
import vendor.google_clockwork.displayoffload.V1_0.TranslationGroup;
/**
* Tests for {@link com.google.android.clockwork.displayoffload.HalAdapter}.
*/
@RunWith(AndroidJUnit4.class)
public class HalResourceStoreTest {
private final Context mContext = ApplicationProvider.getApplicationContext();
private final Integer fakeId1 = 1;
private final Integer fakeId2 = 2;
private final Object fakeResource1 = new Object();
private final Object fakeResource2 = new Object();
private final String fakeStringResource1 = "StringResource1";
private final String fakeStringResource2 = "StringResource2";
private final FakeHalImplV1_1 mFakeHalImplV1_1 = spy(new FakeHalImplV1_1());
private final FakeHalImplV2_0 mFakeHalImplV2_0 = spy(new FakeHalImplV2_0());
@Mock
HalAdapter mHalAdapter;
@Mock
HalTypeConverter mHalTypeConverter;
@Mock
IndentingPrintWriter mIndentingPrintWriter;
private HalResourceStore mHalResourceStore;
@Before
public void setup() throws RemoteException, DisplayOffloadException {
MockitoAnnotations.initMocks(this);
when(mHalAdapter.send(any())).thenReturn(Status.OK);
when(mHalAdapter.getConverter()).thenReturn(mHalTypeConverter);
mHalResourceStore = spy(new HalResourceStore(mHalAdapter));
when(mIndentingPrintWriter.printPair(anyString(), any())).thenReturn(null);
doNothing().when(mIndentingPrintWriter).println();
doNothing().when(mIndentingPrintWriter).println(anyString());
when(mIndentingPrintWriter.printf(anyString(), anyString(), anyString(), anyString()))
.thenReturn(null);
}
@Test
public void testAddReplaceResource() throws RemoteException, DisplayOffloadException {
List<Object> dummyObjects = makeGraph();
int dummyObjectsCount = dummyObjects.size();
for (int i = 0; i < dummyObjectsCount; i++) {
assertThat(mHalResourceStore.get(i)).isEqualTo(dummyObjects.get(i));
}
assertThat(mHalResourceStore.mResources.size()).isEqualTo(dummyObjectsCount);
// Replace
mHalResourceStore.addReplaceResource(1, fakeResource1);
assertThat(mHalResourceStore.get(1)).isEqualTo(fakeResource1);
assertThat(mHalResourceStore.mResources.size()).isEqualTo(dummyObjectsCount);
}
@Test
public void testAddReplaceResourceDuplicateNoAction() {
mHalResourceStore.mResources.put(fakeId1, fakeResource1);
// Add the duplicate pair
mHalResourceStore.addReplaceResource(fakeId1, fakeResource1);
assertThat(mHalResourceStore.mResources.size()).isEqualTo(1);
assertThat(mHalResourceStore.get(fakeId1)).isEqualTo(fakeResource1);
}
@Test
public void testAddReplaceStringIdentifiedObjects() {
String fakeIdString1 = fakeId1.toString();
String fakeIdString2 = fakeId2.toString();
// Add
mHalResourceStore.addReplaceResource(fakeIdString1, fakeStringResource1);
mHalResourceStore.addReplaceResource(fakeIdString2, fakeStringResource2);
assertThat(mHalResourceStore.get(fakeIdString1)).isEqualTo(fakeStringResource1);
assertThat(mHalResourceStore.get(fakeIdString2)).isEqualTo(fakeStringResource2);
assertThat(mHalResourceStore.mStringIdentifiedBindings.size()).isEqualTo(2);
// Replace
mHalResourceStore.addReplaceResource(fakeIdString1, fakeStringResource2);
assertThat(mHalResourceStore.get(fakeIdString1)).isEqualTo(fakeStringResource2);
assertThat(mHalResourceStore.mStringIdentifiedBindings.size()).isEqualTo(2);
}
@Test
public void testAddReplaceStringIdentifiedObjectsDuplicateNoAction() {
String fakeIdString1 = fakeId1.toString();
mHalResourceStore.mStringIdentifiedBindings.put(fakeIdString1, fakeStringResource1);
// Add the duplicate pair
mHalResourceStore.addReplaceResource(fakeIdString1, fakeStringResource1);
assertThat(mHalResourceStore.mStringIdentifiedBindings.size()).isEqualTo(1);
assertThat(mHalResourceStore.get(fakeIdString1)).isEqualTo(fakeStringResource1);
}
@Test
public void testGet() {
mHalResourceStore.addReplaceResource(fakeId1, fakeResource1);
mHalResourceStore.addReplaceResource(fakeId2, fakeResource2);
assertThat(mHalResourceStore.get(fakeId1)).isEqualTo(fakeResource1);
assertThat(mHalResourceStore.get(fakeId2)).isEqualTo(fakeResource2);
mHalResourceStore.addReplaceResource(fakeId1.toString(), fakeStringResource1);
mHalResourceStore.addReplaceResource(fakeId2.toString(), fakeStringResource2);
assertThat(mHalResourceStore.get(fakeId1.toString())).isEqualTo(fakeStringResource1);
assertThat(mHalResourceStore.get(fakeId2.toString())).isEqualTo(fakeStringResource2);
}
@Test
public void testIsEmpty() {
assertThat(mHalResourceStore.isEmpty()).isTrue();
mHalResourceStore.addReplaceResource(fakeId1, fakeResource1);
assertThat(mHalResourceStore.isEmpty()).isFalse();
}
@Test
public void testClear() {
mHalResourceStore.addReplaceResource(fakeId1, fakeResource1);
mHalResourceStore.addReplaceResource(fakeId1.toString(), fakeStringResource1);
assertThat(mHalResourceStore.mResources.isEmpty()).isFalse();
assertThat(mHalResourceStore.mStringIdentifiedBindings.isEmpty()).isFalse();
mHalResourceStore.clear();
assertThat(mHalResourceStore.mResources.isEmpty()).isTrue();
assertThat(mHalResourceStore.mStringIdentifiedBindings.isEmpty()).isTrue();
}
@Test
public void testMarkAllDirty() throws DisplayOffloadException, RemoteException {
// Add resources & send order should be generated by default
mHalResourceStore.addReplaceResource(fakeId1, fakeResource1);
mHalResourceStore.addReplaceResource(fakeId2.toString(), fakeStringResource2);
mHalResourceStore.sendToHal(mHalAdapter);
verify(mHalAdapter, times(2)).send(any());
// String-identified objects always get resent
clearInvocations(mHalAdapter);
mHalResourceStore.sendToHal(mHalAdapter);
verify(mHalAdapter, times(1)).send(any());
// markAllDirty should cause resources to be re-sent
clearInvocations(mHalAdapter);
mHalResourceStore.markAllDirty();
mHalResourceStore.sendToHal(mHalAdapter);
verify(mHalAdapter, times(2)).send(any());
}
@Test
public void testGetRootId() throws DisplayOffloadException {
int rootId = mHalResourceStore.getRootId();
assertThat(rootId).isEqualTo(-1);
makeGraph();
rootId = mHalResourceStore.getRootId();
assertThat(rootId).isEqualTo(0);
}
@Test
public void testGetRootIdMultiRoot() throws DisplayOffloadException {
makeGraphMultiRoot();
int rootId = mHalResourceStore.getRootId();
assertThat(rootId).isEqualTo(RESOURCE_ID_VIRTUAL_ROOT);
}
@Test
public void testGenerateSendOrder() throws DisplayOffloadException {
makeGraph();
mHalResourceStore.generateSendOrder();
assertThat(mHalResourceStore.mSendOrder).isEqualTo(new int[]{4, 3, 2, 1, 0});
}
@Test
public void testGenerateSendOrderEmpty() throws DisplayOffloadException {
mHalResourceStore.generateSendOrder();
assertThat(mHalResourceStore.mSendOrder).isEqualTo(new int[]{});
}
@Test
public void testGenerateSendOrderMultiRoot() throws DisplayOffloadException {
makeGraphMultiRoot();
mHalResourceStore.generateSendOrder();
// 0 and 5 are both root. Who goes first doesn't matter.
int[][] answerList = new int[][]{
new int[]{4, 3, 2, 1, 0, 5, RESOURCE_ID_VIRTUAL_ROOT},
new int[]{4, 3, 2, 1, 5, 0, RESOURCE_ID_VIRTUAL_ROOT}
};
for (int[] answer : answerList) {
if (Arrays.equals(mHalResourceStore.mSendOrder, answer)) {
return;
}
}
fail("No possible answer was matched. The order must be wrong.");
}
@Test
public void testGenerateSendOrderException() throws DisplayOffloadException {
makeGraphCycled();
try {
mHalResourceStore.generateSendOrder();
} catch (DisplayOffloadException e) {
assertThat(e.getErrorType()).isEqualTo(ERROR_LAYOUT_CONTAINS_CYCLE);
}
mHalResourceStore.clear();
makeGraphWithNull();
try {
mHalResourceStore.generateSendOrder();
} catch (DisplayOffloadException e) {
assertThat(e.getErrorType()).isEqualTo(ERROR_LAYOUT_INVALID_RESOURCE);
}
}
@Test
public void testSendToHal() throws RemoteException, DisplayOffloadException {
// Add variables
int variableCount = 5;
for (int i = 0; i < variableCount; i++) {
mHalResourceStore.addReplaceResource("object" + i, "var" + i);
}
// Add resources; size = 5
makeGraph();
mHalResourceStore.sendToHal(mHalAdapter);
verify(mHalAdapter, times(10)).send(any());
}
@Test
public void testSendToHalException() throws DisplayOffloadException, RemoteException {
when(mHalAdapter.send(any())).thenReturn(Status.BAD_VALUE);
makeGraph();
try {
mHalResourceStore.sendToHal(mHalAdapter);
} catch (DisplayOffloadException e) {
assertThat(e.getErrorType()).isEqualTo(ERROR_HAL_STATUS_NOT_OK);
}
when(mHalAdapter.send(any())).thenReturn(Status.OK);
doThrow(new RemoteException("Test Exception.")).when(mHalAdapter).send(any());
try {
mHalResourceStore.sendToHal(mHalAdapter);
} catch (DisplayOffloadException e) {
assertThat(e.getErrorType()).isEqualTo(ERROR_HAL_REMOTE_EXCEPTION);
}
}
private List<Object> makeGraph() throws DisplayOffloadException {
List<Object> objects = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Object dummyObject = new Object();
objects.add(dummyObject);
mHalResourceStore.addReplaceResource(i, dummyObject);
}
when(mHalTypeConverter.getIdReferenced(objects.get(0))).thenReturn(
new ArrayList<>(Arrays.asList(1, 2)));
when(mHalTypeConverter.getIdReferenced(objects.get(1))).thenReturn(
new ArrayList<>(Arrays.asList(2, 3)));
when(mHalTypeConverter.getIdReferenced(objects.get(2))).thenReturn(
new ArrayList<>(Arrays.asList(3)));
when(mHalTypeConverter.getIdReferenced(objects.get(3))).thenReturn(
new ArrayList<>(Arrays.asList(4)));
return objects;
}
private void makeGraphCycled() throws DisplayOffloadException {
List<Object> objects = new ArrayList<>();
for (int i = 0; i < 2; i++) {
Object dummyObject = new Object();
objects.add(dummyObject);
mHalResourceStore.addReplaceResource(i, dummyObject);
}
when(mHalTypeConverter.getIdReferenced(objects.get(0))).thenReturn(
new ArrayList<>(Arrays.asList(1)));
when(mHalTypeConverter.getIdReferenced(objects.get(1))).thenReturn(
new ArrayList<>(Arrays.asList(0)));
}
private void makeGraphWithNull() {
List<Object> objects = new ArrayList<>();
for (int i = 0; i < 1; i++) {
objects.add(null);
mHalResourceStore.addReplaceResource(i, null);
}
}
private void makeGraphMultiRoot() throws DisplayOffloadException {
List<Object> objects = new ArrayList<>();
for (int i = 0; i < 6; i++) {
Object dummyObject = new Object();
objects.add(dummyObject);
mHalResourceStore.addReplaceResource(i, dummyObject);
}
// V1 or V2 doesn't matter here.
// Just need to call createDummyTranslationGroup representing a virtual root.
when(mHalTypeConverter.createDummyTranslationGroup(anyInt(), any(ArrayList.class)))
.thenReturn(Collections.singletonList(
ResourceObject.of(RESOURCE_ID_VIRTUAL_ROOT, new TranslationGroup())));
// TranslationGroup here represents the virtual root.
when(mHalTypeConverter.getIdReferenced(any(TranslationGroup.class)))
.thenReturn(new ArrayList<>(Arrays.asList(0, 5)));
// Graph with 2 roots.
when(mHalTypeConverter.getIdReferenced(objects.get(0))).thenReturn(
new ArrayList<>(Arrays.asList(1, 2)));
when(mHalTypeConverter.getIdReferenced(objects.get(5))).thenReturn(
new ArrayList<>(Arrays.asList(1)));
when(mHalTypeConverter.getIdReferenced(objects.get(1))).thenReturn(
new ArrayList<>(Arrays.asList(2, 3)));
when(mHalTypeConverter.getIdReferenced(objects.get(2))).thenReturn(
new ArrayList<>(Arrays.asList(3)));
when(mHalTypeConverter.getIdReferenced(objects.get(3))).thenReturn(
new ArrayList<>(Arrays.asList(4)));
}
}