Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions core/java/android/provider/Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -6990,6 +6990,12 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean
*/
public static final String SCREENSHOT_SHUTTER_SOUND = "screenshot_shutter_sound";

/**
* Show app volume rows in volume panel
* @hide
*/
public static final String SHOW_APP_VOLUME = "show_app_volume";

/**
* Keys we no longer back up under the current schema, but want to continue to
* process when restoring historical backup datasets.
Expand Down Expand Up @@ -22715,6 +22721,12 @@ private Panel() {
@SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_VOLUME =
"android.settings.panel.action.VOLUME";

/**
* @hide
*/
public static final String ACTION_APP_VOLUME =
"android.settings.panel.action.APP_VOLUME";
}

/**
Expand Down
98 changes: 98 additions & 0 deletions core/jni/android_media_AudioSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include <binder/IBinder.h>
#include <jni.h>
#include <media/AidlConversion.h>
#include <media/AppVolume.h>
#include <media/AudioContainers.h>
#include <media/AudioPolicy.h>
#include <media/AudioSystem.h>
Expand Down Expand Up @@ -95,6 +96,9 @@ static jmethodID gIntegerCstor;
static jclass gMapClass;
static jmethodID gMapPut;

static jclass gAppVolumeClass;
static jmethodID gAppVolumeCstor;

static jclass gAudioHandleClass;
static jmethodID gAudioHandleCstor;
static struct {
Expand Down Expand Up @@ -962,6 +966,88 @@ android_media_AudioSystem_getMasterBalance(JNIEnv *env, jobject thiz)
return balance;
}

static jint
android_media_AudioSystem_setAppVolume(JNIEnv *env, jobject thiz, jstring packageName, jfloat value)
{
const jchar* c_packageName = env->GetStringCritical(packageName, 0);
String8 package8 = String8(reinterpret_cast<const char16_t*>(c_packageName), env->GetStringLength(packageName));
env->ReleaseStringCritical(packageName, c_packageName);
return (jint) check_AudioSystem_Command(AudioSystem::setAppVolume(package8, value));
}

static jint
android_media_AudioSystem_setAppMute(JNIEnv *env, jobject thiz, jstring packageName, jboolean mute)
{
const jchar* c_packageName = env->GetStringCritical(packageName, 0);
String8 package8 = String8(reinterpret_cast<const char16_t*>(c_packageName), env->GetStringLength(packageName));
env->ReleaseStringCritical(packageName, c_packageName);
return (jint) check_AudioSystem_Command(AudioSystem::setAppMute(package8, mute));
}

jint convertAppVolumeFromNative(JNIEnv *env, jobject *jAppVolume, const media::AppVolume *AppVolume)
{
jint jStatus = (jint)AUDIO_JAVA_SUCCESS;
jstring jPackageName;
jfloat jVolume;
jboolean jMute;
jboolean jActive;

if (AppVolume == NULL || jAppVolume == NULL) {
jStatus = (jint)AUDIO_JAVA_ERROR;
goto exit;
}

jPackageName = env->NewStringUTF(AppVolume->packageName);
jVolume = AppVolume->volume;
jMute = AppVolume->muted;
jActive = AppVolume->active;

*jAppVolume = env->NewObject(gAppVolumeClass, gAppVolumeCstor,
jPackageName, jMute, jVolume, jActive);

env->DeleteLocalRef(jPackageName);
exit:
return jStatus;
}

static jint
android_media_AudioSystem_listAppVolumes(JNIEnv *env, jobject clazz, jobject jVolumes)
{
ALOGV("listAppVolumes");

if (jVolumes == NULL) {
ALOGE("listAppVolumes NULL AppVolume ArrayList");
return (jint)AUDIO_JAVA_BAD_VALUE;
}
if (!env->IsInstanceOf(jVolumes, gArrayListClass)) {
ALOGE("listAppVolumes not an arraylist");
return (jint)AUDIO_JAVA_BAD_VALUE;
}

std::vector<media::AppVolume> volumes;

jint jStatus = (jint)AUDIO_JAVA_SUCCESS;
status_t status = AudioSystem::listAppVolumes(&volumes);

if (status != NO_ERROR) {
ALOGE("AudioSystem::listAppVolumes error %d", status);
jStatus = nativeToJavaStatus(status);
return jStatus;
}

for (size_t i = 0; i < volumes.size(); i++) {
jobject jAppVolume;
jStatus = convertAppVolumeFromNative(env, &jAppVolume, &volumes[i]);
if (jStatus != AUDIO_JAVA_SUCCESS) {
return jStatus;
}
env->CallBooleanMethod(jVolumes, gArrayListMethods.add, jAppVolume);
env->DeleteLocalRef(jAppVolume);
}

return jStatus;
}

static jint
android_media_AudioSystem_getPrimaryOutputSamplingRate(JNIEnv *env, jobject clazz)
{
Expand Down Expand Up @@ -3697,6 +3783,13 @@ static const JNINativeMethod gMethods[] = {
android_media_AudioSystem_listenForSystemPropertyChange),
MAKE_JNI_NATIVE_METHOD("triggerSystemPropertyUpdate", "(J)V",
android_media_AudioSystem_triggerSystemPropertyUpdate),

MAKE_JNI_NATIVE_METHOD("setAppVolume", "(Ljava/lang/String;F)I",
android_media_AudioSystem_setAppVolume),
MAKE_JNI_NATIVE_METHOD("setAppMute", "(Ljava/lang/String;Z)I",
android_media_AudioSystem_setAppMute),
MAKE_JNI_NATIVE_METHOD("listAppVolumes", "(Ljava/util/ArrayList;)I",
android_media_AudioSystem_listAppVolumes),
MAKE_AUDIO_SYSTEM_METHOD(setSimulateDeviceConnections),
};

Expand Down Expand Up @@ -3979,6 +4072,11 @@ int register_android_media_AudioSystem(JNIEnv *env)

LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&gVm) != 0);

jclass AppVolumeClass = FindClassOrDie(env, "android/media/AppVolume");
gAppVolumeClass = MakeGlobalRefOrDie(env, AppVolumeClass);
gAppVolumeCstor = GetMethodIDOrDie(env, AppVolumeClass, "<init>",
"(Ljava/lang/String;ZFZ)V");

AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback);

RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
Expand Down
53 changes: 53 additions & 0 deletions media/java/android/media/AppVolume.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2022 Project Kaleidoscope
*
* 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 android.media;

import android.annotation.NonNull;

/**
* @hide
*/
public class AppVolume {
private final String mPackageName;
private final float mVolume;
private final boolean mMute;
private final boolean mActive;

AppVolume(String packageName, boolean mute, float volume, boolean active) {
mPackageName = packageName;
mMute = mute;
mVolume = volume;
mActive = active;
}

@NonNull
public String getPackageName() {
return mPackageName;
}

public float getVolume() {
return mVolume;
}

public boolean isMuted() {
return mMute;
}

public boolean isActive() {
return mActive;
}
}
26 changes: 26 additions & 0 deletions media/java/android/media/AudioManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -1173,6 +1173,32 @@ public void setMasterMute(boolean mute, int flags) {
}
}

