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 de885ec..4d922d6 100644 (file)
 package com.farmerbb.taskbar.util;
 
 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;
@@ -41,6 +45,7 @@ import android.content.pm.Signature;
 import android.content.res.Configuration;
 import android.graphics.Color;
 import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
@@ -49,8 +54,8 @@ import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.support.v4.content.LocalBroadcastManager;
-import android.support.v7.view.ContextThemeWrapper;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.appcompat.view.ContextThemeWrapper;
 import android.util.DisplayMetrics;
 import android.view.Display;
 import android.view.Surface;
@@ -63,10 +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.ShortcutActivity;
-import com.farmerbb.taskbar.activity.StartTaskbarActivity;
+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,10 @@ 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);
     }
@@ -112,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
                 ? () -> {}
@@ -122,25 +131,48 @@ public class U {
                 ? () -> showErrorDialog(context, "SYSTEM_ALERT_WINDOW", finalOnFinish)
                 : onError;
 
-        AlertDialog.Builder builder = new AlertDialog.Builder(context);
-        builder.setTitle(R.string.permission_dialog_title)
-                .setMessage(R.string.permission_dialog_message)
-                .setPositiveButton(R.string.action_grant_permission, (dialog, which) -> {
+        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:" + BuildConfig.APPLICATION_ID)));
+                                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) {
@@ -153,9 +185,9 @@ public class U {
                 : onFinish;
 
         AlertDialog.Builder builder = new AlertDialog.Builder(context);
-        builder.setTitle(R.string.error_dialog_title)
-                .setMessage(context.getString(R.string.error_dialog_message, BuildConfig.APPLICATION_ID, appopCmd))
-                .setPositiveButton(R.string.action_ok, (dialog, which) -> finalOnFinish.run());
+        builder.setTitle(R.string.tb_error_dialog_title)
+                .setMessage(context.getString(R.string.tb_error_dialog_message, context.getPackageName(), appopCmd))
+                .setPositiveButton(R.string.tb_action_ok, (dialog, which) -> finalOnFinish.run());
 
         AlertDialog dialog = builder.create();
         dialog.show();
@@ -164,14 +196,17 @@ public class U {
         return dialog;
     }
 
+    @TargetApi(Build.VERSION_CODES.P)
+    public static void lockDevice(Context context) {
+        sendAccessibilityAction(context, AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN);
+    }
+
     public static void sendAccessibilityAction(Context context, int action) {
         sendAccessibilityAction(context, action, null);
     }
 
     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);
 
@@ -195,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(),
@@ -208,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 {
@@ -220,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 */ }
             });
         }
