1 /*
2  * Copyright (C) 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.systemui.media.dialog;
18 
19 import static android.media.MediaRoute2ProviderService.REASON_INVALID_COMMAND;
20 import static android.media.MediaRoute2ProviderService.REASON_NETWORK_ERROR;
21 import static android.media.MediaRoute2ProviderService.REASON_REJECTED;
22 import static android.media.MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE;
23 import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
24 
25 import android.content.Context;
26 import android.content.pm.ApplicationInfo;
27 import android.util.Log;
28 
29 import com.android.settingslib.media.MediaDevice;
30 import com.android.systemui.shared.system.SysUiStatsLog;
31 
32 import java.util.List;
33 
34 /**
35  * Metric logger for media output features
36  */
37 public class MediaOutputMetricLogger {
38 
39     private static final String TAG = "MediaOutputMetricLogger";
40     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
41 
42     private final Context mContext;
43     private final String mPackageName;
44     private MediaDevice mSourceDevice, mTargetDevice;
45     private int mWiredDeviceCount;
46     private int mConnectedBluetoothDeviceCount;
47     private int mRemoteDeviceCount;
48     private int mAppliedDeviceCountWithinRemoteGroup;
49 
MediaOutputMetricLogger(Context context, String packageName)50     public MediaOutputMetricLogger(Context context, String packageName) {
51         mContext = context;
52         mPackageName = packageName;
53     }
54 
55     /**
56      * Update the endpoints of a content switching operation.
57      * This method should be called before a switching operation, so the metric logger can track
58      * source and target devices.
59      *
60      * @param source the current connected media device
61      * @param target the target media device for content switching to
62      */
updateOutputEndPoints(MediaDevice source, MediaDevice target)63     public void updateOutputEndPoints(MediaDevice source, MediaDevice target) {
64         mSourceDevice = source;
65         mTargetDevice = target;
66 
67         if (DEBUG) {
68             Log.d(TAG, "updateOutputEndPoints -"
69                     + " source:" + mSourceDevice.toString()
70                     + " target:" + mTargetDevice.toString());
71         }
72     }
73 
74     /**
75      * Do the metric logging of content switching success.
76      *
77      * @param selectedDeviceType string representation of the target media device
78      * @param deviceItemList     media item list for device count updating
79      */
logOutputItemSuccess(String selectedDeviceType, List<MediaItem> deviceItemList)80     public void logOutputItemSuccess(String selectedDeviceType, List<MediaItem> deviceItemList) {
81         if (DEBUG) {
82             Log.d(TAG, "logOutputSuccess - selected device: " + selectedDeviceType);
83         }
84 
85         if (mSourceDevice == null && mTargetDevice == null) {
86             return;
87         }
88 
89         updateLoggingMediaItemCount(deviceItemList);
90 
91         SysUiStatsLog.write(
92                 SysUiStatsLog.MEDIAOUTPUT_OP_SWITCH_REPORTED,
93                 getLoggingDeviceType(mSourceDevice, true),
94                 getLoggingDeviceType(mTargetDevice, false),
95                 SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__RESULT__OK,
96                 SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__NO_ERROR,
97                 getLoggingPackageName(),
98                 mWiredDeviceCount,
99                 mConnectedBluetoothDeviceCount,
100                 mRemoteDeviceCount,
101                 mAppliedDeviceCountWithinRemoteGroup,
102                 mTargetDevice.isSuggestedDevice(),
103                 mTargetDevice.hasOngoingSession());
104     }
105 
106     /**
107      * Do the metric logging of volume adjustment.
108      *
109      * @param source the device been adjusted
110      */
logInteractionAdjustVolume(MediaDevice source)111     public void logInteractionAdjustVolume(MediaDevice source) {
112         if (DEBUG) {
113             Log.d(TAG, "logInteraction - AdjustVolume");
114         }
115 
116         SysUiStatsLog.write(
117                 SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
118                 SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__ADJUST_VOLUME,
119                 getInteractionDeviceType(source),
120                 getLoggingPackageName(),
121                 source.isSuggestedDevice());
122     }
123 
124     /**
125      * Do the metric logging of stop casting.
126      */
logInteractionStopCasting()127     public void logInteractionStopCasting() {
128         if (DEBUG) {
129             Log.d(TAG, "logInteraction - Stop casting");
130         }
131 
132         SysUiStatsLog.write(
133                 SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
134                 SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__STOP_CASTING,
135                 SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__UNKNOWN_TYPE,
136                 getLoggingPackageName(),
137                 /*isSuggestedDevice = */false);
138     }
139 
140     /**
141      * Do the metric logging of device expansion.
142      */
logInteractionExpansion(MediaDevice source)143     public void logInteractionExpansion(MediaDevice source) {
144         if (DEBUG) {
145             Log.d(TAG, "logInteraction - Expansion");
146         }
147 
148         SysUiStatsLog.write(
149                 SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
150                 SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__EXPANSION,
151                 getInteractionDeviceType(source),
152                 getLoggingPackageName(),
153                 source.isSuggestedDevice());
154     }
155 
156     /**
157      * Do the metric logging of muting device.
158      */
logInteractionMute(MediaDevice source)159     public void logInteractionMute(MediaDevice source) {
160         if (DEBUG) {
161             Log.d(TAG, "logInteraction - Mute");
162         }
163 
164         SysUiStatsLog.write(
165                 SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
166                 SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__MUTE,
167                 getInteractionDeviceType(source),
168                 getLoggingPackageName(),
169                 source.isSuggestedDevice());
170     }
171 
172     /**
173      * Do the metric logging of unmuting device.
174      */
logInteractionUnmute(MediaDevice source)175     public void logInteractionUnmute(MediaDevice source) {
176         if (DEBUG) {
177             Log.d(TAG, "logInteraction - Unmute");
178         }
179 
180         SysUiStatsLog.write(
181                 SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
182                 SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__UNMUTE,
183                 getInteractionDeviceType(source),
184                 getLoggingPackageName(),
185                 source.isSuggestedDevice());
186     }
187 
188     /**
189      * Do the metric logging of content switching failure.
190      *
191      * @param deviceItemList media item list for device count updating
192      * @param reason         the reason of content switching failure
193      */
logOutputItemFailure(List<MediaItem> deviceItemList, int reason)194     public void logOutputItemFailure(List<MediaItem> deviceItemList, int reason) {
195         if (DEBUG) {
196             Log.e(TAG, "logRequestFailed - " + reason);
197         }
198 
199         if (mSourceDevice == null && mTargetDevice == null) {
200             return;
201         }
202 
203         updateLoggingMediaItemCount(deviceItemList);
204 
205         SysUiStatsLog.write(
206                 SysUiStatsLog.MEDIAOUTPUT_OP_SWITCH_REPORTED,
207                 getLoggingDeviceType(mSourceDevice, true),
208                 getLoggingDeviceType(mTargetDevice, false),
209                 SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__RESULT__ERROR,
210                 getLoggingSwitchOpSubResult(reason),
211                 getLoggingPackageName(),
212                 mWiredDeviceCount,
213                 mConnectedBluetoothDeviceCount,
214                 mRemoteDeviceCount,
215                 mAppliedDeviceCountWithinRemoteGroup,
216                 mTargetDevice.isSuggestedDevice(),
217                 mTargetDevice.hasOngoingSession());
218     }
219 
updateLoggingDeviceCount(List<MediaDevice> deviceList)220     private void updateLoggingDeviceCount(List<MediaDevice> deviceList) {
221         mWiredDeviceCount = mConnectedBluetoothDeviceCount = mRemoteDeviceCount = 0;
222         mAppliedDeviceCountWithinRemoteGroup = 0;
223 
224         for (MediaDevice mediaDevice : deviceList) {
225             if (mediaDevice.isConnected()) {
226                 switch (mediaDevice.getDeviceType()) {
227                     case MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE:
228                     case MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE:
229                         mWiredDeviceCount++;
230                         break;
231                     case MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE:
232                         mConnectedBluetoothDeviceCount++;
233                         break;
234                     case MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE:
235                     case MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE:
236                         mRemoteDeviceCount++;
237                         break;
238                     default:
239                 }
240             }
241         }
242 
243         if (DEBUG) {
244             Log.d(TAG, "connected devices:" + " wired: " + mWiredDeviceCount
245                     + " bluetooth: " + mConnectedBluetoothDeviceCount
246                     + " remote: " + mRemoteDeviceCount);
247         }
248     }
249 
updateLoggingMediaItemCount(List<MediaItem> deviceItemList)250     private void updateLoggingMediaItemCount(List<MediaItem> deviceItemList) {
251         mWiredDeviceCount = mConnectedBluetoothDeviceCount = mRemoteDeviceCount = 0;
252         mAppliedDeviceCountWithinRemoteGroup = 0;
253 
254         for (MediaItem mediaItem : deviceItemList) {
255             if (mediaItem.getMediaDevice().isPresent()
256                     && mediaItem.getMediaDevice().get().isConnected()) {
257                 switch (mediaItem.getMediaDevice().get().getDeviceType()) {
258                     case MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE:
259                     case MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE:
260                         mWiredDeviceCount++;
261                         break;
262                     case MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE:
263                         mConnectedBluetoothDeviceCount++;
264                         break;
265                     case MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE:
266                     case MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE:
267                         mRemoteDeviceCount++;
268                         break;
269                     default:
270                 }
271             }
272         }
273 
274         if (DEBUG) {
275             Log.d(TAG, "connected devices:" + " wired: " + mWiredDeviceCount
276                     + " bluetooth: " + mConnectedBluetoothDeviceCount
277                     + " remote: " + mRemoteDeviceCount);
278         }
279     }
280 
getLoggingDeviceType(MediaDevice device, boolean isSourceDevice)281     private int getLoggingDeviceType(MediaDevice device, boolean isSourceDevice) {
282         if (device == null) {
283             return isSourceDevice
284                     ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__UNKNOWN_TYPE
285                     : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__UNKNOWN_TYPE;
286         }
287         switch (device.getDeviceType()) {
288             case MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE:
289                 return isSourceDevice
290                         ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__BUILTIN_SPEAKER
291                         : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__BUILTIN_SPEAKER;
292             case MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE:
293                 return isSourceDevice
294                         ? SysUiStatsLog
295                         .MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__WIRED_3POINT5_MM_AUDIO
296                         : SysUiStatsLog
297                                 .MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__WIRED_3POINT5_MM_AUDIO;
298             case MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE:
299                 return isSourceDevice
300                         ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__USB_C_AUDIO
301                         : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__USB_C_AUDIO;
302             case MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE:
303                 return isSourceDevice
304                         ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__BLUETOOTH
305                         : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__BLUETOOTH;
306             case MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE:
307                 return isSourceDevice
308                         ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__REMOTE_SINGLE
309                         : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__REMOTE_SINGLE;
310             case MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE:
311                 return isSourceDevice
312                         ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__REMOTE_GROUP
313                         : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__REMOTE_GROUP;
314             case MediaDevice.MediaDeviceType.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER:
315                 return isSourceDevice
316                         ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__AVR
317                         : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__AVR;
318             default:
319                 return isSourceDevice
320                         ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__UNKNOWN_TYPE
321                         : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__UNKNOWN_TYPE;
322         }
323     }
324 
getInteractionDeviceType(MediaDevice device)325     private int getInteractionDeviceType(MediaDevice device) {
326         if (device == null) {
327             return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__UNKNOWN_TYPE;
328         }
329         switch (device.getDeviceType()) {
330             case MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE:
331                 return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__BUILTIN_SPEAKER;
332             case MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE:
333                 return SysUiStatsLog
334                         .MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__WIRED_3POINT5_MM_AUDIO;
335             case MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE:
336                 return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__USB_C_AUDIO;
337             case MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE:
338                 return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__BLUETOOTH;
339             case MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE:
340                 return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__REMOTE_SINGLE;
341             case MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE:
342                 return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__REMOTE_GROUP;
343             default:
344                 return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__UNKNOWN_TYPE;
345         }
346     }
347 
348 
getLoggingSwitchOpSubResult(int reason)349     private int getLoggingSwitchOpSubResult(int reason) {
350         switch (reason) {
351             case REASON_REJECTED:
352                 return SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__REJECTED;
353             case REASON_NETWORK_ERROR:
354                 return SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__NETWORK_ERROR;
355             case REASON_ROUTE_NOT_AVAILABLE:
356                 return SysUiStatsLog
357                         .MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__ROUTE_NOT_AVAILABLE;
358             case REASON_INVALID_COMMAND:
359                 return SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__INVALID_COMMAND;
360             case REASON_UNKNOWN_ERROR:
361             default:
362                 return SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__UNKNOWN_ERROR;
363         }
364     }
365 
getLoggingPackageName()366     private String getLoggingPackageName() {
367         if (mPackageName != null && !mPackageName.isEmpty()) {
368             try {
369                 final ApplicationInfo applicationInfo = mContext.getPackageManager()
370                         .getApplicationInfo(mPackageName, /* default flag */ 0);
371                 if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
372                         || (applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
373                     return mPackageName;
374                 }
375             } catch (Exception ex) {
376                 Log.e(TAG, mPackageName + " is invalid.");
377             }
378         }
379 
380         return "";
381     }
382 }
383