Guest User

GameActivity.java

a guest
Feb 17th, 2020
11
118 days
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /**
  2.  * Copyright (c) 2006-2020 LOVE Development Team
  3.  *
  4.  * This software is provided 'as-is', without any express or implied
  5.  * warranty.  In no event will the authors be held liable for any damages
  6.  * arising from the use of this software.
  7.  *
  8.  * Permission is granted to anyone to use this software for any purpose,
  9.  * including commercial applications, and to alter it and redistribute it
  10.  * freely, subject to the following restrictions:
  11.  *
  12.  * 1. The origin of this software must not be misrepresented; you must not
  13.  *    claim that you wrote the original software. If you use this software
  14.  *    in a product, an acknowledgment in the product documentation would be
  15.  *    appreciated but is not required.
  16.  * 2. Altered source versions must be plainly marked as such, and must not be
  17.  *    misrepresented as being the original software.
  18.  * 3. This notice may not be removed or altered from any source distribution.
  19.  **/
  20.  
  21. package org.love2d.android;
  22.  
  23. import org.libsdl.app.SDLActivity;
  24.  
  25. import java.util.Arrays;
  26. import java.util.List;
  27. import java.io.BufferedOutputStream;
  28. import java.io.File;
  29. import java.io.FileOutputStream;
  30. import java.io.IOException;
  31. import java.io.InputStream;
  32.  
  33. import android.Manifest;
  34. import android.app.AlertDialog;
  35. import android.content.Context;
  36. import android.content.DialogInterface;
  37. import android.content.Intent;
  38. import android.media.AudioManager;
  39. import android.net.Uri;
  40. import android.os.Bundle;
  41. import android.os.Environment;
  42. import android.os.Vibrator;
  43. import android.util.Log;
  44. import android.util.DisplayMetrics;
  45. import android.view.*;
  46. import android.content.pm.PackageManager;
  47.  
  48. import androidx.annotation.Keep;
  49. import androidx.core.app.ActivityCompat;
  50.  
  51. public class GameActivity extends SDLActivity {
  52.     private static DisplayMetrics metrics = new DisplayMetrics();
  53.     private static String gamePath = "";
  54.     private static Context context;
  55.     private static Vibrator vibrator = null;
  56.     protected final int[] externalStorageRequestDummy = new int[1];
  57.     protected final int[] recordAudioRequestDummy = new int[1];
  58.     public static final int EXTERNAL_STORAGE_REQUEST_CODE = 1;
  59.     public static final int RECORD_AUDIO_REQUEST_CODE = 2;
  60.     private static boolean immersiveActive = false;
  61.     private static boolean mustCacheArchive = false;
  62.     private boolean storagePermissionUnnecessary = false;
  63.     public boolean embed = false;
  64.     public int safeAreaTop = 0;
  65.     public int safeAreaLeft = 0;
  66.     public int safeAreaBottom = 0;
  67.     public int safeAreaRight = 0;
  68.  
  69.     @Override
  70.     protected String[] getLibraries() {
  71.         return new String[]{
  72.                 "c++_shared",
  73.                 "mpg123",
  74.                 "openal",
  75.                 "hidapi",
  76.                 "love",
  77.         };
  78.     }
  79.  
  80.     @Override
  81.     protected String getMainSharedObject() {
  82.         String[] libs = getLibraries();
  83.         String libname = "lib" + libs[libs.length - 1] + ".so";
  84.  
  85.         // Since Lollipop, you can simply pass "libname.so" to dlopen
  86.         // and it will resolve correct paths and load correct library.
  87.         // This is mandatory for extractNativeLibs=false support in
  88.         // Marshmallow.
  89.         if (android.os.Build.VERSION.SDK_INT >= 21) {
  90.             return libname;
  91.         } else {
  92.             return getContext().getApplicationInfo().nativeLibraryDir + "/" + libname;
  93.         }
  94.     }
  95.  
  96.     @Override
  97.     protected void onCreate(Bundle savedInstanceState) {
  98.         Log.d("GameActivity", "started");
  99.         removeSafeArea();
  100.  
  101.         context = this.getApplicationContext();
  102.  
  103.         int res = context.checkCallingOrSelfPermission(Manifest.permission.VIBRATE);
  104.         if (res == PackageManager.PERMISSION_GRANTED) {
  105.             vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
  106.         } else {
  107.             Log.d("GameActivity", "Vibration disabled: could not get vibration permission.");
  108.         }
  109.  
  110.         // These 2 variables must be reset or it will use the existing value.
  111.         gamePath = "";
  112.         storagePermissionUnnecessary = false;
  113.         embed = context.getResources().getBoolean(R.bool.embed);
  114.  
  115.         handleIntent(this.getIntent());
  116.  
  117.         super.onCreate(savedInstanceState);
  118.         getWindowManager().getDefaultDisplay().getMetrics(metrics);
  119.  
  120.         if (android.os.Build.VERSION.SDK_INT >= 28) {
  121.             getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
  122.         }
  123.         removeSafeArea();
  124.     }
  125.  
  126.     @Override
  127.     protected void onNewIntent(Intent intent) {
  128.         Log.d("GameActivity", "onNewIntent() with " + intent);
  129.         removeSafeArea();
  130.         if (!embed) {
  131.             handleIntent(intent);
  132.             resetNative();
  133.             startNative();
  134.         }
  135.         removeSafeArea();
  136.     }
  137.  
  138.     protected void handleIntent(Intent intent) {
  139.         Uri game = intent.getData();
  140.  
  141.         if (!embed && game != null) {
  142.             String scheme = game.getScheme();
  143.             String path = game.getPath();
  144.             // If we have a game via the intent data we we try to figure out how we have to load it. We
  145.             // support the following variations:
  146.             // * a main.lua file: set gamePath to the directory containing main.lua
  147.             // * otherwise: set gamePath to the file
  148.             if (scheme.equals("file")) {
  149.                 Log.d("GameActivity", "Received file:// intent with path: " + path);
  150.                 // If we were given the path of a main.lua then use its
  151.                 // directory. Otherwise use full path.
  152.                 List<String> path_segments = game.getPathSegments();
  153.                 if (path_segments.get(path_segments.size() - 1).equals("main.lua")) {
  154.                     gamePath = path.substring(0, path.length() - "main.lua".length());
  155.                 } else {
  156.                     gamePath = path;
  157.                 }
  158.             } else if (scheme.equals("content")) {
  159.                 Log.d("GameActivity", "Received content:// intent with path: " + path);
  160.                 try {
  161.                     String filename = "game.love";
  162.                     String[] pathSegments = path.split("/");
  163.                     if (pathSegments != null && pathSegments.length > 0) {
  164.                         filename = pathSegments[pathSegments.length - 1];
  165.                     }
  166.  
  167.                     String destination_file = this.getCacheDir().getPath() + "/" + filename;
  168.                     InputStream data = getContentResolver().openInputStream(game);
  169.  
  170.                     // copyAssetFile automatically closes the InputStream
  171.                     if (copyAssetFile(data, destination_file)) {
  172.                         gamePath = destination_file;
  173.                         storagePermissionUnnecessary = true;
  174.                     }
  175.                 } catch (Exception e) {
  176.                     Log.d("GameActivity", "could not read content uri " + game.toString() + ": " + e.getMessage());
  177.                 }
  178.             } else {
  179.                 Log.e("GameActivity", "Unsupported scheme: '" + game.getScheme() + "'.");
  180.  
  181.                 AlertDialog.Builder alert_dialog = new AlertDialog.Builder(this);
  182.                 alert_dialog.setMessage("Could not load LÖVE game '" + path
  183.                         + "' as it uses unsupported scheme '" + game.getScheme()
  184.                         + "'. Please contact the developer.");
  185.                 alert_dialog.setTitle("LÖVE for Android Error");
  186.                 alert_dialog.setPositiveButton("Exit",
  187.                         new DialogInterface.OnClickListener() {
  188.                             @Override
  189.                             public void onClick(DialogInterface dialog, int id) {
  190.                                 finish();
  191.                             }
  192.                         });
  193.                 alert_dialog.setCancelable(false);
  194.                 alert_dialog.create().show();
  195.             }
  196.         } else {
  197.             // No game specified via the intent data or embed build is used.
  198.             // Check whether we have a game.love in our assets.
  199.             boolean game_love_in_assets = false;
  200.             try {
  201.                 List<String> assets = Arrays.asList(getAssets().list(""));
  202.                 game_love_in_assets = assets.contains("game.love");
  203.             } catch (Exception e) {
  204.                 Log.d("GameActivity", "could not list application assets:" + e.getMessage());
  205.             }
  206.  
  207.             if (game_love_in_assets) {
  208.                 // If we have a game.love in our assets folder copy it to the cache folder
  209.                 // so that we can load it from native LÖVE code
  210.                 String destination_file = this.getCacheDir().getPath() + "/game.love";
  211.  
  212.                 try {
  213.                     InputStream gameStream = getAssets().open("game.love");
  214.                     if (mustCacheArchive && copyAssetFile(gameStream, destination_file))
  215.                         gamePath = destination_file;
  216.                     else
  217.                         gamePath = "game.love";
  218.                     storagePermissionUnnecessary = true;
  219.                 } catch (IOException e) {
  220.                     Log.d("GameActivity", "Could not open game.love from assets: " + e.getMessage());
  221.                     gamePath = "";
  222.                     storagePermissionUnnecessary = false;
  223.                 }
  224.             } else {
  225.                 gamePath = "";
  226.                 storagePermissionUnnecessary = false;
  227.             }
  228.         }
  229.  
  230.         Log.d("GameActivity", "new gamePath: " + gamePath);
  231.     }
  232.  
  233.     protected void checkLovegameFolder() {
  234.         // If no game.love was found fall back to the game in <external storage>/lovegame
  235.         // if using normal or playstore build
  236.         if (!embed) {
  237.             Log.d("GameActivity", "fallback to lovegame folder");
  238.             if (hasExternalStoragePermission()) {
  239.                 File ext = Environment.getExternalStorageDirectory();
  240.                 if ((new File(ext, "/lovegame/main.lua")).exists()) {
  241.                     gamePath = ext.getPath() + "/lovegame/";
  242.                 }
  243.             } else {
  244.                 Log.d("GameActivity", "Cannot load game from /sdcard/lovegame: permission not granted");
  245.             }
  246.         }
  247.     }
  248.  
  249.     @Override
  250.     protected void onDestroy() {
  251.         if (vibrator != null) {
  252.             Log.d("GameActivity", "Cancelling vibration");
  253.             vibrator.cancel();
  254.         }
  255.         super.onDestroy();
  256.     }
  257.  
  258.     @Override
  259.     protected void onPause() {
  260.         if (vibrator != null) {
  261.             Log.d("GameActivity", "Cancelling vibration");
  262.             vibrator.cancel();
  263.         }
  264.         super.onPause();
  265.     }
  266.  
  267.     @Override
  268.     public void onResume() {
  269.         super.onResume();
  270.  
  271.         if (immersiveActive) {
  272.             int flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
  273.  
  274.             if (android.os.Build.VERSION.SDK_INT >= 16) {
  275.                 flags |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
  276.                         View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
  277.                         View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
  278.                         View.SYSTEM_UI_FLAG_FULLSCREEN;
  279.             }
  280.             if (android.os.Build.VERSION.SDK_INT >= 19) {
  281.                 flags |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
  282.             }
  283.  
  284.             getWindow().getDecorView().setSystemUiVisibility(flags);
  285.         }
  286.  
  287.         removeSafeArea();
  288.     }
  289.  
  290.     private void removeSafeArea()
  291.     {
  292.  
  293.  
  294.         if (android.os.Build.VERSION.SDK_INT >= 19)
  295.         {
  296.             int flags = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
  297.                     View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
  298.                     View.SYSTEM_UI_FLAG_FULLSCREEN |
  299.                     View.SYSTEM_UI_FLAG_VISIBLE;
  300.                     //View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
  301.  
  302.             getWindow().getDecorView().setSystemUiVisibility(flags);
  303.             getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
  304.  
  305.         }
  306.  
  307.  
  308.     }
  309.  
  310.     @Keep
  311.     public void setImmersiveMode(boolean immersive_mode) {
  312.         if (android.os.Build.VERSION.SDK_INT < 11) {
  313.             // The API getWindow().getDecorView().setSystemUiVisibility() was
  314.             // added in Android 11 (a.k.a. Honeycomb, a.k.a. 3.0.x). If we run
  315.             // on this we do nothing.
  316.             return;
  317.         }
  318.  
  319.         immersiveActive = immersive_mode;
  320.  
  321.         final Object lock = new Object();
  322.         final boolean immersive_enabled = immersive_mode;
  323.         synchronized (lock) {
  324.             runOnUiThread(new Runnable() {
  325.                 @Override
  326.                 public void run() {
  327.                     synchronized (lock) {
  328.                         if (immersive_enabled) {
  329.                             int flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
  330.  
  331.                             if (android.os.Build.VERSION.SDK_INT >= 16) {
  332.                                 flags |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
  333.                                         View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
  334.                                         View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
  335.                                         View.SYSTEM_UI_FLAG_FULLSCREEN;
  336.                             }
  337.                             if (android.os.Build.VERSION.SDK_INT >= 19) {
  338.                                 flags |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
  339.                             }
  340.                             getWindow().getDecorView().setSystemUiVisibility(flags);
  341.                         } else if (android.os.Build.VERSION.SDK_INT >= 16) {
  342.                             getWindow().getDecorView().setSystemUiVisibility(
  343.                                     View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
  344.                                             View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
  345.                             );
  346.                         }
  347.  
  348.                         lock.notify();
  349.                     }
  350.                 }
  351.             });
  352.         }
  353.         ;
  354.     }
  355.  
  356.     @Keep
  357.     public boolean getImmersiveMode() {
  358.         return immersiveActive;
  359.     }
  360.  
  361.     @Keep
  362.     public static String getGamePath() {
  363.         GameActivity self = (GameActivity) mSingleton; // use SDL provided one
  364.         Log.d("GameActivity", "called getGamePath(), game path = " + gamePath);
  365.  
  366.         if (gamePath.length() > 0) {
  367.             if(self.storagePermissionUnnecessary || self.hasExternalStoragePermission()) {
  368.                 return gamePath;
  369.             } else {
  370.                 Log.d("GameActivity", "cannot open game " + gamePath + ": no external storage permission given!");
  371.             }
  372.  
  373.         } else {
  374.             self.checkLovegameFolder();
  375.             if (gamePath.length() > 0)
  376.                 return gamePath;
  377.         }
  378.  
  379.         return "";
  380.     }
  381.  
  382.     public static DisplayMetrics getMetrics() {
  383.         return metrics;
  384.     }
  385.  
  386.     @Keep
  387.     public static void vibrate(double seconds) {
  388.         if (vibrator != null) {
  389.             vibrator.vibrate((long) (seconds * 1000.));
  390.         }
  391.     }
  392.  
  393.     @Keep
  394.     public static boolean openURL(String url) {
  395.         Log.d("GameActivity", "opening url = " + url);
  396.         try {
  397.             Intent i = new Intent(Intent.ACTION_VIEW);
  398.             i.setData(Uri.parse(url));
  399.             i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
  400.             context.startActivity(i);
  401.             return true;
  402.         } catch (RuntimeException e) {
  403.             Log.d("GameActivity", "love.system.openURL", e);
  404.             return false;
  405.         }
  406.     }
  407.  
  408.     /**
  409.      * Copies a given file from the assets folder to the destination.
  410.      *
  411.      * @return true if successful
  412.      */
  413.     boolean copyAssetFile(InputStream source_stream, String destinationFileName) {
  414.         boolean success = false;
  415.  
  416.         BufferedOutputStream destination_stream = null;
  417.         try {
  418.             destination_stream = new BufferedOutputStream(new FileOutputStream(destinationFileName, false));
  419.         } catch (IOException e) {
  420.             Log.d("GameActivity", "Could not open destination file: " + e.getMessage());
  421.         }
  422.  
  423.         // perform the copying
  424.         int chunk_read = 0;
  425.         int bytes_written = 0;
  426.  
  427.         assert (source_stream != null && destination_stream != null);
  428.  
  429.         try {
  430.             byte[] buf = new byte[1024];
  431.             chunk_read = source_stream.read(buf);
  432.             do {
  433.                 destination_stream.write(buf, 0, chunk_read);
  434.                 bytes_written += chunk_read;
  435.                 chunk_read = source_stream.read(buf);
  436.             } while (chunk_read != -1);
  437.         } catch (IOException e) {
  438.             Log.d("GameActivity", "Copying failed:" + e.getMessage());
  439.         }
  440.  
  441.         // close streams
  442.         try {
  443.             if (source_stream != null) source_stream.close();
  444.             if (destination_stream != null) destination_stream.close();
  445.             success = true;
  446.         } catch (IOException e) {
  447.             Log.d("GameActivity", "Copying failed: " + e.getMessage());
  448.         }
  449.  
  450.         Log.d("GameActivity", "Successfully copied stream to " + destinationFileName + " (" + bytes_written + " bytes written).");
  451.         return success;
  452.     }
  453.  
  454.     @Keep
  455.     public boolean hasBackgroundMusic() {
  456.         AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
  457.         return audioManager.isMusicActive();
  458.     }
  459.  
  460.     @Keep
  461.     public void showRecordingAudioPermissionMissingDialog() {
  462.         Log.d("GameActivity", "showRecordingAudioPermissionMissingDialog()");
  463.         runOnUiThread(new Runnable() {
  464.             @Override
  465.             public void run() {
  466.                 AlertDialog dialog = new AlertDialog.Builder(mSingleton)
  467.                         .setTitle("Audio Recording Permission Missing")
  468.                         .setMessage("It appears that this game uses mic capabilities. The game may not work correctly without mic permission!")
  469.                         .setNeutralButton("Continue", new DialogInterface.OnClickListener() {
  470.                             public void onClick(DialogInterface di, int id) {
  471.                                 synchronized (recordAudioRequestDummy) {
  472.                                     recordAudioRequestDummy.notify();
  473.                                 }
  474.                             }
  475.                         })
  476.                         .create();
  477.                 dialog.show();
  478.             }
  479.         });
  480.  
  481.         synchronized (recordAudioRequestDummy) {
  482.             try {
  483.                 recordAudioRequestDummy.wait();
  484.             } catch (InterruptedException e) {
  485.                 Log.d("GameActivity", "mic permission dialog", e);
  486.             }
  487.         }
  488.     }
  489.  
  490.     public void showExternalStoragePermissionMissingDialog() {
  491.         AlertDialog dialog = new AlertDialog.Builder(mSingleton)
  492.                 .setTitle("Storage Permission Missing")
  493.                 .setMessage("LÖVE for Android will not be able to run non-packaged games without storage permission.")
  494.                 .setNeutralButton("Continue", null)
  495.                 .create();
  496.         dialog.show();
  497.     }
  498.  
  499.     @Override
  500.     public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
  501.         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  502.  
  503.         if (grantResults.length > 0) {
  504.             Log.d("GameActivity", "Received a request permission result");
  505.  
  506.             switch (requestCode) {
  507.                 case EXTERNAL_STORAGE_REQUEST_CODE: {
  508.                     if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  509.                         Log.d("GameActivity", "Permission granted");
  510.                     } else {
  511.                         Log.d("GameActivity", "Did not get permission.");
  512.                         if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
  513.                             showExternalStoragePermissionMissingDialog();
  514.                         }
  515.                     }
  516.  
  517.                     Log.d("GameActivity", "Unlocking LÖVE thread");
  518.                     synchronized (externalStorageRequestDummy) {
  519.                         externalStorageRequestDummy[0] = grantResults[0];
  520.                         externalStorageRequestDummy.notify();
  521.                     }
  522.                     break;
  523.                 }
  524.                 case RECORD_AUDIO_REQUEST_CODE: {
  525.                     if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  526.                         Log.d("GameActivity", "Mic ermission granted");
  527.                     } else {
  528.                         Log.d("GameActivity", "Did not get mic permission.");
  529.                     }
  530.  
  531.                     Log.d("GameActivity", "Unlocking LÖVE thread");
  532.                     synchronized (recordAudioRequestDummy) {
  533.                         recordAudioRequestDummy[0] = grantResults[0];
  534.                         recordAudioRequestDummy.notify();
  535.                     }
  536.                     break;
  537.                 }
  538.             }
  539.         }
  540.     }
  541.  
  542.     @Keep
  543.     public boolean hasExternalStoragePermission() {
  544.         if (ActivityCompat.checkSelfPermission(this,
  545.                 Manifest.permission.READ_EXTERNAL_STORAGE)
  546.                 == PackageManager.PERMISSION_GRANTED) {
  547.             return true;
  548.         }
  549.  
  550.         Log.d("GameActivity", "Requesting permission and locking LÖVE thread until we have an answer.");
  551.         ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_REQUEST_CODE);
  552.  
  553.         synchronized (externalStorageRequestDummy) {
  554.             try {
  555.                 externalStorageRequestDummy.wait();
  556.             } catch (InterruptedException e) {
  557.                 Log.d("GameActivity", "requesting external storage permission", e);
  558.                 return false;
  559.             }
  560.         }
  561.  
  562.         return ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
  563.     }
  564.  
  565.     @Keep
  566.     public boolean hasRecordAudioPermission() {
  567.         return ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
  568.     }
  569.  
  570.     @Keep
  571.     public void requestRecordAudioPermission() {
  572.         if (ActivityCompat.checkSelfPermission(this,
  573.                 Manifest.permission.RECORD_AUDIO)
  574.                 == PackageManager.PERMISSION_GRANTED) {
  575.             return;
  576.         }
  577.  
  578.         Log.d("GameActivity", "Requesting mic permission and locking LÖVE thread until we have an answer.");
  579.         ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, RECORD_AUDIO_REQUEST_CODE);
  580.  
  581.         synchronized (recordAudioRequestDummy) {
  582.             try {
  583.                 recordAudioRequestDummy.wait();
  584.             } catch (InterruptedException e) {
  585.                 Log.d("GameActivity", "requesting mic permission", e);
  586.             }
  587.         }
  588.     }
  589.  
  590.     @Keep
  591.     public boolean initializeSafeArea() {
  592.         if (android.os.Build.VERSION.SDK_INT >= 28) {
  593.             DisplayCutout cutout = getWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
  594.  
  595.             if (cutout != null) {
  596.                 safeAreaTop = cutout.getSafeInsetTop();
  597.                 safeAreaLeft = cutout.getSafeInsetLeft();
  598.                 safeAreaBottom = cutout.getSafeInsetBottom();
  599.                 safeAreaRight = cutout.getSafeInsetRight();
  600.                 return true;
  601.             }
  602.         }
  603.  
  604.         return false;
  605.     }
  606. }
RAW Paste Data