• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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