blob: ceb957100aec0cba8662857b949322a7661eecd0 [file] [log] [blame]
/*
* Copyright (C) 2023 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.server.credentials.metrics.shared;
import android.annotation.NonNull;
import com.android.server.credentials.MetricUtilities;
import com.android.server.credentials.metrics.EntryEnum;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Some data is directly shared between the
* {@link com.android.server.credentials.metrics.CandidatePhaseMetric} and the
* {@link com.android.server.credentials.metrics.ChosenProviderFinalPhaseMetric}. This
* aims to create an abstraction that holds that information, to avoid duplication.
*
* This class should be immutable and threadsafe once generated.
*/
public class ResponseCollective {
/*
Abstract Function (responseCounts, entryCounts) -> A 'ResponseCollective' containing information
about a chosen or candidate providers available responses, be they entries or credentials.
RepInvariant: mResponseCounts and mEntryCounts are always initialized
Threadsafe and Immutability: Once generated, the maps remain unchangeable. The object is
threadsafe and immutable, and safe from external changes. This is threadsafe because it is
immutable after creation and only allows reads, not writes.
*/
private static final String TAG = "ResponseCollective";
// Stores the deduped credential response information, eg {"response":5} for this provider
private final Map<String, Integer> mResponseCounts;
// Stores the deduped entry information, eg {ENTRY_ENUM:5} for this provider
private final Map<EntryEnum, Integer> mEntryCounts;
public ResponseCollective(@NonNull Map<String, Integer> responseCounts,
@NonNull Map<EntryEnum, Integer> entryCounts) {
mResponseCounts = responseCounts == null ? new LinkedHashMap<>() :
new LinkedHashMap<>(responseCounts);
mEntryCounts = entryCounts == null ? new LinkedHashMap<>() :
new LinkedHashMap<>(entryCounts);
}
/**
* Returns the unique, deduped, response classtypes for logging associated with this provider.
*
* @return a string array for deduped classtypes
*/
public String[] getUniqueResponseStrings() {
String[] result = new String[mResponseCounts.keySet().size()];
mResponseCounts.keySet().toArray(result);
return result;
}
/**
* Returns an unmodifiable map of the entry counts, safe under the immutability of the
* class the original map is held within.
* @return an unmodifiable map of the entry : counts
*/
public Map<EntryEnum, Integer> getEntryCountsMap() {
return Collections.unmodifiableMap(mEntryCounts);
}
/**
* Returns an unmodifiable map of the response counts, safe under the immutability of the
* class the original map is held within.
* @return an unmodifiable map of the response : counts
*/
public Map<String, Integer> getResponseCountsMap() {
return Collections.unmodifiableMap(mResponseCounts);
}
/**
* Returns the unique, deduped, response classtype counts for logging associated with this
* provider.
* @return a string array for deduped classtype counts
*/
public int[] getUniqueResponseCounts() {
return mResponseCounts.values().stream().mapToInt(Integer::intValue).toArray();
}
/**
* Returns the unique, deduped, entry types for logging associated with this provider.
* @return an int array for deduped entries
*/
public int[] getUniqueEntries() {
return mEntryCounts.keySet().stream().mapToInt(Enum::ordinal).toArray();
}
/**
* Returns the unique, deduped, entry classtype counts for logging associated with this
* provider.
* @return a string array for deduped classtype counts
*/
public int[] getUniqueEntryCounts() {
return mEntryCounts.values().stream().mapToInt(Integer::intValue).toArray();
}
/**
* Given a specific {@link EntryEnum}, this provides us with the count of that entry within
* this particular provider.
* @param e the entry enum with which we want to know the count of
* @return a count of this particular entry enum stored by this provider
*/
public int getCountForEntry(EntryEnum e) {
return mEntryCounts.getOrDefault(e, MetricUtilities.ZERO);
}
/**
* Indicates the total number of existing entries for this provider.
* @return a count of the total number of entries for this provider
*/
public int getNumEntriesTotal() {
return mEntryCounts.values().stream().mapToInt(Integer::intValue).sum();
}
/**
* This combines the current collective with another collective, only if that other
* collective is indeed a differing one in memory.
* @param other the other response collective to combine with
* @return a combined {@link ResponseCollective} object
*/
public ResponseCollective combineCollectives(ResponseCollective other) {
if (this == other) {
return this;
}
Map<String, Integer> responseCounts = new LinkedHashMap<>(other.mResponseCounts);
for (String response : mResponseCounts.keySet()) {
responseCounts.merge(response, mResponseCounts.get(response), Integer::sum);
}
Map<EntryEnum, Integer> entryCounts = new LinkedHashMap<>(other.mEntryCounts);
for (EntryEnum entry : mEntryCounts.keySet()) {
entryCounts.merge(entry, mEntryCounts.get(entry), Integer::sum);
}
return new ResponseCollective(responseCounts, entryCounts);
}
/**
* Given two maps of type : counts, this combines the second into the first, to get an aggregate
* deduped type:count output.
* @param first the first map of some type to counts used as the base
* @param second the second map of some type to counts that mixies with the first
* @param <T> The type of the object we are mix-deduping - i.e. responses or entries.
* @return the first map updated with the second map's information for type:counts
*/
public static <T> Map<T, Integer> combineTypeCountMaps(Map<T, Integer> first,
Map<T, Integer> second) {
for (T response : second.keySet()) {
first.put(response, first.getOrDefault(response, 0) + second.get(response));
}
return first;
}
}