/** @hide */
@UnsupportedAppUsage
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public int setAppVolume(String packageName, float volume) {
return AudioSystem.setAppVolume(packageName, volume);
}

/** @hide */
@UnsupportedAppUsage
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public int setAppMute(String packageName, boolean mute) {
return AudioSystem.setAppMute(packageName, mute);
}

/** @hide */
@UnsupportedAppUsage
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public ArrayList<AppVolume> listAppVolumes() {
ArrayList<AppVolume> volumes = new ArrayList<AppVolume>();
int status = AudioSystem.listAppVolumes(volumes);
if (status != AudioManager.SUCCESS) {
return new ArrayList<AppVolume>();
}
return volumes;
}

/**
* Returns the current ringtone mode.
*
Expand Down
7 changes: 7 additions & 0 deletions media/java/android/media/AudioSystem.java
Original file line number Diff line number Diff line change
Expand Up @@ -2006,6 +2006,13 @@ public static int getDevicesForStream(int stream) {
getDevicesForAttributes(attr, true /* forVolume */)));
}

/** @hide */
public static native int setAppVolume(@NonNull String packageName, float volume);
/** @hide */
public static native int setAppMute(@NonNull String packageName, boolean mute);
/** @hide */
public static native int listAppVolumes(ArrayList<AppVolume> volumes);

