318 lines
11 KiB
Java
318 lines
11 KiB
Java
package com.warren.iwanttoheal;
|
|
|
|
import android.content.Intent;
|
|
import android.content.SharedPreferences;
|
|
import android.content.pm.PackageInfo;
|
|
import android.content.pm.PackageManager;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.os.SystemClock;
|
|
import android.view.InputDevice;
|
|
import android.view.KeyEvent;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import com.getcapacitor.BridgeActivity;
|
|
import java.io.File;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
public abstract class ControllerBridgeActivity extends BridgeActivity {
|
|
|
|
public static final String EXTRA_INITIAL_URL = "com.warren.iwanttoheal.INITIAL_URL";
|
|
private static final String PREFS_NAME = "com.warren.iwanttoheal.performance";
|
|
private static final String CACHE_CLEARED_VERSION_KEY = "webview_cache_cleared_version";
|
|
private static final float AXIS_DEAD_ZONE = 0.45f;
|
|
private static final long DPAD_THROTTLE_MS = 55;
|
|
private final long[] lastDpadDispatchAt = new long[16];
|
|
private final Set<String> activeMotionTokens = new HashSet<>();
|
|
private final Map<String, Long> lastMotionDispatchAt = new HashMap<>();
|
|
|
|
@Override
|
|
public void onCreate(Bundle savedInstanceState) {
|
|
boolean shouldClearWebViewCache = shouldClearWebViewCacheForCurrentVersion();
|
|
if (shouldClearWebViewCache) clearWebViewServiceWorkers();
|
|
super.onCreate(savedInstanceState);
|
|
AndroidDisplayPerformance.preferBatteryRefreshRate(getWindow(), getWindowManager().getDefaultDisplay());
|
|
if (bridge != null) {
|
|
if (shouldClearWebViewCache) {
|
|
bridge.getWebView().clearCache(true);
|
|
markWebViewCacheClearedForCurrentVersion();
|
|
}
|
|
}
|
|
loadIntentUrl();
|
|
}
|
|
|
|
@Override
|
|
protected void onNewIntent(Intent intent) {
|
|
super.onNewIntent(intent);
|
|
setIntent(intent);
|
|
loadIntentUrl();
|
|
}
|
|
|
|
@Override
|
|
public void onWindowFocusChanged(boolean hasFocus) {
|
|
super.onWindowFocusChanged(hasFocus);
|
|
if (hasFocus) {
|
|
enableImmersiveMode();
|
|
AndroidDisplayPerformance.preferBatteryRefreshRate(
|
|
getWindow(),
|
|
getWindowManager().getDefaultDisplay()
|
|
);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean dispatchTouchEvent(MotionEvent event) {
|
|
if (
|
|
event.getActionMasked() == MotionEvent.ACTION_DOWN
|
|
&& bridge != null
|
|
&& bridge.getWebView() != null
|
|
) {
|
|
bridge.getWebView().requestFocus();
|
|
}
|
|
return super.dispatchTouchEvent(event);
|
|
}
|
|
|
|
private void loadIntentUrl() {
|
|
if (bridge == null || getIntent() == null) return;
|
|
String initialUrl = getIntent().getStringExtra(EXTRA_INITIAL_URL);
|
|
if (initialUrl == null || initialUrl.isEmpty()) return;
|
|
bridge.getWebView().post(() -> bridge.getWebView().loadUrl(initialUrl));
|
|
}
|
|
|
|
private void enableImmersiveMode() {
|
|
getWindow().getDecorView().setSystemUiVisibility(
|
|
View.SYSTEM_UI_FLAG_FULLSCREEN
|
|
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
|
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
|
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
);
|
|
}
|
|
|
|
private void clearWebViewServiceWorkers() {
|
|
File webViewData = new File(getApplicationInfo().dataDir, "app_webview");
|
|
deleteIfExists(new File(webViewData, "Default/Service Worker"));
|
|
deleteIfExists(new File(webViewData, "Service Worker"));
|
|
}
|
|
|
|
private boolean shouldClearWebViewCacheForCurrentVersion() {
|
|
return performancePreferences().getLong(CACHE_CLEARED_VERSION_KEY, -1L)
|
|
!= currentVersionCode();
|
|
}
|
|
|
|
private void markWebViewCacheClearedForCurrentVersion() {
|
|
performancePreferences()
|
|
.edit()
|
|
.putLong(CACHE_CLEARED_VERSION_KEY, currentVersionCode())
|
|
.apply();
|
|
}
|
|
|
|
private SharedPreferences performancePreferences() {
|
|
return getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
|
|
}
|
|
|
|
private long currentVersionCode() {
|
|
try {
|
|
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
return packageInfo.getLongVersionCode();
|
|
}
|
|
return packageInfo.versionCode;
|
|
} catch (PackageManager.NameNotFoundException exception) {
|
|
return -1L;
|
|
}
|
|
}
|
|
|
|
private void deleteIfExists(File file) {
|
|
if (!file.exists()) return;
|
|
if (file.isDirectory()) {
|
|
File[] children = file.listFiles();
|
|
if (children != null) {
|
|
for (File child : children) {
|
|
deleteIfExists(child);
|
|
}
|
|
}
|
|
}
|
|
file.delete();
|
|
}
|
|
|
|
@Override
|
|
public boolean dispatchKeyEvent(KeyEvent event) {
|
|
String token = controllerToken(event.getKeyCode());
|
|
if (token == null || bridge == null) return super.dispatchKeyEvent(event);
|
|
|
|
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
|
boolean repeat = event.getRepeatCount() > 0;
|
|
if (isDpadToken(token) && shouldThrottleDpad(token)) return true;
|
|
dispatchNativeControllerToken(token, repeat);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean dispatchGenericMotionEvent(MotionEvent event) {
|
|
if (
|
|
bridge == null
|
|
|| event.getActionMasked() != MotionEvent.ACTION_MOVE
|
|
|| !isControllerMotionEvent(event)
|
|
) {
|
|
return super.dispatchGenericMotionEvent(event);
|
|
}
|
|
|
|
Set<String> currentTokens = new HashSet<>();
|
|
float leftStickX = event.getAxisValue(MotionEvent.AXIS_X);
|
|
float leftStickY = event.getAxisValue(MotionEvent.AXIS_Y);
|
|
dispatchNativeControllerMotion(leftStickX, leftStickY);
|
|
addAxisTokens(
|
|
currentTokens,
|
|
event.getAxisValue(MotionEvent.AXIS_HAT_X),
|
|
"Button14",
|
|
"Button15"
|
|
);
|
|
addAxisTokens(
|
|
currentTokens,
|
|
event.getAxisValue(MotionEvent.AXIS_HAT_Y),
|
|
"Button12",
|
|
"Button13"
|
|
);
|
|
addAxisTokens(
|
|
currentTokens,
|
|
leftStickX,
|
|
"Axis0-",
|
|
"Axis0+"
|
|
);
|
|
addAxisTokens(
|
|
currentTokens,
|
|
leftStickY,
|
|
"Axis1-",
|
|
"Axis1+"
|
|
);
|
|
|
|
boolean hadMotionTokens = !activeMotionTokens.isEmpty();
|
|
long now = SystemClock.uptimeMillis();
|
|
for (String token : currentTokens) {
|
|
boolean repeat = activeMotionTokens.contains(token);
|
|
long lastDispatchAt = lastMotionDispatchAt.containsKey(token)
|
|
? lastMotionDispatchAt.get(token)
|
|
: 0L;
|
|
if (!repeat || now - lastDispatchAt >= DPAD_THROTTLE_MS) {
|
|
dispatchNativeControllerToken(token, repeat);
|
|
lastMotionDispatchAt.put(token, now);
|
|
}
|
|
}
|
|
activeMotionTokens.clear();
|
|
activeMotionTokens.addAll(currentTokens);
|
|
|
|
if (currentTokens.isEmpty() && !hadMotionTokens) {
|
|
return super.dispatchGenericMotionEvent(event);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private boolean isControllerMotionEvent(MotionEvent event) {
|
|
int source = event.getSource();
|
|
return (source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK
|
|
|| (source & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD
|
|
|| (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD;
|
|
}
|
|
|
|
private void addAxisTokens(
|
|
Set<String> tokens,
|
|
float value,
|
|
String negativeToken,
|
|
String positiveToken
|
|
) {
|
|
if (value <= -AXIS_DEAD_ZONE) tokens.add(negativeToken);
|
|
if (value >= AXIS_DEAD_ZONE) tokens.add(positiveToken);
|
|
}
|
|
|
|
private void dispatchNativeControllerToken(String token, boolean repeat) {
|
|
if (bridge == null || bridge.getWebView() == null) return;
|
|
String script =
|
|
"window.dispatchEvent(new CustomEvent('ashen-halls-native-controller',"
|
|
+ "{detail:{token:'" + token + "',repeat:" + repeat + "}}));";
|
|
bridge.getWebView().post(
|
|
() -> {
|
|
bridge.getWebView().requestFocus();
|
|
bridge.getWebView().evaluateJavascript(script, null);
|
|
}
|
|
);
|
|
}
|
|
|
|
private void dispatchNativeControllerMotion(float x, float y) {
|
|
if (bridge == null || bridge.getWebView() == null) return;
|
|
String script =
|
|
"window.dispatchEvent(new CustomEvent('ashen-halls-native-controller-motion',"
|
|
+ "{detail:{x:" + x + ",y:" + y + "}}));";
|
|
bridge.getWebView().post(
|
|
() -> {
|
|
bridge.getWebView().requestFocus();
|
|
bridge.getWebView().evaluateJavascript(script, null);
|
|
}
|
|
);
|
|
}
|
|
|
|
private boolean shouldThrottleDpad(String token) {
|
|
int buttonIndex = Integer.parseInt(token.substring("Button".length()));
|
|
long now = SystemClock.uptimeMillis();
|
|
if (now - lastDpadDispatchAt[buttonIndex] < DPAD_THROTTLE_MS) return true;
|
|
lastDpadDispatchAt[buttonIndex] = now;
|
|
return false;
|
|
}
|
|
|
|
private boolean isDpadToken(String token) {
|
|
return token.equals("Button12")
|
|
|| token.equals("Button13")
|
|
|| token.equals("Button14")
|
|
|| token.equals("Button15");
|
|
}
|
|
|
|
private String controllerToken(int keyCode) {
|
|
switch (keyCode) {
|
|
case KeyEvent.KEYCODE_BUTTON_A:
|
|
case KeyEvent.KEYCODE_DPAD_CENTER:
|
|
case KeyEvent.KEYCODE_ENTER:
|
|
case KeyEvent.KEYCODE_NUMPAD_ENTER:
|
|
return "Button0";
|
|
case KeyEvent.KEYCODE_BUTTON_B:
|
|
case KeyEvent.KEYCODE_BACK:
|
|
case KeyEvent.KEYCODE_ESCAPE:
|
|
return "Button1";
|
|
case KeyEvent.KEYCODE_BUTTON_X:
|
|
return "Button2";
|
|
case KeyEvent.KEYCODE_BUTTON_Y:
|
|
return "Button3";
|
|
case KeyEvent.KEYCODE_BUTTON_L1:
|
|
return "Button4";
|
|
case KeyEvent.KEYCODE_BUTTON_R1:
|
|
return "Button5";
|
|
case KeyEvent.KEYCODE_BUTTON_L2:
|
|
return "Button6";
|
|
case KeyEvent.KEYCODE_BUTTON_R2:
|
|
return "Button7";
|
|
case KeyEvent.KEYCODE_BUTTON_SELECT:
|
|
return "Button8";
|
|
case KeyEvent.KEYCODE_BUTTON_START:
|
|
return "Button9";
|
|
case KeyEvent.KEYCODE_BUTTON_THUMBL:
|
|
return "Button10";
|
|
case KeyEvent.KEYCODE_BUTTON_THUMBR:
|
|
return "Button11";
|
|
case KeyEvent.KEYCODE_DPAD_UP:
|
|
return "Button12";
|
|
case KeyEvent.KEYCODE_DPAD_DOWN:
|
|
return "Button13";
|
|
case KeyEvent.KEYCODE_DPAD_LEFT:
|
|
return "Button14";
|
|
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
|
return "Button15";
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
}
|