OSDN Git Service

Default to phone-sized windows on Bliss OS and Android-x86
[android-x86/packages-apps-Taskbar.git] / app / src / main / java / com / farmerbb / taskbar / util / U.java
index 9f1f04e..b9663a2 100644 (file)
 
 package com.farmerbb.taskbar.util;
 
+import android.Manifest;
+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.app.admin.DevicePolicyManager;
 import android.content.ActivityNotFoundException;
@@ -31,9 +33,12 @@ import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.content.pm.Signature;
 import android.content.res.Configuration;
 import android.graphics.Color;
 import android.graphics.Rect;
@@ -46,19 +51,22 @@ 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 android.util.DisplayMetrics;
 import android.view.Display;
 import android.view.Surface;
-import android.view.View;
 import android.view.WindowManager;
 import android.widget.Toast;
 
 import com.farmerbb.taskbar.BuildConfig;
 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.TouchAbsorberActivity;
+import com.farmerbb.taskbar.activity.dark.ContextMenuActivityDark;
 import com.farmerbb.taskbar.receiver.LockDeviceReceiver;
 import com.farmerbb.taskbar.service.DashboardService;
 import com.farmerbb.taskbar.service.NotificationService;
@@ -68,13 +76,13 @@ import com.farmerbb.taskbar.service.TaskbarService;
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 public class U {
 
     private U() {}
 
-    private static SharedPreferences pref;
     private static Integer cachedRotation;
 
     private static final int MAXIMIZED = 0;
@@ -88,13 +96,28 @@ public class U {
     private static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
     private static final int FREEFORM_WORKSPACE_STACK_ID = 2;
 
+    // From android.app.WindowConfiguration
+    private static final int WINDOWING_MODE_FULLSCREEN = 1;
+    private static final int WINDOWING_MODE_FREEFORM = 5;
+
     public static SharedPreferences getSharedPreferences(Context context) {
-        if(pref == null) pref = context.getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", Context.MODE_PRIVATE);
-        return pref;
+        return context.getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", Context.MODE_PRIVATE);
+    }
+
+    public static void showPermissionDialog(Context context) {
+        showPermissionDialog(context, null, null);
     }
 
     @TargetApi(Build.VERSION_CODES.M)
-    public static AlertDialog showPermissionDialog(final Context context) {
+    public static AlertDialog showPermissionDialog(Context context, Runnable onError, Runnable onFinish) {
+        Runnable finalOnFinish = onFinish == null
+                ? () -> {}
+                : onFinish;
+
+        Runnable finalOnError = onError == null
+                ? () -> 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)
@@ -102,8 +125,10 @@ public class U {
                     try {
                         context.startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                                 Uri.parse("package:" + BuildConfig.APPLICATION_ID)));
+
+                        finalOnFinish.run();
                     } catch (ActivityNotFoundException e) {
-                        showErrorDialog(context, "SYSTEM_ALERT_WINDOW");
+                        finalOnError.run();
                     }
                 });
 
@@ -114,14 +139,25 @@ public class U {
         return dialog;
     }
 
-    public static void showErrorDialog(final Context context, String appopCmd) {
+    public static AlertDialog showErrorDialog(Context context, String appopCmd) {
+        return showErrorDialog(context, appopCmd, null);
+    }
+
+    private static AlertDialog showErrorDialog(Context context, String appopCmd, Runnable onFinish) {
+        Runnable finalOnFinish = onFinish == null
+                ? () -> {}
+                : 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, null);
+                .setPositiveButton(R.string.action_ok, (dialog, which) -> finalOnFinish.run());
 
         AlertDialog dialog = builder.create();
         dialog.show();
+        dialog.setCancelable(false);
+
+        return dialog;
     }
 
     public static void lockDevice(Context context) {
@@ -136,40 +172,76 @@ public class U {
             launchApp(context, () -> {
                 Intent intent = new Intent(context, DummyActivity.class);
                 intent.putExtra("device_admin", true);
-                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                context.startActivity(intent, getActivityOptions(ApplicationType.APPLICATION).toBundle());
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
 
-                if(context instanceof Activity)
-                    ((Activity) context).overridePendingTransition(0, 0);
+                try {
+                    context.startActivity(intent, getActivityOptionsBundle(context, ApplicationType.APPLICATION));
+                } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
             });
         }
     }
 
     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);
 
-        if(isAccessibilityServiceEnabled(context)) {
+        boolean isAccessibilityServiceEnabled = isAccessibilityServiceEnabled(context);
+
+        if(!isAccessibilityServiceEnabled
+                && hasWriteSecureSettingsPermission(context)) {
+            String notificationServices = Settings.Secure.getString(context.getContentResolver(),
+                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+
+            String powerMenuService = new ComponentName(context, PowerMenuService.class).flattenToString();
+
+            if(!notificationServices.contains(powerMenuService)) {
+                try {
+                    Settings.Secure.putString(context.getContentResolver(),
+                            Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                            notificationServices.isEmpty()
+                                    ? powerMenuService
+                                    : notificationServices + ":" + powerMenuService);
+                } catch (Exception e) { /* Gracefully fail */ }
+            }
+
+            new Handler().postDelayed(() -> {
+                Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
+                intent.putExtra("action", action);
+                LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+
+                try {
+                    Settings.Secure.putString(context.getContentResolver(),
+                            Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                            notificationServices);
+                } catch (Exception e) { /* Gracefully fail */ }
+
+                if(onComplete != null) onComplete.run();
+            }, 100);
+        } else if(isAccessibilityServiceEnabled) {
             Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
             intent.putExtra("action", action);
             LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+
+            if(onComplete != null) onComplete.run();
         } else {
             launchApp(context, () -> {
-                Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
-                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                Intent intent = new Intent(context, DummyActivity.class);
+                intent.putExtra("accessibility", true);
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
 
                 try {
-                    context.startActivity(intent, getActivityOptions(ApplicationType.APPLICATION).toBundle());
-                    showToastLong(context, R.string.enable_accessibility);
-                } catch (ActivityNotFoundException e) {
-                    showToast(context, R.string.lock_device_not_supported);
-                }
+                    context.startActivity(intent, getActivityOptionsBundle(context, ApplicationType.APPLICATION));
+                } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
             });
         }
     }
 