/** @hide
* Conversion from a device set to a bit mask.
*
Expand Down
32 changes: 32 additions & 0 deletions packages/SystemUI/res/drawable/ic_app_volume.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!--
Copyright (C) 2022 The LibreMobileOS Foundation

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.
-->

<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:pathData="M 10.001 3 C 9.448 3 9 3.448 9.001 4.001 L 9.01 13.55 C 8.41 13.21 7.73 13 7.01 13 C 4.79 13 3 14.79 3 17 C 3 19.21 4.79 21 7.01 21 C 9.23 21 11 19.21 11 17 L 11 7 L 14 7 C 14.552 7 15 6.552 15 6 L 15 4 C 15 3.448 14.552 3 14 3 L 10.001 3 Z"
android:fillColor="@android:color/white"
android:strokeWidth="1"/>
<path
android:pathData="M 14 10 L 15 10 C 15.265 10 15.52 10.105 15.707 10.293 C 15.895 10.48 16 10.735 16 11 L 16 12 C 16 12.265 15.895 12.52 15.707 12.707 C 15.52 12.895 15.265 13 15 13 L 14 13 C 13.735 13 13.48 12.895 13.293 12.707 C 13.105 12.52 13 12.265 13 12 L 13 11 C 13 10.735 13.105 10.48 13.293 10.293 C 13.48 10.105 13.735 10 14 10 M 14 15 L 15 15 C 15.265 15 15.52 15.105 15.707 15.293 C 15.895 15.48 16 15.735 16 16 L 16 17 C 16 17.265 15.895 17.52 15.707 17.707 C 15.52 17.895 15.265 18 15 18 L 14 18 C 13.735 18 13.48 17.895 13.293 17.707 C 13.105 17.52 13 17.265 13 17 L 13 16 C 13 15.735 13.105 15.48 13.293 15.293 C 13.48 15.105 13.735 15 14 15 M 19 10 L 20 10 C 20.265 10 20.52 10.105 20.707 10.293 C 20.895 10.48 21 10.735 21 11 L 21 12 C 21 12.265 20.895 12.52 20.707 12.707 C 20.52 12.895 20.265 13 20 13 L 19 13 C 18.735 13 18.48 12.895 18.293 12.707 C 18.105 12.52 18 12.265 18 12 L 18 11 C 18 10.735 18.105 10.48 18.293 10.293 C 18.48 10.105 18.735 10 19 10 M 19 15 L 20 15 C 20.265 15 20.52 15.105 20.707 15.293 C 20.895 15.48 21 15.735 21 16 L 21 17 C 21 17.265 20.895 17.52 20.707 17.707 C 20.52 17.895 20.265 18 20 18 L 19 18 C 18.735 18 18.48 17.895 18.293 17.707 C 18.105 17.52 18 17.265 18 17 L 18 16 C 18 15.735 18.105 15.48 18.293 15.293 C 18.48 15.105 18.735 15 19 15"
android:fillColor="@android:color/white"
android:strokeWidth="1"/>
</vector>
13 changes: 13 additions & 0 deletions packages/SystemUI/res/layout/volume_dialog_bottom_section.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@
android:soundEffectsEnabled="false"
android:visibility="gone"/>

<ImageButton
android:id="@+id/app_volume_icon"
android:src="@drawable/ic_app_volume"
android:layout_width="@dimen/volume_dialog_button_size"
android:layout_height="@dimen/volume_dialog_button_size"
android:layout_gravity="center"
android:background="@drawable/volume_dialog_captions_button_transition_bg"
android:contentDescription="@string/accessibility_volume_settings"
android:scaleType="centerInside"
android:soundEffectsEnabled="false"
android:tint="@androidprv:color/materialColorPrimary"
android:visibility="gone"/>

<ImageButton
android:id="@+id/volume_dialog_settings"
android:layout_width="@dimen/volume_dialog_button_size"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.android.systemui.volume.dialog.appvolume.domain

import android.content.Context
import android.database.ContentObserver
import android.media.AudioManager
import android.os.Handler
import android.os.Looper
import android.os.UserHandle
import android.provider.Settings
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor
import com.android.systemui.volume.ui.navigation.VolumeNavigator
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.stateIn

/** Exposes [VolumeDialogAppVolumeButtonViewModel]. */
@VolumeDialogScope
class VolumeDialogAppVolumeButtonInteractor
@Inject
constructor(
@Application private val context: Context,
@VolumeDialog private val coroutineScope: CoroutineScope,
private val volumeNavigator: VolumeNavigator,
private val volumePanelNavigationInteractor: VolumePanelNavigationInteractor,
) {
private fun shouldShowAppVolume(): Boolean {
val showAppVolume = Settings.System.getIntForUser(
context.contentResolver,
Settings.System.SHOW_APP_VOLUME,
0,
UserHandle.USER_CURRENT
)
if (showAppVolume == 1) {
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
for (appVolume in audioManager.listAppVolumes()) {
if (appVolume.isActive) {
return true
}
}
}
return false
}

val isVisible: StateFlow<Boolean> =
callbackFlow {
val handler = Handler(Looper.getMainLooper())
val observer = object : ContentObserver(handler) {
override fun onChange(selfChange: Boolean) {
trySend(shouldShowAppVolume())
}
}
context.contentResolver.registerContentObserver(
Settings.System.getUriFor(Settings.System.SHOW_APP_VOLUME),
false,
observer,
UserHandle.USER_CURRENT
)
trySend(shouldShowAppVolume())
awaitClose {
context.contentResolver.unregisterContentObserver(observer)
}
}
.stateIn(coroutineScope, SharingStarted.Eagerly, shouldShowAppVolume())

fun onButtonClicked() {
volumeNavigator.openVolumePanel(
volumePanelNavigationInteractor.getAppVolumePanelRoute()
)
}
}
Loading