1 /* 2 * Copyright (C) 2019 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.settings.media; 18 19 import static android.media.AudioManager.STREAM_DEVICES_CHANGED_ACTION; 20 21 import android.content.BroadcastReceiver; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.media.AudioManager; 26 import android.media.MediaRouter2Manager; 27 import android.media.RoutingSessionInfo; 28 import android.net.Uri; 29 import android.os.UserHandle; 30 import android.os.UserManager; 31 import android.text.TextUtils; 32 import android.util.Log; 33 34 import androidx.annotation.VisibleForTesting; 35 36 import com.android.settings.slices.SliceBackgroundWorker; 37 import com.android.settingslib.RestrictedLockUtilsInternal; 38 import com.android.settingslib.Utils; 39 import com.android.settingslib.media.LocalMediaManager; 40 import com.android.settingslib.media.MediaDevice; 41 import com.android.settingslib.utils.ThreadUtils; 42 43 import java.util.Collection; 44 import java.util.List; 45 import java.util.concurrent.CopyOnWriteArrayList; 46 47 /** 48 * SliceBackgroundWorker for get MediaDevice list and handle MediaDevice state change event. 49 */ 50 public class MediaDeviceUpdateWorker extends SliceBackgroundWorker 51 implements LocalMediaManager.DeviceCallback { 52 53 private static final String TAG = "MediaDeviceUpdateWorker"; 54 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 55 56 public static final String MEDIA_PACKAGE_NAME = "media_package_name"; 57 58 protected final Context mContext; 59 protected final Collection<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>(); 60 private final DevicesChangedBroadcastReceiver mReceiver; 61 private final String mPackageName; 62 @VisibleForTesting 63 MediaRouter2Manager mManager; 64 65 private boolean mIsTouched; 66 private MediaDevice mTopDevice; 67 68 @VisibleForTesting 69 LocalMediaManager mLocalMediaManager; 70 MediaDeviceUpdateWorker(Context context, Uri uri)71 public MediaDeviceUpdateWorker(Context context, Uri uri) { 72 super(context, uri); 73 mContext = context; 74 mPackageName = uri.getQueryParameter(MEDIA_PACKAGE_NAME); 75 mReceiver = new DevicesChangedBroadcastReceiver(); 76 } 77 78 @Override onSlicePinned()79 protected void onSlicePinned() { 80 mMediaDevices.clear(); 81 mIsTouched = false; 82 if (mLocalMediaManager == null || !TextUtils.equals(mPackageName, 83 mLocalMediaManager.getPackageName())) { 84 mLocalMediaManager = new LocalMediaManager(mContext, mPackageName); 85 } 86 87 // Delaying initialization to allow mocking in Roboelectric tests. 88 if (mManager == null) { 89 mManager = MediaRouter2Manager.getInstance(mContext); 90 } 91 92 mLocalMediaManager.registerCallback(this); 93 final IntentFilter intentFilter = new IntentFilter(STREAM_DEVICES_CHANGED_ACTION); 94 mContext.registerReceiver(mReceiver, intentFilter); 95 mLocalMediaManager.startScan(); 96 } 97 98 @Override onSliceUnpinned()99 protected void onSliceUnpinned() { 100 mLocalMediaManager.unregisterCallback(this); 101 mContext.unregisterReceiver(mReceiver); 102 mLocalMediaManager.stopScan(); 103 } 104 105 @Override close()106 public void close() { 107 mLocalMediaManager = null; 108 } 109 110 @Override onDeviceListUpdate(List<MediaDevice> devices)111 public void onDeviceListUpdate(List<MediaDevice> devices) { 112 buildMediaDevices(devices); 113 notifySliceChange(); 114 } 115 buildMediaDevices(List<MediaDevice> devices)116 private void buildMediaDevices(List<MediaDevice> devices) { 117 mMediaDevices.clear(); 118 mMediaDevices.addAll(devices); 119 } 120 121 @Override onSelectedDeviceStateChanged(MediaDevice device, int state)122 public void onSelectedDeviceStateChanged(MediaDevice device, int state) { 123 notifySliceChange(); 124 } 125 126 @Override onDeviceAttributesChanged()127 public void onDeviceAttributesChanged() { 128 notifySliceChange(); 129 } 130 131 @Override onRequestFailed(int reason)132 public void onRequestFailed(int reason) { 133 notifySliceChange(); 134 } 135 getMediaDevices()136 public Collection<MediaDevice> getMediaDevices() { 137 return mMediaDevices; 138 } 139 connectDevice(MediaDevice device)140 public void connectDevice(MediaDevice device) { 141 ThreadUtils.postOnBackgroundThread(() -> { 142 if (mLocalMediaManager.connectDevice(device)) { 143 ThreadUtils.postOnMainThread(() -> { 144 notifySliceChange(); 145 }); 146 } 147 }); 148 } 149 getMediaDeviceById(String id)150 public MediaDevice getMediaDeviceById(String id) { 151 return mMediaDevices.stream() 152 .filter(it -> TextUtils.equals(it.getId(), id)) 153 .findFirst() 154 .orElse(null); 155 } 156 getCurrentConnectedMediaDevice()157 public MediaDevice getCurrentConnectedMediaDevice() { 158 return mLocalMediaManager.getCurrentConnectedDevice(); 159 } 160 setIsTouched(boolean isTouched)161 void setIsTouched(boolean isTouched) { 162 mIsTouched = isTouched; 163 } 164 getIsTouched()165 boolean getIsTouched() { 166 return mIsTouched; 167 } 168 setTopDevice(MediaDevice device)169 void setTopDevice(MediaDevice device) { 170 mTopDevice = device; 171 } 172 getTopDevice()173 MediaDevice getTopDevice() { 174 return getMediaDeviceById(mTopDevice.getId()); 175 } 176 addDeviceToPlayMedia(MediaDevice device)177 boolean addDeviceToPlayMedia(MediaDevice device) { 178 return mLocalMediaManager.addDeviceToPlayMedia(device); 179 } 180 removeDeviceFromPlayMedia(MediaDevice device)181 boolean removeDeviceFromPlayMedia(MediaDevice device) { 182 return mLocalMediaManager.removeDeviceFromPlayMedia(device); 183 } 184 getSelectableMediaDevice()185 List<MediaDevice> getSelectableMediaDevice() { 186 return mLocalMediaManager.getSelectableMediaDevice(); 187 } 188 getSelectedMediaDevice()189 List<MediaDevice> getSelectedMediaDevice() { 190 return mLocalMediaManager.getSelectedMediaDevice(); 191 } 192 getDeselectableMediaDevice()193 List<MediaDevice> getDeselectableMediaDevice() { 194 return mLocalMediaManager.getDeselectableMediaDevice(); 195 } 196 isDeviceIncluded(Collection<MediaDevice> deviceCollection, MediaDevice targetDevice)197 boolean isDeviceIncluded(Collection<MediaDevice> deviceCollection, MediaDevice targetDevice) { 198 for (MediaDevice device : deviceCollection) { 199 if (TextUtils.equals(device.getId(), targetDevice.getId())) { 200 return true; 201 } 202 } 203 return false; 204 } 205 adjustSessionVolume(String sessionId, int volume)206 void adjustSessionVolume(String sessionId, int volume) { 207 mLocalMediaManager.adjustSessionVolume(sessionId, volume); 208 } 209 adjustSessionVolume(int volume)210 void adjustSessionVolume(int volume) { 211 mLocalMediaManager.adjustSessionVolume(volume); 212 } 213 getSessionVolumeMax()214 int getSessionVolumeMax() { 215 return mLocalMediaManager.getSessionVolumeMax(); 216 } 217 getSessionVolume()218 int getSessionVolume() { 219 return mLocalMediaManager.getSessionVolume(); 220 } 221 getSessionName()222 CharSequence getSessionName() { 223 return mLocalMediaManager.getSessionName(); 224 } 225 getActiveRemoteMediaDevices()226 List<RoutingSessionInfo> getActiveRemoteMediaDevices() { 227 return mLocalMediaManager.getRemoteRoutingSessions(); 228 } 229 230 /** 231 * Request to set volume. 232 * 233 * @param device for the targeted device. 234 * @param volume for the new value. 235 * 236 */ adjustVolume(MediaDevice device, int volume)237 public void adjustVolume(MediaDevice device, int volume) { 238 ThreadUtils.postOnBackgroundThread(() -> { 239 mLocalMediaManager.adjustDeviceVolume(device, volume); 240 }); 241 } 242 getPackageName()243 String getPackageName() { 244 return mPackageName; 245 } 246 hasAdjustVolumeUserRestriction()247 boolean hasAdjustVolumeUserRestriction() { 248 if (RestrictedLockUtilsInternal.checkIfRestrictionEnforced( 249 mContext, UserManager.DISALLOW_ADJUST_VOLUME, UserHandle.myUserId()) != null) { 250 return true; 251 } 252 final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 253 return um.hasBaseUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME, 254 UserHandle.of(UserHandle.myUserId())); 255 256 } 257 shouldDisableMediaOutput(String packageName)258 boolean shouldDisableMediaOutput(String packageName) { 259 // TODO: b/291277292 - Remove references to MediaRouter2Manager and implement long-term 260 // solution in SettingsLib. 261 return mManager.getTransferableRoutes(packageName).isEmpty(); 262 } 263 shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo)264 boolean shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo) { 265 return mLocalMediaManager.shouldEnableVolumeSeekBar(sessionInfo); 266 } 267 268 private class DevicesChangedBroadcastReceiver extends BroadcastReceiver { 269 @Override onReceive(Context context, Intent intent)270 public void onReceive(Context context, Intent intent) { 271 final String action = intent.getAction(); 272 if (TextUtils.equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION, action) 273 && Utils.isAudioModeOngoingCall(mContext)) { 274 notifySliceChange(); 275 } 276 } 277 } 278 } 279