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      * @param source the current connected media device
60      * @param target the target media device for content switching to
61      */
updateOutputEndPoints(MediaDevice source, MediaDevice target)62     public void updateOutputEndPoints(MediaDevice source, MediaDevice target) {
63         mSourceDevice = source;
64         mTargetDevice = target;
65 
66         if (DEBUG) {
67             Log.d(TAG, "updateOutputEndPoints -"
68                     + " source:" + mSourceDevice.toString()
69                     + " target:" + mTargetDevice.toString());
70         }
71     }
72 
73     /**
74      * Do the metric logging of content switching success.
75      * @param selectedDeviceType string representation of the target media device
76      * @param deviceList media device list for device count updating
77      */
logOutputSuccess(String selectedDeviceType, List<MediaDevice> deviceList)78     public void logOutputSuccess(String selectedDeviceType, List<MediaDevice> deviceList) {
79         if (DEBUG) {
80             Log.d(TAG, "logOutputSuccess - selected device: " + selectedDeviceType);
81         }
82 
83         updateLoggingDeviceCount(deviceList);
84 
85         SysUiStatsLog.write(
86                 SysUiStatsLog.MEDIAOUTPUT_OP_SWITCH_REPORTED,
87                 getLoggingDeviceType(mSourceDevice, true),
88                 getLoggingDeviceType(mTargetDevice, false),
89                 SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__RESULT__OK,
90                 SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__NO_ERROR,
91                 getLoggingPackageName(),
92                 mWiredDeviceCount,
93                 mConnectedBluetoothDeviceCount,
94                 mRemoteDeviceCount,
95                 mAppliedDeviceCountWithinRemoteGroup);
96     }
97 
98     /**
99      * Do the metric logging of content switching failure.
100      * @param deviceList media device list for device count updating
101      * @param reason the reason of content switching failure
102      */
logOutputFailure(List<MediaDevice> deviceList, int reason)103     public void logOutputFailure(List<MediaDevice> deviceList, int reason) {
104         if (DEBUG) {
105             Log.e(TAG, "logRequestFailed - " + reason);
106         }
107 
108         updateLoggingDeviceCount(deviceList);
109 
110         SysUiStatsLog.write(
111                 SysUiStatsLog.MEDIAOUTPUT_OP_SWITCH_REPORTED,
112                 getLoggingDeviceType(mSourceDevice, true),
113                 getLoggingDeviceType(mTargetDevice, false),
114                 SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__RESULT__ERROR,
115                 getLoggingSwitchOpSubResult(reason),
116                 getLoggingPackageName(),
117                 mWiredDeviceCount,
118                 mConnectedBluetoothDeviceCount,
119                 mRemoteDeviceCount,
120                 mAppliedDeviceCountWithinRemoteGroup);
121     }
122 
updateLoggingDeviceCount(List<MediaDevice> deviceList)123     private void updateLoggingDeviceCount(List<MediaDevice> deviceList) {
124         mWiredDeviceCount = mConnectedBluetoothDeviceCount = mRemoteDeviceCount = 0;
125         mAppliedDeviceCountWithinRemoteGroup = 0;
126 
127         for (MediaDevice mediaDevice : deviceList) {
128             if (mediaDevice.isConnected()) {
129                 switch (mediaDevice.getDeviceType()) {
130                     case MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE:
131                     case MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE:
132                         mWiredDeviceCount++;
133                         break;
134                     case MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE:
135                         mConnectedBluetoothDeviceCount++;
136                         break;
137                     case MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE:
138                     case MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE:
139                         mRemoteDeviceCount++;
140                         break;
141                     default:
142                 }
143             }
144         }
145 
146         if (DEBUG) {
147             Log.d(TAG, "connected devices:" + " wired: " + mWiredDeviceCount
148                     + " bluetooth: " + mConnectedBluetoothDeviceCount
149                     + " remote: " + mRemoteDeviceCount);
150         }
151     }
152 
getLoggingDeviceType(MediaDevice device, boolean isSourceDevice)153     private int getLoggingDeviceType(MediaDevice device, boolean isSourceDevice) {
154         switch (device.getDeviceType()) {
155             case MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE:
156                 return isSourceDevice
157                         ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__BUILTIN_SPEAKER
158                         : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__BUILTIN_SPEAKER;
159             case MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE:
160                 return isSourceDevice
161                         ? SysUiStatsLog
162                         .MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__WIRED_3POINT5_MM_AUDIO
163                         : SysUiStatsLog
164                                 .MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__WIRED_3POINT5_MM_AUDIO;
165             case MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE:
166                 return isSourceDevice
167                         ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__USB_C_AUDIO
168                         : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__USB_C_AUDIO;
169             case MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE:
170                 return isSourceDevice
171                         ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__BLUETOOTH
172                         : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__BLUETOOTH;
173             case MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE:
174                 return isSourceDevice
175                         ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__REMOTE_SINGLE
176                         : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__REMOTE_SINGLE;
177             case MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE:
178                 return isSourceDevice
179                         ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__REMOTE_GROUP
180                         : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__REMOTE_GROUP;
181             default:
182                 return isSourceDevice
183                         ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__UNKNOWN_TYPE
184                         : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__UNKNOWN_TYPE;
185         }
186     }
187 
getLoggingSwitchOpSubResult(int reason)188     private int getLoggingSwitchOpSubResult(int reason) {
189         switch (reason) {
190             case REASON_REJECTED:
191                 return SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__REJECTED;
192             case REASON_NETWORK_ERROR:
193                 return SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__NETWORK_ERROR;
194             case REASON_ROUTE_NOT_AVAILABLE:
195                 return SysUiStatsLog
196                         .MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__ROUTE_NOT_AVAILABLE;
197             case REASON_INVALID_COMMAND:
198                 return SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__INVALID_COMMAND;
199             case REASON_UNKNOWN_ERROR:
200             default:
201                 return SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__UNKNOWN_ERROR;
202         }
203     }
204 
getLoggingPackageName()205     private String getLoggingPackageName() {
206         if (mPackageName != null && !mPackageName.isEmpty()) {
207             try {
208                 final ApplicationInfo applicationInfo = mContext.getPackageManager()
209                         .getApplicationInfo(mPackageName, /* default flag */ 0);
210                 if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
211                         || (applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
212                     return mPackageName;
213                 }
214             } catch (Exception ex) {
215                 Log.e(TAG, mPackageName + " is invalid.");
216             }
217         }
218 
219         return "";
220     }
221 }
222