diff --git a/src/com/android/settings/media/MediaDeviceUpdateWorker.java b/src/com/android/settings/media/MediaDeviceUpdateWorker.java index bce9c340c76..719d2d5641d 100644 --- a/src/com/android/settings/media/MediaDeviceUpdateWorker.java +++ b/src/com/android/settings/media/MediaDeviceUpdateWorker.java @@ -51,8 +51,8 @@ import java.util.concurrent.CopyOnWriteArrayList; public class MediaDeviceUpdateWorker extends SliceBackgroundWorker implements LocalMediaManager.DeviceCallback { - private final Context mContext; - private final Collection mMediaDevices = new CopyOnWriteArrayList<>(); + protected final Context mContext; + protected final Collection mMediaDevices = new CopyOnWriteArrayList<>(); private final DevicesChangedBroadcastReceiver mReceiver; private final String mPackageName; diff --git a/src/com/android/settings/media/MediaOutputSlice.java b/src/com/android/settings/media/MediaOutputSlice.java index 773013e4d9e..4e54d7ba35f 100644 --- a/src/com/android/settings/media/MediaOutputSlice.java +++ b/src/com/android/settings/media/MediaOutputSlice.java @@ -375,7 +375,7 @@ public class MediaOutputSlice implements CustomSliceable { @Override public Class getBackgroundWorkerClass() { - return MediaDeviceUpdateWorker.class; + return MediaOutputSliceWorker.class; } private boolean isVisible() { diff --git a/src/com/android/settings/media/MediaOutputSliceWorker.java b/src/com/android/settings/media/MediaOutputSliceWorker.java new file mode 100644 index 00000000000..357b234acbb --- /dev/null +++ b/src/com/android/settings/media/MediaOutputSliceWorker.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.media; + +import static android.media.MediaRoute2ProviderService.REASON_INVALID_COMMAND; +import static android.media.MediaRoute2ProviderService.REASON_NETWORK_ERROR; +import static android.media.MediaRoute2ProviderService.REASON_REJECTED; +import static android.media.MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE; +import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.net.Uri; +import android.util.Log; + +import com.android.settings.core.instrumentation.SettingsStatsLog; +import com.android.settingslib.media.MediaDevice; + +/** + * SliceBackgroundWorker for the MediaOutputSlice class. + * It inherits from MediaDeviceUpdateWorker and add metrics logging. + */ +public class MediaOutputSliceWorker extends MediaDeviceUpdateWorker { + + private static final String TAG = "MediaOutputSliceWorker"; + private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); + + private MediaDevice mSourceDevice, mTargetDevice; + private int mWiredDeviceCount; + private int mConnectedBluetoothDeviceCount; + private int mRemoteDeviceCount; + private int mAppliedDeviceCountWithinRemoteGroup; + + public MediaOutputSliceWorker(Context context, Uri uri) { + super(context, uri); + } + + @Override + public void connectDevice(MediaDevice device) { + mSourceDevice = mLocalMediaManager.getCurrentConnectedDevice(); + mTargetDevice = device; + + if (DBG) { + Log.d(TAG, "connectDevice -" + + " source:" + mSourceDevice.toString() + + " target:" + mTargetDevice.toString()); + } + + super.connectDevice(device); + } + + private int getLoggingDeviceType(MediaDevice device, boolean isSourceDevice) { + switch (device.getDeviceType()) { + case MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE: + return isSourceDevice + ? SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__BUILTIN_SPEAKER + : SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__BUILTIN_SPEAKER; + case MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE: + return isSourceDevice + ? SettingsStatsLog + .MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__WIRED_3POINT5_MM_AUDIO + : SettingsStatsLog + .MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__WIRED_3POINT5_MM_AUDIO; + case MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE: + return isSourceDevice + ? SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__USB_C_AUDIO + : SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__USB_C_AUDIO; + case MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE: + return isSourceDevice + ? SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__BLUETOOTH + : SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__BLUETOOTH; + case MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE: + return isSourceDevice + ? SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__REMOTE_SINGLE + : SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__REMOTE_SINGLE; + case MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE: + return isSourceDevice + ? SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__REMOTE_GROUP + : SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__REMOTE_GROUP; + default: + return isSourceDevice + ? SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__UNKNOWN_TYPE + : SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__UNKNOWN_TYPE; + } + } + + private int getLoggingSwitchOpSubResult(int reason) { + switch (reason) { + case REASON_REJECTED: + return SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__REJECTED; + case REASON_NETWORK_ERROR: + return SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__NETWORK_ERROR; + case REASON_ROUTE_NOT_AVAILABLE: + return SettingsStatsLog + .MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__ROUTE_NOT_AVAILABLE; + case REASON_INVALID_COMMAND: + return SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__INVALID_COMMAND; + case REASON_UNKNOWN_ERROR: + default: + return SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__UNKNOWN_ERROR; + } + } + + private String getLoggingPackageName() { + final String packageName = getPackageName(); + if (packageName != null && !packageName.isEmpty()) { + try { + final ApplicationInfo applicationInfo = mContext.getPackageManager() + .getApplicationInfo(packageName, /* default flag */ 0); + if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 + || (applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { + return packageName; + } + } catch (Exception ex) { + Log.e(TAG, packageName + "is invalid."); + } + } + + return ""; + } + + private void updateLoggingDeviceCount() { + mWiredDeviceCount = mConnectedBluetoothDeviceCount = mRemoteDeviceCount = 0; + mAppliedDeviceCountWithinRemoteGroup = 0; + + for (MediaDevice mediaDevice : mMediaDevices) { + if (mediaDevice.isConnected()) { + switch (mediaDevice.getDeviceType()) { + case MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE: + case MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE: + mWiredDeviceCount++; + break; + case MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE: + mConnectedBluetoothDeviceCount++; + break; + case MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE: + case MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE: + mRemoteDeviceCount++; + break; + default: + } + } + } + + if (DBG) { + Log.d(TAG, "connected devices:" + " wired: " + mWiredDeviceCount + + " bluetooth: " + mConnectedBluetoothDeviceCount + + " remote: " + mRemoteDeviceCount); + } + } + + @Override + public void onSelectedDeviceStateChanged(MediaDevice device, int state) { + if (DBG) { + Log.d(TAG, "onSelectedDeviceStateChanged - " + device.toString()); + } + + updateLoggingDeviceCount(); + + SettingsStatsLog.write( + SettingsStatsLog.MEDIAOUTPUT_OP_SWITCH_REPORTED, + getLoggingDeviceType(mSourceDevice, true), + getLoggingDeviceType(mTargetDevice, false), + SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__RESULT__OK, + SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__NO_ERROR, + getLoggingPackageName(), + mWiredDeviceCount, + mConnectedBluetoothDeviceCount, + mRemoteDeviceCount, + mAppliedDeviceCountWithinRemoteGroup); + + super.onSelectedDeviceStateChanged(device, state); + } + + @Override + public void onRequestFailed(int reason) { + if (DBG) { + Log.e(TAG, "onRequestFailed - " + reason); + } + + updateLoggingDeviceCount(); + + SettingsStatsLog.write( + SettingsStatsLog.MEDIAOUTPUT_OP_SWITCH_REPORTED, + getLoggingDeviceType(mSourceDevice, true), + getLoggingDeviceType(mTargetDevice, false), + SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__RESULT__ERROR, + getLoggingSwitchOpSubResult(reason), + getLoggingPackageName(), + mWiredDeviceCount, + mConnectedBluetoothDeviceCount, + mRemoteDeviceCount, + mAppliedDeviceCountWithinRemoteGroup); + + super.onRequestFailed(reason); + } +}