1 /* 2 * Copyright 2025 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.media; 18 19 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_SCANNING_STARTED; 20 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_SCANNING_STOPPED; 21 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_FAILED_TO_REROUTE_SYSTEM_MEDIA; 22 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_COMMAND; 23 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_NETWORK_ERROR; 24 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_REJECTED; 25 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTE_NOT_AVAILABLE; 26 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNIMPLEMENTED; 27 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNKNOWN_ERROR; 28 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED; 29 30 import android.annotation.NonNull; 31 import android.media.MediaRoute2ProviderService; 32 import android.util.Log; 33 import android.util.Slog; 34 import com.android.internal.annotations.VisibleForTesting; 35 import java.io.PrintWriter; 36 import java.util.LinkedHashMap; 37 import java.util.Map; 38 39 /** 40 * Logs metrics for MediaRouter2. 41 * 42 * @hide 43 */ 44 final class MediaRouterMetricLogger { 45 private static final String TAG = "MediaRouterMetricLogger"; 46 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 47 private static final int REQUEST_INFO_CACHE_CAPACITY = 100; 48 49 /** LRU cache to store request info. */ 50 private final RequestInfoCache mRequestInfoCache; 51 52 /** Constructor for {@link MediaRouterMetricLogger}. */ MediaRouterMetricLogger()53 public MediaRouterMetricLogger() { 54 mRequestInfoCache = new RequestInfoCache(REQUEST_INFO_CACHE_CAPACITY); 55 } 56 57 /** 58 * Adds a new request info to the cache. 59 * 60 * @param uniqueRequestId The unique request id. 61 * @param eventType The event type. 62 */ addRequestInfo(long uniqueRequestId, int eventType)63 public void addRequestInfo(long uniqueRequestId, int eventType) { 64 RequestInfo requestInfo = new RequestInfo(uniqueRequestId, eventType); 65 mRequestInfoCache.put(requestInfo.mUniqueRequestId, requestInfo); 66 } 67 68 /** 69 * Removes a request info from the cache. 70 * 71 * @param uniqueRequestId The unique request id. 72 */ removeRequestInfo(long uniqueRequestId)73 public void removeRequestInfo(long uniqueRequestId) { 74 mRequestInfoCache.remove(uniqueRequestId); 75 } 76 77 /** 78 * Logs an operation failure. 79 * 80 * @param eventType The event type. 81 * @param result The result of the operation. 82 */ logOperationFailure(int eventType, int result)83 public void logOperationFailure(int eventType, int result) { 84 logMediaRouterEvent(eventType, result); 85 } 86 87 /** 88 * Logs an operation triggered. 89 * 90 * @param eventType The event type. 91 */ logOperationTriggered(int eventType, int result)92 public void logOperationTriggered(int eventType, int result) { 93 logMediaRouterEvent(eventType, result); 94 } 95 96 /** 97 * Logs the result of a request. 98 * 99 * @param uniqueRequestId The unique request id. 100 * @param result The result of the request. 101 */ logRequestResult(long uniqueRequestId, int result)102 public void logRequestResult(long uniqueRequestId, int result) { 103 RequestInfo requestInfo = mRequestInfoCache.get(uniqueRequestId); 104 if (requestInfo == null) { 105 Slog.w( 106 TAG, 107 "logRequestResult: No RequestInfo found for uniqueRequestId=" 108 + uniqueRequestId); 109 return; 110 } 111 112 int eventType = requestInfo.mEventType; 113 logMediaRouterEvent(eventType, result); 114 115 removeRequestInfo(uniqueRequestId); 116 } 117 118 /** 119 * Logs the overall scanning state. 120 * 121 * @param isScanning The scanning state for the user. 122 */ updateScanningState(boolean isScanning)123 public void updateScanningState(boolean isScanning) { 124 if (!isScanning) { 125 logScanningStopped(); 126 } else { 127 logScanningStarted(); 128 } 129 } 130 131 /** Logs the scanning started event. */ logScanningStarted()132 private void logScanningStarted() { 133 logMediaRouterEvent( 134 MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_SCANNING_STARTED, 135 MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED); 136 } 137 138 /** Logs the scanning stopped event. */ logScanningStopped()139 private void logScanningStopped() { 140 logMediaRouterEvent( 141 MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_SCANNING_STOPPED, 142 MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED); 143 } 144 145 /** 146 * Converts a reason code from {@link MediaRoute2ProviderService} to a result code for logging. 147 * 148 * @param reason The reason code from {@link MediaRoute2ProviderService}. 149 * @return The result code for logging. 150 */ convertResultFromReason(int reason)151 public static int convertResultFromReason(int reason) { 152 switch (reason) { 153 case MediaRoute2ProviderService.REASON_UNKNOWN_ERROR: 154 return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNKNOWN_ERROR; 155 case MediaRoute2ProviderService.REASON_REJECTED: 156 return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_REJECTED; 157 case MediaRoute2ProviderService.REASON_NETWORK_ERROR: 158 return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_NETWORK_ERROR; 159 case MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE: 160 return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTE_NOT_AVAILABLE; 161 case MediaRoute2ProviderService.REASON_INVALID_COMMAND: 162 return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_COMMAND; 163 case MediaRoute2ProviderService.REASON_UNIMPLEMENTED: 164 return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNIMPLEMENTED; 165 case MediaRoute2ProviderService.REASON_FAILED_TO_REROUTE_SYSTEM_MEDIA: 166 return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_FAILED_TO_REROUTE_SYSTEM_MEDIA; 167 default: 168 return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED; 169 } 170 } 171 172 /** 173 * Gets the size of the request info cache. 174 * 175 * @return The size of the request info cache. 176 */ 177 @VisibleForTesting getRequestCacheSize()178 public int getRequestCacheSize() { 179 return mRequestInfoCache.size(); 180 } 181 logMediaRouterEvent(int eventType, int result)182 private void logMediaRouterEvent(int eventType, int result) { 183 MediaRouterStatsLog.write( 184 MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED, eventType, result); 185 186 if (DEBUG) { 187 Slog.d(TAG, "logMediaRouterEvent: " + eventType + " " + result); 188 } 189 } 190 191 /** A cache for storing request info that evicts entries when it reaches its capacity. */ 192 class RequestInfoCache extends LinkedHashMap<Long, RequestInfo> { 193 194 public final int capacity; 195 196 /** 197 * Constructor for {@link RequestInfoCache}. 198 * 199 * @param capacity The maximum capacity of the cache. 200 */ RequestInfoCache(int capacity)201 public RequestInfoCache(int capacity) { 202 super(capacity, 1.0f, true); 203 this.capacity = capacity; 204 } 205 206 @Override removeEldestEntry(Map.Entry<Long, RequestInfo> eldest)207 protected boolean removeEldestEntry(Map.Entry<Long, RequestInfo> eldest) { 208 boolean shouldRemove = size() > capacity; 209 if (shouldRemove) { 210 Slog.d(TAG, "Evicted request info: " + eldest.getValue()); 211 logOperationTriggered( 212 eldest.getValue().mEventType, 213 MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED); 214 } 215 return shouldRemove; 216 } 217 } 218 219 /** Class to store request info. */ 220 static class RequestInfo { 221 public final long mUniqueRequestId; 222 public final int mEventType; 223 224 /** 225 * Constructor for {@link RequestInfo}. 226 * 227 * @param uniqueRequestId The unique request id. 228 * @param eventType The event type. 229 */ RequestInfo(long uniqueRequestId, int eventType)230 RequestInfo(long uniqueRequestId, int eventType) { 231 mUniqueRequestId = uniqueRequestId; 232 mEventType = eventType; 233 } 234 235 /** 236 * Dumps the request info. 237 * 238 * @param pw The print writer. 239 * @param prefix The prefix for the output. 240 */ dump(@onNull PrintWriter pw, @NonNull String prefix)241 public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { 242 pw.println(prefix + "RequestInfo"); 243 String indent = prefix + " "; 244 pw.println(indent + "mUniqueRequestId=" + mUniqueRequestId); 245 pw.println(indent + "mEventType=" + mEventType); 246 } 247 } 248 } 249