diff --git a/extension/androidtools/media/AudioManager.hx b/extension/androidtools/media/AudioManager.hx new file mode 100644 index 00000000..176d50d2 --- /dev/null +++ b/extension/androidtools/media/AudioManager.hx @@ -0,0 +1,278 @@ +package extension.androidtools.media; + +import haxe.macro.Tools.TTypedExprTools; +#if (!android && !native) +#error 'extension-androidtools is not supported on your current platform' +#end +import extension.androidtools.jni.JNICache; +import lime.app.Event; +import lime.system.JNI; + +/** + * Utility class for managing volume and ringer mode control. + */ +class AudioManager +{ + /** + * Constant representing the stream type that can be used for changing volume of that stream: voice call. + */ + public static final STREAM_VOICE_CALL:Int = 0; + + /** + * Constant representing the stream type that can be used for changing volume of that stream: system sounds. + */ + public static final STREAM_SYSTEM:Int = 1; + + /** + * Constant representing the stream type that can be used for changing volume of that stream: phone ring. + */ + public static final STREAM_RING:Int = 2; + + /** + * Constant representing the stream type that can be used for changing volume of that stream: music playback. + */ + public static final STREAM_MUSIC:Int = 3; + + /** + * Constant representing the stream type that can be used for changing volume of that stream: alarm. + */ + public static final STREAM_ALARM:Int = 4; + + /** + * Constant representing the stream type that can be used for changing volume of that stream: notification. + */ + public static final STREAM_NOTIFICATION:Int = 5; + + /** + * Constant representing the stream type that can be used for changing volume of that stream: phone calls when connected to bluetooth. + */ + public static final STREAM_BLUETOOTH_SCO:Int = 6; + + /** + * Constant representing the stream type that can be used for changing volume of that stream: enforced system sounds. + */ + public static final STREAM_SYSTEM_ENFORCED:Int = 7; + + /** + * Constant representing the stream type that can be used for changing volume of that stream: DTMF Tones. + */ + public static final STREAM_DTMF:Int = 8; + + /** + * Constant representing the stream type that can be used for changing volume of that stream: tts transmitted through the speaker. + */ + public static final STREAM_TTS:Int = 9; + + /** + * Constant representing the stream type that can be used for changing volume of that stream: accessibility prompts. + */ + public static final STREAM_ACCESSIBILITY:Int = 10; + + /** + * Constant representing the stream type that can be used for changing volume of that stream: virtual assistant. + */ + public static final STREAM_ASSISTANT:Int = 11; + + /** + * Constant representing the direction of volume adjustment: raise the volume. + */ + public static final ADJUST_RAISE:Int = 1; + + /** + * Constant representing the direction of volume adjustment: lower the volume. + */ + public static final ADJUST_LOWER:Int = -1; + + /** + * Constant representing the direction of volume adjustment: same volume. + */ + public static final ADJUST_SAME:Int = 0; + + /** + * Constant representing the direction of volume adjustment: mute the volume. + */ + public static final ADJUST_MUTE:Int = -100; + + /** + * Constant representing the direction of volume adjustment: unmute the volume. + */ + public static final ADJUST_UNMUTE:Int = 100; + + /** + * Constant representing the direction of volume adjustment: toggle mute state. + */ + public static final ADJUST_TOGGLE_MUTE:Int = 101; + + /** + * Constant representing the flags for volume adjustment: show ui. + */ + public static final FLAG_SHOW_UI:Int = 1 << 0; + + /** + * Constant representing the flags for volume adjustment: include ringer modes. + */ + public static final FLAG_ALLOW_RINGER_MODES:Int = 1 << 1; + + /** + * Constant representing the flags for volume adjustment: play sound when changing volume. + */ + public static final FLAG_PLAY_SOUND:Int = 1 << 2; + + /** + * Constant representing the flags for volume adjustment: remove any sound and vibrate. + */ + public static final FLAG_REMOVE_SOUND_AND_VIBRATE:Int = 1 << 3; + + /** + * Constant representing the flags for volume adjustment: vibrate if going into the vibrate ringer mode. + */ + public static final FLAG_VIBRATE:Int = 1 << 4; + + /** + * Constant representing the flags for audio focus status: nothing changed. + */ + public static final AUDIOFOCUS_NONE:Int = 0; + + /** + * Constant representing the flags for audio focus status: gain of audio focus. + */ + public static final AUDIOFOCUS_GAIN:Int = 1; + + /** + * Constant representing the flags for audio focus status: gain of temporary audio focus. + */ + public static final AUDIOFOCUS_GAIN_TRANSIENT:Int = 2; + + /** + * Constant representing the flags for audio focus status: gain of temporary audio focus with ducking another app. + */ + public static final AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:Int = 3; + + /** + * Constant representing the flags for audio focus status: gain of exclusive temporary audio focus. + */ + public static final AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:Int = 4; + + /** + * Constant representing the flags for audio focus status: loss of audio focus. + */ + public static final AUDIOFOCUS_LOSS:Int = -1 * AUDIOFOCUS_GAIN; + + /** + * Constant representing the flags for audio focus status: loss of temporary audio focus. + */ + public static final AUDIOFOCUS_LOSS_TRANSIENT:Int = -1 * AUDIOFOCUS_GAIN_TRANSIENT; + + /** + * Constant representing the flags for audio focus status: loss of temporary audio focus with ducking another app. + */ + public static final AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:Int = -1 * AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK; + + /** + * Constant representing the flags for audio focus change result: unable to change focus. + */ + public static final AUDIOFOCUS_REQUEST_FAILED:Int = 0; + + /** + * Constant representing the flags for audio focus change result: focus was changed successfully. + */ + public static final AUDIOFOCUS_REQUEST_GRANTED:Int = 1; + + /** + * Constant representing the flags for audio focus change result: focus was changed successfully but delayed. + */ + public static final AUDIOFOCUS_REQUEST_DELAYED:Int = 2; + + /** + * Event that is triggered when the audio focus changes. + * + * Listeners can subscribe to this event to be notified when the audio focus state changes. + * + * The event handler receives a dynamic parameter containing information about the focus change. + */ + public static var onFocusChangeEvent(default, null):EventVoid> = new EventVoid>(); + + /** + * Adjusts the volume of a specified audio stream. + * + * @param streamType The type of audio stream to adjust (e.g., STREAM_MUSIC). + * @param direction The direction to adjust the volume (e.g., ADJUST_RAISE, ADJUST_LOWER). + * @param flags Additional operation flags (e.g., FLAG_SHOW_UI). + */ + public static function adjustStreamVolume(streamType:Int, direction:Int, flags:Int):Void + { + final adjustStreamVolumeJNI:Null = JNICache.createStaticMethod('org/haxe/extension/Tools', 'adjustStreamVolume', '(III)V'); + + if (adjustStreamVolumeJNI != null) + adjustStreamVolumeJNI(streamType, direction, flags); + } + + /** + * Retrieves the current volume index for a specified audio stream. + * + * @param streamType The type of audio stream (e.g., STREAM_MUSIC). + * @return The current volume index for the specified stream, or 0 if an error occurs. + */ + public static function getStreamVolume(streamType:Int):Int + { + final getStreamVolumeJNI:Null = JNICache.createStaticMethod('org/haxe/extension/Tools', 'getStreamVolume', '(I)I'); + + if (getStreamVolumeJNI != null) + return getStreamVolumeJNI(); + + return 0; + } + + /** + * Requests audio focus for a given stream and duration, and sets up a callback for focus changes. + * + * @param streamType The type of audio stream for which focus is requested. + * @param durationHint The duration of the audio focus request (e.g., AUDIOFOCUS_GAIN). + * @return The result of the audio focus request (e.g., AUDIOFOCUS_REQUEST_GRANTED or AUDIOFOCUS_REQUEST_FAILED). + */ + public static function requestAudioFocus(?focusChange:Int->Void, streamType:Int, durationHint:Int):Int + { + final requestAudioFocusJNI:Null = JNICache.createStaticMethod('org/haxe/extension/Tools', 'requestAudioFocus', + '(Lorg/haxe/lime/HaxeObject;II)I'); + + if (requestAudioFocusJNI != null) + return requestAudioFocusJNI(new OnAudioFocusChangeListener(), streamType, durationHint); + + return AUDIOFOCUS_REQUEST_FAILED; + } + + /** + * Abandons audio focus for the given callback. + * + * @return The result of the abandon audio focus request (e.g., AUDIOFOCUS_REQUEST_GRANTED or AUDIOFOCUS_REQUEST_FAILED). + */ + public static function abandonAudioFocus():Int + { + final abandonAudioFocusJNI:Null = JNICache.createStaticMethod('org/haxe/extension/Tools', 'abandonAudioFocus', + '(Lorg/haxe/lime/HaxeObject;)I'); + + if (abandonAudioFocusJNI != null) + return abandonAudioFocusJNI(new OnAudioFocusChangeListener()); + + return AUDIOFOCUS_REQUEST_FAILED; + } +} + +/** + * Listener class for handling audio focus changes. + */ +@:noCompletion +private class OnAudioFocusChangeListener #if (lime >= "8.0.0") implements JNISafety #end +{ + public function new():Void {} + + @:keep + #if (lime >= "8.0.0") + @:runOnMainThread + #end + public function onAudioFocusChange(focusChange:Int):Void + { + if (AudioManager.onFocusChangeEvent != null) + AudioManager.onFocusChangeEvent.dispatch(focusChange); + } +} diff --git a/project/androidtools/src/main/java/org/haxe/extension/Tools.java b/project/androidtools/src/main/java/org/haxe/extension/Tools.java index 079f2075..b9f918fc 100644 --- a/project/androidtools/src/main/java/org/haxe/extension/Tools.java +++ b/project/androidtools/src/main/java/org/haxe/extension/Tools.java @@ -10,6 +10,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.graphics.Rect; +import android.media.AudioManager; import android.media.MediaCodecList; import android.media.MediaFormat; import android.net.Uri; @@ -361,7 +362,7 @@ public static void requestPermissions(String[] permissions, int requestCode) { if (Extension.mainActivity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) ungrantedPermissions.add(permission); - } + } if (!ungrantedPermissions.isEmpty()) Extension.mainActivity.requestPermissions(ungrantedPermissions.toArray(new String[0]), requestCode); @@ -470,7 +471,7 @@ public void run() * Each rectangle represents an area of the display that is obstructed by the cutout. * * @return An array of Rect objects representing the bounding rectangles of the display cutout. - * Returns an empty array if there is no cutout or if cutouts are not supported on the device. + * Returns an empty array if there is no cutout or if cutouts are not supported on the device. */ public static Rect[] getCutoutDimensions() { @@ -598,13 +599,112 @@ public static File[] getObbDirs() } /** - * Retrieves the BatteryManager system service for managing battery-related information. + * Adjusts the volume of a specified audio stream. + * + * @param streamType The type of audio stream to adjust (e.g., AudioManager.STREAM_MUSIC). + * @param direction The direction to adjust the volume (e.g., AudioManager.ADJUST_RAISE, AudioManager.ADJUST_LOWER). + * @param flags Additional operation flags (e.g., AudioManager.FLAG_SHOW_UI). + */ + public static void adjustStreamVolume(final int streamType, final int direction, final int flags) + { + try + { + final AudioManager audioManager = (AudioManager) mainContext.getSystemService(Context.AUDIO_SERVICE); + + audioManager.adjustStreamVolume(streamType, direction, flags); + } + catch (Exception e) + { + Log.e(LOG_TAG, e.toString()); + } + } + + /** + * Retrieves the current volume index for a specified audio stream. + * + * @param streamType The type of audio stream (e.g., AudioManager.STREAM_MUSIC). + * @return The current volume index for the specified stream, or 0 if an error occurs. + */ + public static int getStreamVolume(final int streamType) + { + try + { + final AudioManager audioManager = (AudioManager) mainContext.getSystemService(Context.AUDIO_SERVICE); + + return audioManager.getStreamVolume(streamType); + } + catch (Exception e) + { + Log.e(LOG_TAG, e.toString()); + } + + return 0; + } + + /** + * Requests audio focus for a given stream and duration, and sets up a callback for focus changes. + * + * @param haxeCallbackObject The HaxeObject to receive audio focus change callbacks. + * @param streamType The type of audio stream for which focus is requested. + * @param durationHint The duration of the audio focus request (e.g., AudioManager.AUDIOFOCUS_GAIN). + * @return The result of the audio focus request (e.g., AudioManager.AUDIOFOCUS_REQUEST_GRANTED or AUDIOFOCUS_REQUEST_FAILED). + */ + public static int requestAudioFocus(final HaxeObject haxeCallbackObject, final int streamType, final int durationHint) + { + try + { + final AudioManager audioManager = (AudioManager) mainContext.getSystemService(Context.AUDIO_SERVICE); + + final AudioManager.OnAudioFocusChangeListener focusChangeListener = new AudioManager.OnAudioFocusChangeListener() + { + @Override + public void onAudioFocusChange(int focusChange) + { + if (haxeCallbackObject != null) + haxeCallbackObject.call1("onAudioFocusChange", focusChange); + } + }; + + return audioManager.requestAudioFocus(focusChangeListener, streamType, durationHint); + } + catch (Exception e) + { + Log.e(LOG_TAG, e.toString()); + } + + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; + } + + /** + * Abandons audio focus for the given HaxeObject callback. * - * @return A BatteryManager object for managing battery-related information. + * @param haxeCallbackObject The HaxeObject that was used to request audio focus. + * @return The result of the abandon audio focus request (e.g., AudioManager.AUDIOFOCUS_REQUEST_GRANTED or AUDIOFOCUS_REQUEST_FAILED). */ - public static BatteryManager getBatteryManager() + public static int abandonAudioFocus(final HaxeObject haxeCallbackObject) { - return (BatteryManager) mainContext.getSystemService(Context.BATTERY_SERVICE); + try + { + final AudioManager audioManager = (AudioManager) mainContext.getSystemService(Context.AUDIO_SERVICE); + + final AudioManager.OnAudioFocusChangeListener focusChangeListener = new AudioManager.OnAudioFocusChangeListener() + { + @Override + public void onAudioFocusChange(int focusChange) + { + if (haxeCallbackObject != null) + haxeCallbackObject.call1("onAudioFocusChange", focusChange); + } + }; + + return audioManager.abandonAudioFocus(focusChangeListener); + } + catch (Exception e) + { + Log.e(LOG_TAG, e.toString()); + } + + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } @Override