55import android .content .Context ;
66import android .content .Intent ;
77import android .content .IntentFilter ;
8+ import android .media .AudioAttributes ;
89import android .net .ConnectivityManager ;
910import android .net .Network ;
1011import android .net .NetworkCapabilities ;
1314import android .net .wifi .WifiInfo ;
1415import android .net .wifi .WifiManager ;
1516import android .os .BatteryManager ;
17+ import android .os .Build ;
1618import android .os .Message ;
19+ import android .os .VibrationAttributes ;
20+ import android .os .VibrationEffect ;
21+ import android .os .Vibrator ;
1722import android .provider .Settings ;
1823import android .view .Window ;
1924import android .view .WindowManager ;
2025
2126public class PlatformUtils {
27+
28+ private static Vibrator vibrator ;
29+ private static boolean rumbleDisabled ;
30+
2231 public static boolean isBatterySupported () {
2332 Context context = SDLActivity .getContext ();
2433 Intent batteryIntent = context .registerReceiver (null , new IntentFilter (Intent .ACTION_BATTERY_CHANGED ));
@@ -122,4 +131,83 @@ public static float getAppScreenBrightness(Activity activity) {
122131 if (lp .screenBrightness < 0 ) return getSystemScreenBrightness (activity );
123132 return lp .screenBrightness ;
124133 }
134+
135+ public static void initDeviceRumble () {
136+ Context context = SDLActivity .getContext ();
137+ vibrator = (Vibrator ) context .getSystemService (Context .VIBRATOR_SERVICE );
138+ rumbleDisabled = (vibrator == null || !vibrator .hasVibrator ());
139+ // if (rumbleDisabled && BuildConfig.DEBUG)
140+ // {
141+ // Log.w("Rumble", "System does not have a Vibrator, or the permission is disabled. " +
142+ // "Rumble has been turned rest. Subsequent calls to static methods will have no effect.");
143+ // }
144+ }
145+
146+ public static void deviceRumble (short lowFreqMotor , short highFreqMotor ) {
147+ if (rumbleDisabled ) return ;
148+
149+ Context context = SDLActivity .getContext ();
150+
151+ short lowFreqMotorAdjusted = (short )(Math .min (lowFreqMotor , 0xFF ));
152+ short highFreqMotorAdjusted = (short )(Math .min (highFreqMotor , 0xFF ));
153+
154+ rumbleSingleVibrator (vibrator , lowFreqMotorAdjusted , highFreqMotorAdjusted );
155+ }
156+
157+ private static void rumbleSingleVibrator (Vibrator vibrator , short lowFreqMotor , short highFreqMotor ) {
158+ // Since we can only use a single amplitude value, compute the desired amplitude
159+ // by taking 80% of the big motor and 33% of the small motor, then capping to 255.
160+ // NB: This value is now 0-255 as required by VibrationEffect.
161+ // short lowFreqMotorMSB = (short)((lowFreqMotor >> 8) & 0xFF);
162+ // short highFreqMotorMSB = (short)((highFreqMotor >> 8) & 0xFF);
163+ int simulatedAmplitude = Math .min (255 , (int )((lowFreqMotor * 0.80 ) + (highFreqMotor * 0.33 )));
164+
165+ if (simulatedAmplitude == 0 ) {
166+ // This case is easy - just cancel the current effect and get out.
167+ // NB: We cannot simply check lowFreqMotor == highFreqMotor == 0
168+ // because our simulatedAmplitude could be 0 even though our inputs
169+ // are not (ex: lowFreqMotor == 0 && highFreqMotor == 1).
170+ vibrator .cancel ();
171+ return ;
172+ }
173+
174+ // Attempt to use amplitude-based control if we're on Oreo and the device
175+ // supports amplitude-based vibration control.
176+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
177+ if (vibrator .hasAmplitudeControl ()) {
178+ VibrationEffect effect = VibrationEffect .createOneShot (60000 , simulatedAmplitude );
179+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU ) {
180+ VibrationAttributes vibrationAttributes = new VibrationAttributes .Builder ()
181+ .setUsage (VibrationAttributes .USAGE_MEDIA )
182+ .build ();
183+ vibrator .vibrate (effect , vibrationAttributes );
184+ }
185+ else {
186+ AudioAttributes audioAttributes = new AudioAttributes .Builder ()
187+ .setUsage (AudioAttributes .USAGE_GAME )
188+ .build ();
189+ vibrator .vibrate (effect , audioAttributes );
190+ }
191+ return ;
192+ }
193+ }
194+
195+ // If we reach this point, we don't have amplitude controls available, so
196+ // we must emulate it by PWMing the vibration. Ick.
197+ long pwmPeriod = 20 ;
198+ long onTime = (long )((simulatedAmplitude / 255.0 ) * pwmPeriod );
199+ long offTime = pwmPeriod - onTime ;
200+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU ) {
201+ VibrationAttributes vibrationAttributes = new VibrationAttributes .Builder ()
202+ .setUsage (VibrationAttributes .USAGE_MEDIA )
203+ .build ();
204+ vibrator .vibrate (VibrationEffect .createWaveform (new long []{0 , onTime , offTime }, 0 ), vibrationAttributes );
205+ }
206+ else {
207+ AudioAttributes audioAttributes = new AudioAttributes .Builder ()
208+ .setUsage (AudioAttributes .USAGE_GAME )
209+ .build ();
210+ vibrator .vibrate (new long []{0 , onTime , offTime }, 0 , audioAttributes );
211+ }
212+ }
125213}
0 commit comments