1 /*
2  * Copyright (C) 2020 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.slices;
18 
19 import static com.android.settings.slices.CustomSliceRegistry.VOLUME_SLICES_URI;
20 
21 import android.content.ContentProvider;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.media.AudioManager;
26 import android.net.Uri;
27 import android.util.Log;
28 
29 import androidx.annotation.VisibleForTesting;
30 
31 import com.android.settingslib.SliceBroadcastRelay;
32 
33 import java.util.Map;
34 import java.util.concurrent.ConcurrentHashMap;
35 
36 /**
37  * This helper is to handle the broadcasts of volume slices
38  */
39 public class VolumeSliceHelper {
40 
41     private static final String TAG = "VolumeSliceHelper";
42 
43     @VisibleForTesting
44     static Map<Uri, Integer> sRegisteredUri = new ConcurrentHashMap<>();
45     @VisibleForTesting
46     static IntentFilter sIntentFilter;
47 
registerIntentToUri(Context context, IntentFilter intentFilter, Uri sliceUri, int audioStream)48     static void registerIntentToUri(Context context, IntentFilter intentFilter, Uri sliceUri,
49             int audioStream) {
50         Log.d(TAG, "Registering uri for broadcast relay: " + sliceUri);
51         synchronized (sRegisteredUri) {
52             if (sRegisteredUri.isEmpty()) {
53                 SliceBroadcastRelay.registerReceiver(context, VOLUME_SLICES_URI,
54                         VolumeSliceRelayReceiver.class, intentFilter);
55                 sIntentFilter = intentFilter;
56             }
57             sRegisteredUri.put(sliceUri, audioStream);
58         }
59     }
60 
unregisterUri(Context context, Uri sliceUri)61     static boolean unregisterUri(Context context, Uri sliceUri) {
62         if (!sRegisteredUri.containsKey(sliceUri)) {
63             return false;
64         }
65 
66         Log.d(TAG, "Unregistering uri broadcast relay: " + sliceUri);
67         synchronized (sRegisteredUri) {
68             sRegisteredUri.remove(sliceUri);
69             if (sRegisteredUri.isEmpty()) {
70                 sIntentFilter = null;
71                 SliceBroadcastRelay.unregisterReceivers(context, VOLUME_SLICES_URI);
72             }
73         }
74         return true;
75     }
76 
onReceive(Context context, Intent intent)77     static void onReceive(Context context, Intent intent) {
78         final String action = intent.getAction();
79         if (sIntentFilter == null || action == null || !sIntentFilter.hasAction(action)) {
80             return;
81         }
82 
83         final String uriString = intent.getStringExtra(SliceBroadcastRelay.EXTRA_URI);
84         if (uriString == null) {
85             return;
86         }
87 
88         final Uri uri = Uri.parse(uriString);
89         if (!VOLUME_SLICES_URI.equals(ContentProvider.getUriWithoutUserId(uri))) {
90             Log.w(TAG, "Invalid uri: " + uriString);
91             return;
92         }
93 
94         if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) {
95             handleVolumeChanged(context, intent);
96         } else if (AudioManager.STREAM_MUTE_CHANGED_ACTION.equals(action)) {
97             handleMuteChanged(context, intent);
98         } else if (AudioManager.STREAM_DEVICES_CHANGED_ACTION.equals(action)) {
99             handleStreamChanged(context, intent);
100         } else {
101             notifyAllStreamsChanged(context);
102         }
103     }
104 
handleVolumeChanged(Context context, Intent intent)105     private static void handleVolumeChanged(Context context, Intent intent) {
106         final int vol = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
107         final int prevVol = intent.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
108         if (vol != prevVol) {
109             handleStreamChanged(context, intent);
110         }
111     }
112 
113     /**
114      *  When mute is changed, notifyChange on relevant Volume Slice ContentResolvers to mark them
115      *  as needing update.
116      *
117      * In addition to the matching stream, we always notifyChange for the Notification stream
118      * when Ring events are issued. This is to make sure that Notification always gets updated
119      * for RingerMode changes, even if Notification's volume is zero and therefore it would not
120      * get its own AudioManager.VOLUME_CHANGED_ACTION.
121      */
handleMuteChanged(Context context, Intent intent)122     private static void handleMuteChanged(Context context, Intent intent) {
123         final int inputType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
124         handleStreamChanged(context, inputType);
125         if (inputType == AudioManager.STREAM_RING) {
126             handleStreamChanged(context, AudioManager.STREAM_NOTIFICATION);
127         }
128     }
129 
handleStreamChanged(Context context, Intent intent)130     private static void handleStreamChanged(Context context, Intent intent) {
131         final int inputType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
132         handleStreamChanged(context, inputType);
133     }
134 
handleStreamChanged(Context context, int inputType)135     private static void handleStreamChanged(Context context, int inputType) {
136         for (Map.Entry<Uri, Integer> entry : sRegisteredUri.entrySet()) {
137             if (entry.getValue() == inputType) {
138                 context.getContentResolver().notifyChange(entry.getKey(), null /* observer */);
139                 if (inputType != AudioManager.STREAM_RING) { // Two URIs are mapped to ring
140                     break;
141                 }
142             }
143         }
144     }
145 
notifyAllStreamsChanged(Context context)146     private static void notifyAllStreamsChanged(Context context) {
147         sRegisteredUri.keySet().forEach(uri -> {
148             context.getContentResolver().notifyChange(uri, null /* observer */);
149         });
150     }
151 }
152