OSDN Git Service

Abstract logic for setting components enabled / disabled
[android-x86/packages-apps-Taskbar.git] / app / src / main / java / com / farmerbb / taskbar / util / U.java
index 4036003..4d922d6 100644 (file)
@@ -19,15 +19,18 @@ import android.Manifest;
 import android.accessibilityservice.AccessibilityService;
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
+import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.AlertDialog;
 import android.app.AppOpsManager;
 import android.app.Service;
 import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.SharedPreferences;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
@@ -65,8 +68,10 @@ import com.farmerbb.taskbar.R;
 import com.farmerbb.taskbar.activity.ContextMenuActivity;
 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.activity.dark.ContextMenuActivityDark;
+import com.farmerbb.taskbar.content.TaskbarIntent;
 import com.farmerbb.taskbar.service.DashboardService;
 import com.farmerbb.taskbar.service.NotificationService;
 import com.farmerbb.taskbar.service.PowerMenuService;
@@ -78,6 +83,7 @@ import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -104,6 +110,9 @@ public class U {
     private static final int WINDOWING_MODE_FULLSCREEN = 1;
     private static final int WINDOWING_MODE_FREEFORM = 5;
 
+    public static final int EXPORT = 123;
+    public static final int IMPORT = 456;
+
     @SuppressWarnings("deprecation")
     public static SharedPreferences getSharedPreferences(Context context) {
         return context.getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", Context.MODE_PRIVATE);
@@ -113,7 +122,6 @@ public class U {
         showPermissionDialog(context, null, null);
     }
 
-    @TargetApi(Build.VERSION_CODES.M)
     public static AlertDialog showPermissionDialog(Context context, Runnable onError, Runnable onFinish) {
         Runnable finalOnFinish = onFinish == null
                 ? () -> {}
@@ -123,25 +131,48 @@ public class U {
                 ? () -> showErrorDialog(context, "SYSTEM_ALERT_WINDOW", finalOnFinish)
                 : onError;
 
-        AlertDialog.Builder builder = new AlertDialog.Builder(context);
-        builder.setTitle(R.string.tb_permission_dialog_title)
+        AlertDialog.Builder builder;
+        if(hasAndroidTVSettings(context))
+            builder = buildPermissionDialogAndroidTV(context, finalOnError, finalOnFinish);
+        else
+            builder = buildPermissionDialogStandard(context, finalOnError, finalOnFinish);
+
+        AlertDialog dialog = builder.create();
+        dialog.show();
+        dialog.setCancelable(false);
+
+        return dialog;
+    }
+
+    @TargetApi(Build.VERSION_CODES.M)
+    private static AlertDialog.Builder buildPermissionDialogStandard(Context context, Runnable onError, Runnable onFinish) {
+        return new AlertDialog.Builder(context)
+                .setTitle(R.string.tb_permission_dialog_title)
                 .setMessage(R.string.tb_permission_dialog_message)
                 .setPositiveButton(R.string.tb_action_grant_permission, (dialog, which) -> {
                     try {
                         context.startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                                 Uri.parse("package:" + context.getPackageName())));
 
-                        finalOnFinish.run();
+                        onFinish.run();
                     } catch (ActivityNotFoundException e) {
-                        finalOnError.run();
+                        onError.run();
                     }
                 });
+    }
 
-        AlertDialog dialog = builder.create();
-        dialog.show();
-        dialog.setCancelable(false);
-
-        return dialog;
+    private static AlertDialog.Builder buildPermissionDialogAndroidTV(Context context, Runnable onError, Runnable onFinish) {
+        return new AlertDialog.Builder(context)
+                .setTitle(R.string.tb_permission_dialog_title)
+                .setMessage(R.string.tb_permission_dialog_message_alt)
+                .setPositiveButton(R.string.tb_action_open_settings, (dialog, which) -> {
+                    try {
+                        context.startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));
+                        onFinish.run();
+                    } catch (ActivityNotFoundException e) {
+                        onError.run();
+                    }
+                });
     }
 
     public static AlertDialog showErrorDialog(Context context, String appopCmd) {
@@ -175,9 +206,7 @@ public class U {
     }
 
     public static void sendAccessibilityAction(Context context, int action, Runnable onComplete) {
-        ComponentName component = new ComponentName(context, PowerMenuService.class);
-        context.getPackageManager().setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
-                PackageManager.DONT_KILL_APP);
+        setComponentEnabled(context, PowerMenuService.class, true);
 
         boolean isAccessibilityServiceEnabled = isAccessibilityServiceEnabled(context);
 
@@ -201,9 +230,9 @@ public class U {
             }
 
             new Handler().postDelayed(() -> {
-                Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
+                Intent intent = new Intent(TaskbarIntent.ACTION_ACCESSIBILITY_ACTION);
                 intent.putExtra("action", action);
-                LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+                sendBroadcast(context, intent);
 
                 try {
                     Settings.Secure.putString(context.getContentResolver(),
@@ -214,9 +243,9 @@ public class U {
                 if(onComplete != null) onComplete.run();
             }, 100);
         } else if(isAccessibilityServiceEnabled) {
-            Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
+            Intent intent = new Intent(TaskbarIntent.ACTION_ACCESSIBILITY_ACTION);
             intent.putExtra("action", action);
-            LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+            sendBroadcast(context, intent);
 
             if(onComplete != null) onComplete.run();
         } else {
@@ -226,7 +255,7 @@ public class U {
                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
 
                 try {
-                    context.startActivity(intent, getActivityOptionsBundle(context, ApplicationType.APPLICATION, null));
+                    context.startActivity(intent, getActivityOptionsBundle(context, ApplicationType.APP_PORTRAIT, null));
                 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
             });
         }
@@ -274,8 +303,10 @@ public class U {
                 null,
                 false,
                 false,
+                false,
                 shortcut,
-                view);
+                view,
+                null);
     }
 
     public static void launchApp(final Context context,
@@ -288,27 +319,47 @@ public class U {
                 entry,
                 windowSize,
                 launchedFromTaskbar,
+                false,
                 openInNewWindow,
                 null,
-                view);
+                view,
+                null);
+    }
+
+    public static void launchApp(final Context context,
+                                 final AppEntry entry,
+                                 final String windowSize,
+                                 final Runnable onError) {
+        launchApp(context,
+                entry,
+                windowSize,
+                false,
+                true,
+                false,
+                null,
+                null,
+                onError);
     }
 
     private static void launchApp(final Context context,
                                   final AppEntry entry,
                                   final String windowSize,
                                   final boolean launchedFromTaskbar,
+                                  final boolean isPersistentShortcut,
                                   final boolean openInNewWindow,
                                   final ShortcutInfo shortcut,
-                                  final View view) {
-        launchApp(context, launchedFromTaskbar, () -> continueLaunchingApp(context, entry,
-                windowSize, openInNewWindow, shortcut, view));
+                                  final View view,
+                                  final Runnable onError) {
+        launchApp(context, launchedFromTaskbar, isPersistentShortcut, () ->
+                continueLaunchingApp(context, entry, windowSize, openInNewWindow, shortcut, view, onError)
+        );
     }
 
     public static void launchApp(Context context, Runnable runnable) {
-        launchApp(context, true, runnable);
+        launchApp(context, true, false, runnable);
     }
 
