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