@@ -251,7 +286,7 @@ public class U {
     public static void showToast(Context context, String message, int length) {
         cancelToast();
 
-        ToastInterface toast = DependencyUtils.createToast(context, message, length);
+        ToastInterface toast = DependencyUtils.createToast(context.getApplicationContext(), message, length);
         toast.show();
 
         ToastHelper.getInstance().setLastToast(toast);
@@ -262,13 +297,15 @@ public class U {
         if(toast != null) toast.cancel();
     }
 
-    public static void startShortcut(Context context, AppEntry entry, ShortcutInfo shortcut) {
+    public static void startShortcut(Context context, AppEntry entry, ShortcutInfo shortcut, View view) {
         launchApp(context,
                 entry,
                 null,
                 false,
                 false,
+                false,
                 shortcut,
+                view,
                 null);
     }
 
@@ -282,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();
 
@@ -313,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);
@@ -338,14 +395,14 @@ public class U {
         if(checkMultiWindow)
             freeformHackIntent.putExtra("check_multiwindow", true);
 
-        if(canDrawOverlays(context, false))
+        if(canDrawOverlays(context))
             startActivityLowerRight(context, freeformHackIntent);
     }
 
     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);
@@ -358,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()));
@@ -374,7 +432,8 @@ public class U {
         if(pref.getBoolean("disable_animations", false))
             intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
 
-        if(openInNewWindow || pref.getBoolean("force_new_window", false)) {
+        boolean realOpenInNewWindow = openInNewWindow || pref.getBoolean("force_new_window", false);
+        if(realOpenInNewWindow) {
             intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
 
             ActivityInfo activityInfo = intent.resolveActivityInfo(context.getPackageManager(), 0);
@@ -388,52 +447,51 @@ 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());
 
         Bundle bundle = getActivityOptionsBundle(context, type, windowSize, view);
 
-        prepareToStartActivity(context, () -> {
+        prepareToStartActivity(context, realOpenInNewWindow, () -> {
             if(shortcut == null) {
                 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
                 if(entry.getUserId(context) == userManager.getSerialNumberForUser(Process.myUserHandle())) {
                     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);
 
@@ -450,7 +508,7 @@ public class U {
 
         int iconSize = isOverridingFreeformHack(context) && !LauncherHelper.getInstance().isOnHomeScreen()
                 ? 0
-                : context.getResources().getDimensionPixelSize(R.dimen.icon_size);
+                : context.getResources().getDimensionPixelSize(R.dimen.tb_icon_size);
 
         if(position.contains("vertical_left"))
             left = left + iconSize;
@@ -473,80 +531,92 @@ 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.phone_size_width) / 2;
+        int width2 = context.getResources().getDimensionPixelSize(widthDimen) / 2;
         int height1 = display.height / 2;
-        int height2 = context.getResources().getDimensionPixelSize(R.dimen.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, Runnable runnable) {
-        LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_CONTEXT_MENU"));
+    private static void prepareToStartActivity(Context context, boolean openInNewWindow, Runnable runnable) {
+        sendBroadcast(context, TaskbarIntent.ACTION_HIDE_CONTEXT_MENU);
 
-        if(!shouldLaunchTouchAbsorber(context)) {
-            runnable.run();
-            return;
-        }
+        if(!FreeformHackHelper.getInstance().isTouchAbsorberActive()
+                && shouldLaunchTouchAbsorber(context)) {
+            startTouchAbsorberActivity(context);
+            new Handler().postDelayed(runnable, 100);
+        } else if(openInNewWindow) {
+            Intent intent = new Intent(context, DummyActivity.class);
+            intent.putExtra("finish_on_pause", true);
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_NO_ANIMATION);
+            startActivityLowerRight(context, intent);
 
-        startTouchAbsorberActivity(context);
-        new Handler().postDelayed(runnable, 100);
+            new Handler().postDelayed(runnable, 100);
+        } else
+            runnable.run();
     }
 
     public static void startActivityMaximized(Context context, Intent intent) {
         Bundle bundle = launchMode2(context, MAXIMIZED, ApplicationType.CONTEXT_MENU, null);
-        prepareToStartActivity(context, () -> context.startActivity(intent, bundle));
+        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);
@@ -556,7 +626,7 @@ public class U {
         int right = display.width;
         int bottom = display.height;
 
-        int iconSize = context.getResources().getDimensionPixelSize(R.dimen.icon_size);
+        int iconSize = context.getResources().getDimensionPixelSize(R.dimen.tb_icon_size);
 
         if(position.contains("vertical_left"))
             right = iconSize;
@@ -573,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 */ }
     }
 
@@ -603,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);
     }
@@ -614,13 +682,13 @@ public class U {
     public static void checkForUpdates(Context context) {
         String url;
         if(isPlayStoreRelease(context)) {
-            if(BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
+            if(context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
                     && !isPlayStoreInstalled(context))
                 url = "https://github.com/farmerbb/Taskbar/releases";
             else
-                url = "https://play.google.com/store/apps/details?id=" + BuildConfig.APPLICATION_ID;
+                url = "https://play.google.com/store/apps/details?id=" + context.getPackageName();
         } else
-            url = "https://f-droid.org/repository/browse/?fdid=" + BuildConfig.APPLICATION_ID;
+            url = "https://f-droid.org/repository/browse/?fdid=" + context.getPackageName();
 
         Intent intent = new Intent(Intent.ACTION_VIEW);
         intent.setData(Uri.parse(url));
@@ -636,7 +704,7 @@ public class U {
         homeIntent.addCategory(Intent.CATEGORY_HOME);
         ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
 
-        return defaultLauncher.activityInfo.packageName.equals(BuildConfig.APPLICATION_ID);
+        return defaultLauncher.activityInfo.packageName.equals(context.getPackageName());
     }
 
     public static void setCachedRotation(int cachedRotation) {
@@ -765,7 +833,7 @@ public class U {
                 ? (display.height - getStatusBarHeight(context)) / density
                 : display.width / density;
 
-        float iconSize = context.getResources().getDimension(R.dimen.icon_size) / density;
+        float iconSize = context.getResources().getDimension(R.dimen.tb_icon_size) / density;
 
         int userMaxNumOfColumns = Integer.valueOf(pref.getString("max_num_of_recents", "10"));
 
@@ -859,32 +927,6 @@ public class U {
         }
     }
 
-    public static Intent getShortcutIntent(Context context) {
-        Intent shortcutIntent = new Intent(context, ShortcutActivity.class);
-        shortcutIntent.setAction(Intent.ACTION_MAIN);
-        shortcutIntent.putExtra("is_launching_shortcut", true);
-
-        Intent intent = new Intent();
-        intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
-        intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.mipmap.ic_freeform_mode));
-        intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, context.getString(R.string.pref_header_freeform));
-
-        return intent;
-    }
-
-    public static Intent getStartStopIntent(Context context) {
-        Intent shortcutIntent = new Intent(context, StartTaskbarActivity.class);
-        shortcutIntent.setAction(Intent.ACTION_MAIN);
-        shortcutIntent.putExtra("is_launching_shortcut", true);
-
-        Intent intent = new Intent();
-        intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
-        intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.mipmap.ic_launcher));
-        intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, context.getString(R.string.start_taskbar));
-
-        return intent;
-    }
-
     public static boolean canEnableFreeform() {
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
     }
@@ -899,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() {
@@ -914,8 +957,7 @@ public class U {
     }
 
     public static boolean isServiceRunning(Context context, Class<? extends Service> cls) {
-        if(LauncherHelper.getInstance().isOnHomeScreen()
-                && FeatureFlags.homeActivityUIHost()
+        if(LauncherHelper.getInstance().isOnSecondaryHomeScreen()
                 && (cls.equals(TaskbarService.class)
                 || cls.equals(StartMenuService.class)
                 || cls.equals(DashboardService.class)))
@@ -948,19 +990,16 @@ public class U {
             editor.apply();
         }
 
-        return pref.getInt("background_tint", context.getResources().getInteger(R.integer.translucent_gray));
+        return pref.getInt("background_tint", context.getResources().getInteger(R.integer.tb_translucent_gray));
     }
 
     public static int getAccentColor(Context context) {
         SharedPreferences pref = getSharedPreferences(context);
-        return pref.getInt("accent_color", context.getResources().getInteger(R.integer.translucent_white));
+        return pref.getInt("accent_color", context.getResources().getInteger(R.integer.tb_translucent_white));
     }
 
-    @TargetApi(Build.VERSION_CODES.M)
-    public static boolean canDrawOverlays(Context context, boolean forHomeScreen) {
-        return (forHomeScreen && FeatureFlags.homeActivityUIHost() && !canBootToFreeform(context))
-                || Build.VERSION.SDK_INT < Build.VERSION_CODES.M
-                || Settings.canDrawOverlays(context);
+    public static boolean canDrawOverlays(Context context) {
+        return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context);
     }
 
     public static boolean isGame(Context context, String packageName) {
@@ -982,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;
@@ -996,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:
@@ -1020,6 +1068,12 @@ public class U {
             method.invoke(options, stackId);
         } catch (Exception e) { /* Gracefully fail */ }
 
+        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            int launchDisplayId = LauncherHelper.getInstance().getSecondaryDisplayId();
+            if(launchDisplayId != -1)
+                options.setLaunchDisplayId(launchDisplayId);
+        }
+
         return options;
     }
 
@@ -1047,17 +1101,21 @@ public class U {
     public static Bundle getActivityOptionsBundle(Context context, ApplicationType type, View view) {
         SharedPreferences pref = getSharedPreferences(context);
 
-        return getActivityOptionsBundle(context, type, pref.getString("window_size", "standard"), view);
+        return getActivityOptionsBundle(context, type, pref.getString("window_size", context.getString(R.string.tb_def_window_size)), view);
     }
-    
+
     private static Bundle getActivityOptionsBundle(Context context, ApplicationType type, String windowSize, View view) {
         SharedPreferences pref = getSharedPreferences(context);
         if(!canEnableFreeform() || !pref.getBoolean("freeform_hack", false))
             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":
@@ -1071,13 +1129,52 @@ 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) {
         try {
-            ApplicationInfo info = context.getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
+            ApplicationInfo info = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
             int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
             return (info.flags & mask) != 0;
         } catch (PackageManager.NameNotFoundException e) {
@@ -1101,19 +1198,24 @@ public class U {
             validBlissOsBuildProp = true;
 
         return validBlissOsBuildProp
-                && BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
+                && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
                 && isSystemApp(context);
     }
 
     public static boolean isLauncherPermanentlyEnabled(Context context) {
-        if(BuildConfig.APPLICATION_ID.equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
+        if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
             return true;
 
+        return hasSupportLibrary(context, 0);
+    }
+
+    public static boolean hasSupportLibrary(Context context, int minVersion) {
         PackageManager pm = context.getPackageManager();
         try {
-            pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
-            return pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, BuildConfig.APPLICATION_ID) == PackageManager.SIGNATURE_MATCH
-                    && BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
+            PackageInfo pInfo = pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
+            return pInfo.versionCode >= minVersion
+                    && 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;
@@ -1126,29 +1228,32 @@ public class U {
 
     private static float getBaseTaskbarSizeFloat(Context context) {
         SharedPreferences pref = getSharedPreferences(context);
-        float baseTaskbarSize = context.getResources().getDimension(R.dimen.base_taskbar_size);
+        float baseTaskbarSize = context.getResources().getDimension(R.dimen.tb_base_taskbar_size);
         boolean navbarButtonsEnabled = false;
 
-        if(pref.getBoolean("dashboard", false))
-            baseTaskbarSize += context.getResources().getDimension(R.dimen.dashboard_button_size);
+        if(pref.getBoolean("dashboard", context.getResources().getBoolean(R.bool.tb_def_dashboard)))
+            baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_dashboard_button_size);
 
         if(pref.getBoolean("button_back", false)) {
             navbarButtonsEnabled = true;
-            baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
+            baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
         }
 
         if(pref.getBoolean("button_home", false)) {
             navbarButtonsEnabled = true;
-            baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
+            baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
         }
 
         if(pref.getBoolean("button_recents", false)) {
             navbarButtonsEnabled = true;
-            baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
+            baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
         }
 
         if(navbarButtonsEnabled)
-            baseTaskbarSize += context.getResources().getDimension(R.dimen.navbar_buttons_margin);
+            baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_navbar_buttons_margin);
+
+        if(isSystemTrayEnabled(context))
+            baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_systray_size);
 
         return baseTaskbarSize;
     }
@@ -1184,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) {
@@ -1199,7 +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", 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);
@@ -1209,14 +1345,8 @@ public class U {
     }
 
     public static void initPrefs(Context context) {
-        // On smaller-screened devices, set "Grid" as the default start menu layout
-        SharedPreferences pref = getSharedPreferences(context);
-        if(context.getApplicationContext().getResources().getConfiguration().smallestScreenWidthDp < 720
-                && pref.getString("start_menu_layout", "null").equals("null")) {
-            pref.edit().putString("start_menu_layout", "grid").apply();
-        }
-
         // Enable freeform hack automatically on supported devices
+        SharedPreferences pref = getSharedPreferences(context);
         if(canEnableFreeform()) {
             if(!pref.getBoolean("freeform_hack_override", false)) {
                 pref.edit()
@@ -1255,21 +1385,18 @@ public class U {
             editor.putString("refresh_frequency", "0");
             editor.putString("max_num_of_recents", "2147483647");
             editor.putString("sort_order", "true");
-            editor.putString("window_size", "phone_size");
             editor.putString("start_button_image", "app_logo");
-            editor.putBoolean("full_length", true);
-            editor.putBoolean("dashboard", true);
             editor.putBoolean("button_back", true);
             editor.putBoolean("button_home", true);
             editor.putBoolean("button_recents", true);
             editor.putBoolean("auto_hide_navbar", true);
-         // editor.putBoolean("shortcut_icon", false);
+            editor.putBoolean("shortcut_icon", false);
             editor.putBoolean("bliss_os_prefs", true);
             editor.apply();
         }
 
         // Customizations for Android-x86 devices (non-Bliss)
-        if(BuildConfig.APPLICATION_ID.equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
+        if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
                 && isSystemApp(context)
                 && !pref.getBoolean("android_x86_prefs", false)) {
             pref.edit()
@@ -1277,53 +1404,78 @@ public class U {
                     .putString("refresh_frequency", "0")
                     .putString("max_num_of_recents", "2147483647")
                     .putString("sort_order", "true")
-                    .putString("window_size", "phone_size")
-                    .putBoolean("full_length", true)
-                    .putBoolean("dashboard", true)
-                 // .putBoolean("shortcut_icon", false)
+                    .putBoolean("shortcut_icon", false)
                     .putBoolean("android_x86_prefs", true)
                     .apply();
         }
     }
 
     public static DisplayInfo getDisplayInfo(Context context) {
+        return getDisplayInfo(context, false);
+    }
+
+    public static DisplayInfo getDisplayInfo(Context context, boolean fromTaskbar) {
         context = context.getApplicationContext();
+        int displayID = getDisplayID();
+
+        DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
+        Display currentDisplay = null;
 
-        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        Display disp = wm.getDefaultDisplay();
+        for(Display display : dm.getDisplays()) {
+            if(display.getDisplayId() == displayID) {
+                currentDisplay = display;
+                break;
+            }
+        }
+
+        if(currentDisplay == null)
+            return new DisplayInfo(0, 0, 0);
 
         DisplayMetrics metrics = new DisplayMetrics();
-        disp.getMetrics(metrics);
+        currentDisplay.getMetrics(metrics);
 
         DisplayMetrics realMetrics = new DisplayMetrics();
-        disp.getRealMetrics(realMetrics);
+        currentDisplay.getRealMetrics(realMetrics);
 
-        DisplayInfo display = new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);
+        DisplayInfo info = new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);
 
         if(isChromeOs(context)) {
             SharedPreferences pref = getSharedPreferences(context);
             if(!pref.getBoolean("chrome_os_context_menu_fix", true)) {
-                display.width = realMetrics.widthPixels;
-                display.height = realMetrics.heightPixels;
+                info.width = realMetrics.widthPixels;
+                info.height = realMetrics.heightPixels;
             }
 
-            return display;
+            return info;
         }
 
+        // Workaround for incorrect display size on devices with notches in landscape mode
+        if(fromTaskbar && context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)
+            return info;
+
         boolean sameWidth = metrics.widthPixels == realMetrics.widthPixels;
         boolean sameHeight = metrics.heightPixels == realMetrics.heightPixels;
 
         if(sameWidth && !sameHeight) {
-            display.width = realMetrics.widthPixels;
-            display.height = realMetrics.heightPixels - getNavbarHeight(context);
+            info.width = realMetrics.widthPixels;
+            info.height = realMetrics.heightPixels - getNavbarHeight(context);
         }
 
         if(!sameWidth && sameHeight) {
-            display.width = realMetrics.widthPixels - getNavbarHeight(context);
-            display.height = realMetrics.heightPixels;
+            info.width = realMetrics.widthPixels - getNavbarHeight(context);
+            info.height = realMetrics.heightPixels;
         }
 
-        return display;
+        return info;
+    }
+
+    private static int getDisplayID() {
+        LauncherHelper helper = LauncherHelper.getInstance();
+
+        if(helper.isOnSecondaryHomeScreen())
+            return helper.getSecondaryDisplayId();
+        else
+            return Display.DEFAULT_DISPLAY;
     }
 
     public static void pinAppShortcut(Context context) {
@@ -1335,9 +1487,9 @@ public class U {
 
                 mShortcutManager.requestPinShortcut(pinShortcutInfo, null);
             } else
-                showToastLong(context, R.string.pin_shortcut_not_supported);
+                showToastLong(context, R.string.tb_pin_shortcut_not_supported);
         } else {
-            Intent intent = getShortcutIntent(context);
+            Intent intent = ShortcutUtils.getShortcutIntent(context);
             intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
             intent.putExtra("duplicate", false);
 
@@ -1348,14 +1500,15 @@ public class U {
             intent.setPackage(defaultLauncher.activityInfo.packageName);
             context.sendBroadcast(intent);
 
-            showToast(context, R.string.shortcut_created);
+            showToast(context, R.string.tb_shortcut_created);
         }
     }
 
     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();
@@ -1369,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));
     }
@@ -1384,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
@@ -1399,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;
@@ -1421,12 +1582,6 @@ public class U {
         }
     }
 
-    public static boolean visualFeedbackEnabled(Context context) {
-        SharedPreferences pref = getSharedPreferences(context);
-        return (getCurrentApiVersion() < 26.0f || getCurrentApiVersion() >= 28.0f)
-                && pref.getBoolean("visual_feedback", true);
-    }
-
     public static void showRecentAppsDialog(Context context) {
         showRecentAppsDialog(context, null, null);
     }
@@ -1443,7 +1598,7 @@ public class U {
         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isSystemApp(context)) {
             ApplicationInfo applicationInfo = null;
             try {
-                applicationInfo = context.getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
+                applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
             } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
 
             if(applicationInfo != null) {
@@ -1451,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.pref_header_recent_apps)
-                            .setMessage(R.string.enable_recent_apps)
-                            .setPositiveButton(R.string.action_ok, (dialog, which) -> {
-                                try {
-                                    context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
-                                    showToastLong(context, R.string.usage_stats_message);
-
-                                    finalOnFinish.run();
-                                } catch (ActivityNotFoundException e) {
-                                    finalOnError.run();
-                                }
-                            })
-                            .setNegativeButton(R.string.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();
@@ -1479,16 +1625,48 @@ 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);
 
         int theme = -1;
         switch(pref.getString("theme", "light")) {
             case "light":
-                theme = R.style.AppTheme;
+                theme = R.style.Taskbar;
                 break;
             case "dark":
-                theme = R.style.AppTheme_Dark;
+                theme = R.style.Taskbar_Dark;
                 break;
         }
 
@@ -1496,12 +1674,12 @@ public class U {
     }
 
     public static boolean isPlayStoreRelease(Context context) {
-        return isPlayStoreRelease(context, BuildConfig.APPLICATION_ID);
+        return isPlayStoreRelease(context, context.getPackageName());
     }
 
     @SuppressLint("PackageManagerGetSignatures")
     public static boolean isPlayStoreRelease(Context context, String packageName) {
-        Signature playStoreSignature = new Signature(context.getString(R.string.signature));
+        Signature playStoreSignature = new Signature(context.getString(R.string.tb_signature));
         try {
             PackageManager pm = context.getPackageManager();
             PackageInfo info = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
@@ -1515,14 +1693,16 @@ 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()
-                && !U.isOverridingFreeformHack(context)
-                && !U.isChromeOs(context);
+                && !isOverridingFreeformHack(context, false)
+                && !isChromeOs(context);
     }
 
     public static void startForegroundService(Context context, Intent intent) {
@@ -1565,7 +1745,7 @@ public class U {
     @SuppressWarnings("ResultOfMethodCallIgnored")
     public static boolean importCustomStartButtonImage(Context context, Uri uri) {
         try {
-            File imagesDir = new File(context.getFilesDir(), "images");
+            File imagesDir = new File(context.getFilesDir(), "tb_images");
             imagesDir.mkdirs();
 
             File importedFile = new File(imagesDir, "custom_image_new");
@@ -1600,13 +1780,156 @@ public class U {
     }
 
     private static boolean shouldLaunchTouchAbsorber(Context context) {
-        return !FreeformHackHelper.getInstance().isTouchAbsorberActive()
-                        && isOverridingFreeformHack(context)
-                        && !isChromeOs(context)
-                        && getCurrentApiVersion() < 29.0f;
+        return isOverridingFreeformHack(context) && !isChromeOs(context) && getCurrentApiVersion() < 29.0f;
     }
 
     public static boolean isDesktopIconsEnabled(Context context) {
-        return !U.canBootToFreeform(context) && !U.shouldLaunchTouchAbsorber(context);
+        return !canBootToFreeform(context, false) && !shouldLaunchTouchAbsorber(context);
+    }
+
+    public static boolean isSystemTrayEnabled(Context context) {
+        SharedPreferences pref = getSharedPreferences(context);
+
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+                && pref.getBoolean("sys_tray", context.getResources().getBoolean(R.bool.tb_def_sys_tray))
+                && pref.getBoolean("full_length", context.getResources().getBoolean(R.bool.tb_def_full_length))
+                && !getTaskbarPosition(context).contains("vertical");
+    }
+
+    @SuppressWarnings("deprecation")
+    public static boolean isLibrary(Context context) {
+        return !context.getPackageName().equals(BuildConfig.APPLICATION_ID);
+    }
+
+    public static boolean applyDisplayCutoutModeTo(WindowManager.LayoutParams params) {
+        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+            params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+            return true;
+        }
+
+        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);
     }
 }