-    private static void launchApp(Context context, boolean launchedFromTaskbar, Runnable runnable) {
+    private static void launchApp(Context context, boolean launchedFromTaskbar, boolean isPersistentShortcut, Runnable runnable) {
         SharedPreferences pref = getSharedPreferences(context);
         FreeformHackHelper helper = FreeformHackHelper.getInstance();
 
@@ -319,7 +370,7 @@ public class U {
         boolean noAnimation = pref.getBoolean("disable_animations", false);
 
         if(hasFreeformSupport(context)
-                && pref.getBoolean("freeform_hack", false)
+                && (pref.getBoolean("freeform_hack", false) || isPersistentShortcut)
                 && (!helper.isInFreeformWorkspace() || specialLaunch)) {
             new Handler().postDelayed(() -> {
                 startFreeformHack(context, true);
@@ -349,9 +400,9 @@ public class U {
     }
 
     public static void stopFreeformHack(Context context) {
-        LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.FINISH_FREEFORM_ACTIVITY"));
+        sendBroadcast(context, TaskbarIntent.ACTION_FINISH_FREEFORM_ACTIVITY);
 
-        if(isOverridingFreeformHack(context)) {
+        if(isOverridingFreeformHack(context, false)) {
             FreeformHackHelper helper = FreeformHackHelper.getInstance();
             helper.setFreeformHackActive(false);
             helper.setInFreeformWorkspace(false);
@@ -364,7 +415,8 @@ public class U {
                                              String windowSize,
                                              boolean openInNewWindow,
                                              ShortcutInfo shortcut,
-                                             View view) {
+                                             View view,
+                                             Runnable onError) {
         SharedPreferences pref = getSharedPreferences(context);
         Intent intent = new Intent();
         intent.setComponent(ComponentName.unflattenFromString(entry.getComponentName()));
@@ -395,7 +447,7 @@ public class U {
             }
         }
 
-        ApplicationType type = getApplicationType(context, entry.getPackageName());
+        ApplicationType type = getApplicationType(context, entry);
 
         if(windowSize == null)
             windowSize = SavedWindowSizes.getInstance(context).getWindowSize(context, entry.getPackageName());
@@ -409,38 +461,37 @@ public class U {
                     try {
                         context.startActivity(intent, bundle);
                     } catch (ActivityNotFoundException e) {
-                        launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context));
+                        launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context), onError);
                     } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
                 } else
-                    launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context));
+                    launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context), onError);
             } else
