OSDN Git Service

Use framework toasts on Android 7.1+ instead of 8.0+
[android-x86/packages-apps-Taskbar.git] / app / src / main / java / com / farmerbb / taskbar / util / U.java
index 2069970..febae19 100644 (file)
 package com.farmerbb.taskbar.util;
 
 import android.annotation.TargetApi;
+import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.AlertDialog;
+import android.app.Service;
 import android.app.admin.DevicePolicyManager;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
 import android.content.res.Configuration;
+import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
-import android.hardware.display.DisplayManager;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
@@ -54,8 +57,15 @@ import com.farmerbb.taskbar.R;
 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.receiver.LockDeviceReceiver;
+import com.farmerbb.taskbar.service.DashboardService;
+import com.farmerbb.taskbar.service.NotificationService;
+import com.farmerbb.taskbar.service.PowerMenuService;
+import com.farmerbb.taskbar.service.StartMenuService;
+import com.farmerbb.taskbar.service.TaskbarService;
 
+import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -64,40 +74,43 @@ public class U {
     private U() {}
 
     private static SharedPreferences pref;
-    private static Toast toast;
     private static Integer cachedRotation;
 
-    private static final int FULLSCREEN = 0;
+    private static final int MAXIMIZED = 0;
     private static final int LEFT = -1;
     private static final int RIGHT = 1;
 
     public static final int HIDDEN = 0;
     public static final int TOP_APPS = 1;
 
+    // From android.app.ActivityManager.StackId
+    private static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
+    private static final int FREEFORM_WORKSPACE_STACK_ID = 2;
+
     public static SharedPreferences getSharedPreferences(Context context) {
         if(pref == null) pref = context.getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", Context.MODE_PRIVATE);
         return pref;
     }
 
     @TargetApi(Build.VERSION_CODES.M)
-    public static void showPermissionDialog(final Context context) {
+    public static AlertDialog showPermissionDialog(final Context context) {
         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, new DialogInterface.OnClickListener() {
-                    @Override
-                    public void onClick(DialogInterface dialog, int which) {
-                        try {
-                            context.startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION));
-                        } catch (ActivityNotFoundException e) {
-                            showErrorDialog(context, "SYSTEM_ALERT_WINDOW");
-                        }
+                .setPositiveButton(R.string.action_grant_permission, (dialog, which) -> {
+                    try {
+                        context.startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
+                                Uri.parse("package:" + BuildConfig.APPLICATION_ID)));
+                    } catch (ActivityNotFoundException e) {
+                        showErrorDialog(context, "SYSTEM_ALERT_WINDOW");
                     }
                 });
 
         AlertDialog dialog = builder.create();
         dialog.show();
         dialog.setCancelable(false);
+
+        return dialog;
     }
 
     public static void showErrorDialog(final Context context, String appopCmd) {
@@ -111,7 +124,7 @@ public class U {
     }
 
     public static void lockDevice(Context context) {
-        ComponentName component = new ComponentName(BuildConfig.APPLICATION_ID, LockDeviceReceiver.class.getName());
+        ComponentName component = new ComponentName(context, LockDeviceReceiver.class);
         context.getPackageManager().setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                 PackageManager.DONT_KILL_APP);
 
@@ -119,13 +132,48 @@ public class U {
         if(mDevicePolicyManager.isAdminActive(component))
             mDevicePolicyManager.lockNow();
         else {
-            Intent intent = new Intent(context, DummyActivity.class);
-            intent.putExtra("device_admin", true);
-            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            context.startActivity(intent);
+            launchApp(context, () -> {
+                Intent intent = new Intent(context, DummyActivity.class);
+                intent.putExtra("device_admin", true);
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                context.startActivity(intent);
+            });
         }
     }
 
+    public static void sendAccessibilityAction(Context context, int action) {
+        ComponentName component = new ComponentName(context, PowerMenuService.class);
+        context.getPackageManager().setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                PackageManager.DONT_KILL_APP);
+
+        if(isAccessibilityServiceEnabled(context)) {
+            Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
+            intent.putExtra("action", action);
+            LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+        } else {
+            launchApp(context, () -> {
+                Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+                try {
+                    context.startActivity(intent);
+                    showToastLong(context, R.string.enable_accessibility);
+                } catch (ActivityNotFoundException e) {
+                    showToast(context, R.string.lock_device_not_supported);
+                }
+            });
+        }
+    }
+
+    private static boolean isAccessibilityServiceEnabled(Context context) {
+        String accessibilityServices = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+        ComponentName component = new ComponentName(context, PowerMenuService.class);
+
+        return accessibilityServices != null
+                && (accessibilityServices.contains(component.flattenToString())
+                || accessibilityServices.contains(component.flattenToShortString()));
+    }
+
     public static void showToast(Context context, int message) {
         showToast(context, context.getString(message), Toast.LENGTH_SHORT);
     }
