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.RoutingSessionInfo;
27 import android.net.Uri;
28 import android.os.UserHandle;
29 import android.os.UserManager;
30 import android.text.TextUtils;
31 import android.util.Log;
32 
33 import androidx.annotation.VisibleForTesting;
34 
35 import com.android.settings.slices.SliceBackgroundWorker;
36 import com.android.settingslib.RestrictedLockUtilsInternal;
37 import com.android.settingslib.Utils;
38 import com.android.settingslib.media.LocalMediaManager;
39 import com.android.settingslib.media.MediaDevice;
40 import com.android.settingslib.utils.ThreadUtils;
41 
42 import java.util.ArrayList;
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 
63     private boolean mIsTouched;
64     private MediaDevice mTopDevice;
65 
66     @VisibleForTesting
67     LocalMediaManager mLocalMediaManager;
68 
MediaDeviceUpdateWorker(Context context, Uri uri)69     public MediaDeviceUpdateWorker(Context context, Uri uri) {
70         super(context, uri);
71         mContext = context;
72         mPackageName = uri.getQueryParameter(MEDIA_PACKAGE_NAME);
73         mReceiver = new DevicesChangedBroadcastReceiver();
74     }
75 
76     @Override
onSlicePinned()77     protected void onSlicePinned() {
78         mMediaDevices.clear();
79         mIsTouched = false;
80         if (mLocalMediaManager == null || !TextUtils.equals(mPackageName,
81                 mLocalMediaManager.getPackageName())) {
82             mLocalMediaManager = new LocalMediaManager(mContext, mPackageName, null);
83         }
84 
85         mLocalMediaManager.registerCallback(this);
86         final IntentFilter intentFilter = new IntentFilter(STREAM_DEVICES_CHANGED_ACTION);
87         mContext.registerReceiver(mReceiver, intentFilter);
88         mLocalMediaManager.startScan();
89     }
90 
91     @Override
onSliceUnpinned()92     protected void onSliceUnpinned() {
93         mLocalMediaManager.unregisterCallback(this);
94         mContext.unregisterReceiver(mReceiver);
95         mLocalMediaManager.stopScan();
96     }
97 
98     @Override
close()99     public void close() {
100         mLocalMediaManager = null;
101     }
102 
103     @Override
onDeviceListUpdate(List<MediaDevice> devices)104     public void onDeviceListUpdate(List<MediaDevice> devices) {
105         buildMediaDevices(devices);
106         notifySliceChange();
107     }
108 
buildMediaDevices(List<MediaDevice> devices)109     private void buildMediaDevices(List<MediaDevice> devices) {
110         mMediaDevices.clear();
111         mMediaDevices.addAll(devices);
112     }
113 
114     @Override
onSelectedDeviceStateChanged(MediaDevice device, int state)115     public void onSelectedDeviceStateChanged(MediaDevice device, int state) {
116         notifySliceChange();
117     }
118 
119     @Override
onDeviceAttributesChanged()120     public void onDeviceAttributesChanged() {
121         notifySliceChange();
122     }
123 
124     @Override
onRequestFailed(int reason)125     public void onRequestFailed(int reason) {
126         notifySliceChange();
127     }
128 
getMediaDevices()129     public Collection<MediaDevice> getMediaDevices() {
130         return mMediaDevices;
131     }
132 
connectDevice(MediaDevice device)133     public void connectDevice(MediaDevice device) {
134         ThreadUtils.postOnBackgroundThread(() -> {
135             if (mLocalMediaManager.connectDevice(device)) {
136                 ThreadUtils.postOnMainThread(() -> {
137                     notifySliceChange();
138                 });
139             }
140         });
141     }
142 
getMediaDeviceById(String id)143     public MediaDevice getMediaDeviceById(String id) {
144         return mLocalMediaManager.getMediaDeviceById(new ArrayList<>(mMediaDevices), id);
145     }
146 
getCurrentConnectedMediaDevice()147     public MediaDevice getCurrentConnectedMediaDevice() {
148         return mLocalMediaManager.getCurrentConnectedDevice();
149     }
150 
setIsTouched(boolean isTouched)151     void setIsTouched(boolean isTouched) {
152         mIsTouched = isTouched;
153     }
154 
getIsTouched()155     boolean getIsTouched() {
156         return mIsTouched;
157     }
158 
setTopDevice(MediaDevice device)159     void setTopDevice(MediaDevice device) {
160         mTopDevice = device;
161     }
162 
getTopDevice()163     MediaDevice getTopDevice() {
164         return getMediaDeviceById(mTopDevice.getId());
165     }
166 
addDeviceToPlayMedia(MediaDevice device)167     boolean addDeviceToPlayMedia(MediaDevice device) {
168         return mLocalMediaManager.addDeviceToPlayMedia(device);
169     }
170 
removeDeviceFromPlayMedia(MediaDevice device)171     boolean removeDeviceFromPlayMedia(MediaDevice device) {
172         return mLocalMediaManager.removeDeviceFromPlayMedia(device);
173     }
174 
getSelectableMediaDevice()175     List<MediaDevice> getSelectableMediaDevice() {
176         return mLocalMediaManager.getSelectableMediaDevice();
177     }
178 
getSelectedMediaDevice()179     List<MediaDevice> getSelectedMediaDevice() {
180         return mLocalMediaManager.getSelectedMediaDevice();
181     }
182 
getDeselectableMediaDevice()183     List<MediaDevice> getDeselectableMediaDevice() {
184         return mLocalMediaManager.getDeselectableMediaDevice();
185     }
186 
isDeviceIncluded(Collection<MediaDevice> deviceCollection, MediaDevice targetDevice)187     boolean isDeviceIncluded(Collection<MediaDevice> deviceCollection, MediaDevice targetDevice) {
188         for (MediaDevice device : deviceCollection) {
189             if (TextUtils.equals(device.getId(), targetDevice.getId())) {
190                 return true;
191             }
192         }
193         return false;
194     }
195 
adjustSessionVolume(String sessionId, int volume)196     void adjustSessionVolume(String sessionId, int volume) {
197         mLocalMediaManager.adjustSessionVolume(sessionId, volume);
198     }
199 
adjustSessionVolume(int volume)200     void adjustSessionVolume(int volume) {
201         mLocalMediaManager.adjustSessionVolume(volume);
202     }
203 
getSessionVolumeMax()204     int getSessionVolumeMax() {
205         return mLocalMediaManager.getSessionVolumeMax();
206     }
207 
getSessionVolume()208     int getSessionVolume() {
209         return mLocalMediaManager.getSessionVolume();
210     }
211 
getSessionName()212     CharSequence getSessionName() {
213         return mLocalMediaManager.getSessionName();
214     }
215 
getActiveRemoteMediaDevice()216     List<RoutingSessionInfo> getActiveRemoteMediaDevice() {
217         final List<RoutingSessionInfo> sessionInfos = new ArrayList<>();
218         for (RoutingSessionInfo info : mLocalMediaManager.getActiveMediaSession()) {
219             if (!info.isSystemSession()) {
220                 if (DEBUG) {
221                     Log.d(TAG, "getActiveRemoteMediaDevice() info : " + info.toString()
222                             + ", package name : " + info.getClientPackageName());
223                 }
224                 sessionInfos.add(info);
225             }
226         }
227         return sessionInfos;
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             device.requestSetVolume(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         return mLocalMediaManager.shouldDisableMediaOutput(packageName);
260     }
261 
shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo)262     boolean shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo) {
263         return mLocalMediaManager.shouldEnableVolumeSeekBar(sessionInfo);
264     }
265 
266     private class DevicesChangedBroadcastReceiver extends BroadcastReceiver {
267         @Override
onReceive(Context context, Intent intent)268         public void onReceive(Context context, Intent intent) {
269             final String action = intent.getAction();
270             if (TextUtils.equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION, action)
271                     && Utils.isAudioModeOngoingCall(mContext)) {
272                 notifySliceChange();
273             }
274         }
275     }
276 }
277