blob: be870373af63457b758d3c6d66494011ac1976b1 [file] [log] [blame]
/*
* Copyright (C) 2019 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.server.appop;
import android.app.AppOpsManager;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
import android.media.AudioAttributes;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import java.io.PrintWriter;
/**
* AudioRestrictionManager host all audio restriction related logic and states for AppOpsService.
*/
public class AudioRestrictionManager {
static final String TAG = "AudioRestriction";
// Audio restrictions coming from Zen mode API
final SparseArray<SparseArray<Restriction>> mZenModeAudioRestrictions = new SparseArray<>();
// Audio restrictions coming from Camera2 API
@CAMERA_AUDIO_RESTRICTION int mCameraAudioRestriction = CameraDevice.AUDIO_RESTRICTION_NONE;
// Predefined <code, usages> camera audio restriction settings
static final SparseArray<SparseBooleanArray> CAMERA_AUDIO_RESTRICTIONS;
static {
SparseBooleanArray audioMutedUsages = new SparseBooleanArray();
SparseBooleanArray vibrationMutedUsages = new SparseBooleanArray();
for (int usage : AudioAttributes.SDK_USAGES) {
final int suppressionBehavior = AudioAttributes.SUPPRESSIBLE_USAGES.get(usage);
if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_NOTIFICATION ||
suppressionBehavior == AudioAttributes.SUPPRESSIBLE_CALL ||
suppressionBehavior == AudioAttributes.SUPPRESSIBLE_ALARM) {
audioMutedUsages.append(usage, true);
vibrationMutedUsages.append(usage, true);
} else if (suppressionBehavior != AudioAttributes.SUPPRESSIBLE_MEDIA &&
suppressionBehavior != AudioAttributes.SUPPRESSIBLE_SYSTEM &&
suppressionBehavior != AudioAttributes.SUPPRESSIBLE_NEVER) {
Slog.e(TAG, "Unknown audio suppression behavior" + suppressionBehavior);
}
}
CAMERA_AUDIO_RESTRICTIONS = new SparseArray<>();
CAMERA_AUDIO_RESTRICTIONS.append(AppOpsManager.OP_PLAY_AUDIO, audioMutedUsages);
CAMERA_AUDIO_RESTRICTIONS.append(AppOpsManager.OP_VIBRATE, vibrationMutedUsages);
}
private static final class Restriction {
private static final ArraySet<String> NO_EXCEPTIONS = new ArraySet<String>();
int mode;
ArraySet<String> exceptionPackages = NO_EXCEPTIONS;
}
public int checkAudioOperation(int code, int usage, int uid, String packageName) {
synchronized (this) {
// Check for camera audio restrictions
if (mCameraAudioRestriction != CameraDevice.AUDIO_RESTRICTION_NONE) {
if (code == AppOpsManager.OP_VIBRATE || (code == AppOpsManager.OP_PLAY_AUDIO &&
mCameraAudioRestriction ==
CameraDevice.AUDIO_RESTRICTION_VIBRATION_SOUND)) {
final SparseBooleanArray mutedUsages = CAMERA_AUDIO_RESTRICTIONS.get(code);
if (mutedUsages != null) {
if (mutedUsages.get(usage)) {
return AppOpsManager.MODE_IGNORED;
}
}
}
}
final int mode = checkZenModeRestrictionLocked(code, usage, uid, packageName);
if (mode != AppOpsManager.MODE_ALLOWED) {
return mode;
}
}
return AppOpsManager.MODE_ALLOWED;
}
private int checkZenModeRestrictionLocked(int code, int usage, int uid, String packageName) {
final SparseArray<Restriction> usageRestrictions = mZenModeAudioRestrictions.get(code);
if (usageRestrictions != null) {
final Restriction r = usageRestrictions.get(usage);
if (r != null && !r.exceptionPackages.contains(packageName)) {
return r.mode;
}
}
return AppOpsManager.MODE_ALLOWED;
}
public void setZenModeAudioRestriction(int code, int usage, int uid, int mode,
String[] exceptionPackages) {
synchronized (this) {
SparseArray<Restriction> usageRestrictions = mZenModeAudioRestrictions.get(code);
if (usageRestrictions == null) {
usageRestrictions = new SparseArray<Restriction>();
mZenModeAudioRestrictions.put(code, usageRestrictions);
}
usageRestrictions.remove(usage);
if (mode != AppOpsManager.MODE_ALLOWED) {
final Restriction r = new Restriction();
r.mode = mode;
if (exceptionPackages != null) {
final int N = exceptionPackages.length;
r.exceptionPackages = new ArraySet<String>(N);
for (int i = 0; i < N; i++) {
final String pkg = exceptionPackages[i];
if (pkg != null) {
r.exceptionPackages.add(pkg.trim());
}
}
}
usageRestrictions.put(usage, r);
}
}
}
public void setCameraAudioRestriction(@CAMERA_AUDIO_RESTRICTION int mode) {
synchronized (this) {
mCameraAudioRestriction = mode;
}
}
public boolean hasActiveRestrictions() {
boolean hasActiveRestrictions = false;
synchronized (this) {
hasActiveRestrictions = (mZenModeAudioRestrictions.size() > 0 ||
mCameraAudioRestriction != CameraDevice.AUDIO_RESTRICTION_NONE);
}
return hasActiveRestrictions;
}
// return: needSep used by AppOpsService#dump
public boolean dump(PrintWriter pw) {
boolean printedHeader = false;
boolean needSep = hasActiveRestrictions();
synchronized (this) {
for (int o = 0; o < mZenModeAudioRestrictions.size(); o++) {
final String op = AppOpsManager.opToName(mZenModeAudioRestrictions.keyAt(o));
final SparseArray<Restriction> restrictions = mZenModeAudioRestrictions.valueAt(o);
for (int i = 0; i < restrictions.size(); i++) {
if (!printedHeader){
pw.println(" Zen Mode Audio Restrictions:");
printedHeader = true;
}
final int usage = restrictions.keyAt(i);
pw.print(" "); pw.print(op);
pw.print(" usage="); pw.print(AudioAttributes.usageToString(usage));
Restriction r = restrictions.valueAt(i);
pw.print(": mode="); pw.println(AppOpsManager.modeToName(r.mode));
if (!r.exceptionPackages.isEmpty()) {
pw.println(" Exceptions:");
for (int j = 0; j < r.exceptionPackages.size(); j++) {
pw.print(" "); pw.println(r.exceptionPackages.valueAt(j));
}
}
}
}
if (mCameraAudioRestriction != CameraDevice.AUDIO_RESTRICTION_NONE) {
pw.println(" Camera Audio Restriction Mode: " +
cameraRestrictionModeToName(mCameraAudioRestriction));
}
}
return needSep;
}
private static String cameraRestrictionModeToName(@CAMERA_AUDIO_RESTRICTION int mode) {
switch (mode) {
case CameraDevice.AUDIO_RESTRICTION_NONE:
return "None";
case CameraDevice.AUDIO_RESTRICTION_VIBRATION:
return "MuteVibration";
case CameraDevice.AUDIO_RESTRICTION_VIBRATION_SOUND:
return "MuteVibrationAndSound";
default:
return "Unknown";
}
}
}