@@ -135,10 +183,34 @@ public class U {
     }
 
     public static void showToast(Context context, String message, int length) {
-        if(toast != null) toast.cancel();
+        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);
 
-        toast = Toast.makeText(context, message, length);
         toast.show();
+
+        ToastHelper.getInstance().setLastToast(toast);
+    }
+
+    private static void cancelToast() {
+        ToastInterface toast = ToastHelper.getInstance().getLastToast();
+        if(toast != null) toast.cancel();
+    }
+
+    public static void startShortcut(Context context, String packageName, String componentName, ShortcutInfo shortcut) {
+        launchApp(context,
+                packageName,
+                componentName,
+                0,
+                null,
+                false,
+                false,
+                shortcut);
     }
 
     public static void launchApp(final Context context,
@@ -146,71 +218,69 @@ public class U {
                                  final String componentName,
                                  final long userId, final String windowSize,
                                  final boolean launchedFromTaskbar,
-                                 final boolean padStatusBar,
                                  final boolean openInNewWindow) {
-        boolean shouldDelay = false;
+        launchApp(context,
+                packageName,
+                componentName,
+                userId,
+                windowSize,
+                launchedFromTaskbar,
+                openInNewWindow,
+                null);
+    }
 
+    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) {
+        launchApp(context, launchedFromTaskbar, () -> continueLaunchingApp(context, packageName, componentName, userId,
+                windowSize, launchedFromTaskbar, openInNewWindow, shortcut));
+    }
+
+    public static void launchApp(Context context, Runnable runnable) {
+        launchApp(context, true, runnable);
+    }
+
+    private static void launchApp(Context context, boolean launchedFromTaskbar, Runnable runnable) {
         SharedPreferences pref = getSharedPreferences(context);
-        boolean openInFullscreen = pref.getBoolean("open_in_fullscreen", true);
-        boolean freeformHackActive = openInNewWindow
-                ? FreeformHackHelper.getInstance().isInFreeformWorkspace()
-                : (openInFullscreen
-                    ? FreeformHackHelper.getInstance().isInFreeformWorkspace()
-                    : FreeformHackHelper.getInstance().isFreeformHackActive());
+        FreeformHackHelper helper = FreeformHackHelper.getInstance();
+
+        boolean specialLaunch = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+                && FreeformHackHelper.getInstance().isInFreeformWorkspace()
+                && MenuHelper.getInstance().isContextMenuOpen();
 
         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
                 && pref.getBoolean("freeform_hack", false)
-                && !freeformHackActive) {
-            shouldDelay = true;
-
-            new Handler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    startFreeformHack(context, launchedFromTaskbar);
-
-                    new Handler().postDelayed(new Runnable() {
-                        @Override
-                        public void run() {
-                            continueLaunchingApp(context, packageName, componentName, userId,
-                                    windowSize, launchedFromTaskbar, padStatusBar, openInNewWindow);
-                        }
-                    }, 100);
-                }
-            }, launchedFromTaskbar ? 0 : 100);
-        }
+                && (!helper.isInFreeformWorkspace() || specialLaunch)) {
+            new Handler().postDelayed(() -> {
+                startFreeformHack(context, true, launchedFromTaskbar);
 
-        if(!FreeformHackHelper.getInstance().isFreeformHackActive()) {
-            if(!shouldDelay)
-                continueLaunchingApp(context, packageName, componentName, userId,
-                        windowSize, launchedFromTaskbar, padStatusBar, openInNewWindow);
-        } else if(FreeformHackHelper.getInstance().isInFreeformWorkspace() || !openInFullscreen)
-            continueLaunchingApp(context, packageName, componentName, userId,
-                    windowSize, launchedFromTaskbar, padStatusBar, openInNewWindow);
+                new Handler().postDelayed(runnable, helper.isFreeformHackActive() ? 0 : 100);
+            }, launchedFromTaskbar ? 0 : 100);
+        } else
+            runnable.run();
     }
 
     @SuppressWarnings("deprecation")
     @TargetApi(Build.VERSION_CODES.N)