-                launchShortcut(context, shortcut, bundle);
+                launchShortcut(context, shortcut, bundle, onError);
         });
 
-        if(shouldCollapse(context, true))
-            LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_TASKBAR"));
-        else
-            LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_START_MENU"));
+        if(shouldCollapse(context, true)) {
+            sendBroadcast(context, TaskbarIntent.ACTION_HIDE_TASKBAR);
+        } else {
+            sendBroadcast(context, TaskbarIntent.ACTION_HIDE_START_MENU);
+        }
     }
 
-    @TargetApi(Build.VERSION_CODES.N)
-    private static Bundle launchMode1(Context context, ApplicationType type, View view) {
+    private static Bundle launchMode1(Context context, ApplicationType type, View view, int factor) {
         DisplayInfo display = getDisplayInfo(context);
 
-        int width1 = display.width / 8;
+        int width1 = display.width / factor;
         int width2 = display.width - width1;
-        int height1 = display.height / 8;
+        int height1 = display.height / factor;
         int height2 = display.height - height1;
 
-        return getActivityOptions(context, type, view).setLaunchBounds(new Rect(
+        return getActivityOptionsBundle(context, type, view,
                 width1,
                 height1,
                 width2,
                 height2
-        )).toBundle();
+        );
     }
 
-    @TargetApi(Build.VERSION_CODES.N)
     private static Bundle launchMode2(Context context, int launchType, ApplicationType type, View view) {
         DisplayInfo display = getDisplayInfo(context);
 
@@ -480,49 +531,57 @@ public class U {
         else if(launchType == LEFT && isPortrait)
             bottom = halfPortrait;
 
-        return getActivityOptions(context, type, view)
-                .setLaunchBounds(new Rect(left, top, right, bottom)).toBundle();
+        return getActivityOptionsBundle(context, type, view, left, top, right, bottom);
     }
 
-    @TargetApi(Build.VERSION_CODES.N)
     private static Bundle launchMode3(Context context, ApplicationType type, View view) {
         DisplayInfo display = getDisplayInfo(context);
 
+        boolean isLandscape = type == ApplicationType.APP_LANDSCAPE;
+        int widthDimen = isLandscape ? R.dimen.tb_phone_size_height : R.dimen.tb_phone_size_width;
+        int heightDimen = isLandscape ? R.dimen.tb_phone_size_width : R.dimen.tb_phone_size_height;
+
         int width1 = display.width / 2;
-        int width2 = context.getResources().getDimensionPixelSize(R.dimen.tb_phone_size_width) / 2;
+        int width2 = context.getResources().getDimensionPixelSize(widthDimen) / 2;
         int height1 = display.height / 2;
-        int height2 = context.getResources().getDimensionPixelSize(R.dimen.tb_phone_size_height) / 2;
+        int height2 = context.getResources().getDimensionPixelSize(heightDimen) / 2;
 
-        return getActivityOptions(context, type, view).setLaunchBounds(new Rect(
+        return getActivityOptionsBundle(context, type, view,
                 width1 - width2,
                 height1 - height2,
                 width1 + width2,
                 height1 + height2
-        )).toBundle();
+        );
     }
 
-    private static void launchAndroidForWork(Context context, ComponentName componentName, Bundle bundle, long userId) {
+    private static void launchAndroidForWork(Context context, ComponentName componentName, Bundle bundle, long userId, Runnable onError) {
         UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
         LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
 
         try {
             launcherApps.startMainActivity(componentName, userManager.getUserForSerialNumber(userId), null, bundle);
-        } catch (ActivityNotFoundException | NullPointerException e) { /* Gracefully fail */ }
+        } catch (ActivityNotFoundException | NullPointerException
+                | IllegalStateException | SecurityException e) {
+            if(onError != null) launchApp(context, onError);
+        }
     }
 
     @TargetApi(Build.VERSION_CODES.N_MR1)
-    private static void launchShortcut(Context context, ShortcutInfo shortcut, Bundle bundle) {
+    private static void launchShortcut(Context context, ShortcutInfo shortcut, Bundle bundle, Runnable onError) {
         LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
 
         if(launcherApps.hasShortcutHostPermission()) {
             try {
                 launcherApps.startShortcut(shortcut, null, bundle);
-            } catch (ActivityNotFoundException | NullPointerException e) { /* Gracefully fail */ }
+            } catch (ActivityNotFoundException | NullPointerException
+                    | IllegalStateException | SecurityException e) {
+                if(onError != null) launchApp(context, onError);
+            }
         }
     }
 
     private static void prepareToStartActivity(Context context, boolean openInNewWindow, Runnable runnable) {
-        LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_CONTEXT_MENU"));
+        sendBroadcast(context, TaskbarIntent.ACTION_HIDE_CONTEXT_MENU);
 
         if(!FreeformHackHelper.getInstance().isTouchAbsorberActive()
                 && shouldLaunchTouchAbsorber(context)) {
@@ -545,22 +604,19 @@ public class U {
         prepareToStartActivity(context, false, () -> context.startActivity(intent, bundle));
     }
 
-    @TargetApi(Build.VERSION_CODES.N)
     public static void startActivityLowerRight(Context context, Intent intent) {
         DisplayInfo display = getDisplayInfo(context);
         try {
             context.startActivity(intent,
-                    getActivityOptions(context, ApplicationType.FREEFORM_HACK, null)
-                            .setLaunchBounds(new Rect(
-                                    display.width,
-                                    display.height,
-                                    display.width + 1,
-                                    display.height + 1
-                            )).toBundle());
+                    getActivityOptionsBundle(context, ApplicationType.FREEFORM_HACK, null,
+                            display.width,
+                            display.height,
+                            display.width + 1,
+                            display.height + 1
+                    ));
         } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
     }
 
-    @TargetApi(Build.VERSION_CODES.N)
     public static void startTouchAbsorberActivity(Context context) {
         String position = getTaskbarPosition(context);
         DisplayInfo display = getDisplayInfo(context);
@@ -587,8 +643,8 @@ public class U {
 
         try {
             context.startActivity(intent,
-                    getActivityOptions(context, ApplicationType.FREEFORM_HACK, null)
-                            .setLaunchBounds(new Rect(left, top, right, bottom)).toBundle());
+                    getActivityOptionsBundle(context, ApplicationType.FREEFORM_HACK, null,
+                            left, top, right, bottom));
         } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
     }
 
@@ -617,10 +673,8 @@ public class U {
                 intent.putExtra("context_menu_fix", true);
 
             context.startActivity(intent,
-                    getActivityOptions(context, ApplicationType.CONTEXT_MENU, null)
-                            .setLaunchBounds(
-                                    new Rect(0, 0, display.width, display.height)
-                            ).toBundle());
+                    getActivityOptionsBundle(context, ApplicationType.CONTEXT_MENU, null,
+                            0, 0, display.width, display.height));
         } else
             context.startActivity(intent);
     }
@@ -887,10 +941,11 @@ public class U {
     }
 
     public static boolean canBootToFreeform(Context context) {
-        SharedPreferences pref = getSharedPreferences(context);
-        return hasFreeformSupport(context)
-                && pref.getBoolean("freeform_hack", false)
-                && !isOverridingFreeformHack(context);
+        return canBootToFreeform(context, true);
+    }
+
+    private static boolean canBootToFreeform(Context context, boolean checkPref) {
+        return hasFreeformSupport(context) && !isOverridingFreeformHack(context, checkPref);
     }
 
     public static boolean isSamsungDevice() {
@@ -943,7 +998,6 @@ public class U {
         return pref.getInt("accent_color", context.getResources().getInteger(R.integer.tb_translucent_white));
     }
 
-    @TargetApi(Build.VERSION_CODES.M)
     public static boolean canDrawOverlays(Context context) {
         return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context);
     }
@@ -967,13 +1021,21 @@ public class U {
         return getActivityOptions(null, null, view);
     }
 
-    @TargetApi(Build.VERSION_CODES.N)
-    private static ActivityOptions getActivityOptions(Context context, ApplicationType applicationType, View view) {
+    public static ActivityOptions getActivityOptions(Context context, ApplicationType applicationType, View view) {
         ActivityOptions options;
         if(view != null)
             options = ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight());
-        else
+        else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
             options = ActivityOptions.makeBasic();
+        else {
+            try {
+                Constructor<ActivityOptions> constructor = ActivityOptions.class.getDeclaredConstructor();
+                constructor.setAccessible(true);
+                options = constructor.newInstance();
+            } catch (Exception e) {
+                return null;
+            }
+        }
 
         if(applicationType == null)
             return options;
@@ -981,13 +1043,14 @@ public class U {
         int stackId = -1;
 
         switch(applicationType) {
-            case APPLICATION:
+            case APP_PORTRAIT:
+            case APP_LANDSCAPE:
                 if(FreeformHackHelper.getInstance().isFreeformHackActive())
                     stackId = getFreeformWindowModeId();
                 else
                     stackId = getFullscreenWindowModeId();
                 break;
-            case GAME:
+            case APP_FULLSCREEN:
                 stackId = getFullscreenWindowModeId();
                 break;
             case FREEFORM_HACK:
@@ -1047,8 +1110,12 @@ public class U {
             return getActivityOptions(view).toBundle();
 
         switch(windowSize) {
+            case "standard":
+                if(getCurrentApiVersion() > 29.0f)
+                    return launchMode1(context, type, view, 4);
+                break;
             case "large":
-                return launchMode1(context, type, view);
+                return launchMode1(context, type, view, 8);
             case "fullscreen":
                 return launchMode2(context, MAXIMIZED, type, view);
             case "half_left":
@@ -1062,8 +1129,47 @@ public class U {
         return getActivityOptions(context, type, view).toBundle();
     }
 
-    private static ApplicationType getApplicationType(Context context, String packageName) {
-        return isGame(context, packageName) ? ApplicationType.GAME : ApplicationType.APPLICATION;
+    private static Bundle getActivityOptionsBundle(Context context, ApplicationType applicationType, View view,
+                                                   int left, int top, int right, int bottom) {
+        ActivityOptions options = getActivityOptions(context, applicationType, view);
+        if(options == null)
+            return null;
+
+        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
+            return options.toBundle();
+
+        return options.setLaunchBounds(new Rect(left, top, right, bottom)).toBundle();
+    }
+
+    @SuppressLint("SwitchIntDef")
+    private static ApplicationType getApplicationType(Context context, AppEntry entry) {
+        if(isGame(context, entry.getPackageName()))
+            return ApplicationType.APP_FULLSCREEN;
+
+        try {
+            ActivityInfo info = context.getPackageManager().getActivityInfo(
+                    ComponentName.unflattenFromString(entry.getComponentName()),
+                    0
+            );
+
+            switch(info.screenOrientation) {
+                case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
+                case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
+                case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
+                case ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE:
+                    return ApplicationType.APP_LANDSCAPE;
+
+                case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
+                case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:
+                case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:
+                case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT:
+                    return ApplicationType.APP_PORTRAIT;
+            }
+        } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
+
+        return context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
+                ? ApplicationType.APP_LANDSCAPE
+                : ApplicationType.APP_PORTRAIT;
     }
 
     public static boolean isSystemApp(Context context) {
@@ -1100,15 +1206,7 @@ public class U {
         if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
             return true;
 
-        PackageManager pm = context.getPackageManager();
-        try {
-            pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
-            return pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, context.getPackageName()) == PackageManager.SIGNATURE_MATCH
-                    && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
-                    && isSystemApp(context);
-        } catch (PackageManager.NameNotFoundException e) {
-            return false;
-        }
+        return hasSupportLibrary(context, 0);
     }
 
     public static boolean hasSupportLibrary(Context context, int minVersion) {
@@ -1191,7 +1289,7 @@ public class U {
             startTaskbarService(context, false);
         }
 
-        LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.RESTART"));
+        sendBroadcast(context, TaskbarIntent.ACTION_RESTART);
     }
 
     public static void restartNotificationService(Context context) {
@@ -1206,18 +1304,38 @@ public class U {
     }
 
     public static void showHideNavigationBar(Context context, boolean show) {
+        if(!isDesktopModeActive(context)
+                && !isBlissOs(context)
+                && !hasSupportLibrary(context, 7)) {
+            return;
+        }
+
+        int displayID = getDisplayID();
+        int value = show ? 0 : getNavbarHeight(context) * -1;
+
+        if(hasWriteSecureSettingsPermission(context)) {
+            try {
+                setOverscan(displayID, value);
+                return;
+            } catch (Exception e) {
+                // Fallback to next method
+            }
+        }
+
         if(hasSupportLibrary(context, 7)) {
             Intent intent = new Intent(BuildConfig.SUPPORT_APPLICATION_ID + ".CHANGE_OVERSCAN");
             intent.setPackage(BuildConfig.SUPPORT_APPLICATION_ID);
 
-            intent.putExtra("display_id", getDisplayID());
-            intent.putExtra("value", show ? 0 : getNavbarHeight(context) * -1);
+            intent.putExtra("display_id", displayID);
+            intent.putExtra("value", value);
 
             context.sendBroadcast(intent);
             return;
         }
 
         // Show or hide the system navigation bar on Bliss-x86
+        if(!isBlissOs(context)) return;
+
         try {
             if(getCurrentApiVersion() >= 28.0f)
                 Settings.Secure.putInt(context.getContentResolver(), "navigation_bar_visible", show ? 1 : 0);
@@ -1389,7 +1507,8 @@ public class U {
     public static boolean shouldCollapse(Context context, boolean pendingAppLaunch) {
         SharedPreferences pref = getSharedPreferences(context);
         if(pref.getBoolean("hide_taskbar", true)) {
-            if(isOverridingFreeformHack(context))
+            if(!pref.getBoolean("freeform_hack", false)
+                    || isOverridingFreeformHack(context, false))
                 return !LauncherHelper.getInstance().isOnHomeScreen();
             else {
                 FreeformHackHelper helper = FreeformHackHelper.getInstance();
@@ -1403,8 +1522,12 @@ public class U {
     }
 
     public static boolean isOverridingFreeformHack(Context context) {
+        return isOverridingFreeformHack(context, true);
+    }
+
+    public static boolean isOverridingFreeformHack(Context context, boolean checkPref) {
         SharedPreferences pref = getSharedPreferences(context);
-        return pref.getBoolean("freeform_hack", false)
+        return (!checkPref || pref.getBoolean("freeform_hack", false))
                 && ((isChromeOs(context) && pref.getBoolean("chrome_os_context_menu_fix", true))
                 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f));
     }
@@ -1418,7 +1541,7 @@ public class U {
         }
     }
 
-    private static float getCurrentApiVersion() {
+    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);
         else
@@ -1433,12 +1556,16 @@ public class U {
     }
 
     public static String getSecondScreenPackageName(Context context) {
-        return getInstalledPackage(context, Arrays.asList(
+        return getInstalledPackage(context,
                 "com.farmerbb.secondscreen.free",
-                "com.farmerbb.secondscreen"));
+                "com.farmerbb.secondscreen");
     }
 
     // Returns the name of an installed package from a list of package names, in order of preference
+    private static String getInstalledPackage(Context context, String... packageNames) {
+        return getInstalledPackage(context, Arrays.asList(packageNames));
+    }
+
     private static String getInstalledPackage(Context context, List<String> packageNames) {
         if(packageNames == null || packageNames.isEmpty())
             return null;
@@ -1479,20 +1606,11 @@ public class U {
                 int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
 
                 if(mode != AppOpsManager.MODE_ALLOWED) {
-                    AlertDialog.Builder builder = new AlertDialog.Builder(context);
-                    builder.setTitle(R.string.tb_pref_header_recent_apps)
-                            .setMessage(R.string.tb_enable_recent_apps)
-                            .setPositiveButton(R.string.tb_action_ok, (dialog, which) -> {
-                                try {
-                                    context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
-                                    showToastLong(context, R.string.tb_usage_stats_message);
-
-                                    finalOnFinish.run();
-                                } catch (ActivityNotFoundException e) {
-                                    finalOnError.run();
-                                }
-                            })
-                            .setNegativeButton(R.string.tb_action_cancel, (dialog, which) -> finalOnFinish.run());
+                    AlertDialog.Builder builder;
+                    if(hasAndroidTVSettings(context))
+                        builder = buildRecentAppsDialogAndroidTV(context, finalOnError, finalOnFinish);
+                    else
+                        builder = buildRecentAppsDialogStandard(context, finalOnError, finalOnFinish);
 
                     AlertDialog dialog = builder.create();
                     dialog.show();
@@ -1507,6 +1625,38 @@ public class U {
         return null;
     }
 
+    private static AlertDialog.Builder buildRecentAppsDialogStandard(Context context, Runnable onError, Runnable onFinish) {
+        return new AlertDialog.Builder(context)
+                .setTitle(R.string.tb_pref_header_recent_apps)
+                .setMessage(R.string.tb_enable_recent_apps)
+                .setPositiveButton(R.string.tb_action_ok, (dialog, which) -> {
+                    try {
+                        context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
+                        showToastLong(context, R.string.tb_usage_stats_message);
+
+                        onFinish.run();
+                    } catch (ActivityNotFoundException e) {
+                        onError.run();
+                    }
+                })
+                .setNegativeButton(R.string.tb_action_cancel, (dialog, which) -> onFinish.run());
+    }
+
+    private static AlertDialog.Builder buildRecentAppsDialogAndroidTV(Context context, Runnable onError, Runnable onFinish) {
+        return new AlertDialog.Builder(context)
+                .setTitle(R.string.tb_pref_header_recent_apps)
+                .setMessage(R.string.tb_enable_recent_apps_alt)
+                .setPositiveButton(R.string.tb_action_open_settings, (dialog, which) -> {
+                    try {
+                        context.startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));
+                        onFinish.run();
+                    } catch (ActivityNotFoundException e) {
+                        onError.run();
+                    }
+                })
+                .setNegativeButton(R.string.tb_action_cancel, (dialog, which) -> onFinish.run());
+    }
+
     public static Context wrapContext(Context context) {
         SharedPreferences pref = getSharedPreferences(context);
 
@@ -1543,13 +1693,15 @@ public class U {
     }
 
     public static boolean isExternalAccessDisabled(Context context) {
+        if(isLibrary(context)) return true;
+
         SharedPreferences pref = getSharedPreferences(context);
         return !pref.getBoolean("tasker_enabled", true);
     }
 
     public static boolean enableFreeformModeShortcut(Context context) {
         return canEnableFreeform()
-                && !isOverridingFreeformHack(context)
+                && !isOverridingFreeformHack(context, false)
                 && !isChromeOs(context);
     }
 
@@ -1632,7 +1784,7 @@ public class U {
     }
 
     public static boolean isDesktopIconsEnabled(Context context) {
-        return !canBootToFreeform(context) && !shouldLaunchTouchAbsorber(context);
+        return !canBootToFreeform(context, false) && !shouldLaunchTouchAbsorber(context);
     }
 
     public static boolean isSystemTrayEnabled(Context context) {
@@ -1657,4 +1809,127 @@ public class U {
 
         return false;
     }
+
+    private static boolean hasAndroidTVSettings(Context context) {
+        return getInstalledPackage(context, "com.android.tv.settings") != null
+                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
+    }
+
+    public static void restartApp(Activity activity, boolean shouldFade) {
+        Intent restartIntent = new Intent(activity, MainActivity.class);
+        restartIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+        activity.startActivity(restartIntent);
+
+        activity.overridePendingTransition(
+                shouldFade ? android.R.anim.fade_in : 0,
+                shouldFade ? android.R.anim.fade_out : 0
+        );
+
+        System.exit(0);
+    }
+
+    public static boolean isDesktopModeSupported(Context context) {
+        if(isLauncherPermanentlyEnabled(context)
+                || isLibrary(context)
+                || !BuildConfig.DEBUG // TODO remove this line
+                || isChromeOs(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) {
+        if(!isDesktopModeSupported(context)) return false;
+
+        boolean desktopModePrefEnabled;
+
+        try {
+            desktopModePrefEnabled = Settings.Global.getInt(context.getContentResolver(), "force_desktop_mode_on_external_displays") == 1;
+        } catch (Settings.SettingNotFoundException e) {
+            desktopModePrefEnabled = false;
+        }
+
+        return desktopModePrefEnabled && getExternalDisplayID(context) != Display.DEFAULT_DISPLAY;
+    }
+
+    public static boolean shouldStartDesktopMode(Context context) {
+        SharedPreferences pref = getSharedPreferences(context);
+
+        return isDesktopModeSupported(context)
+                && pref.getBoolean("desktop_mode", false)
+                && !pref.getBoolean("launcher", false);
+    }
+
+    // TODO remove this in favor of the existing getDisplayID method?
+    public static int getExternalDisplayID(Context context) {
+        DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
+        Display[] displays = dm.getDisplays();
+
+        return displays[displays.length - 1].getDisplayId();
+    }
+
+    @SuppressLint("PrivateApi")
+    private static Object getWindowManagerService() throws Exception {
+        return Class.forName("android.view.WindowManagerGlobal")
+                .getMethod("getWindowManagerService")
+                .invoke(null);
+    }
+
+    @SuppressLint("PrivateApi")
+    private static void setDensity(int displayID, String value) throws Exception {
+        // From android.os.UserHandle
+        final int USER_CURRENT_OR_SELF = -3;
+
+        if(value.equals("reset")) {
+            Class.forName("android.view.IWindowManager")
+                    .getMethod("clearForcedDisplayDensityForUser", int.class, int.class)
+                    .invoke(getWindowManagerService(), displayID, USER_CURRENT_OR_SELF);
+        } else {
+            int density = Integer.parseInt(value);
+
+            Class.forName("android.view.IWindowManager")
+                    .getMethod("setForcedDisplayDensityForUser", int.class, int.class, int.class)
+                    .invoke(getWindowManagerService(), displayID, density, USER_CURRENT_OR_SELF);
+        }
+    }
+
+    @SuppressLint("PrivateApi")
+    private static void setOverscan(int displayID, int value) throws Exception {
+        Class.forName("android.view.IWindowManager")
+                .getMethod("setOverscan", int.class, int.class, int.class, int.class, int.class)
+                .invoke(getWindowManagerService(), displayID, 0, 0, 0, value);
+    }
+
+    public static void registerReceiver(Context context, BroadcastReceiver receiver, String... actions) {
+        unregisterReceiver(context, receiver);
+
+        IntentFilter intentFilter = new IntentFilter();
+        for(String action : actions) {
+            intentFilter.addAction(action);
+        }
+
+        LocalBroadcastManager.getInstance(context).registerReceiver(receiver, intentFilter);
+    }
+
+    public static void unregisterReceiver(Context context, BroadcastReceiver receiver) {
+        LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver);
+    }
+
+    public static void sendBroadcast(Context context, String action) {
+       sendBroadcast(context, new Intent(action));
+    }
+
+    public static void sendBroadcast(Context context, Intent intent) {
+        LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+    }
+
+    @SuppressWarnings("rawtypes")
+    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
+                        : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                PackageManager.DONT_KILL_APP);
+    }
 }