mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-02 15:56:24 +00:00
262 lines
11 KiB
Java
262 lines
11 KiB
Java
package com.limelight.utils;
|
|
|
|
import android.app.Activity;
|
|
import android.app.AlertDialog;
|
|
import android.app.GameManager;
|
|
import android.app.GameState;
|
|
import android.app.LocaleManager;
|
|
import android.app.UiModeManager;
|
|
import android.content.Context;
|
|
import android.content.DialogInterface;
|
|
import android.content.SharedPreferences;
|
|
import android.content.res.Configuration;
|
|
import android.graphics.Insets;
|
|
import android.os.Build;
|
|
import android.os.LocaleList;
|
|
import android.view.View;
|
|
import android.view.WindowInsets;
|
|
import android.view.WindowManager;
|
|
|
|
import com.limelight.Game;
|
|
import com.limelight.R;
|
|
import com.limelight.nvstream.http.ComputerDetails;
|
|
import com.limelight.preferences.PreferenceConfiguration;
|
|
|
|
import java.util.Locale;
|
|
|
|
public class UiHelper {
|
|
|
|
private static final int TV_VERTICAL_PADDING_DP = 15;
|
|
private static final int TV_HORIZONTAL_PADDING_DP = 15;
|
|
|
|
private static void setGameModeStatus(Context context, boolean streaming, boolean interruptible) {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
GameManager gameManager = context.getSystemService(GameManager.class);
|
|
|
|
if (streaming) {
|
|
gameManager.setGameState(new GameState(false, interruptible ? GameState.MODE_GAMEPLAY_INTERRUPTIBLE : GameState.MODE_GAMEPLAY_UNINTERRUPTIBLE));
|
|
}
|
|
else {
|
|
gameManager.setGameState(new GameState(false, GameState.MODE_NONE));
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void notifyStreamConnecting(Context context) {
|
|
setGameModeStatus(context, true, true);
|
|
}
|
|
|
|
public static void notifyStreamConnected(Context context) {
|
|
setGameModeStatus(context, true, false);
|
|
}
|
|
|
|
public static void notifyStreamEnteringPiP(Context context) {
|
|
setGameModeStatus(context, true, true);
|
|
}
|
|
|
|
public static void notifyStreamExitingPiP(Context context) {
|
|
setGameModeStatus(context, true, false);
|
|
}
|
|
|
|
public static void notifyStreamEnded(Context context) {
|
|
setGameModeStatus(context, false, false);
|
|
}
|
|
|
|
public static void setLocale(Activity activity)
|
|
{
|
|
String locale = PreferenceConfiguration.readPreferences(activity).language;
|
|
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
// On Android 13, migrate this non-default language setting into the OS native API
|
|
LocaleManager localeManager = activity.getSystemService(LocaleManager.class);
|
|
localeManager.setApplicationLocales(LocaleList.forLanguageTags(locale));
|
|
PreferenceConfiguration.completeLanguagePreferenceMigration(activity);
|
|
}
|
|
else {
|
|
Configuration config = new Configuration(activity.getResources().getConfiguration());
|
|
|
|
// Some locales include both language and country which must be separated
|
|
// before calling the Locale constructor.
|
|
if (locale.contains("-"))
|
|
{
|
|
config.locale = new Locale(locale.substring(0, locale.indexOf('-')),
|
|
locale.substring(locale.indexOf('-') + 1));
|
|
}
|
|
else
|
|
{
|
|
config.locale = new Locale(locale);
|
|
}
|
|
|
|
activity.getResources().updateConfiguration(config, activity.getResources().getDisplayMetrics());
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void applyStatusBarPadding(View view) {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
// This applies the padding that we omitted in notifyNewRootView() on Q
|
|
view.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
|
|
@Override
|
|
public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
|
|
view.setPadding(view.getPaddingLeft(),
|
|
view.getPaddingTop(),
|
|
view.getPaddingRight(),
|
|
windowInsets.getTappableElementInsets().bottom);
|
|
return windowInsets;
|
|
}
|
|
});
|
|
view.requestApplyInsets();
|
|
}
|
|
}
|
|
|
|
public static void notifyNewRootView(final Activity activity)
|
|
{
|
|
View rootView = activity.findViewById(android.R.id.content);
|
|
UiModeManager modeMgr = (UiModeManager) activity.getSystemService(Context.UI_MODE_SERVICE);
|
|
|
|
// Set GameState.MODE_NONE initially for all activities
|
|
setGameModeStatus(activity, false, false);
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
// Allow this non-streaming activity to layout under notches.
|
|
//
|
|
// We should NOT do this for the Game activity unless
|
|
// the user specifically opts in, because it can obscure
|
|
// parts of the streaming surface.
|
|
activity.getWindow().getAttributes().layoutInDisplayCutoutMode =
|
|
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
|
}
|
|
|
|
if (modeMgr.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
|
|
// Increase view padding on TVs
|
|
float scale = activity.getResources().getDisplayMetrics().density;
|
|
int verticalPaddingPixels = (int) (TV_VERTICAL_PADDING_DP*scale + 0.5f);
|
|
int horizontalPaddingPixels = (int) (TV_HORIZONTAL_PADDING_DP*scale + 0.5f);
|
|
|
|
rootView.setPadding(horizontalPaddingPixels, verticalPaddingPixels,
|
|
horizontalPaddingPixels, verticalPaddingPixels);
|
|
}
|
|
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
// Draw under the status bar on Android Q devices
|
|
|
|
// Using getDecorView() here breaks the translucent status/navigation bar when gestures are disabled
|
|
activity.findViewById(android.R.id.content).setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
|
|
@Override
|
|
public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
|
|
// Use the tappable insets so we can draw under the status bar in gesture mode
|
|
Insets tappableInsets = windowInsets.getTappableElementInsets();
|
|
view.setPadding(tappableInsets.left,
|
|
tappableInsets.top,
|
|
tappableInsets.right,
|
|
0);
|
|
|
|
// Show a translucent navigation bar if we can't tap there
|
|
if (tappableInsets.bottom != 0) {
|
|
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
|
|
}
|
|
else {
|
|
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
|
|
}
|
|
|
|
return windowInsets;
|
|
}
|
|
});
|
|
|
|
activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
|
|
}
|
|
}
|
|
|
|
public static void showDecoderCrashDialog(Activity activity) {
|
|
final SharedPreferences prefs = activity.getSharedPreferences("DecoderTombstone", 0);
|
|
final int crashCount = prefs.getInt("CrashCount", 0);
|
|
int lastNotifiedCrashCount = prefs.getInt("LastNotifiedCrashCount", 0);
|
|
|
|
// Remember the last crash count we notified at, so we don't
|
|
// display the crash dialog every time the app is started until
|
|
// they stream again
|
|
if (crashCount != 0 && crashCount != lastNotifiedCrashCount) {
|
|
if (crashCount % 3 == 0) {
|
|
// At 3 consecutive crashes, we'll forcefully reset their settings
|
|
PreferenceConfiguration.resetStreamingSettings(activity);
|
|
Dialog.displayDialog(activity,
|
|
activity.getResources().getString(R.string.title_decoding_reset),
|
|
activity.getResources().getString(R.string.message_decoding_reset),
|
|
new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
// Mark notification as acknowledged on dismissal
|
|
prefs.edit().putInt("LastNotifiedCrashCount", crashCount).apply();
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
Dialog.displayDialog(activity,
|
|
activity.getResources().getString(R.string.title_decoding_error),
|
|
activity.getResources().getString(R.string.message_decoding_error),
|
|
new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
// Mark notification as acknowledged on dismissal
|
|
prefs.edit().putInt("LastNotifiedCrashCount", crashCount).apply();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void displayQuitConfirmationDialog(Activity parent, final Runnable onYes, final Runnable onNo) {
|
|
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
switch (which){
|
|
case DialogInterface.BUTTON_POSITIVE:
|
|
if (onYes != null) {
|
|
onYes.run();
|
|
}
|
|
break;
|
|
|
|
case DialogInterface.BUTTON_NEGATIVE:
|
|
if (onNo != null) {
|
|
onNo.run();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(parent);
|
|
builder.setMessage(parent.getResources().getString(R.string.applist_quit_confirmation))
|
|
.setPositiveButton(parent.getResources().getString(R.string.yes), dialogClickListener)
|
|
.setNegativeButton(parent.getResources().getString(R.string.no), dialogClickListener)
|
|
.show();
|
|
}
|
|
|
|
public static void displayDeletePcConfirmationDialog(Activity parent, ComputerDetails computer, final Runnable onYes, final Runnable onNo) {
|
|
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
switch (which){
|
|
case DialogInterface.BUTTON_POSITIVE:
|
|
if (onYes != null) {
|
|
onYes.run();
|
|
}
|
|
break;
|
|
|
|
case DialogInterface.BUTTON_NEGATIVE:
|
|
if (onNo != null) {
|
|
onNo.run();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(parent);
|
|
builder.setMessage(parent.getResources().getString(R.string.delete_pc_msg))
|
|
.setTitle(computer.name)
|
|
.setPositiveButton(parent.getResources().getString(R.string.yes), dialogClickListener)
|
|
.setNegativeButton(parent.getResources().getString(R.string.no), dialogClickListener)
|
|
.show();
|
|
}
|
|
}
|