mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
3 Commits
Mobi-Rower
...
cagnulein-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04b3553014 | ||
|
|
958520a93f | ||
|
|
5350621697 |
@@ -1,16 +1,16 @@
|
||||
// https://www.geeksforgeeks.org/how-to-make-a-floating-window-application-in-android/
|
||||
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.Display;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
@@ -29,175 +29,291 @@ import android.content.SharedPreferences;
|
||||
|
||||
public class FloatingWindowGFG extends Service {
|
||||
|
||||
// The reference variables for the
|
||||
// ViewGroup, WindowManager.LayoutParams,
|
||||
// WindowManager, Button, EditText classes are created
|
||||
private ViewGroup floatView;
|
||||
private int LAYOUT_TYPE;
|
||||
private WindowManager.LayoutParams floatWindowLayoutParam;
|
||||
private WindowManager windowManager;
|
||||
private Button maximizeBtn;
|
||||
// The reference variables for the
|
||||
// ViewGroup, WindowManager.LayoutParams,
|
||||
// WindowManager, Button, EditText classes are created
|
||||
private ViewGroup floatView;
|
||||
private int LAYOUT_TYPE;
|
||||
private WindowManager.LayoutParams floatWindowLayoutParam;
|
||||
private WindowManager windowManager;
|
||||
private Button maximizeBtn;
|
||||
|
||||
// Retrieve the user preference node for the package com.mycompany
|
||||
SharedPreferences sharedPreferences;
|
||||
// Retrieve the user preference node for the package com.mycompany
|
||||
SharedPreferences sharedPreferences;
|
||||
|
||||
// Preference key name
|
||||
final String PREF_NAME_X = "floatWindowLayoutUpdateParamX";
|
||||
final String PREF_NAME_Y = "floatWindowLayoutUpdateParamY";
|
||||
// Preference key names
|
||||
final String PREF_NAME_X = "floatWindowLayoutUpdateParamX";
|
||||
final String PREF_NAME_Y = "floatWindowLayoutUpdateParamY";
|
||||
|
||||
// As FloatingWindowGFG inherits Service class,
|
||||
// it actually overrides the onBind method
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
// As FloatingWindowGFG inherits Service class,
|
||||
// it actually overrides the onBind method
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
// Define an array of window types to try in order of preference
|
||||
private int[] getWindowTypesToTry(boolean isExternalDisplay) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // Android 8.0+
|
||||
if (isExternalDisplay) {
|
||||
// Window types to try for external displays (DeX mode)
|
||||
return new int[] {
|
||||
WindowManager.LayoutParams.TYPE_APPLICATION, // Try regular application window first
|
||||
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, // Then try overlay
|
||||
WindowManager.LayoutParams.TYPE_APPLICATION_PANEL, // Then try panel
|
||||
WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG // Finally try attached dialog
|
||||
};
|
||||
} else {
|
||||
// Window types for the primary display
|
||||
return new int[] {
|
||||
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
|
||||
WindowManager.LayoutParams.TYPE_APPLICATION
|
||||
};
|
||||
}
|
||||
} else { // Pre-Android 8.0
|
||||
return new int[] {
|
||||
WindowManager.LayoutParams.TYPE_TOAST,
|
||||
WindowManager.LayoutParams.TYPE_PHONE,
|
||||
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// The screen height and width are calculated, cause
|
||||
// the height and width of the floating window is set depending on this
|
||||
/*DisplayMetrics metrics = getApplicationContext().getResources().getDisplayMetrics();
|
||||
int width = metrics.widthPixels;
|
||||
int height = metrics.heightPixels;*/
|
||||
// Helper method to convert window type integer to descriptive string for logging
|
||||
private String windowTypeToString(int windowType) {
|
||||
switch (windowType) {
|
||||
case WindowManager.LayoutParams.TYPE_APPLICATION:
|
||||
return "TYPE_APPLICATION";
|
||||
case WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY:
|
||||
return "TYPE_APPLICATION_OVERLAY";
|
||||
case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL:
|
||||
return "TYPE_APPLICATION_PANEL";
|
||||
case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG:
|
||||
return "TYPE_APPLICATION_ATTACHED_DIALOG";
|
||||
case WindowManager.LayoutParams.TYPE_TOAST:
|
||||
return "TYPE_TOAST";
|
||||
case WindowManager.LayoutParams.TYPE_PHONE:
|
||||
return "TYPE_PHONE";
|
||||
case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT:
|
||||
return "TYPE_SYSTEM_ALERT";
|
||||
default:
|
||||
return "TYPE_" + windowType;
|
||||
}
|
||||
}
|
||||
|
||||
// To obtain a WindowManager of a different Display,
|
||||
// we need a Context for that display, so WINDOW_SERVICE is used
|
||||
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
// A LayoutInflater instance is created to retrieve the
|
||||
// LayoutInflater for the floating_layout xml
|
||||
LayoutInflater inflater = (LayoutInflater) getBaseContext().getSystemService(LAYOUT_INFLATER_SERVICE);
|
||||
// Initialize SharedPreferences for position
|
||||
sharedPreferences = getSharedPreferences("FloatingWindowGFG", MODE_PRIVATE);
|
||||
|
||||
// inflate a new view hierarchy from the floating_layout xml
|
||||
floatView = (ViewGroup) inflater.inflate(R.layout.floating_layout, null);
|
||||
try {
|
||||
// Get DisplayManager to access all connected displays
|
||||
DisplayManager displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
|
||||
|
||||
WebView wv = (WebView)floatView.findViewById(R.id.webview);
|
||||
wv.setWebViewClient(new WebViewClient(){
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
view.loadUrl(url);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
WebSettings settings = wv.getSettings();
|
||||
settings.setJavaScriptEnabled(true);
|
||||
wv.loadUrl("http://localhost:" + FloatingHandler._port + "/floating/floating.htm");
|
||||
wv.clearView();
|
||||
wv.measure(100, 100);
|
||||
wv.setAlpha(Float.valueOf(FloatingHandler._alpha) / 100.0f);
|
||||
settings.setBuiltInZoomControls(true);
|
||||
settings.setUseWideViewPort(true);
|
||||
settings.setDomStorageEnabled(true);
|
||||
QLog.d("QZ","loadurl");
|
||||
// Get ALL displays, not just presentation displays
|
||||
Display[] displays = displayManager.getDisplays();
|
||||
|
||||
// Log all available displays for debugging
|
||||
QLog.d("QZ", "Number of displays: " + displays.length);
|
||||
for (int i = 0; i < displays.length; i++) {
|
||||
QLog.d("QZ", "Display " + i + ": " + displays[i].getDisplayId() +
|
||||
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 ?
|
||||
" - Name: " + displays[i].getName() : ""));
|
||||
}
|
||||
|
||||
// WindowManager.LayoutParams takes a lot of parameters to set the
|
||||
// the parameters of the layout. One of them is Layout_type.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// If API Level is more than 26, we need TYPE_APPLICATION_OVERLAY
|
||||
LAYOUT_TYPE = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
} else {
|
||||
// If API Level is lesser than 26, then we can
|
||||
// use TYPE_SYSTEM_ERROR,
|
||||
// TYPE_SYSTEM_OVERLAY, TYPE_PHONE, TYPE_PRIORITY_PHONE.
|
||||
// But these are all
|
||||
// deprecated in API 26 and later. Here TYPE_TOAST works best.
|
||||
LAYOUT_TYPE = WindowManager.LayoutParams.TYPE_TOAST;
|
||||
}
|
||||
// Choose which display to use - use external if available
|
||||
Display targetDisplay;
|
||||
|
||||
// Now the Parameter of the floating-window layout is set.
|
||||
// 1) The Width of the window will be 55% of the phone width.
|
||||
// 2) The Height of the window will be 58% of the phone height.
|
||||
// 3) Layout_Type is already set.
|
||||
// 4) Next Parameter is Window_Flag. Here FLAG_NOT_FOCUSABLE is used. But
|
||||
// problem with this flag is key inputs can't be given to the EditText.
|
||||
// This problem is solved later.
|
||||
// 5) Next parameter is Layout_Format. System chooses a format that supports
|
||||
// translucency by PixelFormat.TRANSLUCENT
|
||||
if (displays.length > 1) {
|
||||
// Use the second display (index 1), which is typically the external one in DeX mode
|
||||
targetDisplay = displays[1];
|
||||
QLog.d("QZ", "Using external display (ID: " + targetDisplay.getDisplayId() + ")");
|
||||
} else {
|
||||
// No external displays, use the default display
|
||||
targetDisplay = displays[0];
|
||||
QLog.d("QZ", "Using default display (ID: " + targetDisplay.getDisplayId() + ")");
|
||||
}
|
||||
|
||||
floatWindowLayoutParam = new WindowManager.LayoutParams(
|
||||
(int) (FloatingHandler._width ),
|
||||
(int) (FloatingHandler._height ),
|
||||
LAYOUT_TYPE,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
|
||||
PixelFormat.TRANSLUCENT
|
||||
);
|
||||
// Get display metrics for the chosen display
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
targetDisplay.getMetrics(metrics);
|
||||
QLog.d("QZ", "Selected display metrics - Width: " + metrics.widthPixels +
|
||||
", Height: " + metrics.heightPixels);
|
||||
|
||||
// The Gravity of the Floating Window is set.
|
||||
// The Window will appear in the center of the screen
|
||||
floatWindowLayoutParam.gravity = Gravity.CENTER;
|
||||
// Create a context specific to the chosen display
|
||||
Context displayContext = createDisplayContext(targetDisplay);
|
||||
|
||||
// X and Y value of the window is set
|
||||
floatWindowLayoutParam.x = 0;
|
||||
floatWindowLayoutParam.y = 0;
|
||||
// Get WindowManager for the specific display
|
||||
windowManager = (WindowManager) displayContext.getSystemService(Context.WINDOW_SERVICE);
|
||||
|
||||
sharedPreferences = getSharedPreferences("FloatingWindowGFG",MODE_PRIVATE);
|
||||
floatWindowLayoutParam.x = sharedPreferences.getInt(PREF_NAME_X, floatWindowLayoutParam.x);
|
||||
floatWindowLayoutParam.y = sharedPreferences.getInt(PREF_NAME_Y, floatWindowLayoutParam.y);
|
||||
// A LayoutInflater instance is created to retrieve the
|
||||
// LayoutInflater for the floating_layout xml
|
||||
LayoutInflater inflater = (LayoutInflater) getBaseContext().getSystemService(LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
// The ViewGroup that inflates the floating_layout.xml is
|
||||
// added to the WindowManager with all the parameters
|
||||
windowManager.addView(floatView, floatWindowLayoutParam);
|
||||
// inflate a new view hierarchy from the floating_layout xml
|
||||
floatView = (ViewGroup) inflater.inflate(R.layout.floating_layout, null);
|
||||
|
||||
// Set up the WebView
|
||||
WebView wv = (WebView)floatView.findViewById(R.id.webview);
|
||||
wv.setWebViewClient(new WebViewClient(){
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
view.loadUrl(url);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
WebSettings settings = wv.getSettings();
|
||||
settings.setJavaScriptEnabled(true);
|
||||
wv.loadUrl("http://localhost:" + FloatingHandler._port + "/floating/floating.htm");
|
||||
wv.clearView();
|
||||
wv.measure(100, 100);
|
||||
wv.setAlpha(Float.valueOf(FloatingHandler._alpha) / 100.0f);
|
||||
settings.setBuiltInZoomControls(true);
|
||||
settings.setUseWideViewPort(true);
|
||||
settings.setDomStorageEnabled(true);
|
||||
QLog.d("QZ", "loadurl");
|
||||
|
||||
// Another feature of the floating window is, the window is movable.
|
||||
// The window can be moved at any position on the screen.
|
||||
floatView.setOnTouchListener(new View.OnTouchListener() {
|
||||
final WindowManager.LayoutParams floatWindowLayoutUpdateParam = floatWindowLayoutParam;
|
||||
double x;
|
||||
double y;
|
||||
double px;
|
||||
double py;
|
||||
// Determine if we're using an external display
|
||||
boolean isExternalDisplay = targetDisplay.getDisplayId() != Display.DEFAULT_DISPLAY;
|
||||
QLog.d("QZ", "Is external display: " + isExternalDisplay);
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
// Get array of window types to try
|
||||
int[] windowTypesToTry = getWindowTypesToTry(isExternalDisplay);
|
||||
|
||||
QLog.d("QZ","onTouch");
|
||||
// Try each window type until one works
|
||||
Exception lastException = null;
|
||||
boolean windowCreated = false;
|
||||
|
||||
switch (event.getAction()) {
|
||||
// When the window will be touched,
|
||||
// the x and y position of that position
|
||||
// will be retrieved
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
x = floatWindowLayoutUpdateParam.x;
|
||||
y = floatWindowLayoutUpdateParam.y;
|
||||
for (int windowType : windowTypesToTry) {
|
||||
try {
|
||||
// Set the current window type
|
||||
LAYOUT_TYPE = windowType;
|
||||
QLog.d("QZ", "Trying window type: " + windowTypeToString(windowType));
|
||||
|
||||
// returns the original raw X
|
||||
// coordinate of this event
|
||||
px = event.getRawX();
|
||||
// Create layout params with current window type
|
||||
floatWindowLayoutParam = new WindowManager.LayoutParams(
|
||||
(int) (FloatingHandler._width),
|
||||
(int) (FloatingHandler._height),
|
||||
LAYOUT_TYPE,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
|
||||
PixelFormat.TRANSLUCENT
|
||||
);
|
||||
|
||||
// returns the original raw Y
|
||||
// coordinate of this event
|
||||
py = event.getRawY();
|
||||
break;
|
||||
// When the window will be dragged around,
|
||||
// it will update the x, y of the Window Layout Parameter
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
floatWindowLayoutUpdateParam.x = (int) ((x + event.getRawX()) - px);
|
||||
floatWindowLayoutUpdateParam.y = (int) ((y + event.getRawY()) - py);
|
||||
// The Gravity of the Floating Window is set.
|
||||
floatWindowLayoutParam.gravity = Gravity.CENTER;
|
||||
|
||||
SharedPreferences.Editor myEdit = sharedPreferences.edit();
|
||||
myEdit.putInt(PREF_NAME_X, floatWindowLayoutUpdateParam.x);
|
||||
myEdit.putInt(PREF_NAME_Y, floatWindowLayoutUpdateParam.y);
|
||||
myEdit.commit();
|
||||
// X and Y value of the window is set - retrieve from preferences if available
|
||||
floatWindowLayoutParam.x = sharedPreferences.getInt(PREF_NAME_X, 0);
|
||||
floatWindowLayoutParam.y = sharedPreferences.getInt(PREF_NAME_Y, 0);
|
||||
|
||||
// updated parameter is applied to the WindowManager
|
||||
windowManager.updateViewLayout(floatView, floatWindowLayoutUpdateParam);
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
// Add additional flags that might help with visibility and placement on external displays
|
||||
if (isExternalDisplay) {
|
||||
// Try various flags that might help with external display visibility
|
||||
floatWindowLayoutParam.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
|
||||
floatWindowLayoutParam.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
|
||||
floatWindowLayoutParam.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
|
||||
|
||||
// Additional flags that might help with DeX mode
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { // Android 9.0+
|
||||
floatWindowLayoutParam.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
|
||||
}
|
||||
}
|
||||
|
||||
// Log before adding the view
|
||||
QLog.d("QZ", "About to add view with window type: " + windowTypeToString(windowType));
|
||||
|
||||
// Add the view to the window manager
|
||||
windowManager.addView(floatView, floatWindowLayoutParam);
|
||||
|
||||
// If we get here, adding the view was successful
|
||||
QLog.d("QZ", "Successfully added view with window type: " + windowTypeToString(windowType));
|
||||
windowCreated = true;
|
||||
break; // Exit the loop since we found a working window type
|
||||
|
||||
} catch (Exception e) {
|
||||
// Log the exception and try the next window type
|
||||
lastException = e;
|
||||
QLog.d("QZ", "Failed to add view with window type " + windowTypeToString(windowType) +
|
||||
": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// If we couldn't create a window with any of the types, log the error
|
||||
if (!windowCreated) {
|
||||
QLog.d("QZ", "Failed to create floating window with any window type. Last error: " +
|
||||
(lastException != null ? lastException.getMessage() : "unknown"));
|
||||
// Return from onCreate to prevent further execution
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up the touch listener for moving the window
|
||||
floatView.setOnTouchListener(new View.OnTouchListener() {
|
||||
final WindowManager.LayoutParams floatWindowLayoutUpdateParam = floatWindowLayoutParam;
|
||||
double x;
|
||||
double y;
|
||||
double px;
|
||||
double py;
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
QLog.d("QZ", "onTouch");
|
||||
|
||||
switch (event.getAction()) {
|
||||
// When the window will be touched,
|
||||
// the x and y position of that position
|
||||
// will be retrieved
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
x = floatWindowLayoutUpdateParam.x;
|
||||
y = floatWindowLayoutUpdateParam.y;
|
||||
|
||||
// returns the original raw X
|
||||
// coordinate of this event
|
||||
px = event.getRawX();
|
||||
|
||||
// returns the original raw Y
|
||||
// coordinate of this event
|
||||
py = event.getRawY();
|
||||
break;
|
||||
// When the window will be dragged around,
|
||||
// it will update the x, y of the Window Layout Parameter
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
floatWindowLayoutUpdateParam.x = (int) ((x + event.getRawX()) - px);
|
||||
floatWindowLayoutUpdateParam.y = (int) ((y + event.getRawY()) - py);
|
||||
|
||||
SharedPreferences.Editor myEdit = sharedPreferences.edit();
|
||||
myEdit.putInt(PREF_NAME_X, floatWindowLayoutUpdateParam.x);
|
||||
myEdit.putInt(PREF_NAME_Y, floatWindowLayoutUpdateParam.y);
|
||||
myEdit.commit();
|
||||
|
||||
// updated parameter is applied to the WindowManager
|
||||
windowManager.updateViewLayout(floatView, floatWindowLayoutUpdateParam);
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
// Log any exceptions that might occur at the top level
|
||||
QLog.d("QZ", "Error in onCreate: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// It is called when stopService()
|
||||
// method is called in MainActivity
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
stopSelf();
|
||||
// Window is removed from the screen
|
||||
windowManager.removeView(floatView);
|
||||
}
|
||||
// method is called in MainActivity
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
stopSelf();
|
||||
try {
|
||||
// Window is removed from the screen
|
||||
if (windowManager != null && floatView != null) {
|
||||
windowManager.removeView(floatView);
|
||||
QLog.d("QZ", "View removed successfully");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
QLog.d("QZ", "Error removing view: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user