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