-    private static boolean isAccessibilityServiceEnabled(Context context) {
+    public static boolean isAccessibilityServiceEnabled(Context context) {
         String accessibilityServices = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
         ComponentName component = new ComponentName(context, PowerMenuService.class);
 
@@ -178,6 +250,11 @@ public class U {
                 || accessibilityServices.contains(component.flattenToShortString()));
     }
 
+    public static boolean hasWriteSecureSettingsPermission(Context context) {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+                && context.checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) == PackageManager.PERMISSION_GRANTED;
+    }
+
     public static void showToast(Context context, int message) {
         showToast(context, context.getString(message), Toast.LENGTH_SHORT);
     }
@@ -189,13 +266,7 @@ public class U {
     public static void showToast(Context context, String message, int length) {
         cancelToast();
 
-        ToastInterface toast;
-        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1
-                || BuildConfig.APPLICATION_ID.equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
-            toast = new ToastFrameworkImpl(context, message, length);
-        else
-            toast = new ToastCompatImpl(context, message, length);
-
+        ToastInterface toast = DependencyUtils.createToast(context, message, length);
         toast.show();
 
         ToastHelper.getInstance().setLastToast(toast);
@@ -234,14 +305,14 @@ public class U {
     }
 
     private static void launchApp(final Context context,
-                                 final String packageName,
-                                 final String componentName,
-                                 final long userId, final String windowSize,
-                                 final boolean launchedFromTaskbar,
-                                 final boolean openInNewWindow,
-                                 final ShortcutInfo shortcut) {
+                                  final String packageName,
+                                  final String componentName,
+                                  final long userId, final String windowSize,
+                                  final boolean launchedFromTaskbar,
+                                  final boolean openInNewWindow,
+                                  final ShortcutInfo shortcut) {
         launchApp(context, launchedFromTaskbar, () -> continueLaunchingApp(context, packageName, componentName, userId,
-                windowSize, launchedFromTaskbar, openInNewWindow, shortcut));
+                windowSize, openInNewWindow, shortcut));
     }
 
     public static void launchApp(Context context, Runnable runnable) {
@@ -252,39 +323,50 @@ public class U {
         SharedPreferences pref = getSharedPreferences(context);
         FreeformHackHelper helper = FreeformHackHelper.getInstance();
 
-        boolean specialLaunch = Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1
-                && FreeformHackHelper.getInstance().isInFreeformWorkspace()
+        boolean specialLaunch = hasBrokenSetLaunchBoundsApi()
+                && helper.isInFreeformWorkspace()
                 && MenuHelper.getInstance().isContextMenuOpen();
 
-        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
+        boolean noAnimation = pref.getBoolean("disable_animations", false);
+
+        if(hasFreeformSupport(context)
                 && pref.getBoolean("freeform_hack", false)
                 && (!helper.isInFreeformWorkspace() || specialLaunch)) {
             new Handler().postDelayed(() -> {
-                startFreeformHack(context, true, launchedFromTaskbar);
+                startFreeformHack(context, true);
 
                 new Handler().postDelayed(runnable, helper.isFreeformHackActive() ? 0 : 100);
             }, launchedFromTaskbar ? 0 : 100);
         } else
-            runnable.run();
+            new Handler().postDelayed(runnable, !launchedFromTaskbar && noAnimation ? 100 : 0);
+    }
+
+    public static void startFreeformHack(Context context) {
+        startFreeformHack(context, false);
     }
 
-    @SuppressWarnings("deprecation")
     @TargetApi(Build.VERSION_CODES.N)
-    public static void startFreeformHack(Context context, boolean checkMultiWindow, boolean launchedFromTaskbar) {
+    public static void startFreeformHack(Context context, boolean checkMultiWindow) {
         Intent freeformHackIntent = new Intent(context, InvisibleActivityFreeform.class);
-        freeformHackIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
+        freeformHackIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
+                | Intent.FLAG_ACTIVITY_NO_ANIMATION);
 
         if(checkMultiWindow)
             freeformHackIntent.putExtra("check_multiwindow", true);
 
-        if(launchedFromTaskbar) {
-            SharedPreferences pref = getSharedPreferences(context);
-            if(pref.getBoolean("disable_animations", false))
-                freeformHackIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-        }
-
         if(canDrawOverlays(context))
-            launchAppLowerRight(context, freeformHackIntent);
+            startActivityLowerRight(context, freeformHackIntent);
+    }
+
+    public static void stopFreeformHack(Context context) {
+        LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.FINISH_FREEFORM_ACTIVITY"));
+
+        if(isOverridingFreeformHack(context)) {
+            FreeformHackHelper helper = FreeformHackHelper.getInstance();
+            helper.setFreeformHackActive(false);
+            helper.setInFreeformWorkspace(false);
+        }
     }
 
     @TargetApi(Build.VERSION_CODES.N)
@@ -293,7 +375,6 @@ public class U {
                                              String componentName,
                                              long userId,
                                              String windowSize,
-                                             boolean launchedFromTaskbar,
                                              boolean openInNewWindow,
                                              ShortcutInfo shortcut) {
         SharedPreferences pref = getSharedPreferences(context);
@@ -308,10 +389,8 @@ public class U {
                 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1)
             intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
 
-        if(launchedFromTaskbar) {
-            if(pref.getBoolean("disable_animations", false))
-                intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-        }
+        if(pref.getBoolean("disable_animations", false))
+            intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
 
         if(openInNewWindow || pref.getBoolean("force_new_window", false)) {
             intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
@@ -332,176 +411,107 @@ public class U {
         if(windowSize == null)
             windowSize = SavedWindowSizes.getInstance(context).getWindowSize(context, packageName);
 
-        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N
-                || !pref.getBoolean("freeform_hack", false)
-                || windowSize.equals("standard")) {
-            launchStandard(context, intent, userId, shortcut, type);
-        } else switch(windowSize) {
-            case "large":
-                launchMode1(context, intent, userId, shortcut, type);
-                break;
-            case "fullscreen":
-                launchMode2(context, intent, MAXIMIZED, userId, shortcut, type);
-                break;
-            case "half_left":
-                launchMode2(context, intent, LEFT, userId, shortcut, type);
-                break;
-            case "half_right":
-                launchMode2(context, intent, RIGHT, userId, shortcut, type);
-                break;
-            case "phone_size":
-                launchMode3(context, intent, userId, shortcut, type);
-                break;
-        }
+        Bundle bundle = getActivityOptionsBundle(context, type, windowSize);
+
+        prepareToStartActivity(context, () -> {
+            if(shortcut == null) {
+                UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+                if(userId == userManager.getSerialNumberForUser(Process.myUserHandle())) {
+                    try {
+                        context.startActivity(intent, bundle);
+                    } catch (ActivityNotFoundException e) {
+                        launchAndroidForWork(context, intent.getComponent(), bundle, userId);
+                    } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
+                } else
+                    launchAndroidForWork(context, intent.getComponent(), bundle, userId);
+            } else
+                launchShortcut(context, shortcut, bundle);
+        });
 
         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"));
     }
-    
-    private static void launchStandard(Context context, Intent intent, long userId, ShortcutInfo shortcut, ApplicationType type) {
-        Bundle bundle = Build.VERSION.SDK_INT < Build.VERSION_CODES.N ? null : getActivityOptions(type).toBundle();
-        if(shortcut == null) {
-            UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-            if(userId == userManager.getSerialNumberForUser(Process.myUserHandle())) {
-                try {
-                    context.startActivity(intent, bundle);
-                } catch (ActivityNotFoundException e) {
-                    launchAndroidForWork(context, intent.getComponent(), bundle, userId);
-                } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
-            } else
-                launchAndroidForWork(context, intent.getComponent(), bundle, userId);
-        } else
-            launchShortcut(context, shortcut, bundle);
-    }
 
     @SuppressWarnings("deprecation")
     @TargetApi(Build.VERSION_CODES.N)
-    private static void launchMode1(Context context, Intent intent, long userId, ShortcutInfo shortcut, ApplicationType type) {
-        DisplayMetrics metrics = getRealDisplayMetrics(context);
+    private static Bundle launchMode1(Context context, ApplicationType type) {
+        DisplayInfo display = getDisplayInfo(context);
 
-        int width1 = metrics.widthPixels / 8;
-        int width2 = metrics.widthPixels - width1;
-        int height1 = metrics.heightPixels / 8;
-        int height2 = metrics.heightPixels - height1;
+        int width1 = display.width / 8;
+        int width2 = display.width - width1;
+        int height1 = display.height / 8;
+        int height2 = display.height - height1;
 
-        Bundle bundle = getActivityOptions(type).setLaunchBounds(new Rect(
+        return getActivityOptions(context, type).setLaunchBounds(new Rect(
                 width1,
                 height1,
                 width2,
                 height2
         )).toBundle();
-
-        if(shortcut == null) {
-            UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-            if(userId == userManager.getSerialNumberForUser(Process.myUserHandle())) {
-                try {
-                    context.startActivity(intent, bundle);
-                } catch (ActivityNotFoundException e) {
-                    launchAndroidForWork(context, intent.getComponent(), bundle, userId);
-                } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
-            } else
-                launchAndroidForWork(context, intent.getComponent(), bundle, userId);
-        } else
-            launchShortcut(context, shortcut, bundle);
     }
 
     @SuppressWarnings("deprecation")
     @TargetApi(Build.VERSION_CODES.N)
-    private static void launchMode2(Context context, Intent intent, int launchType, long userId, ShortcutInfo shortcut, ApplicationType type) {
-        DisplayMetrics metrics = getRealDisplayMetrics(context);
-        
+    private static Bundle launchMode2(Context context, int launchType, ApplicationType type) {
+        DisplayInfo display = getDisplayInfo(context);
+
         int statusBarHeight = getStatusBarHeight(context);
         String position = getTaskbarPosition(context);
 
         boolean isPortrait = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
         boolean isLandscape = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
 
-        int left = launchType == RIGHT && isLandscape
-                ? metrics.widthPixels / 2
-                : 0;
-
-        int top = launchType == RIGHT && isPortrait
-                ? metrics.heightPixels / 2
-                : statusBarHeight;
-
-        int right = launchType == LEFT && isLandscape
-                ? metrics.widthPixels / 2
-                : metrics.widthPixels;
-
-        int bottom = launchType == LEFT && isPortrait
-                ? metrics.heightPixels / 2
-                : metrics.heightPixels;
-
-        SharedPreferences pref = getSharedPreferences(context);
-        int iconSize;
-
-        if(isChromeOs(context) && pref.getBoolean("hide_taskbar", true))
-            iconSize = 0;
+        int left = 0;
+        int top = statusBarHeight;
+        int right = display.width;
+        int bottom = display.height;
+
+        int iconSize = isOverridingFreeformHack(context) && !LauncherHelper.getInstance().isOnHomeScreen()
+                ? 0
+                : context.getResources().getDimensionPixelSize(R.dimen.icon_size);
+
+        if(position.contains("vertical_left"))
+            left = left + iconSize;
+        else if(position.contains("vertical_right"))
+            right = right - iconSize;
+        else if(position.contains("bottom"))
+            bottom = bottom - iconSize;
         else
-            iconSize = context.getResources().getDimensionPixelSize(R.dimen.icon_size);
-
-        if(position.contains("vertical_left")) {
-            if(launchType != RIGHT || isPortrait) left = left + iconSize;
-        } else if(position.contains("vertical_right")) {
-            if(launchType != LEFT || isPortrait) right = right - iconSize;
-        } else if(position.contains("bottom")) {
-            if(isLandscape || (launchType != LEFT && isPortrait))
-                bottom = bottom - iconSize;
-        } else if(isLandscape || (launchType != RIGHT && isPortrait))
             top = top + iconSize;
 
-        Bundle bundle = getActivityOptions(type).setLaunchBounds(new Rect(
-                left,
-                top,
-                right,
-                bottom
-        )).toBundle();
-
-        if(shortcut == null) {
-            UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-            if(userId == userManager.getSerialNumberForUser(Process.myUserHandle())) {
-                try {
-                    context.startActivity(intent, bundle);
-                } catch (ActivityNotFoundException e) {
-                    launchAndroidForWork(context, intent.getComponent(), bundle, userId);
-                } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
-            } else
-                launchAndroidForWork(context, intent.getComponent(), bundle, userId);
-        } else
-            launchShortcut(context, shortcut, bundle);
+        if(launchType == RIGHT && isLandscape)
+            left = (right / 2) + ((iconSize / 2) * (position.contains("vertical_left") ? 1 : 0));
+        else if(launchType == RIGHT && isPortrait)
+            top = (bottom / 2) + ((iconSize / 2)
+                    * ((position.equals("top_left") || position.equals("top_right")) ? 1 : 0));
+        else if(launchType == LEFT && isLandscape)
+            right = (right / 2) + ((iconSize / 2) * (position.contains("vertical_left") ? 1 : 0));
+        else if(launchType == LEFT && isPortrait)
+            bottom = (bottom / 2) + ((iconSize / 2)
+                    * ((position.equals("top_left") || position.equals("top_right")) ? 1 : 0));
+
+        return getActivityOptions(context, type)
+                .setLaunchBounds(new Rect(left, top, right, bottom)).toBundle();
     }
 
     @SuppressWarnings("deprecation")
     @TargetApi(Build.VERSION_CODES.N)
-    private static void launchMode3(Context context, Intent intent, long userId, ShortcutInfo shortcut, ApplicationType type) {
-        DisplayMetrics metrics = getRealDisplayMetrics(context);
+    private static Bundle launchMode3(Context context, ApplicationType type) {
+        DisplayInfo display = getDisplayInfo(context);
 
-        int width1 = metrics.widthPixels / 2;
+        int width1 = display.width / 2;
         int width2 = context.getResources().getDimensionPixelSize(R.dimen.phone_size_width) / 2;
-        int height1 = metrics.heightPixels / 2;
+        int height1 = display.height / 2;
         int height2 = context.getResources().getDimensionPixelSize(R.dimen.phone_size_height) / 2;
 
-        Bundle bundle = getActivityOptions(type).setLaunchBounds(new Rect(
+        return getActivityOptions(context, type).setLaunchBounds(new Rect(
                 width1 - width2,
                 height1 - height2,
                 width1 + width2,
                 height1 + height2
         )).toBundle();
-
-        if(shortcut == null) {
-            UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-            if(userId == userManager.getSerialNumberForUser(Process.myUserHandle())) {
-                try {
-                    context.startActivity(intent, bundle);
-                } catch (ActivityNotFoundException e) {
-                    launchAndroidForWork(context, intent.getComponent(), bundle, userId);
-                } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
-            } else
-                launchAndroidForWork(context, intent.getComponent(), bundle, userId);
-        } else
-            launchShortcut(context, shortcut, bundle);
     }
 
     private static void launchAndroidForWork(Context context, ComponentName componentName, Bundle bundle, long userId) {
@@ -524,48 +534,124 @@ public class U {
         }
     }
 
-    public static void launchAppMaximized(Context context, Intent intent) {
-        UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-        long userId = userManager.getSerialNumberForUser(Process.myUserHandle());
+    private static void prepareToStartActivity(Context context, Runnable runnable) {
+        boolean shouldLaunchTouchAbsorber =
+                !FreeformHackHelper.getInstance().isTouchAbsorberActive()
+                        && isOverridingFreeformHack(context)
+                        && !isChromeOs(context);
 
-        launchMode2(context, intent, MAXIMIZED, userId, null, ApplicationType.CONTEXT_MENU);
+        if(!shouldLaunchTouchAbsorber) {
+            runnable.run();
+            return;
+        }
+
+        startTouchAbsorberActivity(context);
+        new Handler().postDelayed(runnable, 100);
+    }
+
+    public static void startActivityMaximized(Context context, Intent intent) {
+        Bundle bundle = launchMode2(context, MAXIMIZED, ApplicationType.CONTEXT_MENU);
+        prepareToStartActivity(context, () -> context.startActivity(intent, bundle));
     }
 
-    @SuppressWarnings("deprecation")
     @TargetApi(Build.VERSION_CODES.N)
-    public static void launchAppLowerRight(Context context, Intent intent) {
-        DisplayMetrics metrics = getRealDisplayMetrics(context);
+    public static void startActivityLowerRight(Context context, Intent intent) {
+        DisplayInfo display = getDisplayInfo(context);
         try {
-            context.startActivity(intent, getActivityOptions(ApplicationType.FREEFORM_HACK).setLaunchBounds(new Rect(
-                    metrics.widthPixels,
-                    metrics.heightPixels,
-                    metrics.widthPixels + 1,
-                    metrics.heightPixels + 1
-            )).toBundle());
+            context.startActivity(intent,
+                    getActivityOptions(context, ApplicationType.FREEFORM_HACK)
+                            .setLaunchBounds(new Rect(
+                                    display.width,
+                                    display.height,
+                                    display.width + 1,
+                                    display.height + 1
+                            )).toBundle());
         } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
     }
 
-    public static void checkForUpdates(Context context) {
-        if(!BuildConfig.APPLICATION_ID.equals(BuildConfig.ANDROIDX86_APPLICATION_ID)) {
-            if(!BuildConfig.DEBUG) {
-                String url;
-                try {
-                    context.getPackageManager().getPackageInfo("com.android.vending", 0);
-                    url = "https://play.google.com/store/apps/details?id=" + BuildConfig.APPLICATION_ID;
-                } catch (PackageManager.NameNotFoundException e) {
-                    url = "https://f-droid.org/repository/browse/?fdid=" + BuildConfig.BASE_APPLICATION_ID;
-                }
+    @TargetApi(Build.VERSION_CODES.N)
+    public static void startTouchAbsorberActivity(Context context) {
+        String position = getTaskbarPosition(context);
+        DisplayInfo display = getDisplayInfo(context);
 
-                Intent intent = new Intent(Intent.ACTION_VIEW);
-                intent.setData(Uri.parse(url));
-                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        int left = 0;
+        int top = 0;
+        int right = display.width;
+        int bottom = display.height;
 
-                try {
-                    context.startActivity(intent);
-                } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
-            } else
-                showToast(context, R.string.debug_build);
+        int iconSize = context.getResources().getDimensionPixelSize(R.dimen.icon_size);
+
+        if(position.contains("vertical_left"))
+            right = iconSize;
+        else if(position.contains("vertical_right"))
+            left = right - iconSize;
+        else if(position.contains("bottom"))
+            top = bottom - iconSize;
+        else
+            bottom = iconSize;
+
+        Intent intent = new Intent(context, TouchAbsorberActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+
+        try {
+            context.startActivity(intent,
+                    getActivityOptions(context, ApplicationType.FREEFORM_HACK)
+                            .setLaunchBounds(new Rect(left, top, right, bottom)).toBundle());
+        } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
+    }
+
+    public static void startContextMenuActivity(Context context, Bundle args) {
+        SharedPreferences pref = getSharedPreferences(context);
+        Intent intent = null;
+
+        switch(pref.getString("theme", "light")) {
+            case "light":
+                intent = new Intent(context, ContextMenuActivity.class);
+                break;
+            case "dark":
+                intent = new Intent(context, ContextMenuActivityDark.class);
+                break;
+        }
+
+        if(intent != null) {
+            intent.putExtra("args", args);
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         }
+
+        if(hasFreeformSupport(context) && FreeformHackHelper.getInstance().isInFreeformWorkspace()) {
+            DisplayInfo display = getDisplayInfo(context);
+
+            if(intent != null && hasBrokenSetLaunchBoundsApi())
+                intent.putExtra("context_menu_fix", true);
+
+            context.startActivity(intent,
+                    getActivityOptions(context, ApplicationType.CONTEXT_MENU)
+                            .setLaunchBounds(
+                                    new Rect(0, 0, display.width, display.height)
+                            ).toBundle());
+        } else
+            context.startActivity(intent);
+    }
+
+    public static void checkForUpdates(Context context) {
+        String url;
+        if(isPlayStoreRelease(context)) {
+            if(BuildConfig.APPLICATION_ID.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;
+        } else
+            url = "https://f-droid.org/repository/browse/?fdid=" + BuildConfig.APPLICATION_ID;
+
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.setData(Uri.parse(url));
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        try {
+            context.startActivity(intent);
+        } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
     }
 
     public static boolean launcherIsDefault(Context context) {
@@ -693,15 +779,16 @@ public class U {
 
     private static int getMaxNumOfColumns(Context context) {
         SharedPreferences pref = getSharedPreferences(context);
-        DisplayMetrics metrics = getRealDisplayMetrics(context);
-        float baseTaskbarSize = getBaseTaskbarSizeFloat(context) / metrics.density;
+        DisplayInfo display = getDisplayInfo(context);
+        float density = display.density / 160;
+        float baseTaskbarSize = getBaseTaskbarSizeFloat(context) / density;
         int numOfColumns = 0;
 
         float maxScreenSize = getTaskbarPosition(context).contains("vertical")
-                ? (metrics.heightPixels - getStatusBarHeight(context)) / metrics.density
-                : metrics.widthPixels / metrics.density;
+                ? (display.height - getStatusBarHeight(context)) / density
+                : display.width / density;
 
-        float iconSize = context.getResources().getDimension(R.dimen.icon_size) / metrics.density;
+        float iconSize = context.getResources().getDimension(R.dimen.icon_size) / density;
 
         int userMaxNumOfColumns = Integer.valueOf(pref.getString("max_num_of_recents", "10"));
 
@@ -722,12 +809,20 @@ public class U {
     }
 
     public static int getStatusBarHeight(Context context) {
-        int statusBarHeight = 0;
-        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
+        return getSystemDimen(context, "status_bar_height");
+    }
+
+    private static int getNavbarHeight(Context context) {
+        return getSystemDimen(context, "navigation_bar_height");
+    }
+
+    private static int getSystemDimen(Context context, String id) {
+        int value = 0;
+        int resourceId = context.getResources().getIdentifier(id, "dimen", "android");
         if(resourceId > 0)
-            statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
+            value = context.getResources().getDimensionPixelSize(resourceId);
 
-        return statusBarHeight;
+        return value;
     }
 
     public static void refreshPinnedIcons(Context context) {
@@ -813,23 +908,32 @@ public class U {
         return intent;
     }
 
+    public static boolean canEnableFreeform() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
+    }
+
+    @TargetApi(Build.VERSION_CODES.N)
     public static boolean hasFreeformSupport(Context context) {
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
+        return canEnableFreeform()
                 && (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
-                || Settings.Global.getInt(context.getContentResolver(), "enable_freeform_support", -1) == 1
+                || Settings.Global.getInt(context.getContentResolver(), "enable_freeform_support", 0) != 0
                 || (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
-                && Settings.Global.getInt(context.getContentResolver(), "force_resizable_activities", -1) == 1));
+                && Settings.Global.getInt(context.getContentResolver(), "force_resizable_activities", 0) != 0));
     }
 
-    public static boolean hasPartialFreeformSupport() {
-         return Build.MANUFACTURER.equalsIgnoreCase("Samsung");
+    public static boolean isSamsungDevice() {
+        return Build.MANUFACTURER.equalsIgnoreCase("Samsung");
+    }
+
+    public static boolean isNvidiaDevice() {
+        return Build.MANUFACTURER.equalsIgnoreCase("NVIDIA");
     }
 
     public static boolean isServiceRunning(Context context, Class<? extends Service> cls) {
         return isServiceRunning(context, cls.getName());
     }
 
-    public static boolean isServiceRunning(Context context, String className) {
+    private static boolean isServiceRunning(Context context, String className) {
         ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
         for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
             if(className.equals(service.service.getClassName()))
@@ -882,39 +986,86 @@ public class U {
     }
 
     @TargetApi(Build.VERSION_CODES.N)
-    public static ActivityOptions getActivityOptions(ApplicationType applicationType) {
+    public static ActivityOptions getActivityOptions(Context context, ApplicationType applicationType) {
         ActivityOptions options = ActivityOptions.makeBasic();
-        Integer stackId = null;
+        int stackId = -1;
 
         switch(applicationType) {
             case APPLICATION:
-                if(!FreeformHackHelper.getInstance().isFreeformHackActive())
-                    stackId = FULLSCREEN_WORKSPACE_STACK_ID;
+                if(FreeformHackHelper.getInstance().isFreeformHackActive())
+                    stackId = getFreeformWindowModeId();
                 else
-                    stackId = FREEFORM_WORKSPACE_STACK_ID;
+                    stackId = getFullscreenWindowModeId();
                 break;
             case GAME:
-                stackId = FULLSCREEN_WORKSPACE_STACK_ID;
+                stackId = getFullscreenWindowModeId();
                 break;
             case FREEFORM_HACK:
-                stackId = FREEFORM_WORKSPACE_STACK_ID;
+                stackId = getFreeformWindowModeId();
                 break;
             case CONTEXT_MENU:
-                if(!FreeformHackHelper.getInstance().isFreeformHackActive()
-                        || Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1)
-                    stackId = FULLSCREEN_WORKSPACE_STACK_ID;
-                else
-                    stackId = FREEFORM_WORKSPACE_STACK_ID;
+                if(hasBrokenSetLaunchBoundsApi()
+                        || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f))
+                    stackId = getFullscreenWindowModeId();
+                break;
         }
 
         try {
-            Method method = ActivityOptions.class.getMethod("setLaunchStackId", int.class);
+            Method method = ActivityOptions.class.getMethod(getWindowingModeMethodName(), int.class);
             method.invoke(options, stackId);
         } catch (Exception e) { /* Gracefully fail */ }
 
         return options;
     }
 
+    private static int getFullscreenWindowModeId() {
+        if(getCurrentApiVersion() >= 28.0f)
+            return WINDOWING_MODE_FULLSCREEN;
+        else
+            return FULLSCREEN_WORKSPACE_STACK_ID;
+    }
+
+    private static int getFreeformWindowModeId() {
+        if(getCurrentApiVersion() >= 28.0f)
+            return WINDOWING_MODE_FREEFORM;
+        else
+            return FREEFORM_WORKSPACE_STACK_ID;
+    }
+
+    private static String getWindowingModeMethodName() {
+        if(getCurrentApiVersion() >= 28.0f)
+            return "setLaunchWindowingMode";
+        else
+            return "setLaunchStackId";
+    }
+
+    public static Bundle getActivityOptionsBundle(Context context, ApplicationType type) {
+        SharedPreferences pref = getSharedPreferences(context);
+
+        return getActivityOptionsBundle(context, type, pref.getString("window_size", "standard"));
+    }
+    
+    private static Bundle getActivityOptionsBundle(Context context, ApplicationType type, String windowSize) {
+        SharedPreferences pref = getSharedPreferences(context);
+        if(!canEnableFreeform() || !pref.getBoolean("freeform_hack", false))
+            return null;
+        
+        switch(windowSize) {
+            case "large":
+                return launchMode1(context, type);
+            case "fullscreen":
+                return launchMode2(context, MAXIMIZED, type);
+            case "half_left":
+                return launchMode2(context, LEFT, type);
+            case "half_right":
+                return launchMode2(context, RIGHT, type);
+            case "phone_size":
+                return launchMode3(context, type);
+        }
+
+        return getActivityOptions(context, type).toBundle();
+    }
+
     private static ApplicationType getApplicationType(Context context, String packageName) {
         return isGame(context, packageName) ? ApplicationType.GAME : ApplicationType.APPLICATION;
     }
@@ -933,7 +1084,26 @@ public class U {
         return context.getPackageManager().hasSystemFeature("org.chromium.arc");
     }
 
-    public static boolean hasSupportLibrary(Context context) {
+    public static boolean isBlissOs(Context context) {
+        boolean validBlissOsBuildProp = false;
+
+        String blissVersion = DependencyUtils.getSystemProperty("ro.bliss.version");
+        if(blissVersion != null && !blissVersion.isEmpty())
+            validBlissOsBuildProp = true;
+
+        String buildUser = DependencyUtils.getSystemProperty("ro.build.user");
+        if(buildUser != null && buildUser.equals("electrikjesus"))
+            validBlissOsBuildProp = true;
+
+        return validBlissOsBuildProp
+                && BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
+                && isSystemApp(context);
+    }
+
+    public static boolean isLauncherPermanentlyEnabled(Context context) {
+        if(BuildConfig.APPLICATION_ID.equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
+            return true;
+
         PackageManager pm = context.getPackageManager();
         try {
             pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
@@ -1031,24 +1201,60 @@ 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 < 600
+        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
-        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+        if(canEnableFreeform()) {
             if(!pref.getBoolean("freeform_hack_override", false)) {
                 pref.edit()
-                        .putBoolean("freeform_hack", hasFreeformSupport(context) && !hasPartialFreeformSupport())
+                        .putBoolean("freeform_hack", hasFreeformSupport(context) && !isSamsungDevice())
                         .putBoolean("save_window_sizes", false)
                         .putBoolean("freeform_hack_override", true)
                         .apply();
             } else if(!hasFreeformSupport(context)) {
                 pref.edit().putBoolean("freeform_hack", false).apply();
 
-                LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.FINISH_FREEFORM_ACTIVITY"));
+                stopFreeformHack(context);
+            }
+        } else {
+            boolean freeformWasEnabled = pref.getBoolean("freeform_hack", false)
+                    || pref.getBoolean("show_freeform_disabled_message", false);
+
+            pref.edit()
+                    .putBoolean("freeform_hack", false)
+                    .putBoolean("show_freeform_disabled_message", freeformWasEnabled)
+                    .apply();
+
+            SavedWindowSizes.getInstance(context).clear(context);
+            stopFreeformHack(context);
+        }
+
+        // Customizations for BlissOS
+        if(isBlissOs(context) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+                && !pref.getBoolean("bliss_os_prefs", false)) {
+            SharedPreferences.Editor editor = pref.edit();
+
+            if(hasFreeformSupport(context)) {
+                editor.putBoolean("freeform_hack", true);
             }
+
+            editor.putString("recents_amount", "running_apps_only");
+            editor.putString("refresh_frequency", "0");
+            editor.putString("max_num_of_recents", "2147483647");
+            editor.putString("sort_order", "true");
+            editor.putString("window_size", "phone_size");
+            editor.putBoolean("full_length", true);
+            editor.putBoolean("dashboard", true);
+            editor.putBoolean("app_drawer_icon", true);
+            editor.putBoolean("button_back", true);
+            editor.putBoolean("button_home", true);
+            editor.putBoolean("button_recents", true);
+            editor.putBoolean("auto_hide_navbar", true);
+            editor.putBoolean("bliss_os_prefs", true);
+            editor.apply();
         }
 
         // Customizations for Android-x86 devices (non-Bliss)
@@ -1060,6 +1266,7 @@ 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("android_x86_prefs", true)
@@ -1067,40 +1274,77 @@ public class U {
         }
     }
 
-    public static DisplayMetrics getRealDisplayMetrics(Context context) {
-        DisplayMetrics metrics = new DisplayMetrics();
+    public static DisplayInfo getDisplayInfo(Context context) {
+        context = context.getApplicationContext();
+
         WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         Display disp = wm.getDefaultDisplay();
 
-        SharedPreferences pref = getSharedPreferences(context);
-        if(isChromeOs(context) && !pref.getBoolean("chrome_os_context_menu_fix", true))
-            disp.getRealMetrics(metrics);
-        else
-            disp.getMetrics(metrics);
+        DisplayMetrics metrics = new DisplayMetrics();
+        disp.getMetrics(metrics);
+
+        DisplayMetrics realMetrics = new DisplayMetrics();
+        disp.getRealMetrics(realMetrics);
+
+        DisplayInfo display = 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;
+            }
+
+            return display;
+        }
+
+        boolean sameWidth = metrics.widthPixels == realMetrics.widthPixels;
+        boolean sameHeight = metrics.heightPixels == realMetrics.heightPixels;
 
-        return metrics;
+        if(sameWidth && !sameHeight) {
+            display.width = realMetrics.widthPixels;
+            display.height = realMetrics.heightPixels - getNavbarHeight(context);
+        }
+
+        if(!sameWidth && sameHeight) {
+            display.width = realMetrics.widthPixels - getNavbarHeight(context);
+            display.height = realMetrics.heightPixels;
+        }
+
+        return display;
     }
 
-    static void pinAppShortcut(Context context) {
-        Intent intent = getShortcutIntent(context);
-        intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
-        intent.putExtra("duplicate", false);
+    public static void pinAppShortcut(Context context) {
+        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            ShortcutManager mShortcutManager = context.getSystemService(ShortcutManager.class);
 
-        Intent homeIntent = new Intent(Intent.ACTION_MAIN);
-        homeIntent.addCategory(Intent.CATEGORY_HOME);
-        ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
+            if(mShortcutManager.isRequestPinShortcutSupported()) {
+                ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder(context, "freeform_mode").build();
+
+                mShortcutManager.requestPinShortcut(pinShortcutInfo, null);
+            } else
+                showToastLong(context, R.string.pin_shortcut_not_supported);
+        } else {
+            Intent intent = getShortcutIntent(context);
+            intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
+            intent.putExtra("duplicate", false);
+
+            Intent homeIntent = new Intent(Intent.ACTION_MAIN);
+            homeIntent.addCategory(Intent.CATEGORY_HOME);
+            ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
 
-        intent.setPackage(defaultLauncher.activityInfo.packageName);
-        context.sendBroadcast(intent);
+            intent.setPackage(defaultLauncher.activityInfo.packageName);
+            context.sendBroadcast(intent);
 
-        showToast(context, R.string.shortcut_created);
+            showToast(context, R.string.shortcut_created);
+        }
     }
 
     public static boolean shouldCollapse(Context context, boolean pendingAppLaunch) {
         SharedPreferences pref = getSharedPreferences(context);
         if(pref.getBoolean("hide_taskbar", true)) {
-            if(isChromeOs(context))
-                return true;
+            if(isOverridingFreeformHack(context))
+                return !LauncherHelper.getInstance().isOnHomeScreen();
             else {
                 FreeformHackHelper helper = FreeformHackHelper.getInstance();
                 if(pendingAppLaunch)
@@ -1112,13 +1356,183 @@ public class U {
             return false;
     }
 
-    @SuppressWarnings("unchecked")
-    public static <T extends View> T findViewById(Activity target, int id) {
-        return (T) target.findViewById(id);
+    public static boolean isOverridingFreeformHack(Context context) {
+        SharedPreferences pref = getSharedPreferences(context);
+        return pref.getBoolean("freeform_hack", false)
+                && ((isChromeOs(context) && pref.getBoolean("chrome_os_context_menu_fix", true))
+                || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f));
     }
 
-    @SuppressWarnings("unchecked")
-    public static <T extends View> T findViewById(View target, int id) {
-        return (T) target.findViewById(id);
+    public static boolean isPlayStoreInstalled(Context context) {
+        try {
+            context.getPackageManager().getPackageInfo("com.android.vending", 0);
+            return true;
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    private 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
+            return (float) Build.VERSION.SDK_INT;
+    }
+
+    public static boolean hasBrokenSetLaunchBoundsApi() {
+        return getCurrentApiVersion() >= 26.0f
+                && getCurrentApiVersion() < 28.0f
+                && !isSamsungDevice()
+                && !isNvidiaDevice();
+    }
+
+    public static String getSecondScreenPackageName(Context context) {
+        return getInstalledPackage(context, Arrays.asList(
+                "com.farmerbb.secondscreen.free",
+                "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, List<String> packageNames) {
+        if(packageNames == null || packageNames.isEmpty())
+            return null;
+
+        List<String> packages = packageNames instanceof ArrayList ? packageNames : new ArrayList<>(packageNames);
+        String packageName = packages.get(0);
+
+        try {
+            context.getPackageManager().getPackageInfo(packageName, 0);
+            return packageName;
+        } catch (PackageManager.NameNotFoundException e) {
+            packages.remove(0);
+            return getInstalledPackage(context, packages);
+        }
+    }
+
+    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);
+    }
+
+    public static AlertDialog showRecentAppsDialog(Context context, Runnable onError, Runnable onFinish) {
+        Runnable finalOnFinish = onFinish == null
+                ? () -> {}
+                : onFinish;
+
+        Runnable finalOnError = onError == null
+                ? () -> showErrorDialog(context, "GET_USAGE_STATS", finalOnFinish)
+                : onError;
+
+        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isSystemApp(context)) {
+            ApplicationInfo applicationInfo = null;
+            try {
+                applicationInfo = context.getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
+            } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
+
+            if(applicationInfo != null) {
+                AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+                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 dialog = builder.create();
+                    dialog.show();
+                    dialog.setCancelable(false);
+
+                    return dialog;
+                }
+            }
+        }
+
+        finalOnFinish.run();
+        return null;
+    }
+
+    public static Context wrapContext(Context context) {
+        SharedPreferences pref = getSharedPreferences(context);
+
+        int theme = -1;
+        switch(pref.getString("theme", "light")) {
+            case "light":
+                theme = R.style.AppTheme;
+                break;
+            case "dark":
+                theme = R.style.AppTheme_Dark;
+                break;
+        }
+
+        return theme > -1 ? new ContextThemeWrapper(context, theme) : context;
+    }
+
+    @SuppressLint("PackageManagerGetSignatures")
+    public static boolean isPlayStoreRelease(Context context) {
+        Signature playStoreSignature = new Signature(context.getString(R.string.signature));
+        try {
+            PackageManager pm = context.getPackageManager();
+            PackageInfo info = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
+            for(Signature signature : info.signatures) {
+                if(signature.equals(playStoreSignature))
+                    return true;
+            }
+        } catch (Exception e) { /* Gracefully fail */ }
+
+        return false;
+    }
+
+    public static boolean isExternalAccessDisabled(Context context) {
+        SharedPreferences pref = getSharedPreferences(context);
+        return !pref.getBoolean("tasker_enabled", true);
+    }
+
+    public static boolean enableFreeformModeShortcut(Context context) {
+        return canEnableFreeform()
+                && !U.isOverridingFreeformHack(context)
+                && !U.isChromeOs(context);
+    }
+
+    public static void startForegroundService(Context context, Intent intent) {
+        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            if(Settings.canDrawOverlays(context))
+                context.startForegroundService(intent);
+        } else
+            context.startService(intent);
+    }
+
+    public static int getOverlayType() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+                ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+                : WindowManager.LayoutParams.TYPE_PHONE;
+    }
+
+    public static boolean isDelegatingHomeActivity(Context context) {
+        Intent homeIntent = new Intent(Intent.ACTION_MAIN);
+        homeIntent.addCategory(Intent.CATEGORY_HOME);
+
+        final List<ResolveInfo> listOfLaunchers = context.getPackageManager().queryIntentActivities(homeIntent, 0);
+        for(ResolveInfo launcher : listOfLaunchers) {
+            if(launcher.activityInfo.packageName.equals(BuildConfig.SUPPORT_APPLICATION_ID))
+                return true;
+        }
+
+        return false;
     }
 }