1 /* 2 * Copyright (C) 2023 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.credentials.metrics.shared; 18 19 import android.annotation.NonNull; 20 21 import com.android.server.credentials.MetricUtilities; 22 import com.android.server.credentials.metrics.EntryEnum; 23 24 import java.util.Collections; 25 import java.util.LinkedHashMap; 26 import java.util.Map; 27 28 /** 29 * Some data is directly shared between the 30 * {@link com.android.server.credentials.metrics.CandidatePhaseMetric} and the 31 * {@link com.android.server.credentials.metrics.ChosenProviderFinalPhaseMetric}. This 32 * aims to create an abstraction that holds that information, to avoid duplication. 33 * 34 * This class should be immutable and threadsafe once generated. 35 */ 36 public class ResponseCollective { 37 /* 38 Abstract Function (responseCounts, entryCounts) -> A 'ResponseCollective' containing information 39 about a chosen or candidate providers available responses, be they entries or credentials. 40 41 RepInvariant: mResponseCounts and mEntryCounts are always initialized 42 43 Threadsafe and Immutability: Once generated, the maps remain unchangeable. The object is 44 threadsafe and immutable, and safe from external changes. This is threadsafe because it is 45 immutable after creation and only allows reads, not writes. 46 */ 47 48 private static final String TAG = "ResponseCollective"; 49 50 // Stores the deduped credential response information, eg {"response":5} for this provider 51 private final Map<String, Integer> mResponseCounts; 52 // Stores the deduped entry information, eg {ENTRY_ENUM:5} for this provider 53 private final Map<EntryEnum, Integer> mEntryCounts; 54 ResponseCollective(@onNull Map<String, Integer> responseCounts, @NonNull Map<EntryEnum, Integer> entryCounts)55 public ResponseCollective(@NonNull Map<String, Integer> responseCounts, 56 @NonNull Map<EntryEnum, Integer> entryCounts) { 57 mResponseCounts = responseCounts == null ? new LinkedHashMap<>() : 58 new LinkedHashMap<>(responseCounts); 59 mEntryCounts = entryCounts == null ? new LinkedHashMap<>() : 60 new LinkedHashMap<>(entryCounts); 61 } 62 63 /** 64 * Returns the unique, deduped, response classtypes for logging associated with this provider. 65 * 66 * @return a string array for deduped classtypes 67 */ getUniqueResponseStrings()68 public String[] getUniqueResponseStrings() { 69 String[] result = new String[mResponseCounts.keySet().size()]; 70 mResponseCounts.keySet().toArray(result); 71 return result; 72 } 73 74 /** 75 * Returns an unmodifiable map of the entry counts, safe under the immutability of the 76 * class the original map is held within. 77 * @return an unmodifiable map of the entry : counts 78 */ getEntryCountsMap()79 public Map<EntryEnum, Integer> getEntryCountsMap() { 80 return Collections.unmodifiableMap(mEntryCounts); 81 } 82 83 /** 84 * Returns an unmodifiable map of the response counts, safe under the immutability of the 85 * class the original map is held within. 86 * @return an unmodifiable map of the response : counts 87 */ getResponseCountsMap()88 public Map<String, Integer> getResponseCountsMap() { 89 return Collections.unmodifiableMap(mResponseCounts); 90 } 91 92 /** 93 * Returns the unique, deduped, response classtype counts for logging associated with this 94 * provider. 95 * @return a string array for deduped classtype counts 96 */ getUniqueResponseCounts()97 public int[] getUniqueResponseCounts() { 98 return mResponseCounts.values().stream().mapToInt(Integer::intValue).toArray(); 99 } 100 101 /** 102 * Returns the unique, deduped, entry types for logging associated with this provider. 103 * @return an int array for deduped entries 104 */ getUniqueEntries()105 public int[] getUniqueEntries() { 106 return mEntryCounts.keySet().stream().mapToInt(Enum::ordinal).toArray(); 107 } 108 109 /** 110 * Returns the unique, deduped, entry classtype counts for logging associated with this 111 * provider. 112 * @return a string array for deduped classtype counts 113 */ getUniqueEntryCounts()114 public int[] getUniqueEntryCounts() { 115 return mEntryCounts.values().stream().mapToInt(Integer::intValue).toArray(); 116 } 117 118 /** 119 * Given a specific {@link EntryEnum}, this provides us with the count of that entry within 120 * this particular provider. 121 * @param e the entry enum with which we want to know the count of 122 * @return a count of this particular entry enum stored by this provider 123 */ getCountForEntry(EntryEnum e)124 public int getCountForEntry(EntryEnum e) { 125 return mEntryCounts.getOrDefault(e, MetricUtilities.ZERO); 126 } 127 128 /** 129 * Indicates the total number of existing entries for this provider. 130 * @return a count of the total number of entries for this provider 131 */ getNumEntriesTotal()132 public int getNumEntriesTotal() { 133 return mEntryCounts.values().stream().mapToInt(Integer::intValue).sum(); 134 } 135 136 /** 137 * This combines the current collective with another collective, only if that other 138 * collective is indeed a differing one in memory. 139 * @param other the other response collective to combine with 140 * @return a combined {@link ResponseCollective} object 141 */ combineCollectives(ResponseCollective other)142 public ResponseCollective combineCollectives(ResponseCollective other) { 143 if (this == other) { 144 return this; 145 } 146 147 Map<String, Integer> responseCounts = new LinkedHashMap<>(other.mResponseCounts); 148 for (String response : mResponseCounts.keySet()) { 149 responseCounts.merge(response, mResponseCounts.get(response), Integer::sum); 150 } 151 152 Map<EntryEnum, Integer> entryCounts = new LinkedHashMap<>(other.mEntryCounts); 153 for (EntryEnum entry : mEntryCounts.keySet()) { 154 entryCounts.merge(entry, mEntryCounts.get(entry), Integer::sum); 155 } 156 157 return new ResponseCollective(responseCounts, entryCounts); 158 } 159 160 /** 161 * Given two maps of type : counts, this combines the second into the first, to get an aggregate 162 * deduped type:count output. 163 * @param first the first map of some type to counts used as the base 164 * @param second the second map of some type to counts that mixies with the first 165 * @param <T> The type of the object we are mix-deduping - i.e. responses or entries. 166 * @return the first map updated with the second map's information for type:counts 167 */ combineTypeCountMaps(Map<T, Integer> first, Map<T, Integer> second)168 public static <T> Map<T, Integer> combineTypeCountMaps(Map<T, Integer> first, 169 Map<T, Integer> second) { 170 for (T response : second.keySet()) { 171 first.put(response, first.getOrDefault(response, 0) + second.get(response)); 172 } 173 return first; 174 } 175 } 176