package com.warren.iwanttoheal; import android.app.Presentation; import android.content.Context; import android.graphics.Color; import android.hardware.display.DisplayManager; import android.os.Bundle; import android.view.Display; import android.view.View; import android.view.ViewGroup; import android.webkit.WebResourceRequest; import android.webkit.WebResourceResponse; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.FrameLayout; import com.getcapacitor.JSArray; import com.getcapacitor.JSObject; import com.getcapacitor.Plugin; import com.getcapacitor.PluginCall; import com.getcapacitor.PluginMethod; import com.getcapacitor.annotation.CapacitorPlugin; @CapacitorPlugin(name = "DualScreen") public class DualScreenPlugin extends Plugin { private TopDisplayPresentation presentation; @PluginMethod public void getDisplays(PluginCall call) { DisplayManager displayManager = (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE); Display currentDisplay = getActivity().getDisplay(); JSArray displays = new JSArray(); for (Display display : displayManager.getDisplays()) { displays.put(describeDisplay(display, currentDisplay)); } JSObject result = new JSObject(); result.put("currentDisplayId", currentDisplay == null ? -1 : currentDisplay.getDisplayId()); result.put("displays", displays); call.resolve(result); } @PluginMethod public void openTopDisplay(PluginCall call) { getActivity().runOnUiThread(() -> { try { DisplayLaunchPlan launchPlan = createLaunchPlan(call.getInt("displayId")); if (launchPlan.targetDisplay == null) { call.reject("No secondary Android display is available."); return; } Display currentDisplay = getActivity().getDisplay(); if ( currentDisplay != null && currentDisplay.getDisplayId() == launchPlan.targetDisplay.getDisplayId() ) { JSObject result = describeDisplay(currentDisplay, currentDisplay); result.put("opened", true); result.put("alreadyOnMainDisplay", true); call.resolve(result); return; } String gameUrl = bridge.getLocalUrl(); String topGameUrl = gameUrl + "/?display=top"; String controlsUrl = gameUrl + "/?display=bottom"; String targetUrl = launchPlan.targetIsTopDisplay ? topGameUrl : controlsUrl; String currentUrl = launchPlan.currentIsTopDisplay ? topGameUrl : controlsUrl; closePresentation(); presentation = new TopDisplayPresentation( getActivity(), launchPlan.targetDisplay, targetUrl ); presentation.setOnDismissListener(dialog -> { presentation = null; notifyListeners("displayDisconnected", new JSObject()); }); presentation.show(); bridge.getWebView().loadUrl(currentUrl); JSObject result = describeDisplay(launchPlan.targetDisplay, getActivity().getDisplay()); result.put("opened", true); result.put("presentationMode", true); result.put("targetIsTopDisplay", launchPlan.targetIsTopDisplay); result.put("currentIsTopDisplay", launchPlan.currentIsTopDisplay); call.resolve(result); } catch (Exception exception) { closePresentation(); call.reject("Unable to open the upper display.", exception); } }); } @PluginMethod public void closeTopDisplay(PluginCall call) { getActivity().runOnUiThread(() -> { closePresentation(); call.resolve(); }); } @Override protected void handleOnDestroy() { closePresentation(); } private DisplayLaunchPlan createLaunchPlan(Integer requestedDisplayId) { DisplayManager displayManager = (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE); Display currentDisplay = getActivity().getDisplay(); int currentDisplayId = currentDisplay == null ? -1 : currentDisplay.getDisplayId(); Display topDisplay = largestDisplay(displayManager.getDisplays()); boolean currentIsTopDisplay = topDisplay != null && topDisplay.getDisplayId() == currentDisplayId; Display targetDisplay = null; if (requestedDisplayId != null) { Display requested = displayManager.getDisplay(requestedDisplayId); if ( requested != null && requested.getDisplayId() != currentDisplayId && requested.getState() != Display.STATE_OFF ) { targetDisplay = requested; } } if (targetDisplay == null && currentIsTopDisplay) { targetDisplay = smallestOtherDisplay(displayManager.getDisplays(), currentDisplayId); } if (targetDisplay == null && topDisplay != null && topDisplay.getDisplayId() != currentDisplayId) { targetDisplay = topDisplay; } if (targetDisplay == null) { targetDisplay = largestOtherDisplay( displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION), currentDisplayId ); } if (targetDisplay == null) { targetDisplay = largestOtherDisplay(displayManager.getDisplays(), currentDisplayId); } boolean targetIsTopDisplay = topDisplay != null && targetDisplay != null && targetDisplay.getDisplayId() == topDisplay.getDisplayId(); return new DisplayLaunchPlan(targetDisplay, targetIsTopDisplay, currentIsTopDisplay); } private Display largestDisplay(Display[] displays) { Display selected = null; long selectedPixels = -1; for (Display display : displays) { if (display.getState() == Display.STATE_OFF) continue; long pixels = pixelCount(display); if (pixels > selectedPixels) { selected = display; selectedPixels = pixels; } } return selected; } private Display largestOtherDisplay(Display[] displays, int currentDisplayId) { Display selected = null; long selectedPixels = -1; for (Display display : displays) { if ( display.getDisplayId() == currentDisplayId || display.getState() == Display.STATE_OFF ) { continue; } long pixels = pixelCount(display); if (pixels > selectedPixels) { selected = display; selectedPixels = pixels; } } return selected; } private Display smallestOtherDisplay(Display[] displays, int currentDisplayId) { Display selected = null; long selectedPixels = Long.MAX_VALUE; for (Display display : displays) { if ( display.getDisplayId() == currentDisplayId || display.getState() == Display.STATE_OFF ) { continue; } long pixels = pixelCount(display); if (pixels < selectedPixels) { selected = display; selectedPixels = pixels; } } return selected; } private long pixelCount(Display display) { Display.Mode mode = display.getMode(); return (long) mode.getPhysicalWidth() * mode.getPhysicalHeight(); } private JSObject describeDisplay(Display display, Display currentDisplay) { Display.Mode mode = display.getMode(); JSObject result = new JSObject(); result.put("id", display.getDisplayId()); result.put("name", display.getName()); result.put("width", mode.getPhysicalWidth()); result.put("height", mode.getPhysicalHeight()); result.put("refreshRate", mode.getRefreshRate()); result.put( "isCurrent", currentDisplay != null && currentDisplay.getDisplayId() == display.getDisplayId() ); result.put( "isPresentation", (display.getFlags() & Display.FLAG_PRESENTATION) == Display.FLAG_PRESENTATION ); return result; } private void closePresentation() { if (presentation == null) return; presentation.setOnDismissListener(null); presentation.dismiss(); presentation = null; } private final class DisplayLaunchPlan { private final Display targetDisplay; private final boolean targetIsTopDisplay; private final boolean currentIsTopDisplay; DisplayLaunchPlan( Display targetDisplay, boolean targetIsTopDisplay, boolean currentIsTopDisplay ) { this.targetDisplay = targetDisplay; this.targetIsTopDisplay = targetIsTopDisplay; this.currentIsTopDisplay = currentIsTopDisplay; } } private final class TopDisplayPresentation extends Presentation { private final String initialUrl; TopDisplayPresentation(Context context, Display display, String initialUrl) { super(context, display); this.initialUrl = initialUrl; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setBackgroundDrawableResource(android.R.color.black); 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 ); FrameLayout container = new FrameLayout(getContext()); container.setBackgroundColor(Color.BLACK); WebView webView = new WebView(getContext()); configureWebView(webView); container.addView( webView, new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) ); setContentView(container); webView.loadUrl(initialUrl); } } private void configureWebView(WebView webView) { WebSettings settings = webView.getSettings(); settings.setJavaScriptEnabled(true); settings.setDomStorageEnabled(true); settings.setDatabaseEnabled(true); settings.setMediaPlaybackRequiresUserGesture(false); settings.setAllowFileAccess(true); settings.setAllowContentAccess(true); webView.setBackgroundColor(Color.BLACK); webView.setWebViewClient(new WebViewClient() { @Override public WebResourceResponse shouldInterceptRequest( WebView view, WebResourceRequest request ) { return bridge.getLocalServer().shouldInterceptRequest(request); } }); } }