-    public static void startFreeformHack(Context context, boolean launchedFromTaskbar) {
+    public static void startFreeformHack(Context context, boolean checkMultiWindow, boolean launchedFromTaskbar) {
         Intent freeformHackIntent = new Intent(context, InvisibleActivityFreeform.class);
-        freeformHackIntent.putExtra("check_multiwindow", true);
         freeformHackIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
 
+        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);
         }
 
-        DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
-        Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
-        try {
-            context.startActivity(freeformHackIntent, ActivityOptions.makeBasic().setLaunchBounds(new Rect(
-                    display.getWidth(),
-                    display.getHeight(),
-                    display.getWidth() + 1,
-                    display.getHeight() + 1
-            )).toBundle());
-        } catch (IllegalArgumentException e) { /* Gracefully fail */ }
+        if(canDrawOverlays(context))
+            launchAppLowerRight(context, freeformHackIntent);
     }
 
     @TargetApi(Build.VERSION_CODES.N)
@@ -220,20 +290,26 @@ public class U {
                                              long userId,
                                              String windowSize,
                                              boolean launchedFromTaskbar,
-                                             boolean padStatusBar,
-                                             boolean openInNewWindow) {
+                                             boolean openInNewWindow,
+                                             ShortcutInfo shortcut) {
         SharedPreferences pref = getSharedPreferences(context);
         Intent intent = new Intent();
         intent.setComponent(ComponentName.unflattenFromString(componentName));
+        intent.setAction(Intent.ACTION_MAIN);
+        intent.addCategory(Intent.CATEGORY_LAUNCHER);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
 
+        if(FreeformHackHelper.getInstance().isInFreeformWorkspace()
+                && 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(openInNewWindow) {
+        if(openInNewWindow || pref.getBoolean("force_new_window", false)) {
             intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
 
             ActivityInfo activityInfo = intent.resolveActivityInfo(context.getPackageManager(), 0);
@@ -247,52 +323,30 @@ public class U {
             }
         }
 
-        if(windowSize == null) {
-            if(pref.getBoolean("save_window_sizes", true))
-                windowSize = SavedWindowSizes.getInstance(context).getWindowSize(context, packageName);
-            else
-                windowSize = pref.getString("window_size", "standard");
-        }
+        ApplicationType type = getApplicationType(context, packageName);
 
-        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N || !pref.getBoolean("freeform_hack", false)) {
-            UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-            if(userId == userManager.getSerialNumberForUser(Process.myUserHandle())) {
-                try {
-                    context.startActivity(intent, null);
-                } catch (ActivityNotFoundException e) {
-                    launchAndroidForWork(context, intent.getComponent(), null, userId);
-                } catch (IllegalArgumentException e) { /* Gracefully fail */ }
-            } else
-                launchAndroidForWork(context, intent.getComponent(), null, userId);
+        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 "standard":
-                if(FreeformHackHelper.getInstance().isInFreeformWorkspace()) {
-                    UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-                    if(userId == userManager.getSerialNumberForUser(Process.myUserHandle())) {
-                        try {
-                            context.startActivity(intent);
-                        } catch (ActivityNotFoundException e) {
-                            launchAndroidForWork(context, intent.getComponent(), null, userId);
-                        } catch (IllegalArgumentException e) { /* Gracefully fail */ }
-                    } else
-                        launchAndroidForWork(context, intent.getComponent(), null, userId);
-                } else
-                    launchMode1(context, intent, 1, userId);
-                break;
             case "large":
-                launchMode1(context, intent, 2, userId);
+                launchMode1(context, intent, userId, shortcut, type);
                 break;
             case "fullscreen":
-                launchMode2(context, intent, padStatusBar, FULLSCREEN, userId);
+                launchMode2(context, intent, MAXIMIZED, userId, shortcut, type);
                 break;
             case "half_left":
-                launchMode2(context, intent, padStatusBar, LEFT, userId);
+                launchMode2(context, intent, LEFT, userId, shortcut, type);
                 break;
             case "half_right":
-                launchMode2(context, intent, padStatusBar, RIGHT, userId);
+                launchMode2(context, intent, RIGHT, userId, shortcut, type);
                 break;
             case "phone_size":
-                launchMode3(context, intent, userId);
+                launchMode3(context, intent, userId, shortcut, type);
                 break;
         }
 
@@ -302,77 +356,79 @@ public class U {
             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, int factor, long userId) {
-        DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
-        Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
+    private static void launchMode1(Context context, Intent intent, long userId, ShortcutInfo shortcut, ApplicationType type) {
+        DisplayMetrics metrics = getRealDisplayMetrics(context);
 
-        int width1 = display.getWidth() / (4 * factor);
-        int width2 = display.getWidth() - width1;
-        int height1 = display.getHeight() / (4 * factor);
-        int height2 = display.getHeight() - height1;
+        int width1 = metrics.widthPixels / 8;
+        int width2 = metrics.widthPixels - width1;
+        int height1 = metrics.heightPixels / 8;
+        int height2 = metrics.heightPixels - height1;
 
-        Bundle bundle = ActivityOptions.makeBasic().setLaunchBounds(new Rect(
+        Bundle bundle = getActivityOptions(type).setLaunchBounds(new Rect(
                 width1,
                 height1,
                 width2,
                 height2
         )).toBundle();
 
-        UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-        if(userId == userManager.getSerialNumberForUser(Process.myUserHandle())) {
-            try {
-                context.startActivity(intent, bundle);
-            } catch (ActivityNotFoundException e) {
+        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);
-            } catch (IllegalArgumentException e) { /* Gracefully fail */ }
         } else
-            launchAndroidForWork(context, intent.getComponent(), bundle, userId);
+            launchShortcut(context, shortcut, bundle);
     }
 
     @SuppressWarnings("deprecation")
     @TargetApi(Build.VERSION_CODES.N)
-    private static void launchMode2(Context context, Intent intent, boolean padStatusBar, int launchType, long userId) {
-        DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
-        Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
-
+    private static void launchMode2(Context context, Intent intent, int launchType, long userId, ShortcutInfo shortcut, ApplicationType type) {
+        DisplayMetrics metrics = getRealDisplayMetrics(context);
+        
         int statusBarHeight = getStatusBarHeight(context);
-
         String position = getTaskbarPosition(context);
-        boolean overridePad = position.equals("top_left") || position.equals("top_right");
 
         boolean isPortrait = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
         boolean isLandscape = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
 
         int left = launchType == RIGHT && isLandscape
-                ? display.getWidth() / 2
+                ? metrics.widthPixels / 2
                 : 0;
 
-        int top;
-        if(launchType == RIGHT && isPortrait) {
-            top = (display.getHeight() / 2)
-                    + (!padStatusBar && overridePad ? statusBarHeight / 2 : 0)
-                    + (!padStatusBar && !overridePad ? statusBarHeight / 2 : 0);
-        } else {
-            top = padStatusBar || overridePad ? statusBarHeight : 0;
-        }
+        int top = launchType == RIGHT && isPortrait
+                ? metrics.heightPixels / 2
+                : statusBarHeight;
 
         int right = launchType == LEFT && isLandscape
-                ? display.getWidth() / 2
-                : display.getWidth();
-
-        int bottom;
-        if(launchType == LEFT && isPortrait) {
-            bottom = display.getHeight() / 2
-                    + (!padStatusBar && overridePad ? statusBarHeight / 2 : 0)
-                    - (!padStatusBar && !overridePad ? statusBarHeight / 2 : 0);
-        } else {
-            bottom = display.getHeight()
-                    + ((!padStatusBar && overridePad) || (!padStatusBar && launchType == RIGHT && isPortrait)
-                    ? statusBarHeight
-                    : 0);
-        }
+                ? metrics.widthPixels / 2
+                : metrics.widthPixels;
+
+        int bottom = launchType == LEFT && isPortrait
+                ? metrics.heightPixels / 2
+                : metrics.heightPixels;
 
         int iconSize = context.getResources().getDimensionPixelSize(R.dimen.icon_size);
 
@@ -386,76 +442,120 @@ public class U {
         } else if(isLandscape || (launchType != RIGHT && isPortrait))
             top = top + iconSize;
 
-        Bundle bundle = ActivityOptions.makeBasic().setLaunchBounds(new Rect(
+        Bundle bundle = getActivityOptions(type).setLaunchBounds(new Rect(
                 left,
                 top,
                 right,
                 bottom
         )).toBundle();
 
-        UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-        if(userId == userManager.getSerialNumberForUser(Process.myUserHandle())) {
-            try {
-                context.startActivity(intent, bundle);
-            } catch (ActivityNotFoundException e) {
+        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);
-            } catch (IllegalArgumentException e) { /* Gracefully fail */ }
         } else
-            launchAndroidForWork(context, intent.getComponent(), bundle, userId);
+            launchShortcut(context, shortcut, bundle);
     }
 
     @SuppressWarnings("deprecation")
     @TargetApi(Build.VERSION_CODES.N)
-    private static void launchMode3(Context context, Intent intent, long userId) {
-        DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
-        Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
+    private static void launchMode3(Context context, Intent intent, long userId, ShortcutInfo shortcut, ApplicationType type) {
+        DisplayMetrics metrics = getRealDisplayMetrics(context);
 
-        int width1 = display.getWidth() / 2;
+        int width1 = metrics.widthPixels / 2;
         int width2 = context.getResources().getDimensionPixelSize(R.dimen.phone_size_width) / 2;
-        int height1 = display.getHeight() / 2;
+        int height1 = metrics.heightPixels / 2;
         int height2 = context.getResources().getDimensionPixelSize(R.dimen.phone_size_height) / 2;
 
-        Bundle bundle = ActivityOptions.makeBasic().setLaunchBounds(new Rect(
+        Bundle bundle = getActivityOptions(type).setLaunchBounds(new Rect(
                 width1 - width2,
                 height1 - height2,
                 width1 + width2,
                 height1 + height2
         )).toBundle();
 
-        UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-        if(userId == userManager.getSerialNumberForUser(Process.myUserHandle())) {
-            try {
-                context.startActivity(intent, bundle);
-            } catch (ActivityNotFoundException e) {
+        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);
-            } catch (IllegalArgumentException e) { /* Gracefully fail */ }
         } else
-            launchAndroidForWork(context, intent.getComponent(), bundle, userId);
+            launchShortcut(context, shortcut, bundle);
     }
 
     private static void launchAndroidForWork(Context context, ComponentName componentName, Bundle bundle, long userId) {
         UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
         LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
 
-        launcherApps.startMainActivity(componentName, userManager.getUserForSerialNumber(userId), null, bundle);
+        try {
+            launcherApps.startMainActivity(componentName, userManager.getUserForSerialNumber(userId), null, bundle);
+        } catch (ActivityNotFoundException | NullPointerException e) { /* Gracefully fail */ }
     }
 
-    public static void checkForUpdates(Context context) {
-        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_MR1)
+    private static void launchShortcut(Context context, ShortcutInfo shortcut, Bundle bundle) {
+        LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
+
+        if(launcherApps.hasShortcutHostPermission()) {
+            try {
+                launcherApps.startShortcut(shortcut, null, bundle);
+            } catch (ActivityNotFoundException | NullPointerException e) { /* Gracefully fail */ }
         }
+    }
 
-        Intent intent = new Intent(Intent.ACTION_VIEW);
-        intent.setData(Uri.parse(url));
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+    public static void launchAppMaximized(Context context, Intent intent) {
+        UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        long userId = userManager.getSerialNumberForUser(Process.myUserHandle());
 
+        launchMode2(context, intent, MAXIMIZED, userId, null, ApplicationType.CONTEXT_MENU);
+    }
+
+    @SuppressWarnings("deprecation")
+    @TargetApi(Build.VERSION_CODES.N)
+    public static void launchAppLowerRight(Context context, Intent intent) {
+        DisplayMetrics metrics = getRealDisplayMetrics(context);
         try {
-            context.startActivity(intent);
-        } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
+            context.startActivity(intent, getActivityOptions(ApplicationType.FREEFORM_HACK).setLaunchBounds(new Rect(
+                    metrics.widthPixels,
+                    metrics.heightPixels,
+                    metrics.widthPixels + 1,
+                    metrics.heightPixels + 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;
+                }
+
+                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 */ }
+            } else
+                showToast(context, R.string.debug_build);
+        }
     }
 
     public static boolean launcherIsDefault(Context context) {
@@ -581,20 +681,18 @@ public class U {
         return position;
     }
 
-    public static int getMaxNumOfColumns(Context context) {
-        // The base Taskbar size without any recent apps added.
-        // Someday this might be automatically calculated, but today is not that day.
-        float baseTaskbarSize = 92;
+    private static int getMaxNumOfColumns(Context context) {
+        SharedPreferences pref = getSharedPreferences(context);
+        DisplayMetrics metrics = getRealDisplayMetrics(context);
+        float baseTaskbarSize = getBaseTaskbarSizeFloat(context) / metrics.density;
         int numOfColumns = 0;
 
-        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
         float maxScreenSize = getTaskbarPosition(context).contains("vertical")
                 ? (metrics.heightPixels - getStatusBarHeight(context)) / metrics.density
                 : metrics.widthPixels / metrics.density;
 
         float iconSize = context.getResources().getDimension(R.dimen.icon_size) / metrics.density;
 
-        SharedPreferences pref = getSharedPreferences(context);
         int userMaxNumOfColumns = Integer.valueOf(pref.getString("max_num_of_recents", "10"));
 
         while(baseTaskbarSize + iconSize < maxScreenSize
@@ -642,7 +740,21 @@ public class U {
             for(UserHandle handle : userHandles) {
                 List<LauncherActivityInfo> list = launcherApps.getActivityList(entry.getPackageName(), handle);
                 if(!list.isEmpty()) {
-                    appInfo = list.get(0);
+                    // Google App workaround
+                    if(!entry.getPackageName().equals("com.google.android.googlequicksearchbox"))
+                        appInfo = list.get(0);
+                    else {
+                        boolean added = false;
+                        for(LauncherActivityInfo info : list) {
+                            if(info.getName().equals("com.google.android.googlequicksearchbox.SearchActivity")) {
+                                appInfo = info;
+                                added = true;
+                            }
+                        }
+
+                        if(!added) appInfo = list.get(0);
+                    }
+
                     break;
                 }
             }
@@ -680,10 +792,288 @@ public class U {
         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);
+
+        BitmapDrawable drawable = (BitmapDrawable) context.getDrawable(R.mipmap.ic_launcher);
+
+        Intent intent = new Intent();
+        intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
+        if(drawable != null) intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, drawable.getBitmap());
+        intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, context.getString(R.string.start_taskbar));
+
+        return intent;
+    }
+
     public static boolean hasFreeformSupport(Context context) {
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
                 && (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
                 || Settings.Global.getInt(context.getContentResolver(), "enable_freeform_support", -1) == 1
-                || Settings.Global.getInt(context.getContentResolver(), "force_resizable_activities", -1) == 1);
+                || (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
+                && Settings.Global.getInt(context.getContentResolver(), "force_resizable_activities", -1) == 1));
+    }
+
+    public static boolean hasPartialFreeformSupport() {
+         return Build.MANUFACTURER.equalsIgnoreCase("Samsung");
+    }
+
+    public static boolean isServiceRunning(Context context, Class<? extends Service> cls) {
+        return isServiceRunning(context, cls.getName());
+    }
+
+    public 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()))
+                return true;
+        }
+
+        return false;
+    }
+
+    public static int getBackgroundTint(Context context) {
+        SharedPreferences pref = getSharedPreferences(context);
+
+        // Import old background tint preference
+        if(pref.contains("show_background")) {
+            SharedPreferences.Editor editor = pref.edit();
+
+            if(!pref.getBoolean("show_background", true))
+                editor.putInt("background_tint", Color.TRANSPARENT).apply();
+
+            editor.remove("show_background");
+            editor.apply();
+        }
+
+        return pref.getInt("background_tint", context.getResources().getInteger(R.integer.translucent_gray));
+    }
+
+    public static int getAccentColor(Context context) {
+        SharedPreferences pref = getSharedPreferences(context);
+        return pref.getInt("accent_color", context.getResources().getInteger(R.integer.translucent_white));
+    }
+
+    @TargetApi(Build.VERSION_CODES.M)
+    public static boolean canDrawOverlays(Context context) {
+        return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context);
+    }
+
+    public static boolean isGame(Context context, String packageName) {
+        SharedPreferences pref = getSharedPreferences(context);
+        if(pref.getBoolean("launch_games_fullscreen", true)) {
+            PackageManager pm = context.getPackageManager();
+
+            try {
+                ApplicationInfo info = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
+                return (info.flags & ApplicationInfo.FLAG_IS_GAME) != 0 || (info.metaData != null && info.metaData.getBoolean("isGame", false));
+            } catch (PackageManager.NameNotFoundException e) {
+                return false;
+            }
+        } else
+            return false;
+    }
+
+    @TargetApi(Build.VERSION_CODES.M)
+    public static ActivityOptions getActivityOptions(ApplicationType applicationType) {
+        ActivityOptions options = ActivityOptions.makeBasic();
+        Integer stackId = null;
+
+        switch(applicationType) {
+            case APPLICATION:
+                if(!FreeformHackHelper.getInstance().isFreeformHackActive())
+                    stackId = FULLSCREEN_WORKSPACE_STACK_ID;
+                break;
+            case GAME:
+                stackId = FULLSCREEN_WORKSPACE_STACK_ID;
+                break;
+            case FREEFORM_HACK:
+                stackId = FREEFORM_WORKSPACE_STACK_ID;
+                break;
+            case CONTEXT_MENU:
+                if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+                    stackId = FULLSCREEN_WORKSPACE_STACK_ID;
+        }
+
+        if(stackId != null) {
+            try {
+                Method method = ActivityOptions.class.getMethod("setLaunchStackId", int.class);
+                method.invoke(options, stackId);
+            } catch (Exception e) { /* Gracefully fail */ }
+        }
+
+        return options;
+    }
+
+    private static ApplicationType getApplicationType(Context context, String packageName) {
+        return isGame(context, packageName) ? ApplicationType.GAME : ApplicationType.APPLICATION;
+    }
+
+    public static boolean isSystemApp(Context context) {
+        try {
+            ApplicationInfo info = context.getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
+            int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
+            return (info.flags & mask) != 0;
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    public static boolean isChromeOs(Context context) {
+        return context.getPackageManager().hasSystemFeature("org.chromium.arc");
+    }
+
+    public static boolean hasSupportLibrary(Context context) {
+        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)
+                    && isSystemApp(context);
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    public static int getBaseTaskbarSize(Context context) {
+        return Math.round(getBaseTaskbarSizeFloat(context));
+    }
+
+    private static float getBaseTaskbarSizeFloat(Context context) {
+        SharedPreferences pref = getSharedPreferences(context);
+        float baseTaskbarSize = context.getResources().getDimension(R.dimen.base_taskbar_size);
+        boolean navbarButtonsEnabled = false;
+
+        if(pref.getBoolean("dashboard", false))
+            baseTaskbarSize += context.getResources().getDimension(R.dimen.dashboard_button_size);
+
+        if(pref.getBoolean("button_back", false)) {
+            navbarButtonsEnabled = true;
+            baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
+        }
+
+        if(pref.getBoolean("button_home", false)) {
+            navbarButtonsEnabled = true;
+            baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
+        }
+
+        if(pref.getBoolean("button_recents", false)) {
+            navbarButtonsEnabled = true;
+            baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
+        }
+
+        if(navbarButtonsEnabled)
+            baseTaskbarSize += context.getResources().getDimension(R.dimen.navbar_buttons_margin);
+
+        return baseTaskbarSize;
+    }
+
+    private static void startTaskbarService(Context context, boolean fullRestart) {
+        context.startService(new Intent(context, TaskbarService.class));
+        context.startService(new Intent(context, StartMenuService.class));
+        context.startService(new Intent(context, DashboardService.class));
+        if(fullRestart) context.startService(new Intent(context, NotificationService.class));
+    }
+
+    private static void stopTaskbarService(Context context, boolean fullRestart) {
+        context.stopService(new Intent(context, TaskbarService.class));
+        context.stopService(new Intent(context, StartMenuService.class));
+        context.stopService(new Intent(context, DashboardService.class));
+        if(fullRestart) context.stopService(new Intent(context, NotificationService.class));
+    }
+
+    public static void restartTaskbar(Context context) {
+        SharedPreferences pref = getSharedPreferences(context);
+        if(pref.getBoolean("taskbar_active", false) && !pref.getBoolean("is_hidden", false)) {
+            pref.edit()
+                    .putBoolean("is_restarting", true)
+                    .putBoolean("skip_auto_hide_navbar", true)
+                    .apply();
+
+            stopTaskbarService(context, true);
+            startTaskbarService(context, true);
+        } else if(isServiceRunning(context, StartMenuService.class)) {
+            pref.edit().putBoolean("skip_auto_hide_navbar", true).apply();
+
+            stopTaskbarService(context, false);
+            startTaskbarService(context, false);
+        }
+    }
+
+    public static void restartNotificationService(Context context) {
+        if(isServiceRunning(context, NotificationService.class)) {
+            SharedPreferences pref = getSharedPreferences(context);
+            pref.edit().putBoolean("is_restarting", true).apply();
+
+            Intent intent = new Intent(context, NotificationService.class);
+            context.stopService(intent);
+            context.startService(intent);
+        }
+    }
+
+    public static void showHideNavigationBar(Context context, boolean show) {
+        // Show or hide the system navigation bar on Bliss-x86
+        try {
+            Settings.System.putInt(context.getContentResolver(), "navigation_bar_show", show ? 1 : 0);
+        } catch (Exception e) { /* Gracefully fail */ }
+    }
+
+    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
+                && 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(!pref.getBoolean("freeform_hack_override", false)) {
+                pref.edit()
+                        .putBoolean("freeform_hack", hasFreeformSupport(context) && !hasPartialFreeformSupport())
+                        .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"));
+            }
+        }
+
+        // Customizations for Android-x86 devices (non-Bliss)
+        if(BuildConfig.APPLICATION_ID.equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
+                && isSystemApp(context)
+                && !pref.getBoolean("android_x86_prefs", false)) {
+            pref.edit()
+                    .putString("recents_amount", "running_apps_only")
+                    .putString("refresh_frequency", "0")
+                    .putString("max_num_of_recents", "2147483647")
+                    .putBoolean("full_length", true)
+                    .putBoolean("dashboard", true)
+                    .putBoolean("android_x86_prefs", true)
+                    .apply();
+        }
+    }
+
+    public static DisplayMetrics getRealDisplayMetrics(Context context) {
+        DisplayMetrics metrics = new DisplayMetrics();
+        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", false))
+            disp.getRealMetrics(metrics);
+        else
+            disp.getMetrics(metrics);
+
+        return metrics;
+    }
+
+    public static int getOverlayType() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+                ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+                : WindowManager.LayoutParams.TYPE_PHONE;
     }
 }