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.toArray()) {
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) {
audioMutedUsages.append(usage, true)51                 audioMutedUsages.append(usage, true);
vibrationMutedUsages.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) {
Slog.e(TAG, "Unknown audio suppression behavior" + suppressionBehavior)56                 Slog.e(TAG, "Unknown audio suppression behavior" + suppressionBehavior);
57             }
58         }
59         CAMERA_AUDIO_RESTRICTIONS = new SparseArray<>();
CAMERA_AUDIO_RESTRICTIONS.append(AppOpsManager.OP_PLAY_AUDIO, audioMutedUsages)60         CAMERA_AUDIO_RESTRICTIONS.append(AppOpsManager.OP_PLAY_AUDIO, audioMutedUsages);
CAMERA_AUDIO_RESTRICTIONS.append(AppOpsManager.OP_VIBRATE, vibrationMutedUsages)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 
checkAudioOperation(int code, int usage, int uid, String packageName)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 
86             final int mode = checkZenModeRestrictionLocked(code, usage, uid, packageName);
87             if (mode != AppOpsManager.MODE_ALLOWED) {
88                 return mode;
89             }
90         }
91         return AppOpsManager.MODE_ALLOWED;
92     }
93 
checkZenModeRestrictionLocked(int code, int usage, int uid, String packageName)94     private int checkZenModeRestrictionLocked(int code, int usage, int uid, String packageName) {
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 
setZenModeAudioRestriction(int code, int usage, int uid, int mode, String[] exceptionPackages)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 
setCameraAudioRestriction(@AMERA_AUDIO_RESTRICTION int mode)132     public void setCameraAudioRestriction(@CAMERA_AUDIO_RESTRICTION int mode) {
133         synchronized (this) {
134             mCameraAudioRestriction = mode;
135         }
136     }
137 
hasActiveRestrictions()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
dump(PrintWriter pw)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 
cameraRestrictionModeToName(@AMERA_AUDIO_RESTRICTION int mode)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 }
197