1 /* 2 * Copyright 2020 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.metrics; 18 19 import android.content.Context; 20 import android.content.pm.PackageManager; 21 import android.media.metrics.IMediaMetricsManager; 22 import android.media.metrics.NetworkEvent; 23 import android.media.metrics.PlaybackErrorEvent; 24 import android.media.metrics.PlaybackMetrics; 25 import android.media.metrics.PlaybackStateEvent; 26 import android.media.metrics.TrackChangeEvent; 27 import android.os.Binder; 28 import android.provider.DeviceConfig; 29 import android.provider.DeviceConfig.Properties; 30 import android.util.Base64; 31 import android.util.Slog; 32 import android.util.StatsEvent; 33 import android.util.StatsLog; 34 35 import com.android.internal.annotations.GuardedBy; 36 import com.android.server.SystemService; 37 38 import java.security.SecureRandom; 39 import java.util.Arrays; 40 import java.util.List; 41 42 /** 43 * System service manages media metrics. 44 */ 45 public final class MediaMetricsManagerService extends SystemService { 46 private static final String TAG = "MediaMetricsManagerService"; 47 48 private static final String MEDIA_METRICS_MODE = "media_metrics_mode"; 49 private static final String PLAYER_METRICS_PER_APP_ATTRIBUTION_ALLOWLIST = 50 "player_metrics_per_app_attribution_allowlist"; 51 private static final String PLAYER_METRICS_APP_ALLOWLIST = "player_metrics_app_allowlist"; 52 53 private static final String PLAYER_METRICS_PER_APP_ATTRIBUTION_BLOCKLIST = 54 "player_metrics_per_app_attribution_blocklist"; 55 private static final String PLAYER_METRICS_APP_BLOCKLIST = "player_metrics_app_blocklist"; 56 57 private static final int MEDIA_METRICS_MODE_OFF = 0; 58 private static final int MEDIA_METRICS_MODE_ON = 1; 59 private static final int MEDIA_METRICS_MODE_BLOCKLIST = 2; 60 private static final int MEDIA_METRICS_MODE_ALLOWLIST = 3; 61 62 // Cascading logging levels. The higher value, the more constrains (less logging data). 63 // The unused values between 2 consecutive levels are reserved for potential extra levels. 64 private static final int LOGGING_LEVEL_EVERYTHING = 0; 65 private static final int LOGGING_LEVEL_NO_UID = 1000; 66 private static final int LOGGING_LEVEL_BLOCKED = 99999; 67 68 private static final String FAILED_TO_GET = "failed_to_get"; 69 private final SecureRandom mSecureRandom; 70 @GuardedBy("mLock") 71 private Integer mMode = null; 72 @GuardedBy("mLock") 73 private List<String> mAllowlist = null; 74 @GuardedBy("mLock") 75 private List<String> mNoUidAllowlist = null; 76 @GuardedBy("mLock") 77 private List<String> mBlockList = null; 78 @GuardedBy("mLock") 79 private List<String> mNoUidBlocklist = null; 80 private final Object mLock = new Object(); 81 private final Context mContext; 82 83 /** 84 * Initializes the playback metrics manager service. 85 * 86 * @param context The system server context. 87 */ MediaMetricsManagerService(Context context)88 public MediaMetricsManagerService(Context context) { 89 super(context); 90 mContext = context; 91 mSecureRandom = new SecureRandom(); 92 } 93 94 @Override onStart()95 public void onStart() { 96 publishBinderService(Context.MEDIA_METRICS_SERVICE, new BinderService()); 97 DeviceConfig.addOnPropertiesChangedListener( 98 DeviceConfig.NAMESPACE_MEDIA, 99 mContext.getMainExecutor(), 100 this::updateConfigs); 101 } 102 updateConfigs(Properties properties)103 private void updateConfigs(Properties properties) { 104 synchronized (mLock) { 105 mMode = properties.getInt( 106 MEDIA_METRICS_MODE, 107 MEDIA_METRICS_MODE_BLOCKLIST); 108 List<String> newList = getListLocked(PLAYER_METRICS_APP_ALLOWLIST); 109 if (newList != null || mMode != MEDIA_METRICS_MODE_ALLOWLIST) { 110 // don't overwrite the list if the mode IS MEDIA_METRICS_MODE_ALLOWLIST 111 // but failed to get 112 mAllowlist = newList; 113 } 114 newList = getListLocked(PLAYER_METRICS_PER_APP_ATTRIBUTION_ALLOWLIST); 115 if (newList != null || mMode != MEDIA_METRICS_MODE_ALLOWLIST) { 116 mNoUidAllowlist = newList; 117 } 118 newList = getListLocked(PLAYER_METRICS_APP_BLOCKLIST); 119 if (newList != null || mMode != MEDIA_METRICS_MODE_BLOCKLIST) { 120 mBlockList = newList; 121 } 122 newList = getListLocked(PLAYER_METRICS_PER_APP_ATTRIBUTION_BLOCKLIST); 123 if (newList != null || mMode != MEDIA_METRICS_MODE_BLOCKLIST) { 124 mNoUidBlocklist = newList; 125 } 126 } 127 } 128 129 @GuardedBy("mLock") getListLocked(String listName)130 private List<String> getListLocked(String listName) { 131 final long identity = Binder.clearCallingIdentity(); 132 String listString = FAILED_TO_GET; 133 try { 134 listString = DeviceConfig.getString( 135 DeviceConfig.NAMESPACE_MEDIA, listName, FAILED_TO_GET); 136 } finally { 137 Binder.restoreCallingIdentity(identity); 138 } 139 if (listString.equals(FAILED_TO_GET)) { 140 Slog.d(TAG, "failed to get " + listName + " from DeviceConfig"); 141 return null; 142 } 143 String[] pkgArr = listString.split(","); 144 return Arrays.asList(pkgArr); 145 } 146 147 private final class BinderService extends IMediaMetricsManager.Stub { 148 @Override reportPlaybackMetrics(String sessionId, PlaybackMetrics metrics, int userId)149 public void reportPlaybackMetrics(String sessionId, PlaybackMetrics metrics, int userId) { 150 int level = loggingLevel(); 151 if (level == LOGGING_LEVEL_BLOCKED) { 152 return; 153 } 154 StatsEvent statsEvent = StatsEvent.newBuilder() 155 .setAtomId(320) 156 .writeInt(level == LOGGING_LEVEL_EVERYTHING ? Binder.getCallingUid() : 0) 157 .writeString(sessionId) 158 .writeLong(metrics.getMediaDurationMillis()) 159 .writeInt(metrics.getStreamSource()) 160 .writeInt(metrics.getStreamType()) 161 .writeInt(metrics.getPlaybackType()) 162 .writeInt(metrics.getDrmType()) 163 .writeInt(metrics.getContentType()) 164 .writeString(metrics.getPlayerName()) 165 .writeString(metrics.getPlayerVersion()) 166 .writeByteArray(new byte[0]) // TODO: write experiments proto 167 .writeInt(metrics.getVideoFramesPlayed()) 168 .writeInt(metrics.getVideoFramesDropped()) 169 .writeInt(metrics.getAudioUnderrunCount()) 170 .writeLong(metrics.getNetworkBytesRead()) 171 .writeLong(metrics.getLocalBytesRead()) 172 .writeLong(metrics.getNetworkTransferDurationMillis()) 173 // Raw bytes type not allowed in atoms 174 .writeString(Base64.encodeToString(metrics.getDrmSessionId(), Base64.DEFAULT)) 175 .usePooledBuffer() 176 .build(); 177 StatsLog.write(statsEvent); 178 } 179 180 @Override reportPlaybackStateEvent( String sessionId, PlaybackStateEvent event, int userId)181 public void reportPlaybackStateEvent( 182 String sessionId, PlaybackStateEvent event, int userId) { 183 int level = loggingLevel(); 184 if (level == LOGGING_LEVEL_BLOCKED) { 185 return; 186 } 187 StatsEvent statsEvent = StatsEvent.newBuilder() 188 .setAtomId(322) 189 .writeString(sessionId) 190 .writeInt(event.getState()) 191 .writeLong(event.getTimeSinceCreatedMillis()) 192 .usePooledBuffer() 193 .build(); 194 StatsLog.write(statsEvent); 195 } 196 getSessionIdInternal(int userId)197 private String getSessionIdInternal(int userId) { 198 byte[] byteId = new byte[12]; // 96 bits (128 bits when expanded to Base64 string) 199 mSecureRandom.nextBytes(byteId); 200 String id = Base64.encodeToString( 201 byteId, Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE); 202 return id; 203 } 204 205 @Override getPlaybackSessionId(int userId)206 public String getPlaybackSessionId(int userId) { 207 return getSessionIdInternal(userId); 208 } 209 210 @Override getRecordingSessionId(int userId)211 public String getRecordingSessionId(int userId) { 212 return getSessionIdInternal(userId); 213 } 214 215 @Override reportPlaybackErrorEvent( String sessionId, PlaybackErrorEvent event, int userId)216 public void reportPlaybackErrorEvent( 217 String sessionId, PlaybackErrorEvent event, int userId) { 218 int level = loggingLevel(); 219 if (level == LOGGING_LEVEL_BLOCKED) { 220 return; 221 } 222 StatsEvent statsEvent = StatsEvent.newBuilder() 223 .setAtomId(323) 224 .writeString(sessionId) 225 .writeString(event.getExceptionStack()) 226 .writeInt(event.getErrorCode()) 227 .writeInt(event.getSubErrorCode()) 228 .writeLong(event.getTimeSinceCreatedMillis()) 229 .usePooledBuffer() 230 .build(); 231 StatsLog.write(statsEvent); 232 } 233 reportNetworkEvent( String sessionId, NetworkEvent event, int userId)234 public void reportNetworkEvent( 235 String sessionId, NetworkEvent event, int userId) { 236 int level = loggingLevel(); 237 if (level == LOGGING_LEVEL_BLOCKED) { 238 return; 239 } 240 StatsEvent statsEvent = StatsEvent.newBuilder() 241 .setAtomId(321) 242 .writeString(sessionId) 243 .writeInt(event.getNetworkType()) 244 .writeLong(event.getTimeSinceCreatedMillis()) 245 .usePooledBuffer() 246 .build(); 247 StatsLog.write(statsEvent); 248 } 249 250 @Override reportTrackChangeEvent( String sessionId, TrackChangeEvent event, int userId)251 public void reportTrackChangeEvent( 252 String sessionId, TrackChangeEvent event, int userId) { 253 int level = loggingLevel(); 254 if (level == LOGGING_LEVEL_BLOCKED) { 255 return; 256 } 257 StatsEvent statsEvent = StatsEvent.newBuilder() 258 .setAtomId(324) 259 .writeString(sessionId) 260 .writeInt(event.getTrackState()) 261 .writeInt(event.getTrackChangeReason()) 262 .writeString(event.getContainerMimeType()) 263 .writeString(event.getSampleMimeType()) 264 .writeString(event.getCodecName()) 265 .writeInt(event.getBitrate()) 266 .writeLong(event.getTimeSinceCreatedMillis()) 267 .writeInt(event.getTrackType()) 268 .writeString(event.getLanguage()) 269 .writeString(event.getLanguageRegion()) 270 .writeInt(event.getChannelCount()) 271 .writeInt(event.getAudioSampleRate()) 272 .writeInt(event.getWidth()) 273 .writeInt(event.getHeight()) 274 .writeFloat(event.getVideoFrameRate()) 275 .usePooledBuffer() 276 .build(); 277 StatsLog.write(statsEvent); 278 } 279 loggingLevel()280 private int loggingLevel() { 281 synchronized (mLock) { 282 int uid = Binder.getCallingUid(); 283 284 if (mMode == null) { 285 final long identity = Binder.clearCallingIdentity(); 286 try { 287 mMode = DeviceConfig.getInt( 288 DeviceConfig.NAMESPACE_MEDIA, 289 MEDIA_METRICS_MODE, 290 MEDIA_METRICS_MODE_BLOCKLIST); 291 } finally { 292 Binder.restoreCallingIdentity(identity); 293 } 294 } 295 296 if (mMode == MEDIA_METRICS_MODE_ON) { 297 return LOGGING_LEVEL_EVERYTHING; 298 } 299 if (mMode == MEDIA_METRICS_MODE_OFF) { 300 return LOGGING_LEVEL_BLOCKED; 301 } 302 303 PackageManager pm = getContext().getPackageManager(); 304 String[] packages = pm.getPackagesForUid(uid); 305 if (packages == null || packages.length == 0) { 306 // The valid application UID range is from 307 // android.os.Process.FIRST_APPLICATION_UID to 308 // android.os.Process.LAST_APPLICATION_UID. 309 // UIDs outside this range will not have a package. 310 Slog.d(TAG, "empty package from uid " + uid); 311 // block the data if the mode is MEDIA_METRICS_MODE_ALLOWLIST 312 return mMode == MEDIA_METRICS_MODE_BLOCKLIST 313 ? LOGGING_LEVEL_NO_UID : LOGGING_LEVEL_BLOCKED; 314 } 315 if (mMode == MEDIA_METRICS_MODE_BLOCKLIST) { 316 if (mBlockList == null) { 317 mBlockList = getListLocked(PLAYER_METRICS_APP_BLOCKLIST); 318 if (mBlockList == null) { 319 // failed to get the blocklist. Block it. 320 return LOGGING_LEVEL_BLOCKED; 321 } 322 } 323 Integer level = loggingLevelInternal( 324 packages, mBlockList, PLAYER_METRICS_APP_BLOCKLIST); 325 if (level != null) { 326 return level; 327 } 328 if (mNoUidBlocklist == null) { 329 mNoUidBlocklist = 330 getListLocked(PLAYER_METRICS_PER_APP_ATTRIBUTION_BLOCKLIST); 331 if (mNoUidBlocklist == null) { 332 // failed to get the blocklist. Block it. 333 return LOGGING_LEVEL_BLOCKED; 334 } 335 } 336 level = loggingLevelInternal( 337 packages, 338 mNoUidBlocklist, 339 PLAYER_METRICS_PER_APP_ATTRIBUTION_BLOCKLIST); 340 if (level != null) { 341 return level; 342 } 343 // Not detected in any blocklist. Log everything. 344 return LOGGING_LEVEL_EVERYTHING; 345 } 346 if (mMode == MEDIA_METRICS_MODE_ALLOWLIST) { 347 if (mNoUidAllowlist == null) { 348 mNoUidAllowlist = 349 getListLocked(PLAYER_METRICS_PER_APP_ATTRIBUTION_ALLOWLIST); 350 if (mNoUidAllowlist == null) { 351 // failed to get the allowlist. Block it. 352 return LOGGING_LEVEL_BLOCKED; 353 } 354 } 355 Integer level = loggingLevelInternal( 356 packages, 357 mNoUidAllowlist, 358 PLAYER_METRICS_PER_APP_ATTRIBUTION_ALLOWLIST); 359 if (level != null) { 360 return level; 361 } 362 if (mAllowlist == null) { 363 mAllowlist = getListLocked(PLAYER_METRICS_APP_ALLOWLIST); 364 if (mAllowlist == null) { 365 // failed to get the allowlist. Block it. 366 return LOGGING_LEVEL_BLOCKED; 367 } 368 } 369 level = loggingLevelInternal( 370 packages, mAllowlist, PLAYER_METRICS_APP_ALLOWLIST); 371 if (level != null) { 372 return level; 373 } 374 // Not detected in any allowlist. Block. 375 return LOGGING_LEVEL_BLOCKED; 376 } 377 } 378 // Blocked by default. 379 return LOGGING_LEVEL_BLOCKED; 380 } 381 loggingLevelInternal( String[] packages, List<String> cached, String listName)382 private Integer loggingLevelInternal( 383 String[] packages, List<String> cached, String listName) { 384 if (inList(packages, cached)) { 385 return listNameToLoggingLevel(listName); 386 } 387 return null; 388 } 389 inList(String[] packages, List<String> arr)390 private boolean inList(String[] packages, List<String> arr) { 391 for (String p : packages) { 392 for (String element : arr) { 393 if (p.equals(element)) { 394 return true; 395 } 396 } 397 } 398 return false; 399 } 400 listNameToLoggingLevel(String listName)401 private int listNameToLoggingLevel(String listName) { 402 switch (listName) { 403 case PLAYER_METRICS_APP_BLOCKLIST: 404 return LOGGING_LEVEL_BLOCKED; 405 case PLAYER_METRICS_APP_ALLOWLIST: 406 return LOGGING_LEVEL_EVERYTHING; 407 case PLAYER_METRICS_PER_APP_ATTRIBUTION_ALLOWLIST: 408 case PLAYER_METRICS_PER_APP_ATTRIBUTION_BLOCKLIST: 409 return LOGGING_LEVEL_NO_UID; 410 default: 411 return LOGGING_LEVEL_BLOCKED; 412 } 413 } 414 } 415 } 416