1 /* 2 * Copyright 2023 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 package com.android.server.audio; 17 18 import static android.media.audio.Flags.autoPublicVolumeApiHardening; 19 20 import android.Manifest; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.app.ActivityManager; 24 import android.app.AppOpsManager; 25 import android.content.Context; 26 import android.content.pm.PackageManager; 27 import android.media.AudioFocusRequest; 28 import android.media.AudioManager; 29 import android.os.Binder; 30 import android.os.Build; 31 import android.os.UserHandle; 32 import android.text.TextUtils; 33 import android.util.Slog; 34 35 import com.android.server.utils.EventLogger; 36 37 import java.io.PrintWriter; 38 39 /** 40 * Class to encapsulate all audio API hardening operations 41 */ 42 public class HardeningEnforcer { 43 44 private static final String TAG = "AS.HardeningEnforcer"; 45 private static final boolean DEBUG = false; 46 private static final int LOG_NB_EVENTS = 20; 47 48 final Context mContext; 49 final AppOpsManager mAppOps; 50 final boolean mIsAutomotive; 51 52 final ActivityManager mActivityManager; 53 final PackageManager mPackageManager; 54 55 final EventLogger mEventLogger = new EventLogger(LOG_NB_EVENTS, 56 "Hardening enforcement"); 57 58 /** 59 * Matches calls from {@link AudioManager#setStreamVolume(int, int, int)} 60 */ 61 public static final int METHOD_AUDIO_MANAGER_SET_STREAM_VOLUME = 100; 62 /** 63 * Matches calls from {@link AudioManager#adjustVolume(int, int)} 64 */ 65 public static final int METHOD_AUDIO_MANAGER_ADJUST_VOLUME = 101; 66 /** 67 * Matches calls from {@link AudioManager#adjustSuggestedStreamVolume(int, int, int)} 68 */ 69 public static final int METHOD_AUDIO_MANAGER_ADJUST_SUGGESTED_STREAM_VOLUME = 102; 70 /** 71 * Matches calls from {@link AudioManager#adjustStreamVolume(int, int, int)} 72 */ 73 public static final int METHOD_AUDIO_MANAGER_ADJUST_STREAM_VOLUME = 103; 74 /** 75 * Matches calls from {@link AudioManager#setRingerMode(int)} 76 */ 77 public static final int METHOD_AUDIO_MANAGER_SET_RINGER_MODE = 200; 78 /** 79 * Matches calls from {@link AudioManager#requestAudioFocus(AudioFocusRequest)} 80 * and legacy variants 81 */ 82 public static final int METHOD_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS = 300; 83 HardeningEnforcer(Context ctxt, boolean isAutomotive, AppOpsManager appOps, PackageManager pm)84 public HardeningEnforcer(Context ctxt, boolean isAutomotive, AppOpsManager appOps, 85 PackageManager pm) { 86 mContext = ctxt; 87 mIsAutomotive = isAutomotive; 88 mAppOps = appOps; 89 mActivityManager = ctxt.getSystemService(ActivityManager.class); 90 mPackageManager = pm; 91 } 92 dump(PrintWriter pw)93 protected void dump(PrintWriter pw) { 94 // log 95 mEventLogger.dump(pw); 96 } 97 98 /** 99 * Checks whether the call in the current thread should be allowed or blocked 100 * @param volumeMethod name of the method to check, for logging purposes 101 * @return false if the method call is allowed, true if it should be a no-op 102 */ blockVolumeMethod(int volumeMethod)103 protected boolean blockVolumeMethod(int volumeMethod) { 104 // for Auto, volume methods require MODIFY_AUDIO_SETTINGS_PRIVILEGED 105 if (mIsAutomotive) { 106 if (!autoPublicVolumeApiHardening()) { 107 // automotive hardening flag disabled, no blocking on auto 108 return false; 109 } 110 if (mContext.checkCallingOrSelfPermission( 111 Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) 112 == PackageManager.PERMISSION_GRANTED) { 113 return false; 114 } 115 if (Binder.getCallingUid() < UserHandle.AID_APP_START) { 116 return false; 117 } 118 // TODO metrics? 119 // TODO log for audio dumpsys? 120 Slog.e(TAG, "Preventing volume method " + volumeMethod + " for " 121 + getPackNameForUid(Binder.getCallingUid())); 122 return true; 123 } 124 // not blocking 125 return false; 126 } 127 128 /** 129 * Checks whether the call in the current thread should be allowed or blocked 130 * @param focusMethod name of the method to check, for logging purposes 131 * @param clientId id of the requester 132 * @param durationHint focus type being requested 133 * @param attributionTag attribution of the caller 134 * @param targetSdk target SDK of the caller 135 * @return false if the method call is allowed, true if it should be a no-op 136 */ 137 @SuppressWarnings("AndroidFrameworkCompatChange") blockFocusMethod(int callingUid, int focusMethod, @NonNull String clientId, int durationHint, @NonNull String packageName, String attributionTag, int targetSdk)138 protected boolean blockFocusMethod(int callingUid, int focusMethod, @NonNull String clientId, 139 int durationHint, @NonNull String packageName, String attributionTag, int targetSdk) { 140 if (packageName.isEmpty()) { 141 packageName = getPackNameForUid(callingUid); 142 } 143 144 if (noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, callingUid, packageName, attributionTag)) { 145 if (DEBUG) { 146 Slog.i(TAG, "blockFocusMethod pack:" + packageName + " NOT blocking"); 147 } 148 return false; 149 } else if (targetSdk < Build.VERSION_CODES.VANILLA_ICE_CREAM) { 150 if (DEBUG) { 151 Slog.i(TAG, "blockFocusMethod pack:" + packageName + " NOT blocking due to sdk=" 152 + targetSdk); 153 } 154 return false; 155 } 156 157 String errorMssg = "Focus request DENIED for uid:" + callingUid 158 + " clientId:" + clientId + " req:" + durationHint 159 + " procState:" + mActivityManager.getUidProcessState(callingUid); 160 161 // TODO metrics 162 mEventLogger.enqueueAndSlog(errorMssg, EventLogger.Event.ALOGI, TAG); 163 164 return true; 165 } 166 getPackNameForUid(int uid)167 private String getPackNameForUid(int uid) { 168 final long token = Binder.clearCallingIdentity(); 169 try { 170 final String[] names = mPackageManager.getPackagesForUid(uid); 171 if (names == null 172 || names.length == 0 173 || TextUtils.isEmpty(names[0])) { 174 return "[" + uid + "]"; 175 } 176 return names[0]; 177 } finally { 178 Binder.restoreCallingIdentity(token); 179 } 180 } 181 182 /** 183 * Notes the given op without throwing 184 * @param op the appOp code 185 * @param uid the calling uid 186 * @param packageName the package name of the caller 187 * @param attributionTag attribution of the caller 188 * @return return false if the operation is not allowed 189 */ noteOp(int op, int uid, @NonNull String packageName, @Nullable String attributionTag)190 private boolean noteOp(int op, int uid, @NonNull String packageName, 191 @Nullable String attributionTag) { 192 if (mAppOps.noteOpNoThrow(op, uid, packageName, attributionTag, null) 193 != AppOpsManager.MODE_ALLOWED) { 194 return false; 195 } 196 return true; 197 } 198 } 199