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