package me.barrasso.android.volume.utils; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.media.AudioManager; import android.media.AudioRoutesInfo; import android.media.IAudioRoutesObserver; import android.os.Build; import android.os.Handler; import android.os.IInterface; import android.os.Message; import android.os.SystemClock; import android.os.Vibrator; import android.provider.Settings; import android.text.TextUtils; import android.view.KeyEvent; import java.lang.reflect.*; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import me.barrasso.android.volume.media.VolumeMediaReceiver; import me.barrasso.android.volume.popup.PopupWindowManager; import static me.barrasso.android.volume.LogUtils.LOGD; import static me.barrasso.android.volume.LogUtils.LOGE; import static me.barrasso.android.volume.LogUtils.LOGI; public final class AudioHelper { public static final String TAG = AudioHelper.class.getSimpleName(); private static Method mDispatchMediaKeyEvent; private static IInterface sService; public static IInterface getService() { if (null == sService) sService = ReflectionUtils.getIInterface(Context.AUDIO_SERVICE, "android.media.IAudioService$Stub"); return sService; } public static void freeResources() { mMethodMap.clear(); } public static class DefaultHashMap<K,V> extends HashMap<K,V> { protected V defaultValue; public DefaultHashMap(V defaultValue) { this.defaultValue = defaultValue; } @Override public V get(Object k) { return containsKey(k) ? super.get(k) : defaultValue; } } // Cache Reflection calls to optimize frequent calls. private static WeakHashMap<String, Method> mMethodMap = new WeakHashMap<String, Method>(); private static Map<String, Boolean> mMethodSuccessMap = new DefaultHashMap<String, Boolean>(true); /** @return The name used to uniquely store and look up a cached method. */ private static String methodMapName(Class<?> clazz, String methodName) { return clazz.getName() + '#' + methodName; } private static boolean shouldTryMethod(String methodMapName) { return mMethodSuccessMap.get(methodMapName); } /** * Information available from AudioService about the current routes. * @hide */ public static final int MAIN_SPEAKER = 0; public static final int MAIN_HEADSET = 1<<0; public static final int MAIN_HEADPHONES = 1<<1; public static final int MAIN_DOCK_SPEAKERS = 1<<2; public static final int MAIN_HDMI = 1<<3; /** @return an android.media.AudioRoutesInfo Object upon successful registration. */ public static AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) { IInterface service = getService(); if (null == service) return null; try { Method watch = service.getClass().getDeclaredMethod("startWatchingRoutes", IAudioRoutesObserver.class); if (null != watch) { watch.setAccessible(true); // Info the listener immediately of the current route. AudioRoutesInfo info = (AudioRoutesInfo) watch.invoke(service, observer); observer.dispatchAudioRoutesChanged(info); return info; } } catch (Throwable t) { LOGE(TAG, "Error invoking android.media.IAudioService#startWatchingRoutes", t); } return null; } protected static AudioRoutesObserver mAudioRoutesObserver; protected static synchronized AudioRoutesObserver getAudioRoutesObserver() { if (null == mAudioRoutesObserver) mAudioRoutesObserver = new AudioRoutesObserver(); return mAudioRoutesObserver; } protected static class AudioRoutesObserver extends IAudioRoutesObserver.Stub { @Override public void dispatchAudioRoutesChanged(AudioRoutesInfo info) { if (null != mHelper) mHelper.dispatchAudioRoutesChanged(info); } } /** @return The value for a static integer flag, or null. */ public static Integer _getInternalResourceIdentifier(final String name, final String type) { try { Class<?> clazz = Class.forName("com.android.internal.R$" + type); if (null != clazz) { Field aFlag = clazz.getField(name); if (null != aFlag) { aFlag.setAccessible(true); Integer ret = (Integer) aFlag.get(null); LOGI(TAG, clazz.getName() + '#' + name + '=' + String.valueOf(ret)); return ret; } } } catch (Throwable e) { LOGE(TAG, "Error retrieving " + "com.android.internal.R$" + type + "#" + name + " flag.", e); } return null; } public static boolean _isVoiceCapable() { Integer id = _getInternalResourceIdentifier("config_voice_capable", "bool"); return (null == id) || Resources.getSystem().getBoolean(id); } public static boolean _useFixedVolume() { Integer id = _getInternalResourceIdentifier("config_useFixedVolume", "bool"); return (null != id) && Resources.getSystem().getBoolean(id); } public static boolean _useMasterVolume() { Integer id = _getInternalResourceIdentifier("config_useMasterVolume", "bool"); return (null != id) && Resources.getSystem().getBoolean(id); } public static boolean _safeVolumeEnabled() { Integer id = _getInternalResourceIdentifier("config_safe_media_volume_enabled", "bool"); return (null != id) && Resources.getSystem().getBoolean(id); } public static int _safeVolumeIndex() { // The default safe volume index read here will be replaced by the actual value when // the mcc is read by onConfigureSafeVolume() Integer id = _getInternalResourceIdentifier("config_safe_media_volume_index", "integer"); return (null == id) ? -1 : Resources.getSystem().getInteger(id); } public static int _cameraSoundForced() { Integer id = _getInternalResourceIdentifier("config_camera_sound_forced", "bool"); return (null == id) ? -1 : Resources.getSystem().getInteger(id); } public static boolean _dispatchMediaKeyEvent(KeyEvent event) { try { IInterface service = getService(); if (null == mDispatchMediaKeyEvent) mDispatchMediaKeyEvent = service.getClass().getDeclaredMethod( "dispatchMediaKeyEvent", KeyEvent.class); if (null != mDispatchMediaKeyEvent) { mDispatchMediaKeyEvent.setAccessible(true); mDispatchMediaKeyEvent.invoke(service, event); return true; } } catch (Throwable t) { LOGE(TAG, "Error dispatchMediaKeyEvent()", t); } return false; } @TargetApi(Build.VERSION_CODES.KITKAT) public static void dispatchMediaKeyEvent(AudioManager manager, KeyEvent event) { manager.dispatchMediaKeyEvent(event); } public static boolean _dispatchMediaKeyEvent(AudioManager mManager, int keyCode) { long eventtime = SystemClock.uptimeMillis(); KeyEvent keyDown = new KeyEvent(eventtime, eventtime, KeyEvent.ACTION_DOWN, keyCode, 0); KeyEvent keyUp = KeyEvent.changeAction(keyDown, KeyEvent.ACTION_UP); // KitKat this API is public, so use it! if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { dispatchMediaKeyEvent(mManager, keyUp); dispatchMediaKeyEvent(mManager, keyDown); return true; } // Otherwise try dispatching using Reflection. return (_dispatchMediaKeyEvent(keyDown) && _dispatchMediaKeyEvent(keyUp)); } private static Boolean IS_HTC = null; /** * @return True if the device was made by HTC, has HTC Sense, etc. */ public static boolean isHTC(Context context) { if (null != IS_HTC) return IS_HTC; IS_HTC = _isHTC(context); return IS_HTC; } private static boolean _isHTC(Context context) { // CHECK: Build prop to see if HTC is there. if (Build.MANUFACTURER.contains("HTC")) return true; // CHECK: available features, like HTC sense. FeatureInfo[] features = context.getPackageManager().getSystemAvailableFeatures(); for (FeatureInfo feature : features) { if (!TextUtils.isEmpty(feature.name) && feature.name.startsWith("com.htc")) { return true; } } // CHECK: the HTC Sense launcher package. PackageManager pm = context.getPackageManager(); Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_HOME); List<ResolveInfo> list = pm.queryIntentActivities( intent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo info : list) { if (info.activityInfo != null) { if ("com.htc.launcher.Launcher".equals(info.activityInfo.name)) { return true; } } } return false; } /** * android.media.IAudioService#dispatchMediaKeyEvent(KeyEvent), except if this * method fails for any reason, fall back on broadcasting an event. */ public boolean dispatchMediaKeyEvent(Context mContext, int keyCode) { // We'll try the public API for API 19+, then the reflected API, then // finally we'll resort to broadcasting the action ourselves! if (isHTC(mContext) || !_dispatchMediaKeyEvent(mManager, keyCode)) { long eventtime = SystemClock.uptimeMillis(); KeyEvent keyDown = new KeyEvent(eventtime, eventtime, KeyEvent.ACTION_DOWN, keyCode, 0); KeyEvent keyUp = KeyEvent.changeAction(keyDown, KeyEvent.ACTION_UP); Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyDown); mContext.sendOrderedBroadcast(keyIntent, null); keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyUp); mContext.sendOrderedBroadcast(keyIntent, null); return false; } return true; } /** Use to send {@link android.content.Intent#ACTION_MEDIA_BUTTON} to this application. */ public static void dispatchMediaKeyEventSelf(Context mContext, int keyCode) { long eventtime = SystemClock.uptimeMillis(); KeyEvent keyDown = new KeyEvent(eventtime, eventtime, KeyEvent.ACTION_DOWN, keyCode, 0); KeyEvent keyUp = KeyEvent.changeAction(keyDown, KeyEvent.ACTION_UP); Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); keyIntent.setPackage(mContext.getPackageName()); keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyDown); mContext.sendOrderedBroadcast(keyIntent, null); keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyUp); mContext.sendOrderedBroadcast(keyIntent, null); } /** @return The value for a static integer flag, or null. */ public static Integer _getStaticFlag(final String clazzName, final String flagName) { try { Class<?> clazz = Class.forName(clazzName); if (null != clazz) { Field aFlag = clazz.getField(flagName); if (null != aFlag) { aFlag.setAccessible(true); Integer ret = (Integer) aFlag.get(null); LOGD(TAG, clazzName + '#' + flagName + '=' + String.valueOf(ret)); return ret; } } } catch (Throwable e) { LOGE(TAG, "Error retrieving " + clazzName + " flag.", e); } return null; } public static int getAudioServiceFlag(String flagName, int defVal) { Integer ret = _getStaticFlag("android.media.AudioService", flagName); return ((ret == null) ? defVal : ret); } public static int getAudioSystemFlag(String flagName, int defVal) { Integer ret = _getStaticFlag("android.media.AudioSystem", flagName); return ((ret == null) ? defVal : ret); } public static int getAudioManagerFlag(String flagName, int defVal) { Integer ret = _getStaticFlag(AudioManager.class.getName(), flagName); return ((ret == null) ? defVal : ret); } public static Class<?>[] fromObjects(Object[] objs) { if (null == objs) return null; Class<?>[] clazz = new Class<?>[objs.length]; for (int i = 0; i < clazz.length; ++i) clazz[i] = simplifyClass(objs[i].getClass()); return clazz; } public static Class<?> simplifyClass(Class<?> clazz) { if (Integer.class.equals(clazz)) return Integer.TYPE; else if (Boolean.class.equals(clazz)) return Boolean.TYPE; else if (Long.class.equals(clazz)) return Long.TYPE; else if (Double.class.equals(clazz)) return Double.TYPE; else if (Byte.class.equals(clazz)) return Byte.TYPE; else if (Short.class.equals(clazz)) return Short.TYPE; else if (Float.class.equals(clazz)) return Float.TYPE; return clazz; } public static Integer _intMethod(AudioManager manager, String methodName, Object[] vals) { return (Integer) _audioMethod(manager, methodName, vals); } public static Boolean _boolMethod(AudioManager manager, String methodName, Object[] vals) { return (Boolean) _audioMethod(manager, methodName, vals); } public static Object _audioMethod(AudioManager manager, String methodName, Object[] vals) { String methodMapName = methodMapName(AudioManager.class, methodName); try { if (!shouldTryMethod(methodMapName)) return null; boolean hadMethod = mMethodMap.containsKey(methodMapName); Method bMethod = (hadMethod) ? mMethodMap.get(methodMapName) : AudioManager.class.getDeclaredMethod(methodName, fromObjects(vals)); if (null != bMethod) { bMethod.setAccessible(true); if (!hadMethod) mMethodMap.put(methodMapName, bMethod); Object ret = bMethod.invoke(manager, vals); if (null != ret) { mMethodSuccessMap.put(methodMapName, true); return ret; } } } catch (Throwable t) { LOGE(TAG, "Error invoking AudioManager#" + methodName, t); mMethodSuccessMap.put(methodMapName, false); return null; } return new Object(); } public static Boolean _boolServiceMethod(AudioManager manager, String methodName, Object[] vals) { return (Boolean) _audioServiceMethod(manager, methodName, vals); } public static Integer _intServiceMethod(AudioManager manager, String methodName, Object[] vals) { return (Integer) _audioServiceMethod(manager, methodName, vals); } /** * Analog for AudioSystem.isStreamActive(int, int), used to determine if a stream is active. */ public static Boolean isStreamActive(int stream, int inPastMs) { String clazz = "android.media.AudioSystem"; String methodMapName = clazz + "#isStreamActive"; try { if (!shouldTryMethod(methodMapName)) return null; boolean hadMethod = mMethodMap.containsKey(methodMapName); Method mMethod = null; if (hadMethod) mMethod = mMethodMap.get(methodMapName); else { Class<?> aSystem = Class.forName(clazz); mMethod = aSystem.getDeclaredMethod("isStreamActive", Integer.TYPE, Integer.TYPE); } if (null != mMethod) { mMethod.setAccessible(true); if (!hadMethod) mMethodMap.put(methodMapName, mMethod); Object ret = mMethod.invoke(null, stream, inPastMs); mMethodSuccessMap.put(methodMapName, null != ret); return (Boolean) ret; } } catch (Throwable t) { LOGE(TAG, "Error invoking AudioSystem#isStreamActive(int, int)", t); mMethodSuccessMap.put(methodMapName, false); } return null; } /** * @return Null if an error occurred, else it returns an empty {@link java.lang.Object} if the * method succeed but has no return (void) value, or the value returned. */ public static Object _audioServiceMethod(AudioManager manager, String methodName, Object[] vals) { try { IInterface service = _getAudioService(manager); if (null != service) { String methodMapName = methodMapName(service.getClass(), methodName); if (!shouldTryMethod(methodMapName)) return null; boolean hadMethod = mMethodMap.containsKey(methodMapName); Method bMethod = (hadMethod) ? mMethodMap.get(methodMapName) : service.getClass().getDeclaredMethod(methodName, fromObjects(vals)); if (null != bMethod) { bMethod.setAccessible(true); if (!hadMethod) mMethodMap.put(methodMapName, bMethod); Object ret = bMethod.invoke(service, vals); mMethodSuccessMap.put(methodMapName, null != ret); if (null != ret) return ret; } } } catch (Throwable t) { LOGE(TAG, "Error invoking AudioService#" + methodName, t); return null; } return new Object(); } public static Object _iaudioServiceMethod(String methodName, Object[] vals) { try { IInterface service = getService(); if (null != service) { String methodMapName = methodMapName(service.getClass(), methodName); if (!shouldTryMethod(methodMapName)) return null; boolean hadMethod = mMethodMap.containsKey(methodMapName); Method bMethod = (hadMethod) ? mMethodMap.get(methodMapName) : service.getClass().getDeclaredMethod(methodName, fromObjects(vals)); if (null != bMethod) { bMethod.setAccessible(true); if (!hadMethod) mMethodMap.put(methodMapName, bMethod); Object ret = bMethod.invoke(service, vals); mMethodSuccessMap.put(methodMapName, null != ret); if (null != ret) return ret; } } } catch (Throwable t) { LOGE(TAG, "Error invoking AudioService#" + methodName, t); return null; } return new Object(); } @TargetApi(Build.VERSION_CODES.KITKAT) public static String _stringRemoteMethod(Object controller, String methodName, Object[] vals) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return null; // Not supported try { Object ret = _remoteControllerMethod(controller, methodName, vals); if (null == ret) return null; if (ret instanceof String) return (String) ret; } catch (ClassCastException cce) { LOGE(TAG, "Error casting to String RemoteController#" + methodName, cce); } return null; } @TargetApi(Build.VERSION_CODES.KITKAT) public static Object _remoteControllerMethod(Object controller, String methodName, Object[] vals) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return null; // Not supported String methodMapName = "android.media.RemoteController" + '#' +methodName; try { if (!shouldTryMethod(methodMapName)) return null; boolean hadMethod = mMethodMap.containsKey(methodMapName); Class<?> rContClass = Class.forName("android.media.RemoteController"); Method bMethod = (hadMethod) ? mMethodMap.get(methodMapName) : rContClass.getDeclaredMethod(methodName, fromObjects(vals)); if (null != bMethod && controller.getClass().equals(rContClass)) { bMethod.setAccessible(true); if (!hadMethod) mMethodMap.put(methodMapName, bMethod); Object ret = bMethod.invoke(controller, vals); if (null != ret) { mMethodSuccessMap.put(methodMapName, true); return ret; } } } catch (Throwable t) { LOGE(TAG, "Error invoking RemoteController#" + methodName, t); mMethodSuccessMap.put(methodMapName, false); return null; } return new Object(); } public static IInterface _getAudioService(AudioManager manager) { String methodMapName = methodMapName(AudioManager.class, "getService"); try { boolean hadMethod = mMethodMap.containsKey(methodMapName); if (!shouldTryMethod(methodMapName)) return null; Method bMethod = (hadMethod) ? mMethodMap.get(methodMapName) : AudioManager.class.getDeclaredMethod("getService"); if (null != bMethod) { bMethod.setAccessible(true); if (!hadMethod) mMethodMap.put(methodMapName, bMethod); mMethodSuccessMap.put(methodMapName, true); return (IInterface) bMethod.invoke(null); } } catch (Throwable t) { LOGE(TAG, "Error retrieving AudioService", t); mMethodSuccessMap.put(methodMapName, false); } return null; } // mSafeMediaVolumeIndex is the cached value of config_safe_media_volume_index property private int mSafeMediaVolumeIndex; private boolean mSafeMediaVolumeEnabled; // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced, private static final int mSafeMediaVolumeDevices = Constants.DEVICE_OUT_WIRED_HEADSET | Constants.DEVICE_OUT_WIRED_HEADPHONE; /** @return True if safe volume won't have an effect on this volume change event. */ public boolean checkSafeMediaVolume(int streamType, int index, int device) { if ((streamType == AudioManager.STREAM_MUSIC) && ((device & mSafeMediaVolumeDevices) != 0) && ((index - mSafeMediaVolumeIndex) == 1)) { return false; } return true; } /** @return True if safe media volume is enabled. */ public boolean isSafeMediaVolumeEnabled(Context context) { // NOTE: We check system-wide setting, CyanogenMod override, then finally the state. if (!mSafeMediaVolumeEnabled) return false; Boolean cmOverride = isCyanogenModSafeVolumeEnabled(context); if (null != cmOverride) return cmOverride; final int state = Settings.Global.getInt(context.getContentResolver(), Constants.AUDIO_SAFE_VOLUME_STATE, Constants.SAFE_MEDIA_VOLUME_NOT_CONFIGURED); LOGI(TAG, Constants.AUDIO_SAFE_VOLUME_STATE + '=' + state); return (state == Constants.SAFE_MEDIA_VOLUME_ACTIVE); } private Boolean isCyanogenModSafeVolumeEnabled(Context context) { // This is a CyanogenMod-specific modification found as far as KitKat (perhaps further). // Android "L" Preview introduced IAudioService#disableSafeMediaVolume() as a public method, // but it is guarded by the STATUS_BAR_SERVICE permission. try { final int enabled = Settings.System.getInt(context.getContentResolver(), Constants.SAFE_HEADSET_VOLUME); return (enabled == 1); } catch (Settings.SettingNotFoundException sne) { LOGE(TAG, "Could not find Setting.System#" + Constants.SAFE_HEADSET_VOLUME); return null; } } private boolean mUseMasterVolume; private boolean mUseFixedVolume; private boolean mHasVibrator; private boolean mVoiceCapable; protected int mRingerModeAffectedStreams; private Handler mHandler; private AudioManager mManager; private Vibrator vibrator; protected AudioRoutesInfo mAudioRoutesInfo; private static AudioHelper mHelper; public static synchronized AudioHelper getHelper(Context context, AudioManager manager) { if (null == mHelper) mHelper = new AudioHelper(context, manager); // Register to watch for changes in AudioRoutesInfo. startWatchingRoutes(getAudioRoutesObserver()); return mHelper; } public AudioHelper(Context context, AudioManager manager) { if (null == manager) manager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mManager = manager; mVoiceCapable = _isVoiceCapable(); mUseFixedVolume = _useFixedVolume(); mUseMasterVolume = _useMasterVolume(); mSafeMediaVolumeIndex = _safeVolumeIndex(); mSafeMediaVolumeEnabled = _safeVolumeEnabled(); vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); mHasVibrator = (null != vibrator && vibrator.hasVibrator()); updateRingerModeAffectedStreams(context); } public void vibrate(long milliseconds) { if (mHasVibrator) vibrator.vibrate(milliseconds); } public void setHandler(Handler handler) { mHandler = handler; dispatchAudioRoutesChanged(mAudioRoutesInfo); } protected void dispatchAudioRoutesChanged(AudioRoutesInfo info) { mAudioRoutesInfo = info; if (null != mHandler) { Message.obtain(mHandler, VolumeMediaReceiver.MSG_AUDIO_ROUTES_CHANGED, info).sendToTarget(); } } public boolean isVoiceCapable() { return mVoiceCapable; } public boolean hasVibrator() { return mHasVibrator; } public boolean useFixedVolume() { return mUseFixedVolume; } public boolean useMasterVolume() { return mUseMasterVolume; } public int getSafeMediaVolumeIndex() { return mSafeMediaVolumeIndex; } public boolean isLocalOrRemoteMusicActive() { Boolean isActive; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { isActive = (boolMethod("isLocalOrRemoteMusicActive", null)); // KitKat+ } else { isActive = (boolMethod("isMusicActiveRemotely", null)); // JellyBean MR2, Lollipop } if (null == isActive) isActive = mManager.isMusicActive(); return isActive; } protected Method wmCsd; public void closeSystemDialogs(Context context, String reason) { LOGI(TAG, "closeSystemDialogs(" + reason + ')'); IInterface wm = PopupWindowManager.getIWindowManager(); try { if (null == wmCsd) wmCsd = wm.getClass().getDeclaredMethod("closeSystemDialogs", String.class); if (null != wmCsd) { wmCsd.setAccessible(true); wmCsd.invoke(wm, reason); return; } } catch (Throwable t) { LOGE(TAG, "Could not invoke IWindowManager#closeSystemDialogs"); } // Backup is to send the intent ourselves. Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); intent.putExtra("reason", reason); context.sendBroadcast(intent); } public void setWiredDeviceConnectionState(int device, int state, String name) { _iaudioServiceMethod("setWiredDeviceConnectionState", new Object[] { device, state, name }); } /** * forces the stream controlled by hard volume keys * specifying streamType == -1 releases control to the * logic. * * @hide */ public void forceVolumeControlStream(final int streamType) { _audioMethod(mManager, "forceVolumeControlStream", new Integer[] { streamType }); } public Boolean boolMethod(String methodName, Object[] vals) { return _boolMethod(mManager, methodName, vals); } public Integer intMethod(String methodName, Object[] vals) { return _intMethod(mManager, methodName, vals); } public Object audioMethod(String methodName, Object[] vals) { return _audioMethod(mManager, methodName, vals); } public Object serviceMethod(String methodName, Object[] vals) { return _audioServiceMethod(mManager, methodName, vals); } public Integer intServiceMethod(String methodName, Object[] vals) { return _intServiceMethod(mManager, methodName, vals); } public Integer intIServiceMethod(String methodName, Object[] vals) { return (Integer) _iaudioServiceMethod(methodName, vals); } public Boolean boolServiceMethod(String methodName, Object[] vals) { return _boolServiceMethod(mManager, methodName, vals); } // NOTE: this method was added on Android "L" Preview, so it's best to use it. private Boolean _isStreamAffectedByRingerMode(int streamType) { Object res = _iaudioServiceMethod("isStreamAffectedByRingerMode", new Integer[] { streamType }); if (null != res && res instanceof Boolean) return (Boolean) res; return null; } public boolean isStreamAffectedByRingerMode(int streamType) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Boolean res = _isStreamAffectedByRingerMode(streamType); if (null != res) return res; } return (mRingerModeAffectedStreams & (1 << streamType)) != 0; } boolean updateRingerModeAffectedStreams(Context context) { int ringerModeAffectedStreams; // make sure settings for ringer mode are consistent with device type: non voice capable // devices (tablets) include media stream in silent mode whereas phones don't. ringerModeAffectedStreams = Settings.System.getInt(context.getContentResolver(), Settings.System.MODE_RINGER_STREAMS_AFFECTED, ((1 << AudioManager.STREAM_RING)|(1 << AudioManager.STREAM_NOTIFICATION)| (1 << AudioManager.STREAM_SYSTEM))); // ringtone, notification and system streams are always affected by ringer mode ringerModeAffectedStreams |= (1 << AudioManager.STREAM_RING)| (1 << AudioManager.STREAM_NOTIFICATION)| (1 << AudioManager.STREAM_SYSTEM); if (mVoiceCapable) { ringerModeAffectedStreams &= ~(1 << AudioManager.STREAM_MUSIC); } else { ringerModeAffectedStreams |= (1 << AudioManager.STREAM_MUSIC); } if (ringerModeAffectedStreams != mRingerModeAffectedStreams) { mRingerModeAffectedStreams = ringerModeAffectedStreams; return true; } return false; } }