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.session.MediaController;
27 import android.media.session.MediaSessionManager;
28 import android.media.session.PlaybackState;
29 import android.net.Uri;
30 import android.text.TextUtils;
31 import android.util.Log;
32 
33 import androidx.annotation.Nullable;
34 
35 import com.android.settings.bluetooth.Utils;
36 import com.android.settings.slices.SliceBackgroundWorker;
37 import com.android.settingslib.bluetooth.BluetoothCallback;
38 import com.android.settingslib.bluetooth.LocalBluetoothManager;
39 import com.android.settingslib.media.LocalMediaManager;
40 import com.android.settingslib.media.MediaDevice;
41 import com.android.settingslib.utils.ThreadUtils;
42 
43 import com.google.common.annotations.VisibleForTesting;
44 
45 import java.util.Collection;
46 import java.util.List;
47 import java.util.concurrent.CopyOnWriteArrayList;
48 
49 /**
50  * Listener for background change from {@code BluetoothCallback} to update media output indicator.
51  */
52 public class MediaOutputIndicatorWorker extends SliceBackgroundWorker implements BluetoothCallback,
53         LocalMediaManager.DeviceCallback {
54 
55     private static final String TAG = "MediaOutputIndWorker";
56 
57     private final DevicesChangedBroadcastReceiver mReceiver;
58     private final Context mContext;
59     private final Collection<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
60 
61     private LocalBluetoothManager mLocalBluetoothManager;
62     private String mPackageName;
63 
64     @VisibleForTesting
65     LocalMediaManager mLocalMediaManager;
66 
MediaOutputIndicatorWorker(Context context, Uri uri)67     public MediaOutputIndicatorWorker(Context context, Uri uri) {
68         super(context, uri);
69         mReceiver = new DevicesChangedBroadcastReceiver();
70         mContext = context;
71     }
72 
73     @Override
onSlicePinned()74     protected void onSlicePinned() {
75         mMediaDevices.clear();
76         mLocalBluetoothManager = Utils.getLocalBtManager(getContext());
77         if (mLocalBluetoothManager == null) {
78             Log.e(TAG, "Bluetooth is not supported on this device");
79             return;
80         }
81         final IntentFilter intentFilter = new IntentFilter(STREAM_DEVICES_CHANGED_ACTION);
82         mContext.registerReceiver(mReceiver, intentFilter);
83         mLocalBluetoothManager.getEventManager().registerCallback(this);
84 
85         ThreadUtils.postOnBackgroundThread(() -> {
86             final MediaController controller = getActiveLocalMediaController();
87             if (controller == null) {
88                 mPackageName = null;
89             } else {
90                 mPackageName = controller.getPackageName();
91             }
92             if (mLocalMediaManager == null || !TextUtils.equals(mPackageName,
93                     mLocalMediaManager.getPackageName())) {
94                 mLocalMediaManager = new LocalMediaManager(mContext, mPackageName,
95                         null /* notification */);
96             }
97             mLocalMediaManager.registerCallback(this);
98             mLocalMediaManager.startScan();
99         });
100     }
101 
102     @Override
onSliceUnpinned()103     protected void onSliceUnpinned() {
104         if (mLocalMediaManager != null) {
105             mLocalMediaManager.unregisterCallback(this);
106             mLocalMediaManager.stopScan();
107         }
108 
109         if (mLocalBluetoothManager == null) {
110             Log.e(TAG, "Bluetooth is not supported on this device");
111             return;
112         }
113         mLocalBluetoothManager.getEventManager().unregisterCallback(this);
114         mContext.unregisterReceiver(mReceiver);
115     }
116 
117     @Override
close()118     public void close() {
119         mLocalBluetoothManager = null;
120         mLocalMediaManager = null;
121     }
122 
123     @Override
onAudioModeChanged()124     public void onAudioModeChanged() {
125         notifySliceChange();
126     }
127 
128     @Nullable
getActiveLocalMediaController()129     MediaController getActiveLocalMediaController() {
130         final MediaSessionManager mMediaSessionManager = mContext.getSystemService(
131                 MediaSessionManager.class);
132 
133         for (MediaController controller : mMediaSessionManager.getActiveSessions(null)) {
134             final MediaController.PlaybackInfo pi = controller.getPlaybackInfo();
135             if (pi == null) {
136                 return null;
137             }
138             final PlaybackState playbackState = controller.getPlaybackState();
139             if (playbackState == null) {
140                 return null;
141             }
142             if (pi.getPlaybackType() == MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL
143                     && playbackState.getState() == PlaybackState.STATE_PLAYING) {
144                 return controller;
145             }
146         }
147         return null;
148     }
149 
150     @Override
onDeviceListUpdate(List<MediaDevice> devices)151     public void onDeviceListUpdate(List<MediaDevice> devices) {
152         buildMediaDevices(devices);
153         notifySliceChange();
154     }
155 
buildMediaDevices(List<MediaDevice> devices)156     private void buildMediaDevices(List<MediaDevice> devices) {
157         mMediaDevices.clear();
158         mMediaDevices.addAll(devices);
159     }
160 
161     @Override
onSelectedDeviceStateChanged(MediaDevice device, int state)162     public void onSelectedDeviceStateChanged(MediaDevice device, int state) {
163         notifySliceChange();
164     }
165 
166     @Override
onDeviceAttributesChanged()167     public void onDeviceAttributesChanged() {
168         notifySliceChange();
169     }
170 
getMediaDevices()171     Collection<MediaDevice> getMediaDevices() {
172         return mMediaDevices;
173     }
174 
getCurrentConnectedMediaDevice()175     MediaDevice getCurrentConnectedMediaDevice() {
176         return mLocalMediaManager.getCurrentConnectedDevice();
177     }
178 
getPackageName()179     String getPackageName() {
180         return mPackageName;
181     }
182 
183     private class DevicesChangedBroadcastReceiver extends BroadcastReceiver {
184         @Override
onReceive(Context context, Intent intent)185         public void onReceive(Context context, Intent intent) {
186             final String action = intent.getAction();
187             if (TextUtils.equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION, action)) {
188                 notifySliceChange();
189             }
190         }
191     }
192 }
193