OSDN Git Service

Minor cleanup for Project Sakura PR
[android-x86/packages-apps-Taskbar.git] / app / src / main / java / com / farmerbb / taskbar / util / U.java
index 1e6eb22..b38c57a 100644 (file)
@@ -62,7 +62,7 @@ import android.os.UserManager;
 import android.provider.Settings;
 
 import androidx.annotation.DimenRes;
-import androidx.core.content.ContextCompat;
+import androidx.annotation.VisibleForTesting;
 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
 import androidx.appcompat.view.ContextThemeWrapper;
 import android.util.DisplayMetrics;
@@ -79,6 +79,11 @@ import com.farmerbb.taskbar.activity.DummyActivity;
 import com.farmerbb.taskbar.activity.InvisibleActivityFreeform;
 import com.farmerbb.taskbar.activity.MainActivity;
 import com.farmerbb.taskbar.activity.TouchAbsorberActivity;
+import com.farmerbb.taskbar.helper.GlobalHelper;
+import com.farmerbb.taskbar.helper.FreeformHackHelper;
+import com.farmerbb.taskbar.helper.LauncherHelper;
+import com.farmerbb.taskbar.helper.MenuHelper;
+import com.farmerbb.taskbar.helper.ToastHelper;
 import com.farmerbb.taskbar.service.DashboardService;
 import com.farmerbb.taskbar.service.NotificationService;
 import com.farmerbb.taskbar.service.PowerMenuService;
@@ -96,6 +101,7 @@ import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 
 import static com.farmerbb.taskbar.util.Constants.*;
 
@@ -123,7 +129,6 @@ public class U {
 
     public static final int IMAGE_REQUEST_CODE = 1001;
 
-    @SuppressWarnings("deprecation")
     public static SharedPreferences getSharedPreferences(Context context) {
         return context.getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", Context.MODE_PRIVATE);
     }
@@ -151,9 +156,12 @@ public class U {
 
     @TargetApi(Build.VERSION_CODES.M)
     private static AlertDialog.Builder buildPermissionDialogStandard(Context context, Callbacks callbacks) {
+        String message = context.getString(R.string.tb_permission_dialog_message, getAppName(context))
+                + context.getString(R.string.tb_permission_dialog_instructions_phone);
+
         return new AlertDialog.Builder(context)
                 .setTitle(R.string.tb_permission_dialog_title)
-                .setMessage(R.string.tb_permission_dialog_message)
+                .setMessage(message)
                 .setPositiveButton(R.string.tb_action_grant_permission, (dialog, which) -> {
                     try {
                         Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context.getPackageName()));
@@ -168,9 +176,12 @@ public class U {
     }
 
     private static AlertDialog.Builder buildPermissionDialogAndroidTV(Context context, Callbacks callbacks) {
+        String message = context.getString(R.string.tb_permission_dialog_message, getAppName(context))
+                + context.getString(R.string.tb_permission_dialog_instructions_tv);
+
         return new AlertDialog.Builder(context)
                 .setTitle(R.string.tb_permission_dialog_title)
-                .setMessage(R.string.tb_permission_dialog_message_alt)
+                .setMessage(message)
                 .setPositiveButton(R.string.tb_action_open_settings, (dialog, which) -> {
                     try {
                         Intent intent = new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS);
@@ -231,25 +242,25 @@ public class U {
                             finalServices.isEmpty()
                                     ? powerMenuService
                                     : finalServices + ":" + powerMenuService);
-                } catch (Exception e) { /* Gracefully fail */ }
+                } catch (Exception ignored) {}
             }
 
-            new Handler().postDelayed(() -> {
+            newHandler().postDelayed(() -> {
                 Intent intent = new Intent(ACTION_ACCESSIBILITY_ACTION);
-                intent.putExtra("action", action);
+                intent.putExtra(EXTRA_ACTION, action);
                 sendBroadcast(context, intent);
 
                 try {
                     Settings.Secure.putString(context.getContentResolver(),
                             Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                             finalServices);
-                } catch (Exception e) { /* Gracefully fail */ }
+                } catch (Exception ignored) {}
 
                 if(onComplete != null) onComplete.run();
             }, 100);
         } else if(isAccessibilityServiceEnabled) {
             Intent intent = new Intent(ACTION_ACCESSIBILITY_ACTION);
-            intent.putExtra("action", action);
+            intent.putExtra(EXTRA_ACTION, action);
             sendBroadcast(context, intent);
 
             if(onComplete != null) onComplete.run();
@@ -261,7 +272,7 @@ public class U {
 
                 try {
                     context.startActivity(intent, getActivityOptionsBundle(context, ApplicationType.APP_PORTRAIT, null));
-                } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
+                } catch (IllegalArgumentException | SecurityException ignored) {}
             });
         }
     }
@@ -373,19 +384,19 @@ public class U {
                 && helper.isInFreeformWorkspace()
                 && MenuHelper.getInstance().isContextMenuOpen();
 
-        boolean noAnimation =
-                pref.getBoolean(PREF_DISABLE_ANIMATIONS, false);
+        boolean noAnimation = pref.getBoolean(PREF_DISABLE_ANIMATIONS, false);
+        boolean isAndroidR = getCurrentApiVersion() > 29.0;
 
         if(hasFreeformSupport(context)
                 && (isFreeformModeEnabled(context) || isPersistentShortcut)
                 && (!helper.isInFreeformWorkspace() || specialLaunch)) {
-            new Handler().postDelayed(() -> {
+            newHandler().postDelayed(() -> {
                 startFreeformHack(context, true);
 
-                new Handler().postDelayed(runnable, helper.isFreeformHackActive() ? 0 : 100);
+                newHandler().postDelayed(runnable, helper.isFreeformHackActive() ? 0 : isAndroidR ? 300 : 100);
             }, launchedFromTaskbar ? 0 : 100);
         } else
-            new Handler().postDelayed(runnable, !launchedFromTaskbar && noAnimation ? 100 : 0);
+            newHandler().postDelayed(runnable, !launchedFromTaskbar && noAnimation ? 100 : isAndroidR ? 100 : 0);
     }
 
     public static void startFreeformHack(Context context) {
@@ -457,7 +468,7 @@ public class U {
                         context.startActivity(intent, bundle);
                     } catch (ActivityNotFoundException e) {
                         launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context), onError);
-                    } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
+                    } catch (IllegalArgumentException | SecurityException ignored) {}
                 } else
                     launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context), onError);
             } else
