/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.slices; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_SLICES_URI; import android.content.ContentProvider; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; import android.net.Uri; import android.util.Log; import androidx.annotation.VisibleForTesting; import com.android.settingslib.SliceBroadcastRelay; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * This helper is to handle the broadcasts of volume slices */ public class VolumeSliceHelper { private static final String TAG = "VolumeSliceHelper"; @VisibleForTesting static Map sRegisteredUri = new ConcurrentHashMap<>(); @VisibleForTesting static IntentFilter sIntentFilter; static void registerIntentToUri(Context context, IntentFilter intentFilter, Uri sliceUri, int audioStream) { Log.d(TAG, "Registering uri for broadcast relay: " + sliceUri); synchronized (sRegisteredUri) { if (sRegisteredUri.isEmpty()) { SliceBroadcastRelay.registerReceiver(context, VOLUME_SLICES_URI, VolumeSliceRelayReceiver.class, intentFilter); sIntentFilter = intentFilter; } sRegisteredUri.put(sliceUri, audioStream); } } static boolean unregisterUri(Context context, Uri sliceUri) { if (!sRegisteredUri.containsKey(sliceUri)) { return false; } Log.d(TAG, "Unregistering uri broadcast relay: " + sliceUri); synchronized (sRegisteredUri) { sRegisteredUri.remove(sliceUri); if (sRegisteredUri.isEmpty()) { sIntentFilter = null; SliceBroadcastRelay.unregisterReceivers(context, VOLUME_SLICES_URI); } } return true; } static void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (sIntentFilter == null || action == null || !sIntentFilter.hasAction(action)) { return; } final String uriString = intent.getStringExtra(SliceBroadcastRelay.EXTRA_URI); if (uriString == null) { return; } final Uri uri = Uri.parse(uriString); if (!VOLUME_SLICES_URI.equals(ContentProvider.getUriWithoutUserId(uri))) { Log.w(TAG, "Invalid uri: " + uriString); return; } if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) { handleVolumeChanged(context, intent); } else if (AudioManager.STREAM_MUTE_CHANGED_ACTION.equals(action)) { handleMuteChanged(context, intent); } else if (AudioManager.STREAM_DEVICES_CHANGED_ACTION.equals(action)) { handleStreamChanged(context, intent); } else { notifyAllStreamsChanged(context); } } private static void handleVolumeChanged(Context context, Intent intent) { final int vol = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1); final int prevVol = intent.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1); if (vol != prevVol) { handleStreamChanged(context, intent); } } /** * When mute is changed, notifyChange on relevant Volume Slice ContentResolvers to mark them * as needing update. * * In addition to the matching stream, we always notifyChange for the Notification stream * when Ring events are issued. This is to make sure that Notification always gets updated * for RingerMode changes, even if Notification's volume is zero and therefore it would not * get its own AudioManager.VOLUME_CHANGED_ACTION. */ private static void handleMuteChanged(Context context, Intent intent) { final int inputType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); handleStreamChanged(context, inputType); if (inputType == AudioManager.STREAM_RING) { handleStreamChanged(context, AudioManager.STREAM_NOTIFICATION); } } private static void handleStreamChanged(Context context, Intent intent) { final int inputType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); handleStreamChanged(context, inputType); } private static void handleStreamChanged(Context context, int inputType) { for (Map.Entry entry : sRegisteredUri.entrySet()) { if (entry.getValue() == inputType) { context.getContentResolver().notifyChange(entry.getKey(), null /* observer */); if (inputType != AudioManager.STREAM_RING) { // Two URIs are mapped to ring break; } } } } private static void notifyAllStreamsChanged(Context context) { sRegisteredUri.keySet().forEach(uri -> { context.getContentResolver().notifyChange(uri, null /* observer */); }); } }