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.net.Uri;
29 import android.text.TextUtils;
30 import android.util.Log;
31 
32 import androidx.annotation.Nullable;
33 
34 import com.android.settings.bluetooth.Utils;
35 import com.android.settings.slices.SliceBackgroundWorker;
36 import com.android.settingslib.bluetooth.BluetoothCallback;
37 import com.android.settingslib.bluetooth.LocalBluetoothManager;
38 import com.android.settingslib.media.LocalMediaManager;
39 import com.android.settingslib.media.MediaDevice;
40 import com.android.settingslib.utils.ThreadUtils;
41 
42 import com.google.common.annotations.VisibleForTesting;
43 
44 import java.util.Collection;
45 import java.util.List;
46 import java.util.concurrent.CopyOnWriteArrayList;
47 
48 /**
49  * Listener for background change from {@code BluetoothCallback} to update media output indicator.
50  */
51 public class MediaOutputIndicatorWorker extends SliceBackgroundWorker implements BluetoothCallback,
52         LocalMediaManager.DeviceCallback {
53 
54     private static final String TAG = "MediaOutputIndWorker";
55     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
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         return MediaOutputUtils.getActiveLocalMediaController(mContext.getSystemService(
131                 MediaSessionManager.class));
132     }
133 
134     @Override
onDeviceListUpdate(List<MediaDevice> devices)135     public void onDeviceListUpdate(List<MediaDevice> devices) {
136         buildMediaDevices(devices);
137         notifySliceChange();
138     }
139 
buildMediaDevices(List<MediaDevice> devices)140     private void buildMediaDevices(List<MediaDevice> devices) {
141         mMediaDevices.clear();
142         mMediaDevices.addAll(devices);
143     }
144 
145     @Override
onSelectedDeviceStateChanged(MediaDevice device, int state)146     public void onSelectedDeviceStateChanged(MediaDevice device, int state) {
147         notifySliceChange();
148     }
149 
150     @Override
onDeviceAttributesChanged()151     public void onDeviceAttributesChanged() {
152         notifySliceChange();
153     }
154 
getMediaDevices()155     Collection<MediaDevice> getMediaDevices() {
156         return mMediaDevices;
157     }
158 
getCurrentConnectedMediaDevice()159     MediaDevice getCurrentConnectedMediaDevice() {
160         return mLocalMediaManager.getCurrentConnectedDevice();
161     }
162 
getPackageName()163     String getPackageName() {
164         return mPackageName;
165     }
166 
167     private class DevicesChangedBroadcastReceiver extends BroadcastReceiver {
168         @Override
onReceive(Context context, Intent intent)169         public void onReceive(Context context, Intent intent) {
170             final String action = intent.getAction();
171             if (TextUtils.equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION, action)) {
172                 notifySliceChange();
173             }
174         }
175     }
176 }
177