@@ -505,7 +516,7 @@ public class U {
         int right = display.width;
         int bottom = display.height;
 
-        int iconSize = isOverridingFreeformHack(context) && !LauncherHelper.getInstance().isOnHomeScreen()
+        int iconSize = isOverridingFreeformHack(context) && !LauncherHelper.getInstance().isOnHomeScreen(context)
                 ? 0 : context.getResources().getDimensionPixelSize(R.dimen.tb_icon_size);
 
         if(TaskbarPosition.isVerticalLeft(position))
@@ -589,7 +600,7 @@ public class U {
         if(!FreeformHackHelper.getInstance().isTouchAbsorberActive()
                 && shouldLaunchTouchAbsorber(context)) {
             startTouchAbsorberActivity(context);
-            new Handler().postDelayed(runnable, 100);
+            newHandler().postDelayed(runnable, 100);
         } else if(openInNewWindow) {
             Intent intent = new Intent(context, DummyActivity.class);
             intent.putExtra("finish_on_pause", true);
@@ -597,7 +608,7 @@ public class U {
                     | Intent.FLAG_ACTIVITY_NO_ANIMATION);
             startActivityLowerRight(context, intent);
 
-            new Handler().postDelayed(runnable, 100);
+            newHandler().postDelayed(runnable, 100);
         } else
             runnable.run();
     }
@@ -617,7 +628,7 @@ public class U {
                             display.width + 1,
                             display.height + 1
                     ));
