diff options
10 files changed, 207 insertions, 24 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index e86c5129496a..e0e5c54582cc 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -592,6 +592,7 @@ package android.app { field public static final int FOREGROUND_SERVICE_API_TYPE_MICROPHONE = 6; // 0x6 field public static final int FOREGROUND_SERVICE_API_TYPE_PHONE_CALL = 7; // 0x7 field public static final int FOREGROUND_SERVICE_API_TYPE_USB = 8; // 0x8 + field @FlaggedApi("android.media.audio.foreground_audio_control") public static final int PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL = 64; // 0x40 field public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 2; // 0x2 field public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1; // 0x1 field public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 4; // 0x4 diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index a8d183a1d6dd..237d31c67fe2 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.activityTypeToString; import static android.app.WindowConfiguration.windowingModeToString; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; +import static android.media.audio.Flags.FLAG_FOREGROUND_AUDIO_CONTROL; import android.Manifest; import android.annotation.ColorInt; @@ -794,6 +795,7 @@ public class ActivityManager { PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK, PROCESS_CAPABILITY_BFSL, PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK, + PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL, }) @Retention(RetentionPolicy.SOURCE) public @interface ProcessCapability {} @@ -943,6 +945,14 @@ public class ActivityManager { public static final int PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK = 1 << 5; /** + * @hide + * Process can access volume APIs and can request audio focus with GAIN. + */ + @FlaggedApi(FLAG_FOREGROUND_AUDIO_CONTROL) + @SystemApi + public static final int PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL = 1 << 6; + + /** * @hide all capabilities, the ORing of all flags in {@link ProcessCapability}. * * Don't expose it as TestApi -- we may add new capabilities any time, which could @@ -953,7 +963,8 @@ public class ActivityManager { | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE | PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK | PROCESS_CAPABILITY_BFSL - | PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK; + | PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK + | PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL; /** * All implicit capabilities. There are capabilities that process automatically have. @@ -975,6 +986,7 @@ public class ActivityManager { pw.print((caps & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0 ? 'N' : '-'); pw.print((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-'); pw.print((caps & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0 ? 'U' : '-'); + pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL) != 0 ? 'A' : '-'); } /** @hide */ @@ -986,6 +998,7 @@ public class ActivityManager { sb.append((caps & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0 ? 'N' : '-'); sb.append((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-'); sb.append((caps & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0 ? 'U' : '-'); + sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL) != 0 ? 'A' : '-'); } /** diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 2100425a6771..409badb347c4 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -16,7 +16,9 @@ package android.app; + import static android.location.flags.Flags.FLAG_LOCATION_BYPASS; +import static android.media.audio.Flags.foregroundAudioControl; import static android.permission.flags.Flags.FLAG_OP_ENABLE_MOBILE_DATA_BY_USER; import static android.view.contentprotection.flags.Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED; import static android.view.contentprotection.flags.Flags.FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED; @@ -3229,6 +3231,10 @@ public class AppOpsManager { * @hide */ public static @Mode int opToDefaultMode(int op) { + if (op == OP_TAKE_AUDIO_FOCUS && foregroundAudioControl()) { + // when removing the flag, change the entry in sAppOpInfos for OP_TAKE_AUDIO_FOCUS + return AppOpsManager.MODE_FOREGROUND; + } return sAppOpInfos[op].defaultMode; } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 7d82f0c2a63e..df46e5dafce8 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -19,6 +19,7 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL; import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL_IMPLICIT; import static android.app.ActivityManager.PROCESS_CAPABILITY_BFSL; +import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; @@ -67,7 +68,10 @@ import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE; import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION; +import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE; +import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL; +import static android.media.audio.Flags.foregroundAudioControl; import static android.os.Process.SCHED_OTHER; import static android.os.Process.THREAD_GROUP_BACKGROUND; import static android.os.Process.THREAD_GROUP_DEFAULT; @@ -2266,6 +2270,15 @@ public class OomAdjuster { (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION) != 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0; + if (foregroundAudioControl()) { // flag check + final int fgsAudioType = FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK + | FOREGROUND_SERVICE_TYPE_CAMERA + | FOREGROUND_SERVICE_TYPE_MICROPHONE + | FOREGROUND_SERVICE_TYPE_PHONE_CALL; + capabilityFromFGS |= (psr.getForegroundServiceTypes() & fgsAudioType) != 0 + ? PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL : 0; + } + final boolean enabled = state.getCachedCompatChange( CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY); if (enabled) { diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java index 57d233e7c503..562beaf50a7f 100644 --- a/services/core/java/com/android/server/am/ProcessServiceRecord.java +++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java @@ -205,10 +205,10 @@ final class ProcessServiceRecord { } /** - * Returns the FGS typps, but it doesn't tell if the types include "NONE" or not, so - * do not use it outside of this class. + * Returns the FGS types, but it doesn't tell if the types include "NONE" or not, use + * {@link #hasForegroundServices()} */ - private int getForegroundServiceTypes() { + int getForegroundServiceTypes() { return mHasForegroundServices ? mFgServiceTypes : 0; } diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java index 23a384ff5d3b..bc6ef2005584 100644 --- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java @@ -16,6 +16,7 @@ package com.android.server.appop; +import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; @@ -30,6 +31,7 @@ import static android.app.AppOpsManager.OP_CAMERA; import static android.app.AppOpsManager.OP_NONE; import static android.app.AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO; +import static android.app.AppOpsManager.OP_TAKE_AUDIO_FOCUS; import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE; import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED; import static android.app.AppOpsManager.UID_STATE_TOP; @@ -139,7 +141,6 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { } private int evalModeInternal(int uid, int code, int uidState, int uidCapability) { - if (getUidAppWidgetVisible(uid) || mActivityManagerInternal.isPendingTopUid(uid) || mActivityManagerInternal.isTempAllowlistedForFgsWhileInUse(uid)) { return MODE_ALLOWED; @@ -173,6 +174,8 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { case OP_RECORD_AUDIO: case OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO: return PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; + case OP_TAKE_AUDIO_FOCUS: + return PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL; default: return PROCESS_CAPABILITY_NONE; } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index c59f4f7888ce..559a1d647ea4 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -34,6 +34,7 @@ import static android.media.audio.Flags.autoPublicVolumeApiHardening; import static android.media.audio.Flags.automaticBtDeviceType; import static android.media.audio.Flags.featureSpatialAudioHeadtrackingLowLatency; import static android.media.audio.Flags.focusFreezeTestApi; +import static android.media.audio.Flags.foregroundAudioControl; import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration; import static android.os.Process.FIRST_APPLICATION_UID; import static android.os.Process.INVALID_UID; @@ -1356,7 +1357,8 @@ public class AudioService extends IAudioService.Stub mMusicFxHelper = new MusicFxHelper(mContext, mAudioHandler); - mHardeningEnforcer = new HardeningEnforcer(mContext, isPlatformAutomotive()); + mHardeningEnforcer = new HardeningEnforcer(mContext, isPlatformAutomotive(), mAppOps, + context.getPackageManager()); } private void initVolumeStreamStates() { @@ -4517,7 +4519,8 @@ public class AudioService extends IAudioService.Stub } private void dumpFlags(PrintWriter pw) { - pw.println("\nFun with Flags: "); + + pw.println("\nFun with Flags:"); pw.println("\tandroid.media.audio.autoPublicVolumeApiHardening:" + autoPublicVolumeApiHardening()); pw.println("\tandroid.media.audio.Flags.automaticBtDeviceType:" @@ -4528,8 +4531,8 @@ public class AudioService extends IAudioService.Stub + focusFreezeTestApi()); pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:" + disablePrescaleAbsoluteVolume()); - pw.println("\tandroid.media.audiopolicy.enableFadeManagerConfiguration:" - + enableFadeManagerConfiguration()); + pw.println("\tandroid.media.audio.foregroundAudioControl:" + + foregroundAudioControl()); } private void dumpAudioMode(PrintWriter pw) { @@ -10175,10 +10178,38 @@ public class AudioService extends IAudioService.Stub .record(); return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } + + // does caller have system privileges to bypass HardeningEnforcer + boolean permissionOverridesCheck = false; + if ((mContext.checkCallingOrSelfPermission( + Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + == PackageManager.PERMISSION_GRANTED) + || (mContext.checkCallingOrSelfPermission(Manifest.permission.MODIFY_AUDIO_ROUTING) + == PackageManager.PERMISSION_GRANTED)) { + permissionOverridesCheck = true; + } else if (uid < UserHandle.AID_APP_START) { + permissionOverridesCheck = true; + } + + final long token = Binder.clearCallingIdentity(); + try { + if (!permissionOverridesCheck && mHardeningEnforcer.blockFocusMethod(uid, + HardeningEnforcer.METHOD_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS, + clientId, durationHint, callingPackageName)) { + final String reason = "Audio focus request blocked by hardening"; + Log.w(TAG, reason); + mmi.set(MediaMetrics.Property.EARLY_RETURN, reason).record(); + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; + } + } finally { + Binder.restoreCallingIdentity(token); + } + mmi.record(); return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd, clientId, callingPackageName, attributionTag, flags, sdk, - forceFocusDuckingForAccessibility(aa, durationHint, uid), -1 /*testUid, ignored*/); + forceFocusDuckingForAccessibility(aa, durationHint, uid), -1 /*testUid, ignored*/, + permissionOverridesCheck); } /** see {@link AudioManager#requestAudioFocusForTest(AudioFocusRequest, String, int, int)} */ @@ -10195,7 +10226,7 @@ public class AudioService extends IAudioService.Stub } return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd, clientId, callingPackageName, null, flags, - sdk, false /*forceDuck*/, fakeUid); + sdk, false /*forceDuck*/, fakeUid, true /*permissionOverridesCheck*/); } public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, AudioAttributes aa, @@ -11639,6 +11670,7 @@ public class AudioService extends IAudioService.Stub pw.println("\nMessage handler is null"); } dumpFlags(pw); + mHardeningEnforcer.dump(pw); mMediaFocusControl.dump(pw); dumpStreamStates(pw); dumpVolumeGroups(pw); diff --git a/services/core/java/com/android/server/audio/HardeningEnforcer.java b/services/core/java/com/android/server/audio/HardeningEnforcer.java index 4ceb83b2e1c9..409ed17001b7 100644 --- a/services/core/java/com/android/server/audio/HardeningEnforcer.java +++ b/services/core/java/com/android/server/audio/HardeningEnforcer.java @@ -18,13 +18,21 @@ package com.android.server.audio; import static android.media.audio.Flags.autoPublicVolumeApiHardening; import android.Manifest; +import android.annotation.NonNull; +import android.app.ActivityManager; +import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; +import android.media.AudioFocusRequest; import android.media.AudioManager; import android.os.Binder; import android.os.UserHandle; import android.text.TextUtils; -import android.util.Log; +import android.util.Slog; + +import com.android.server.utils.EventLogger; + +import java.io.PrintWriter; /** * Class to encapsulate all audio API hardening operations @@ -32,10 +40,19 @@ import android.util.Log; public class HardeningEnforcer { private static final String TAG = "AS.HardeningEnforcer"; + private static final boolean DEBUG = false; + private static final int LOG_NB_EVENTS = 20; final Context mContext; + final AppOpsManager mAppOps; final boolean mIsAutomotive; + final ActivityManager mActivityManager; + final PackageManager mPackageManager; + + final EventLogger mEventLogger = new EventLogger(LOG_NB_EVENTS, + "Hardening enforcement"); + /** * Matches calls from {@link AudioManager#setStreamVolume(int, int, int)} */ @@ -56,10 +73,24 @@ public class HardeningEnforcer { * Matches calls from {@link AudioManager#setRingerMode(int)} */ public static final int METHOD_AUDIO_MANAGER_SET_RINGER_MODE = 200; + /** + * Matches calls from {@link AudioManager#requestAudioFocus(AudioFocusRequest)} + * and legacy variants + */ + public static final int METHOD_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS = 300; - public HardeningEnforcer(Context ctxt, boolean isAutomotive) { + public HardeningEnforcer(Context ctxt, boolean isAutomotive, AppOpsManager appOps, + PackageManager pm) { mContext = ctxt; mIsAutomotive = isAutomotive; + mAppOps = appOps; + mActivityManager = ctxt.getSystemService(ActivityManager.class); + mPackageManager = pm; + } + + protected void dump(PrintWriter pw) { + // log + mEventLogger.dump(pw); } /** @@ -84,7 +115,7 @@ public class HardeningEnforcer { } // TODO metrics? // TODO log for audio dumpsys? - Log.e(TAG, "Preventing volume method " + volumeMethod + " for " + Slog.e(TAG, "Preventing volume method " + volumeMethod + " for " + getPackNameForUid(Binder.getCallingUid())); return true; } @@ -92,10 +123,40 @@ public class HardeningEnforcer { return false; } + /** + * Checks whether the call in the current thread should be allowed or blocked + * @param focusMethod name of the method to check, for logging purposes + * @param clientId id of the requester + * @param durationHint focus type being requested + * @return false if the method call is allowed, true if it should be a no-op + */ + protected boolean blockFocusMethod(int callingUid, int focusMethod, @NonNull String clientId, + int durationHint, @NonNull String packageName) { + if (packageName.isEmpty()) { + packageName = getPackNameForUid(callingUid); + } + + if (checkAppOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, callingUid, packageName)) { + if (DEBUG) { + Slog.i(TAG, "blockFocusMethod pack:" + packageName + " NOT blocking"); + } + return false; + } + + String errorMssg = "Focus request DENIED for uid:" + callingUid + + " clientId:" + clientId + " req:" + durationHint + + " procState:" + mActivityManager.getUidProcessState(callingUid); + + // TODO metrics + mEventLogger.enqueueAndSlog(errorMssg, EventLogger.Event.ALOGI, TAG); + + return true; + } + private String getPackNameForUid(int uid) { final long token = Binder.clearCallingIdentity(); try { - final String[] names = mContext.getPackageManager().getPackagesForUid(uid); + final String[] names = mPackageManager.getPackagesForUid(uid); if (names == null || names.length == 0 || TextUtils.isEmpty(names[0])) { @@ -106,4 +167,18 @@ public class HardeningEnforcer { Binder.restoreCallingIdentity(token); } } + + /** + * Checks the given op without throwing + * @param op the appOp code + * @param uid the calling uid + * @param packageName the package name of the caller + * @return return false if the operation is not allowed + */ + private boolean checkAppOp(int op, int uid, @NonNull String packageName) { + if (mAppOps.checkOpNoThrow(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) { + return false; + } + return true; + } } diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java index 1376bde2fb71..35d38e2373f5 100644 --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -1090,11 +1090,14 @@ public class MediaFocusControl implements PlayerFocusEnforcer { * accessibility. * @param testUid ignored if flags doesn't contain AudioManager.AUDIOFOCUS_FLAG_TEST * otherwise the UID being injected for testing + * @param permissionOverridesCheck true if permission checks guaranteed that the call should + * go through, false otherwise (e.g. non-privileged caller) * @return */ protected int requestAudioFocus(@NonNull AudioAttributes aa, int focusChangeHint, IBinder cb, IAudioFocusDispatcher fd, @NonNull String clientId, @NonNull String callingPackageName, - String attributionTag, int flags, int sdk, boolean forceDuck, int testUid) { + String attributionTag, int flags, int sdk, boolean forceDuck, int testUid, + boolean permissionOverridesCheck) { new MediaMetrics.Item(mMetricsId) .setUid(Binder.getCallingUid()) .set(MediaMetrics.Property.CALLING_PACKAGE, callingPackageName) @@ -1126,10 +1129,9 @@ public class MediaFocusControl implements PlayerFocusEnforcer { return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } - if ((flags != AudioManager.AUDIOFOCUS_FLAG_TEST) - // note we're using the real uid for appOp evaluation - && (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(), - callingPackageName, attributionTag, null) != AppOpsManager.MODE_ALLOWED)) { + final int res = mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(), + callingPackageName, attributionTag, null); + if (!permissionOverridesCheck && res != AppOpsManager.MODE_ALLOWED) { return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } diff --git a/services/core/java/com/android/server/utils/EventLogger.java b/services/core/java/com/android/server/utils/EventLogger.java index 4772bbfe97dd..2e1049b9ea32 100644 --- a/services/core/java/com/android/server/utils/EventLogger.java +++ b/services/core/java/com/android/server/utils/EventLogger.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.Log; +import android.util.Slog; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -84,6 +85,17 @@ public class EventLogger { enqueue(event.printLog(logType, tag)); } + /** + * Add a string-based event to the system log, and print it to the log with a specific severity. + * @param msg the message to appear in the log + * @param logType the log severity (verbose/info/warning/error) + * @param tag the tag under which the log entry will appear + */ + public synchronized void enqueueAndSlog(String msg, @Event.LogType int logType, String tag) { + final Event event = new StringEvent(msg); + enqueue(event.printSlog(logType, tag)); + } + /** Dumps events into the given {@link DumpSink}. */ public synchronized void dump(DumpSink dumpSink) { dumpSink.sink(mTag, new ArrayList<>(mEvents)); @@ -138,7 +150,7 @@ public class EventLogger { /** * Causes the string message for the event to appear in the logcat. * Here is an example of how to create a new event (a StringEvent), adding it to the logger - * (an instance of AudioEventLogger) while also making it show in the logcat: + * (an instance of EventLogger) while also making it show in the logcat: * <pre> * myLogger.log( * (new StringEvent("something for logcat and logger")).printLog(MyClass.TAG) ); @@ -167,9 +179,9 @@ public class EventLogger { /** * Same as {@link #printLog(String)} with a log type - * @param type one of {@link #ALOGI}, {@link #ALOGE}, {@link #ALOGV} - * @param tag - * @return + * @param type one of {@link #ALOGI}, {@link #ALOGE}, {@link #ALOGV}, {@link #ALOGW} + * @param tag the tag the log entry will be printed under + * @return the event itself */ public Event printLog(@LogType int type, String tag) { switch (type) { @@ -191,6 +203,32 @@ public class EventLogger { } /** + * Causes the string message for the event to appear in the system log. + * @param type one of {@link #ALOGI}, {@link #ALOGE}, {@link #ALOGV}, {@link #ALOGW} + * @param tag the tag the log entry will be printed under + * @return the event itself + * @see #printLog(int, String) + */ + public Event printSlog(@LogType int type, String tag) { + switch (type) { + case ALOGI: + Slog.i(tag, eventToString()); + break; + case ALOGE: + Slog.e(tag, eventToString()); + break; + case ALOGW: + Slog.w(tag, eventToString()); + break; + case ALOGV: + default: + Slog.v(tag, eventToString()); + break; + } + return this; + } + + /** * Convert event to String. * This method is only called when the logger history is about to the dumped, * so this method is where expensive String conversions should be made, not when the Event |