Yin-Chia Yeh | 51d8516 | 2019-08-06 15:31:39 -0700 | [diff] [blame] | 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.server.appop; |
| 18 | |
| 19 | import android.app.AppOpsManager; |
| 20 | import android.hardware.camera2.CameraDevice; |
| 21 | import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION; |
| 22 | import android.media.AudioAttributes; |
| 23 | import android.util.ArraySet; |
| 24 | import android.util.Slog; |
| 25 | import android.util.SparseArray; |
| 26 | import android.util.SparseBooleanArray; |
| 27 | |
| 28 | import java.io.PrintWriter; |
| 29 | |
| 30 | /** |
| 31 | * AudioRestrictionManager host all audio restriction related logic and states for AppOpsService. |
| 32 | */ |
| 33 | public class AudioRestrictionManager { |
| 34 | static final String TAG = "AudioRestriction"; |
| 35 | |
| 36 | // Audio restrictions coming from Zen mode API |
| 37 | final SparseArray<SparseArray<Restriction>> mZenModeAudioRestrictions = new SparseArray<>(); |
| 38 | // Audio restrictions coming from Camera2 API |
| 39 | @CAMERA_AUDIO_RESTRICTION int mCameraAudioRestriction = CameraDevice.AUDIO_RESTRICTION_NONE; |
| 40 | // Predefined <code, usages> camera audio restriction settings |
| 41 | static final SparseArray<SparseBooleanArray> CAMERA_AUDIO_RESTRICTIONS; |
| 42 | |
| 43 | static { |
| 44 | SparseBooleanArray audioMutedUsages = new SparseBooleanArray(); |
| 45 | SparseBooleanArray vibrationMutedUsages = new SparseBooleanArray(); |
| 46 | for (int usage : AudioAttributes.SDK_USAGES) { |
| 47 | final int suppressionBehavior = AudioAttributes.SUPPRESSIBLE_USAGES.get(usage); |
| 48 | if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_NOTIFICATION || |
| 49 | suppressionBehavior == AudioAttributes.SUPPRESSIBLE_CALL || |
| 50 | suppressionBehavior == AudioAttributes.SUPPRESSIBLE_ALARM) { |
| 51 | audioMutedUsages.append(usage, true); |
| 52 | vibrationMutedUsages.append(usage, true); |
| 53 | } else if (suppressionBehavior != AudioAttributes.SUPPRESSIBLE_MEDIA && |
| 54 | suppressionBehavior != AudioAttributes.SUPPRESSIBLE_SYSTEM && |
| 55 | suppressionBehavior != AudioAttributes.SUPPRESSIBLE_NEVER) { |
| 56 | Slog.e(TAG, "Unknown audio suppression behavior" + suppressionBehavior); |
| 57 | } |
| 58 | } |
| 59 | CAMERA_AUDIO_RESTRICTIONS = new SparseArray<>(); |
| 60 | CAMERA_AUDIO_RESTRICTIONS.append(AppOpsManager.OP_PLAY_AUDIO, audioMutedUsages); |
| 61 | CAMERA_AUDIO_RESTRICTIONS.append(AppOpsManager.OP_VIBRATE, vibrationMutedUsages); |
| 62 | } |
| 63 | |
| 64 | private static final class Restriction { |
| 65 | private static final ArraySet<String> NO_EXCEPTIONS = new ArraySet<String>(); |
| 66 | int mode; |
| 67 | ArraySet<String> exceptionPackages = NO_EXCEPTIONS; |
| 68 | } |
| 69 | |
| 70 | public int checkAudioOperation(int code, int usage, int uid, String packageName) { |
| 71 | synchronized (this) { |
| 72 | // Check for camera audio restrictions |
| 73 | if (mCameraAudioRestriction != CameraDevice.AUDIO_RESTRICTION_NONE) { |
| 74 | if (code == AppOpsManager.OP_VIBRATE || (code == AppOpsManager.OP_PLAY_AUDIO && |
| 75 | mCameraAudioRestriction == |
| 76 | CameraDevice.AUDIO_RESTRICTION_VIBRATION_SOUND)) { |
| 77 | final SparseBooleanArray mutedUsages = CAMERA_AUDIO_RESTRICTIONS.get(code); |
| 78 | if (mutedUsages != null) { |
| 79 | if (mutedUsages.get(usage)) { |
| 80 | return AppOpsManager.MODE_IGNORED; |
| 81 | } |
| 82 | } |
| 83 | } |
| 84 | } |
| 85 | |
Yin-Chia Yeh | 3eb79d6 | 2019-08-19 15:55:34 -0700 | [diff] [blame] | 86 | final int mode = checkZenModeRestrictionLocked(code, usage, uid, packageName); |
Yin-Chia Yeh | 51d8516 | 2019-08-06 15:31:39 -0700 | [diff] [blame] | 87 | if (mode != AppOpsManager.MODE_ALLOWED) { |
| 88 | return mode; |
| 89 | } |
| 90 | } |
| 91 | return AppOpsManager.MODE_ALLOWED; |
| 92 | } |
| 93 | |
Yin-Chia Yeh | 3eb79d6 | 2019-08-19 15:55:34 -0700 | [diff] [blame] | 94 | private int checkZenModeRestrictionLocked(int code, int usage, int uid, String packageName) { |
Yin-Chia Yeh | 51d8516 | 2019-08-06 15:31:39 -0700 | [diff] [blame] | 95 | final SparseArray<Restriction> usageRestrictions = mZenModeAudioRestrictions.get(code); |
| 96 | if (usageRestrictions != null) { |
| 97 | final Restriction r = usageRestrictions.get(usage); |
| 98 | if (r != null && !r.exceptionPackages.contains(packageName)) { |
| 99 | return r.mode; |
| 100 | } |
| 101 | } |
| 102 | return AppOpsManager.MODE_ALLOWED; |
| 103 | } |
| 104 | |
| 105 | public void setZenModeAudioRestriction(int code, int usage, int uid, int mode, |
| 106 | String[] exceptionPackages) { |
| 107 | synchronized (this) { |
| 108 | SparseArray<Restriction> usageRestrictions = mZenModeAudioRestrictions.get(code); |
| 109 | if (usageRestrictions == null) { |
| 110 | usageRestrictions = new SparseArray<Restriction>(); |
| 111 | mZenModeAudioRestrictions.put(code, usageRestrictions); |
| 112 | } |
| 113 | usageRestrictions.remove(usage); |
| 114 | if (mode != AppOpsManager.MODE_ALLOWED) { |
| 115 | final Restriction r = new Restriction(); |
| 116 | r.mode = mode; |
| 117 | if (exceptionPackages != null) { |
| 118 | final int N = exceptionPackages.length; |
| 119 | r.exceptionPackages = new ArraySet<String>(N); |
| 120 | for (int i = 0; i < N; i++) { |
| 121 | final String pkg = exceptionPackages[i]; |
| 122 | if (pkg != null) { |
| 123 | r.exceptionPackages.add(pkg.trim()); |
| 124 | } |
| 125 | } |
| 126 | } |
| 127 | usageRestrictions.put(usage, r); |
| 128 | } |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | public void setCameraAudioRestriction(@CAMERA_AUDIO_RESTRICTION int mode) { |
| 133 | synchronized (this) { |
| 134 | mCameraAudioRestriction = mode; |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | public boolean hasActiveRestrictions() { |
| 139 | boolean hasActiveRestrictions = false; |
| 140 | synchronized (this) { |
| 141 | hasActiveRestrictions = (mZenModeAudioRestrictions.size() > 0 || |
| 142 | mCameraAudioRestriction != CameraDevice.AUDIO_RESTRICTION_NONE); |
| 143 | } |
| 144 | return hasActiveRestrictions; |
| 145 | } |
| 146 | |
| 147 | // return: needSep used by AppOpsService#dump |
| 148 | public boolean dump(PrintWriter pw) { |
| 149 | boolean printedHeader = false; |
| 150 | boolean needSep = hasActiveRestrictions(); |
| 151 | |
| 152 | synchronized (this) { |
| 153 | for (int o = 0; o < mZenModeAudioRestrictions.size(); o++) { |
| 154 | final String op = AppOpsManager.opToName(mZenModeAudioRestrictions.keyAt(o)); |
| 155 | final SparseArray<Restriction> restrictions = mZenModeAudioRestrictions.valueAt(o); |
| 156 | for (int i = 0; i < restrictions.size(); i++) { |
| 157 | if (!printedHeader){ |
| 158 | pw.println(" Zen Mode Audio Restrictions:"); |
| 159 | printedHeader = true; |
| 160 | |
| 161 | } |
| 162 | final int usage = restrictions.keyAt(i); |
| 163 | pw.print(" "); pw.print(op); |
| 164 | pw.print(" usage="); pw.print(AudioAttributes.usageToString(usage)); |
| 165 | Restriction r = restrictions.valueAt(i); |
| 166 | pw.print(": mode="); pw.println(AppOpsManager.modeToName(r.mode)); |
| 167 | if (!r.exceptionPackages.isEmpty()) { |
| 168 | pw.println(" Exceptions:"); |
| 169 | for (int j = 0; j < r.exceptionPackages.size(); j++) { |
| 170 | pw.print(" "); pw.println(r.exceptionPackages.valueAt(j)); |
| 171 | } |
| 172 | } |
| 173 | } |
| 174 | } |
| 175 | if (mCameraAudioRestriction != CameraDevice.AUDIO_RESTRICTION_NONE) { |
| 176 | pw.println(" Camera Audio Restriction Mode: " + |
| 177 | cameraRestrictionModeToName(mCameraAudioRestriction)); |
| 178 | } |
| 179 | } |
| 180 | return needSep; |
| 181 | } |
| 182 | |
| 183 | private static String cameraRestrictionModeToName(@CAMERA_AUDIO_RESTRICTION int mode) { |
| 184 | switch (mode) { |
| 185 | case CameraDevice.AUDIO_RESTRICTION_NONE: |
| 186 | return "None"; |
| 187 | case CameraDevice.AUDIO_RESTRICTION_VIBRATION: |
| 188 | return "MuteVibration"; |
| 189 | case CameraDevice.AUDIO_RESTRICTION_VIBRATION_SOUND: |
| 190 | return "MuteVibrationAndSound"; |
| 191 | default: |
| 192 | return "Unknown"; |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | } |