blob: 667e5722ae95f77168832cb55f86de5a07f305ae [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.android.clockwork.displayoffload.DebugUtils.DEBUG_HAL;
import static com.android.clockwork.displayoffload.DebugUtils.dumpObjectDetails;
import static com.android.clockwork.displayoffload.DebugUtils.dumpObjectIdType;
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.IDisplayOffloadCallbacks.FIELD_HAL_STATUS;
import static com.google.android.clockwork.ambient.offload.IDisplayOffloadCallbacks.FIELD_TRACE;
import android.annotation.NonNull;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import com.android.clockwork.displayoffload.HalTypeConverter.HalTypeConverterSupplier;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.google.android.clockwork.ambient.offload.IDisplayOffloadService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import vendor.google_clockwork.displayoffload.V1_0.Status;
/**
* Class that encapsulates add/replace/validation/send logic for HAL resource types.
*/
class HalResourceStore {
private static final String TAG = "HalResourceStore";
private static final boolean DEBUG = false;
@VisibleForTesting
@GuardedBy("this")
final ArrayMap<Integer, Object> mResources = new ArrayMap<>();
@VisibleForTesting
@GuardedBy("this")
final ArrayMap<String, Object> mStringIdentifiedBindings = new ArrayMap<>();
@GuardedBy("this")
private final ArraySet<Integer> mDirtyIds = new ArraySet<>();
@GuardedBy("this")
private final ArraySet<String> mDirtyStringIds = new ArraySet<>();
private final HalTypeConverterSupplier mHalTypeConverter;
@VisibleForTesting
@GuardedBy("this")
int[] mSendOrder = new int[]{};
@GuardedBy("this")
private boolean mDirtySendOrder = true;
public HalResourceStore(HalTypeConverterSupplier halTypeConverter) {
mHalTypeConverter = halTypeConverter;
}
/**
* Store all the {@link ResourceObject} in objects to HalResourceStore.
*
* @param resourceObjects A list of {@link ResourceObject}.
*/
public synchronized void addReplaceResource(@NonNull List<ResourceObject> resourceObjects) {
for (ResourceObject resourceObject : resourceObjects) {
addReplaceResource(resourceObject);
}
}
/**
* Store a single {@link ResourceObject} to HalResourceStore.
*
* @param resourceObject A single {@link ResourceObject}.
*/
public synchronized void addReplaceResource(@NonNull ResourceObject resourceObject) {
if (resourceObject.useStringName()) {
addReplaceResource(resourceObject.getName(), resourceObject.getObject());
} else {
addReplaceResource(resourceObject.getId(), resourceObject.getObject());
}
}
/**
* Store one pair of <Integer, Object> to HalResourceStore.
*
* @param id The integer identifier/key to query the HalResourceStore for the hal resource.
* @param object The resource corresponding to id.
*/
@VisibleForTesting
synchronized void addReplaceResource(int id, @NonNull Object object) {
if (Objects.equals(object, mResources.get(id))) return;
Log.d(
TAG,
"addReplaceResource: id="
+ dumpObjectIdType(id, object)
+ (DEBUG ? (" object=" + dumpObjectDetails(object)) : ""));
mDirtySendOrder = true;
mDirtyIds.add(id);
mResources.put(id, object);
}
/**
* Store one pair of <String, Object> to HalResourceStore.
*
* @param name The string identifier/key to query the HalResourceStore for the hal resource.
* @param object The resource corresponding to id.
*/
@VisibleForTesting
synchronized void addReplaceResource(String name, @NonNull Object object) {
if (Objects.equals(object, mStringIdentifiedBindings.get(name))) return;
Log.d(
TAG,
"addReplaceResource: name="
+ dumpObjectIdType(name, object)
+ (DEBUG ? (" object=" + dumpObjectDetails(object)) : ""));
mDirtySendOrder = true;
mDirtyStringIds.add(name);
mStringIdentifiedBindings.put(name, object);
}
public synchronized Object get(int id) {
return mResources.get(id);
}
public synchronized Object get(String name) {
return mStringIdentifiedBindings.get(name);
}
public synchronized boolean isEmpty() {
return mResources.isEmpty();
}
public synchronized void clear() {
mResources.clear();
mStringIdentifiedBindings.clear();
mDirtyIds.clear();
mDirtyStringIds.clear();
mDirtySendOrder = false;
}
public synchronized void markAllDirty() {
// No need to mark sendOrder as dirty since it's already done
mDirtyIds.clear();
mDirtyStringIds.clear();
mDirtyStringIds.addAll(mStringIdentifiedBindings.keySet());
mDirtyIds.addAll(mResources.keySet());
}
private static void updateAdjacencyListIndegree(
Map<Integer, List<Integer>> adj,
Map<Integer, Integer> indegree,
int node,
List<Integer> children) {
// Indegree is as the number of dependant/children that one resource has
indegree.putIfAbsent(node, 0);
if (children == null) {
return;
}
// AdjacencyList records connection between parent resource with its children
adj.put(node, children);
// Compute indegree for each resource
for (int child : children) {
indegree.put(child, indegree.getOrDefault(child, 0) + 1);
}
}
public synchronized int getRootId() throws DisplayOffloadException {
if (mDirtySendOrder) {
generateSendOrder();
}
if (mSendOrder == null || mSendOrder.length == 0) {
return -1;
}
return mSendOrder[mSendOrder.length - 1];
}
public synchronized void generateSendOrder() throws DisplayOffloadException {
if (mResources.size() == 0) {
// No HAL resources
mDirtySendOrder = false;
mDirtyIds.clear();
mSendOrder = new int[]{};
return;
}
// Always regenerate virtual root.
mResources.remove(IDisplayOffloadService.RESOURCE_ID_VIRTUAL_ROOT);
mDirtyIds.remove(IDisplayOffloadService.RESOURCE_ID_VIRTUAL_ROOT);
// Indegree is the number of resources that depends on a certain resource
Map<Integer, Integer> inDegree = new ArrayMap<>();
for (Map.Entry<Integer, Object> entry : mResources.entrySet()) {
Object object = entry.getValue();
if (object == null) {
// mResources is incorrect
mDirtySendOrder = true;
// Exception
throw new DisplayOffloadException(ERROR_LAYOUT_INVALID_RESOURCE);
}
inDegree.putIfAbsent(entry.getKey(), 0);
final List<Integer> children = mHalTypeConverter.getConverter().getIdReferenced(object);
if (DEBUG) {
Log.i(
TAG,
"Children of "
+ dumpObjectIdType(entry.getKey(), object)
+ " : "
+ children);
}
if (children != null) {
for (int child : children) {
inDegree.put(child, inDegree.getOrDefault(child, 0) + 1);
}
}
}
ArrayList<Integer> possibleRootIds = new ArrayList<>(inDegree.size());
for (Map.Entry<Integer, Integer> entry : inDegree.entrySet()) {
if (entry.getValue() == 0) {
// Is a possible root
possibleRootIds.add(entry.getKey());
}
}
if (DEBUG) {
Log.i(TAG, "Possible roots are " + possibleRootIds);
}
if (possibleRootIds.size() > 1) {
// Multiple roots
Log.i(TAG, "Multiple roots possible. Inserting virtual root.");
addReplaceResource(
mHalTypeConverter.getConverter().createDummyTranslationGroup(
IDisplayOffloadService.RESOURCE_ID_VIRTUAL_ROOT, possibleRootIds));
} else {
Log.i(TAG, "Single root. No need for virtual root.");
}
inDegree.clear();
Map<Integer, List<Integer>> references = new ArrayMap<>();
for (Map.Entry<Integer, Object> entry : mResources.entrySet()) {
int id = entry.getKey();
Object object = entry.getValue();
if (object == null) {
// mResources is incorrect
mDirtySendOrder = true;
// Exception
throw new DisplayOffloadException(ERROR_LAYOUT_INVALID_RESOURCE);
}
updateAdjacencyListIndegree(references, inDegree, id,
mHalTypeConverter.getConverter().getIdReferenced(object));
}
int[] order = new int[inDegree.size()];
int index = inDegree.size() - 1;
// Check for cycle references
while (!inDegree.isEmpty()) {
boolean hasZeroInDegree = false;
ArrayList<Integer> toRemove = new ArrayList<>();
for (Map.Entry<Integer, Integer> entry : inDegree.entrySet()) {
int id = entry.getKey();
if (entry.getValue() == 0) {
hasZeroInDegree = true;
order[index--] = id;
if (references.get(id) != null) {
for (int child : references.get(id)) {
inDegree.put(child, inDegree.get(child) - 1);
}
}
toRemove.add(id);
}
}
for (int i : toRemove) {
inDegree.remove(i);
}
if (!hasZeroInDegree) {
// No zero in degree is found, graph has a cycle.
mDirtySendOrder = true;
throw new DisplayOffloadException(ERROR_LAYOUT_CONTAINS_CYCLE);
}
}
mDirtySendOrder = false;
mSendOrder = order;
}
private void throwIfHalFailure(int status, Object o) throws DisplayOffloadException {
// Check HAL status
if (status == Status.OK) {
return;
}
Bundle exceptionBundle = new Bundle();
exceptionBundle.putInt(FIELD_HAL_STATUS, status);
throw new DisplayOffloadException(
ERROR_HAL_STATUS_NOT_OK, exceptionBundle, "HAL call failed for: " + o.toString());
}
public synchronized void sendToHal(HalAdapter halAdapter)
throws DisplayOffloadException {
if (mDirtySendOrder) {
// Need to re-generate order
generateSendOrder();
}
if (DEBUG) {
Log.d(TAG, "sendToHal: sendOrder: " + Arrays.toString(mSendOrder));
}
try {
int status;
// Send all variables first
for (String name : mDirtyStringIds) {
Object o = get(name);
if (DEBUG_HAL) {
Log.d(TAG, "send: " + dumpObjectIdType(name, o));
}
status = halAdapter.send(o);
throwIfHalFailure(status, name);
}
for (int i : mSendOrder) {
if (!mDirtyIds.contains(i)) {
// Resource not dirty, can be skipped
Log.d(TAG, "sendToHal: skip sending to HAL: id=" + i);
} else {
Object o = get(i);
if (DEBUG_HAL) {
Log.d(TAG, "send: " + dumpObjectIdType(i, o));
}
status = halAdapter.send(o);
throwIfHalFailure(status, i);
}
}
if (mSendOrder.length == 0) {
// Nothing to send, don't set root.
Log.w(TAG, "mSendOrder.length = 0, empty layout.");
} else {
int root = mSendOrder[mSendOrder.length - 1];
status = halAdapter.setRoot(root);
throwIfHalFailure(status, "setting root as " + root);
}
} catch (RemoteException e) {
Bundle exceptionBundle = new Bundle();
exceptionBundle.putString(FIELD_TRACE, Utils.getStackTrace(e));
throw new DisplayOffloadException(
ERROR_HAL_REMOTE_EXCEPTION,
exceptionBundle,
"RemoteException occurred: " + e.getMessage());
} catch (DisplayOffloadException e) {
Log.e(TAG, "Exception while sending to HAL.", e);
throw e;
}
mDirtyIds.clear();
}
void dumpRecursively(int index, IndentingPrintWriter ipw) throws DisplayOffloadException {
Object o = mResources.get(index);
ipw.printf(dumpObjectIdType(index, o));
ipw.println();
if (o == null) return;
ipw.increaseIndent();
for (int child : mHalTypeConverter.getConverter().getIdReferenced(o)) {
dumpRecursively(child, ipw);
}
ipw.decreaseIndent();
}
void dump(IndentingPrintWriter ipw) {
ipw.printPair("mDirtySendOrder", mDirtySendOrder);
ipw.printPair("mSendOrder", Arrays.toString(mSendOrder));
ipw.println();
ipw.printPair("mDirtyStringIds", mDirtyStringIds);
ipw.println();
ipw.printPair("mDirtyIds", mDirtyIds);
ipw.println();
int rootId =
mSendOrder != null && mSendOrder.length != 0
? mSendOrder[mSendOrder.length - 1]
: -1;
ipw.printPair("Root ID", rootId);
ipw.println();
ipw.println("mResources");
ipw.println("[Tree View]");
ipw.increaseIndent();
if (mSendOrder.length > 0) {
try {
dumpRecursively(rootId, ipw);
} catch (DisplayOffloadException e) {
ipw.println(e);
}
}
ipw.println();
ipw.decreaseIndent();
ipw.println("[List View]");
ipw.increaseIndent();
for (int key : mSendOrder) {
Object object = mResources.get(key);
ipw.printf("%-25.25s:[%s]", dumpObjectIdType(key, object), dumpObjectDetails(object));
ipw.println();
}
ipw.decreaseIndent();
ipw.println("mStringIdentifiedBindings");
ipw.increaseIndent();
mStringIdentifiedBindings.forEach(
(key, object) -> {
ipw.printf(
"%-25.25s:[%s]",
dumpObjectIdType(key, object), dumpObjectDetails(object));
ipw.println();
});
ipw.decreaseIndent();
}
}