-        } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
+        } catch (IllegalArgumentException | SecurityException ignored) {}
     }
 
     public static void startTouchAbsorberActivity(Context context) {
@@ -648,7 +659,7 @@ public class U {
             context.startActivity(intent,
                     getActivityOptionsBundle(context, ApplicationType.FREEFORM_HACK, null,
                             left, top, right, bottom));
-        } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
+        } catch (IllegalArgumentException | SecurityException ignored) {}
     }
 
     public static void startContextMenuActivity(Context context, Bundle args) {
@@ -659,8 +670,9 @@ public class U {
         if(hasFreeformSupport(context) && FreeformHackHelper.getInstance().isInFreeformWorkspace()) {
             DisplayInfo display = getDisplayInfo(context);
 
-            if(hasBrokenSetLaunchBoundsApi())
-                intent.putExtra("context_menu_fix", true);
+            if(hasBrokenSetLaunchBoundsApi()) {
+                intent.putExtra(EXTRA_CONTEXT_MENU_FIX, true);
+            }
 
             context.startActivity(intent,
                     getActivityOptionsBundle(context, ApplicationType.CONTEXT_MENU, null,
@@ -678,7 +690,7 @@ public class U {
             else
                 url = "https://play.google.com/store/apps/details?id=" + context.getPackageName();
         } else
-            url = "https://f-droid.org/repository/browse/?fdid=" + context.getPackageName();
+            url = "https://f-droid.org/packages/" + context.getPackageName();
 
         Intent intent = new Intent(Intent.ACTION_VIEW);
         intent.setData(Uri.parse(url));
@@ -686,7 +698,7 @@ public class U {
 
         try {
             context.startActivity(intent);
-        } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
+        } catch (ActivityNotFoundException ignored) {}
     }
 
     public static boolean launcherIsDefault(Context context) {
@@ -694,14 +706,18 @@ public class U {
         homeIntent.addCategory(Intent.CATEGORY_HOME);
         ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
 
-        return defaultLauncher.activityInfo.packageName.equals(context.getPackageName());
+        try {
+            return defaultLauncher.activityInfo.packageName.equals(context.getPackageName());
+        } catch (NullPointerException e) {
+            return false;
+        }
     }
 
     private static int getMaxNumOfColumns(Context context) {
         SharedPreferences pref = getSharedPreferences(context);
         DisplayInfo display = getDisplayInfo(context);
-        float density = display.currentDensity / 160;
-        float baseTaskbarSize = getBaseTaskbarSizeFloat(context) / density;
+        float density = display.currentDensity / 160.0f;
+        float baseTaskbarSize = getBaseTaskbarSize(context) / density;
         int numOfColumns = 0;
 
         float maxScreenSize = TaskbarPosition.isVertical(context)
@@ -710,7 +726,7 @@ public class U {
 
         float iconSize = context.getResources().getDimension(R.dimen.tb_icon_size) / density;
 
-        int userMaxNumOfColumns = Integer.valueOf(pref.getString(PREF_MAX_NUM_OF_RECENTS, "10"));
+        int userMaxNumOfColumns = Integer.parseInt(pref.getString(PREF_MAX_NUM_OF_RECENTS, "10"));
 
         while(baseTaskbarSize + iconSize < maxScreenSize
                 && numOfColumns < userMaxNumOfColumns) {
@@ -725,17 +741,18 @@ public class U {
         SharedPreferences pref = getSharedPreferences(context);
         return pref.getBoolean(PREF_DISABLE_SCROLLING_LIST, false)
                 ? getMaxNumOfColumns(context)
-                : Integer.valueOf(pref.getString(PREF_MAX_NUM_OF_RECENTS, "10"));
+                : Integer.parseInt(pref.getString(PREF_MAX_NUM_OF_RECENTS, "10"));
     }
 
     public static int getStatusBarHeight(Context context) {
-        return LauncherHelper.getInstance().isOnSecondaryHomeScreen()
+        return LauncherHelper.getInstance().isOnSecondaryHomeScreen(context)
                 ? 0 : getSystemDimen(context, "status_bar_height");
     }
 
-    private static int getNavbarHeight(Context context) {
+    public static int getNavbarHeight(Context context) {
         SharedPreferences pref = getSharedPreferences(context);
-        boolean isNavbarHidden = LauncherHelper.getInstance().isOnSecondaryHomeScreen()
+        boolean isNavbarHidden = isShowHideNavbarSupported()
+                && LauncherHelper.getInstance().isOnSecondaryHomeScreen(context)
                 && pref.getBoolean(PREF_AUTO_HIDE_NAVBAR_DESKTOP_MODE, false);
 
         return isNavbarHidden ? 0 : getSystemDimen(context, "navigation_bar_height");
@@ -839,7 +856,7 @@ public class U {
     }
 
     public static boolean isServiceRunning(Context context, Class<? extends Service> cls) {
-        if(LauncherHelper.getInstance().isOnSecondaryHomeScreen()
+        if(LauncherHelper.getInstance().isOnSecondaryHomeScreen(context)
                 && (cls.equals(TaskbarService.class)
                 || cls.equals(StartMenuService.class)
                 || cls.equals(DashboardService.class)))
@@ -943,15 +960,19 @@ public class U {
                 break;
             case CONTEXT_MENU:
                 if(hasBrokenSetLaunchBoundsApi()
-                        || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f))
+                        || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f)
+                        || (isChromeOs(context) && getCurrentApiVersion() >= 30.0f))
                     stackId = getFullscreenWindowModeId();
                 break;
         }
 
-        try {
-            Method method = ActivityOptions.class.getMethod(getWindowingModeMethodName(), int.class);
-            method.invoke(options, stackId);
-        } catch (Exception e) { /* Gracefully fail */ }
+        if(stackId != -1) {
+            allowReflection();
+            try {
+                Method method = ActivityOptions.class.getMethod(getWindowingModeMethodName(), int.class);
+                method.invoke(options, stackId);
+            } catch (Exception ignored) {}
+        }
 
         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
             int launchDisplayId = LauncherHelper.getInstance().getSecondaryDisplayId();
@@ -1052,7 +1073,7 @@ public class U {
                 case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT:
                     return ApplicationType.APP_PORTRAIT;
             }
-        } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
+        } catch (PackageManager.NameNotFoundException ignored) {}
 
         return context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
                 ? ApplicationType.APP_LANDSCAPE
@@ -1073,6 +1094,11 @@ public class U {
         return context.getPackageManager().hasSystemFeature("org.chromium.arc");
     }
 
+    @TargetApi(Build.VERSION_CODES.O)
+    public static boolean isAndroidTV(Context context) {
+        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY);
+    }
+
     public static boolean isBlissOs(Context context) {
         boolean validBlissOsBuildProp = false;
 
@@ -1080,15 +1106,38 @@ public class U {
         if(blissVersion != null && !blissVersion.isEmpty())
             validBlissOsBuildProp = true;
 
-        String buildUser = getSystemProperty("ro.build.user");
-        if(buildUser != null && buildUser.equals("electrikjesus"))
-            validBlissOsBuildProp = true;
-
         return validBlissOsBuildProp
                 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
                 && isSystemApp(context);
     }
 
+    public static boolean isProjectSakura(Context context) {
+        boolean validSakuraBuildProp = false;
+
+        // TODO replace with more specific logic for Project Sakura
+        String sakuraVersion = getSystemProperty("ro.lineage.build.version");
+        if(sakuraVersion != null && !sakuraVersion.isEmpty())
+            validSakuraBuildProp = true;
+
+        return validSakuraBuildProp
+                && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
+                && isSystemApp(context);
+    }
+
+    public static boolean isAndroidGeneric(Context context) {
+        if(isBlissOs(context) || isProjectSakura(context)) return true;
+
+        boolean validAndroidGenericBuildProp = false;
+
+        String manufacturer = getSystemProperty("ro.ag.product.manufacturer");
+        if(manufacturer != null && manufacturer.equals("Android-Generic"))
+            validAndroidGenericBuildProp = true;
+
+        return validAndroidGenericBuildProp
+                && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
+                && isSystemApp(context);
+    }
+
     public static boolean isLauncherPermanentlyEnabled(Context context) {
         if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
             return true;
@@ -1109,13 +1158,18 @@ public class U {
         }
     }
 
-    public static int getBaseTaskbarSize(Context context) {
-        return Math.round(getBaseTaskbarSizeFloat(context));
+    @VisibleForTesting
+    public static float getBaseTaskbarSize(Context context) {
+        return getBaseTaskbarSizeStart(context) + getBaseTaskbarSizeEnd(context, null);
     }
 
-    private static float getBaseTaskbarSizeFloat(Context context) {
+    public static float getBaseTaskbarSizeStart(Context context) {
         SharedPreferences pref = getSharedPreferences(context);
-        float baseTaskbarSize = context.getResources().getDimension(R.dimen.tb_base_taskbar_size);
+        float baseTaskbarSize = context.getResources().getDimension(R.dimen.tb_base_size_start_plus_divider);
+
+        baseTaskbarSize += pref.getBoolean(PREF_ALT_BUTTON_CONFIG, false)
+                ? context.getResources().getDimension(R.dimen.tb_base_size_collapse_button) : 0;
+
         boolean navbarButtonsEnabled = false;
 
         if(getBooleanPrefWithDefault(context, PREF_DASHBOARD))
@@ -1139,8 +1193,28 @@ public class U {
         if(navbarButtonsEnabled)
             baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_navbar_buttons_margin);
 
-        if(isSystemTrayEnabled(context))
-            baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_systray_size);
+        return baseTaskbarSize;
+    }
+
+    public static float getBaseTaskbarSizeEnd(Context context, Map<Integer, Boolean> sysTrayIconStates) {
+        SharedPreferences pref = getSharedPreferences(context);
+        float baseTaskbarSize = pref.getBoolean(PREF_ALT_BUTTON_CONFIG, false)
+                ? 0 : context.getResources().getDimension(R.dimen.tb_base_size_collapse_button);
+
+        if(isSystemTrayEnabled(context)) {
+            float sysTraySize = context.getResources().getDimension(R.dimen.tb_systray_size);
+
+            if(sysTrayIconStates != null) {
+                for(Integer key : sysTrayIconStates.keySet()) {
+                    if(!sysTrayIconStates.get(key)) {
+                        sysTraySize -= context.getResources().getDimension(key == R.id.notification_count
+                                ? R.dimen.tb_systray_icon_size_notifications : R.dimen.tb_systray_icon_size);
+                    }
+                }
+            }
+
+            baseTaskbarSize += sysTraySize;
+        }
 
         return baseTaskbarSize;
     }
@@ -1192,29 +1266,31 @@ public class U {
     }
 
     public static void showHideNavigationBar(Context context, boolean show) {
-        showHideNavigationBar(context, getTaskbarDisplayID(), show, 0);
+        showHideNavigationBar(context, getTaskbarDisplayID(context), show, 0);
     }
 
     public static void showHideNavigationBar(Context context, int displayID, boolean show, int delay) {
-        if(!isDesktopModeActive(context)
+        if(!isShowHideNavbarSupported()
+                || (!isDesktopModeActive(context)
                 && !isBlissOs(context)
-                && !hasSupportLibrary(context, 7)) {
+                && !isProjectSakura(context)
+                && !hasSupportLibrary(context, 7))) {
             return;
         }
 
-        int value = show ? 0 : getSystemDimen(context, "navigation_bar_height") * -1;
+        Lazy<Integer> value = () -> show ? 0 : getSystemDimen(context, "navigation_bar_height") * -1;
 
         if(hasWriteSecureSettingsPermission(context)) {
             Runnable runnable = () -> {
                 try {
-                    setOverscan(displayID, value);
-                } catch (Exception e) { /* Gracefully fail */ }
+                    setOverscan(displayID, value.get());
+                } catch (Exception ignored) {}
             };
 
             if(delay == 0)
                 runnable.run();
             else
-                new Handler().postDelayed(runnable, delay);
+                newHandler().postDelayed(runnable, delay);
 
             return;
         }
@@ -1224,21 +1300,25 @@ public class U {
             intent.setPackage(BuildConfig.SUPPORT_APPLICATION_ID);
 
             intent.putExtra("display_id", displayID);
-            intent.putExtra("value", value);
+            intent.putExtra("value", value.get());
 
             context.sendBroadcast(intent);
             return;
         }
 
-        // Show or hide the system navigation bar on Bliss-x86
-        if(!isBlissOs(context)) return;
+        // Show or hide the system navigation bar on Bliss-x86 and Project Sakura
+        if(!isBlissOs(context) || !isProjectSakura(context)) return;
 
         try {
             if(getCurrentApiVersion() >= 28.0f)
                 Settings.Secure.putInt(context.getContentResolver(), "navigation_bar_visible", show ? 1 : 0);
             else
                 Settings.System.putInt(context.getContentResolver(), "navigation_bar_show", show ? 1 : 0);
-        } catch (Exception e) { /* Gracefully fail */ }
+        } catch (Exception ignored) {}
+    }
+
+    public static boolean isShowHideNavbarSupported() {
+        return getCurrentApiVersion() <= 29.0f;
     }
 
     public static void initPrefs(Context context) {
@@ -1270,7 +1350,7 @@ public class U {
         }
 
         // Customizations for BlissOS
-        if(isBlissOs(context) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+        if(isAndroidGeneric(context) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
                 && !pref.getBoolean(PREF_BLISS_OS_PREFS, false)) {
             SharedPreferences.Editor editor = pref.edit();
 
@@ -1278,11 +1358,11 @@ public class U {
                 editor.putBoolean(PREF_FREEFORM_HACK, true);
             }
 
+            editor.putString(PREF_START_BUTTON_IMAGE, PREF_START_BUTTON_IMAGE_APP_LOGO);
             editor.putString(PREF_RECENTS_AMOUNT, PREF_RECENTS_AMOUNT_RUNNING_APPS_ONLY);
             editor.putString(PREF_REFRESH_FREQUENCY, "0");
             editor.putString(PREF_MAX_NUM_OF_RECENTS, "2147483647");
             editor.putString(PREF_SORT_ORDER, "true");
-            editor.putString(PREF_START_BUTTON_IMAGE, "app_logo");
             editor.putBoolean(PREF_BUTTON_BACK, true);
             editor.putBoolean(PREF_BUTTON_HOME, true);
             editor.putBoolean(PREF_BUTTON_RECENTS, true);
@@ -1308,12 +1388,8 @@ public class U {
     }
 
     public static DisplayInfo getDisplayInfo(Context context) {
-        return getDisplayInfo(context, false);
-    }
-
-    public static DisplayInfo getDisplayInfo(Context context, boolean fromTaskbar) {
         context = getDisplayContext(context);
-        int displayID = getTaskbarDisplayID();
+        int displayID = getTaskbarDisplayID(context);
 
         DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
         Display currentDisplay = null;
@@ -1326,50 +1402,19 @@ public class U {
         }
 
         if(currentDisplay == null)
-            return new DisplayInfo(0, 0, 0, 0);
+            return new DisplayInfo(0, 0, 0, 0, false);
 
         DisplayMetrics metrics = new DisplayMetrics();
-        currentDisplay.getMetrics(metrics);
+        currentDisplay.getRealMetrics(metrics);
 
-        DisplayMetrics realMetrics = new DisplayMetrics();
-        currentDisplay.getRealMetrics(realMetrics);
-
-        DisplayInfo info = new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi, 0);
-
-        if(isChromeOs(context)) {
-            SharedPreferences pref = getSharedPreferences(context);
-            if(!pref.getBoolean(PREF_CHROME_OS_CONTEXT_MENU_FIX, true)) {
-                info.width = realMetrics.widthPixels;
-                info.height = realMetrics.heightPixels;
-            }
-
-            return info;
-        }
-
-        // Workaround for incorrect display size on devices with notches in landscape mode
-        if(fromTaskbar && getDisplayOrientation(context) == Configuration.ORIENTATION_LANDSCAPE)
-            return info;
-
-        boolean sameWidth = metrics.widthPixels == realMetrics.widthPixels;
-        boolean sameHeight = metrics.heightPixels == realMetrics.heightPixels;
-
-        if(sameWidth && !sameHeight) {
-            info.width = realMetrics.widthPixels;
-            info.height = realMetrics.heightPixels - getNavbarHeight(context);
-        }
-
-        if(!sameWidth && sameHeight) {
-            info.width = realMetrics.widthPixels - getNavbarHeight(context);
-            info.height = realMetrics.heightPixels;
-        }
-
-        return info;
+        boolean displayDefaultsToFreeform = displayDefaultsToFreeform(context, currentDisplay);
+        return new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi, 0, displayDefaultsToFreeform);
     }
 
-    private static int getTaskbarDisplayID() {
+    private static int getTaskbarDisplayID(Context context) {
         LauncherHelper helper = LauncherHelper.getInstance();
 
-        if(helper.isOnSecondaryHomeScreen())
+        if(helper.isOnSecondaryHomeScreen(context))
             return helper.getSecondaryDisplayId();
         else
             return Display.DEFAULT_DISPLAY;
@@ -1406,7 +1451,7 @@ public class U {
         if(pref.getBoolean(PREF_HIDE_TASKBAR, true)) {
             if(!isFreeformModeEnabled(context)
                     || isOverridingFreeformHack(context, false))
-                return !LauncherHelper.getInstance().isOnHomeScreen();
+                return !LauncherHelper.getInstance().isOnHomeScreen(context);
             else {
                 FreeformHackHelper helper = FreeformHackHelper.getInstance();
                 if(pendingAppLaunch)
@@ -1425,9 +1470,10 @@ public class U {
     public static boolean isOverridingFreeformHack(Context context, boolean checkPref) {
         SharedPreferences pref = getSharedPreferences(context);
         return (!checkPref || isFreeformModeEnabled(context))
-                && ((isChromeOs(context) && (pref.getBoolean(PREF_CHROME_OS_CONTEXT_MENU_FIX, true)
+                && ((isChromeOs(context) && (getChromeOsContextMenuFix(context)
                 || (pref.getBoolean(PREF_LAUNCHER, false) && launcherIsDefault(context))))
-                || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f));
+                || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f)
+                || (isChromeOs(context) && getCurrentApiVersion() >= 30.0f));
     }
 
     public static boolean isPlayStoreInstalled(Context context) {
@@ -1441,7 +1487,7 @@ public class U {
 
     public static float getCurrentApiVersion() {
         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
-            return Float.valueOf(Build.VERSION.SDK_INT + "." + Build.VERSION.PREVIEW_SDK_INT);
+            return Float.parseFloat(Build.VERSION.SDK_INT + "." + Build.VERSION.PREVIEW_SDK_INT);
         else
             return (float) Build.VERSION.SDK_INT;
     }
@@ -1492,7 +1538,7 @@ public class U {
             ApplicationInfo applicationInfo = null;
             try {
                 applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
-            } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
+            } catch (PackageManager.NameNotFoundException ignored) {}
 
             if(applicationInfo != null) {
                 AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
@@ -1519,19 +1565,22 @@ public class U {
     }
 
     private static AlertDialog.Builder buildRecentAppsDialogStandard(Context context, Callbacks callbacks) {
+        String message = context.getString(R.string.tb_enable_recent_apps, getAppName(context))
+                + context.getString(R.string.tb_enable_recent_apps_instructions_phone);
+
         return new AlertDialog.Builder(context)
                 .setTitle(R.string.tb_pref_header_recent_apps)
-                .setMessage(R.string.tb_enable_recent_apps)
+                .setMessage(message)
                 .setPositiveButton(R.string.tb_action_ok, (dialog, which) -> {
                     try {
                         Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
 
                         LauncherHelper helper = LauncherHelper.getInstance();
-                        if(helper.isOnHomeScreen() || helper.isOnSecondaryHomeScreen())
+                        if(helper.isOnHomeScreen(context))
                             applyOpenInNewWindow(context, intent);
 
                         context.startActivity(intent);
-                        showToastLong(context, R.string.tb_usage_stats_message);
+                        showToast(context, context.getString(R.string.tb_usage_stats_message, getAppName(context)), Toast.LENGTH_LONG);
 
                         callbacks.onFinish.run();
                     } catch (ActivityNotFoundException e) {
@@ -1542,9 +1591,12 @@ public class U {
     }
 
     private static AlertDialog.Builder buildRecentAppsDialogAndroidTV(Context context, Callbacks callbacks) {
+        String message = context.getString(R.string.tb_enable_recent_apps, getAppName(context))
+                + context.getString(R.string.tb_enable_recent_apps_instructions_tv);
+
         return new AlertDialog.Builder(context)
                 .setTitle(R.string.tb_pref_header_recent_apps)
-                .setMessage(R.string.tb_enable_recent_apps_alt)
+                .setMessage(message)
                 .setPositiveButton(R.string.tb_action_open_settings, (dialog, which) -> {
                     try {
                         context.startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));
@@ -1580,7 +1632,7 @@ public class U {
                 if(signature.equals(playStoreSignature))
                     return true;
             }
-        } catch (Exception e) { /* Gracefully fail */ }
+        } catch (Exception ignored) {}
 
         return false;
     }
@@ -1626,7 +1678,8 @@ public class U {
     }
 
     @SuppressLint("PrivateApi")
-    private static String getSystemProperty(String key) {
+    public static String getSystemProperty(String key) {
+        allowReflection();
         try {
             Class<?> cls = Class.forName("android.os.SystemProperties");
             return cls.getMethod("get", String.class).invoke(null, key).toString();
@@ -1686,7 +1739,7 @@ public class U {
     public static void applyCustomImage(Context context, String filename, ImageView view, Drawable errorDrawable) {
         File file = new File(context.getFilesDir() + "/tb_images", filename);
         if(file.exists()) {
-            Handler handler = new Handler();
+            Handler handler = newHandler();
             new Thread(() -> {
                 Bitmap bitmap = BitmapFactory.decodeFile(file.getPath());
                 handler.post(() -> {
@@ -1707,8 +1760,8 @@ public class U {
     public static String getDefaultStartButtonImage(Context context) {
         SharedPreferences pref = getSharedPreferences(context);
         return pref.getBoolean(PREF_APP_DRAWER_ICON, false)
-                ? "app_logo"
-                : "default";
+                ? PREF_START_BUTTON_IMAGE_APP_LOGO
+                : PREF_START_BUTTON_IMAGE_DEFAULT;
     }
 
     private static boolean shouldLaunchTouchAbsorber(Context context) {
@@ -1716,7 +1769,9 @@ public class U {
     }
 
     public static boolean isDesktopIconsEnabled(Context context) {
-        return !canBootToFreeform(context, false) && !shouldLaunchTouchAbsorber(context);
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1
+                && !canBootToFreeform(context, false)
+                && !shouldLaunchTouchAbsorber(context);
     }
 
     public static boolean isSystemTrayEnabled(Context context) {
@@ -1728,7 +1783,6 @@ public class U {
                 && !TaskbarPosition.isVertical(context);
     }
 
-    @SuppressWarnings("deprecation")
     public static boolean isLibrary(Context context) {
         return !context.getPackageName().equals(BuildConfig.APPLICATION_ID);
     }
@@ -1742,7 +1796,7 @@ public class U {
         return false;
     }
 
-    private static boolean hasAndroidTVSettings(Context context) {
+    public static boolean hasAndroidTVSettings(Context context) {
         return getInstalledPackage(context, "com.android.tv.settings") != null
                 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
     }
@@ -1761,14 +1815,14 @@ public class U {
     }
 
     public static boolean isDesktopModeSupported(Context context) {
-        if(isLauncherPermanentlyEnabled(context) || isChromeOs(context))
+        if(isLauncherPermanentlyEnabled(context) || isChromeOs(context) || isAndroidTV(context))
             return false;
 
         return Build.VERSION.SDK_INT > Build.VERSION_CODES.P
                 && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
     }
 
-    public static boolean isDesktopModeActive(Context context) {
+    public static boolean isDesktopModePrefEnabled(Context context) {
         if(!isDesktopModeSupported(context) || !hasFreeformSupport(context))
             return false;
 
@@ -1780,7 +1834,11 @@ public class U {
             desktopModePrefEnabled = false;
         }
 
-        return desktopModePrefEnabled && getExternalDisplayID(context) != Display.DEFAULT_DISPLAY;
+        return desktopModePrefEnabled;
+    }
+
+    public static boolean isDesktopModeActive(Context context) {
+        return isDesktopModePrefEnabled(context) && getExternalDisplayID(context) != Display.DEFAULT_DISPLAY;
     }
 
     private static Display getExternalDisplay(Context context) {
@@ -1797,7 +1855,7 @@ public class U {
     public static DisplayInfo getExternalDisplayInfo(Context context) {
         Display display = getExternalDisplay(context);
         if(display == null)
-            return new DisplayInfo(0, 0, 0, 0);
+            return new DisplayInfo(0, 0, 0, 0, false);
 
         DisplayMetrics metrics = new DisplayMetrics();
         display.getRealMetrics(metrics);
@@ -1809,11 +1867,13 @@ public class U {
             defaultDensity = 0;
         }
 
-        return new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi, defaultDensity);
+        boolean displayDefaultsToFreeform = displayDefaultsToFreeform(context, display);
+        return new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi, defaultDensity, displayDefaultsToFreeform);
     }
 
     @SuppressLint("PrivateApi")
     private static Object getWindowManagerService() throws Exception {
+        allowReflection();
         return Class.forName("android.view.WindowManagerGlobal")
                 .getMethod("getWindowManagerService")
                 .invoke(null);
@@ -1824,6 +1884,7 @@ public class U {
         // From android.os.UserHandle
         final int USER_CURRENT_OR_SELF = -3;
 
+        allowReflection();
         if(value.equals("reset")) {
             Class.forName("android.view.IWindowManager")
                     .getMethod("clearForcedDisplayDensityForUser", int.class, int.class)
@@ -1839,6 +1900,7 @@ public class U {
 
     @SuppressLint("PrivateApi")
     private static void setOverscan(int displayID, int value) throws Exception {
+        allowReflection();
         Class.forName("android.view.IWindowManager")
                 .getMethod("setOverscan", int.class, int.class, int.class, int.class, int.class)
                 .invoke(getWindowManagerService(), displayID, 0, 0, 0, value);
@@ -1846,6 +1908,7 @@ public class U {
 
     @SuppressLint("PrivateApi")
     private static Integer getDefaultDensity(int displayID) throws Exception {
+        allowReflection();
         return (Integer) Class.forName("android.view.IWindowManager")
                 .getMethod("getInitialDisplayDensity", int.class)
                 .invoke(getWindowManagerService(), displayID);
@@ -1874,8 +1937,7 @@ public class U {
         LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
     }
 
-    @SuppressWarnings("rawtypes")
-    public static void setComponentEnabled(Context context, Class clazz, boolean enabled) {
+    public static void setComponentEnabled(Context context, Class<?> clazz, boolean enabled) {
         ComponentName component = new ComponentName(context, clazz);
         context.getPackageManager().setComponentEnabledSetting(component,
                 enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
@@ -1962,9 +2024,8 @@ public class U {
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && !isChromeOs(context) && !isLibrary(context);
     }
 
-    @SuppressWarnings("rawtypes")
-    public static Intent getThemedIntent(Context context, Class clazz) {
-        Class newClass;
+    public static Intent getThemedIntent(Context context, Class<?> clazz) {
+        Class<?> newClass;
 
         if(!isDarkTheme(context))
             newClass = clazz;
@@ -1983,31 +2044,23 @@ public class U {
 
     public static boolean getBooleanPrefWithDefault(Context context, String key) {
         context = getDisplayContext(context);
-        int resId = getDefaultPrefResID(context, key, R.bool.class);
+        int resId = getDefaultPrefResID(key, R.bool.class);
 
         SharedPreferences pref = getSharedPreferences(context);
-        return pref.getBoolean(key, context.getResources().getBoolean(resId));
+        boolean def = pref.getBoolean(key + "_default", context.getResources().getBoolean(resId));
+        return pref.getBoolean(key, def);
     }
 
     public static int getIntPrefWithDefault(Context context, String key) {
         context = getDisplayContext(context);
-        int resId = getDefaultPrefResID(context, key, R.integer.class);
+        int resId = getDefaultPrefResID(key, R.integer.class);
 
         SharedPreferences pref = getSharedPreferences(context);
-        return pref.getInt(key, context.getResources().getInteger(resId));
+        int def = pref.getInt(key + "_default", context.getResources().getInteger(resId));
+        return pref.getInt(key, def);
     }
 
-    public static void sanitizePrefs(Context context, String... keys) {
-        SharedPreferences pref = getSharedPreferences(context);
-
-        for(String key : keys) {
-            if(!pref.getBoolean(key + "_is_modified", false))
-                pref.edit().remove(key).apply();
-        }
-    }
-
-    @SuppressWarnings("rawtypes")
-    private static int getDefaultPrefResID(Context context, String key, Class rClass) {
+    private static int getDefaultPrefResID(String key, Class<?> rClass) {
         int resId;
 
         try {
@@ -2018,7 +2071,6 @@ public class U {
             return 0;
         }
 
-        sanitizePrefs(context, key);
         return resId;
     }
 
@@ -2056,4 +2108,50 @@ public class U {
             }
         }
     }
+
+    public static boolean isConsumerBuild(Context context) {
+        return !BuildConfig.DEBUG
+                && !context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
+                && !isLibrary(context);
+    }
+
+    public static void allowReflection() {
+        GlobalHelper helper = GlobalHelper.getInstance();
+        if(helper.isReflectionAllowed()) return;
+
+        try {
+            Method forName = Class.class.getDeclaredMethod("forName", String.class);
+            Method getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class);
+
+            Class<?> vmRuntimeClass = (Class<?>) forName.invoke(null, "dalvik.system.VMRuntime");
+            Method getRuntime = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null);
+            Method setHiddenApiExemptions = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", new Class[]{String[].class});
+
+            Object vmRuntime = getRuntime.invoke(null);
+            setHiddenApiExemptions.invoke(vmRuntime, new Object[]{new String[]{"L"}});
+        } catch (Throwable ignored) {}
+
+        helper.setReflectionAllowed(true);
+    }
+
+    public static Handler newHandler() {
+        return new Handler(Looper.getMainLooper());
+    }
+
+    public static CharSequence getAppName(Context context) {
+        return context.getApplicationInfo().loadLabel(context.getPackageManager());
+    }
+
+    public static boolean getChromeOsContextMenuFix(Context context) {
+        if(getCurrentApiVersion() >= 30.0f) return false;
+
+        SharedPreferences pref = getSharedPreferences(context);
+        return pref.getBoolean(PREF_CHROME_OS_CONTEXT_MENU_FIX, true);
+    }
+
+    private static boolean displayDefaultsToFreeform(Context context, Display display) {
+        Context dispContext = context.createDisplayContext(display);
+        String configString = dispContext.getResources().getConfiguration().toString();
+        return configString.contains("mDisplayWindowingMode=freeform");
+    }
 }