1 /* Copyright 2016 Braden Farmer
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
16 package com.farmerbb.taskbar.util;
18 import android.Manifest;
19 import android.accessibilityservice.AccessibilityService;
20 import android.annotation.SuppressLint;
21 import android.annotation.TargetApi;
22 import android.app.ActivityManager;
23 import android.app.ActivityOptions;
24 import android.app.AlertDialog;
25 import android.app.AppOpsManager;
26 import android.app.Service;
27 import android.content.ActivityNotFoundException;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.SharedPreferences;
32 import android.content.pm.ActivityInfo;
33 import android.content.pm.ApplicationInfo;
34 import android.content.pm.LauncherActivityInfo;
35 import android.content.pm.LauncherApps;
36 import android.content.pm.PackageInfo;
37 import android.content.pm.PackageManager;
38 import android.content.pm.ResolveInfo;
39 import android.content.pm.ShortcutInfo;
40 import android.content.pm.ShortcutManager;
41 import android.content.pm.Signature;
42 import android.content.res.Configuration;
43 import android.graphics.Color;
44 import android.graphics.Rect;
45 import android.hardware.display.DisplayManager;
46 import android.net.Uri;
47 import android.os.Build;
48 import android.os.Bundle;
49 import android.os.Handler;
50 import android.os.Process;
51 import android.os.UserHandle;
52 import android.os.UserManager;
53 import android.provider.Settings;
54 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
55 import androidx.appcompat.view.ContextThemeWrapper;
56 import android.util.DisplayMetrics;
57 import android.view.Display;
58 import android.view.Surface;
59 import android.view.View;
60 import android.view.WindowManager;
61 import android.widget.Toast;
63 import com.farmerbb.taskbar.BuildConfig;
64 import com.farmerbb.taskbar.R;
65 import com.farmerbb.taskbar.activity.ContextMenuActivity;
66 import com.farmerbb.taskbar.activity.DummyActivity;
67 import com.farmerbb.taskbar.activity.InvisibleActivityFreeform;
68 import com.farmerbb.taskbar.activity.TouchAbsorberActivity;
69 import com.farmerbb.taskbar.activity.dark.ContextMenuActivityDark;
70 import com.farmerbb.taskbar.service.DashboardService;
71 import com.farmerbb.taskbar.service.NotificationService;
72 import com.farmerbb.taskbar.service.PowerMenuService;
73 import com.farmerbb.taskbar.service.StartMenuService;
74 import com.farmerbb.taskbar.service.TaskbarService;
76 import java.io.BufferedInputStream;
77 import java.io.BufferedOutputStream;
79 import java.io.FileOutputStream;
80 import java.io.IOException;
81 import java.lang.reflect.Constructor;
82 import java.lang.reflect.Method;
83 import java.util.ArrayList;
84 import java.util.Arrays;
85 import java.util.List;
91 private static Integer cachedRotation;
93 private static final int MAXIMIZED = 0;
94 private static final int LEFT = -1;
95 private static final int RIGHT = 1;
97 public static final int HIDDEN = 0;
98 public static final int TOP_APPS = 1;
100 // From android.app.ActivityManager.StackId
101 private static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
102 private static final int FREEFORM_WORKSPACE_STACK_ID = 2;
104 // From android.app.WindowConfiguration
105 private static final int WINDOWING_MODE_FULLSCREEN = 1;
106 private static final int WINDOWING_MODE_FREEFORM = 5;
108 @SuppressWarnings("deprecation")
109 public static SharedPreferences getSharedPreferences(Context context) {
110 return context.getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", Context.MODE_PRIVATE);
113 public static void showPermissionDialog(Context context) {
114 showPermissionDialog(context, null, null);
117 @TargetApi(Build.VERSION_CODES.M)
118 public static AlertDialog showPermissionDialog(Context context, Runnable onError, Runnable onFinish) {
119 Runnable finalOnFinish = onFinish == null
123 Runnable finalOnError = onError == null
124 ? () -> showErrorDialog(context, "SYSTEM_ALERT_WINDOW", finalOnFinish)
127 AlertDialog.Builder builder = new AlertDialog.Builder(context);
128 builder.setTitle(R.string.tb_permission_dialog_title)
129 .setMessage(R.string.tb_permission_dialog_message)
130 .setPositiveButton(R.string.tb_action_grant_permission, (dialog, which) -> {
132 context.startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
133 Uri.parse("package:" + context.getPackageName())));
136 } catch (ActivityNotFoundException e) {
141 AlertDialog dialog = builder.create();
143 dialog.setCancelable(false);
148 public static AlertDialog showErrorDialog(Context context, String appopCmd) {
149 return showErrorDialog(context, appopCmd, null);
152 private static AlertDialog showErrorDialog(Context context, String appopCmd, Runnable onFinish) {
153 Runnable finalOnFinish = onFinish == null
157 AlertDialog.Builder builder = new AlertDialog.Builder(context);
158 builder.setTitle(R.string.tb_error_dialog_title)
159 .setMessage(context.getString(R.string.tb_error_dialog_message, context.getPackageName(), appopCmd))
160 .setPositiveButton(R.string.tb_action_ok, (dialog, which) -> finalOnFinish.run());
162 AlertDialog dialog = builder.create();
164 dialog.setCancelable(false);
169 @TargetApi(Build.VERSION_CODES.P)
170 public static void lockDevice(Context context) {
171 sendAccessibilityAction(context, AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN);
174 public static void sendAccessibilityAction(Context context, int action) {
175 sendAccessibilityAction(context, action, null);
178 public static void sendAccessibilityAction(Context context, int action, Runnable onComplete) {
179 ComponentName component = new ComponentName(context, PowerMenuService.class);
180 context.getPackageManager().setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
181 PackageManager.DONT_KILL_APP);
183 boolean isAccessibilityServiceEnabled = isAccessibilityServiceEnabled(context);
185 if(!isAccessibilityServiceEnabled
186 && hasWriteSecureSettingsPermission(context)) {
187 String services = Settings.Secure.getString(context.getContentResolver(),
188 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
190 String finalServices = services == null ? "" : services;
192 String powerMenuService = new ComponentName(context, PowerMenuService.class).flattenToString();
194 if(!finalServices.contains(powerMenuService)) {
196 Settings.Secure.putString(context.getContentResolver(),
197 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
198 finalServices.isEmpty()
200 : finalServices + ":" + powerMenuService);
201 } catch (Exception e) { /* Gracefully fail */ }
204 new Handler().postDelayed(() -> {
205 Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
206 intent.putExtra("action", action);
207 LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
210 Settings.Secure.putString(context.getContentResolver(),
211 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
213 } catch (Exception e) { /* Gracefully fail */ }
215 if(onComplete != null) onComplete.run();
217 } else if(isAccessibilityServiceEnabled) {
218 Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
219 intent.putExtra("action", action);
220 LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
222 if(onComplete != null) onComplete.run();
224 launchApp(context, () -> {
225 Intent intent = new Intent(context, DummyActivity.class);
226 intent.putExtra("accessibility", true);
227 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
230 context.startActivity(intent, getActivityOptionsBundle(context, ApplicationType.APP_PORTRAIT, null));
231 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
236 public static boolean isAccessibilityServiceEnabled(Context context) {
237 String accessibilityServices = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
238 ComponentName component = new ComponentName(context, PowerMenuService.class);
240 return accessibilityServices != null
241 && (accessibilityServices.contains(component.flattenToString())
242 || accessibilityServices.contains(component.flattenToShortString()));
245 public static boolean hasWriteSecureSettingsPermission(Context context) {
246 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
247 && context.checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) == PackageManager.PERMISSION_GRANTED;
250 public static void showToast(Context context, int message) {
251 showToast(context, context.getString(message), Toast.LENGTH_SHORT);
254 public static void showToastLong(Context context, int message) {
255 showToast(context, context.getString(message), Toast.LENGTH_LONG);
258 public static void showToast(Context context, String message, int length) {
261 ToastInterface toast = DependencyUtils.createToast(context.getApplicationContext(), message, length);
264 ToastHelper.getInstance().setLastToast(toast);
267 public static void cancelToast() {
268 ToastInterface toast = ToastHelper.getInstance().getLastToast();
269 if(toast != null) toast.cancel();
272 public static void startShortcut(Context context, AppEntry entry, ShortcutInfo shortcut, View view) {
282 public static void launchApp(final Context context,
283 final AppEntry entry,
284 final String windowSize,
285 final boolean launchedFromTaskbar,
286 final boolean openInNewWindow,
297 private static void launchApp(final Context context,
298 final AppEntry entry,
299 final String windowSize,
300 final boolean launchedFromTaskbar,
301 final boolean openInNewWindow,
302 final ShortcutInfo shortcut,
304 launchApp(context, launchedFromTaskbar, () -> continueLaunchingApp(context, entry,
305 windowSize, openInNewWindow, shortcut, view));
308 public static void launchApp(Context context, Runnable runnable) {
309 launchApp(context, true, runnable);
312 private static void launchApp(Context context, boolean launchedFromTaskbar, Runnable runnable) {
313 SharedPreferences pref = getSharedPreferences(context);
314 FreeformHackHelper helper = FreeformHackHelper.getInstance();
316 boolean specialLaunch = hasBrokenSetLaunchBoundsApi()
317 && helper.isInFreeformWorkspace()
318 && MenuHelper.getInstance().isContextMenuOpen();
320 boolean noAnimation = pref.getBoolean("disable_animations", false);
322 if(hasFreeformSupport(context)
323 && pref.getBoolean("freeform_hack", false)
324 && (!helper.isInFreeformWorkspace() || specialLaunch)) {
325 new Handler().postDelayed(() -> {
326 startFreeformHack(context, true);
328 new Handler().postDelayed(runnable, helper.isFreeformHackActive() ? 0 : 100);
329 }, launchedFromTaskbar ? 0 : 100);
331 new Handler().postDelayed(runnable, !launchedFromTaskbar && noAnimation ? 100 : 0);
334 public static void startFreeformHack(Context context) {
335 startFreeformHack(context, false);
338 @TargetApi(Build.VERSION_CODES.N)
339 public static void startFreeformHack(Context context, boolean checkMultiWindow) {
340 Intent freeformHackIntent = new Intent(context, InvisibleActivityFreeform.class);
341 freeformHackIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
342 | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
343 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
346 freeformHackIntent.putExtra("check_multiwindow", true);
348 if(canDrawOverlays(context))
349 startActivityLowerRight(context, freeformHackIntent);
352 public static void stopFreeformHack(Context context) {
353 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.FINISH_FREEFORM_ACTIVITY"));
355 if(isOverridingFreeformHack(context, false)) {
356 FreeformHackHelper helper = FreeformHackHelper.getInstance();
357 helper.setFreeformHackActive(false);
358 helper.setInFreeformWorkspace(false);
362 @TargetApi(Build.VERSION_CODES.N)
363 private static void continueLaunchingApp(Context context,
366 boolean openInNewWindow,
367 ShortcutInfo shortcut,
369 SharedPreferences pref = getSharedPreferences(context);
370 Intent intent = new Intent();
371 intent.setComponent(ComponentName.unflattenFromString(entry.getComponentName()));
372 intent.setAction(Intent.ACTION_MAIN);
373 intent.addCategory(Intent.CATEGORY_LAUNCHER);
374 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
375 intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
377 if(FreeformHackHelper.getInstance().isInFreeformWorkspace()
378 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1)
379 intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
381 if(pref.getBoolean("disable_animations", false))
382 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
384 boolean realOpenInNewWindow = openInNewWindow || pref.getBoolean("force_new_window", false);
385 if(realOpenInNewWindow) {
386 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
388 ActivityInfo activityInfo = intent.resolveActivityInfo(context.getPackageManager(), 0);
389 if(activityInfo != null) {
390 switch(activityInfo.launchMode) {
391 case ActivityInfo.LAUNCH_SINGLE_TASK:
392 case ActivityInfo.LAUNCH_SINGLE_INSTANCE:
393 intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
399 ApplicationType type = getApplicationType(context, entry);
401 if(windowSize == null)
402 windowSize = SavedWindowSizes.getInstance(context).getWindowSize(context, entry.getPackageName());
404 Bundle bundle = getActivityOptionsBundle(context, type, windowSize, view);
406 prepareToStartActivity(context, realOpenInNewWindow, () -> {
407 if(shortcut == null) {
408 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
409 if(entry.getUserId(context) == userManager.getSerialNumberForUser(Process.myUserHandle())) {
411 context.startActivity(intent, bundle);
412 } catch (ActivityNotFoundException e) {
413 launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context));
414 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
416 launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context));
418 launchShortcut(context, shortcut, bundle);
421 if(shouldCollapse(context, true))
422 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_TASKBAR"));
424 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_START_MENU"));
427 private static Bundle launchMode1(Context context, ApplicationType type, View view) {
428 DisplayInfo display = getDisplayInfo(context);
430 int width1 = display.width / 8;
431 int width2 = display.width - width1;
432 int height1 = display.height / 8;
433 int height2 = display.height - height1;
435 return getActivityOptionsBundle(context, type, view,
443 private static Bundle launchMode2(Context context, int launchType, ApplicationType type, View view) {
444 DisplayInfo display = getDisplayInfo(context);
446 int statusBarHeight = getStatusBarHeight(context);
447 String position = getTaskbarPosition(context);
449 boolean isPortrait = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
450 boolean isLandscape = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
453 int top = statusBarHeight;
454 int right = display.width;
455 int bottom = display.height;
457 int iconSize = isOverridingFreeformHack(context) && !LauncherHelper.getInstance().isOnHomeScreen()
459 : context.getResources().getDimensionPixelSize(R.dimen.tb_icon_size);
461 if(position.contains("vertical_left"))
462 left = left + iconSize;
463 else if(position.contains("vertical_right"))
464 right = right - iconSize;
465 else if(position.contains("bottom"))
466 bottom = bottom - iconSize;
468 top = top + iconSize;
470 int halfLandscape = (right / 2) + ((iconSize / 2) * (position.contains("vertical_left") ? 1 : 0));
471 int halfPortrait = (bottom / 2) + ((iconSize / 2) * ((position.equals("top_left") || position.equals("top_right")) ? 1 : 0));
473 if(launchType == RIGHT && isLandscape)
474 left = halfLandscape;
475 else if(launchType == RIGHT && isPortrait)
477 else if(launchType == LEFT && isLandscape)
478 right = halfLandscape;
479 else if(launchType == LEFT && isPortrait)
480 bottom = halfPortrait;
482 return getActivityOptionsBundle(context, type, view, left, top, right, bottom);
485 private static Bundle launchMode3(Context context, ApplicationType type, View view) {
486 DisplayInfo display = getDisplayInfo(context);
488 boolean isLandscape = type == ApplicationType.APP_LANDSCAPE;
489 int widthDimen = isLandscape ? R.dimen.tb_phone_size_height : R.dimen.tb_phone_size_width;
490 int heightDimen = isLandscape ? R.dimen.tb_phone_size_width : R.dimen.tb_phone_size_height;
492 int width1 = display.width / 2;
493 int width2 = context.getResources().getDimensionPixelSize(widthDimen) / 2;
494 int height1 = display.height / 2;
495 int height2 = context.getResources().getDimensionPixelSize(heightDimen) / 2;
497 return getActivityOptionsBundle(context, type, view,
505 private static void launchAndroidForWork(Context context, ComponentName componentName, Bundle bundle, long userId) {
506 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
507 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
510 launcherApps.startMainActivity(componentName, userManager.getUserForSerialNumber(userId), null, bundle);
511 } catch (ActivityNotFoundException | NullPointerException
512 | IllegalStateException | SecurityException e) { /* Gracefully fail */ }
515 @TargetApi(Build.VERSION_CODES.N_MR1)
516 private static void launchShortcut(Context context, ShortcutInfo shortcut, Bundle bundle) {
517 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
519 if(launcherApps.hasShortcutHostPermission()) {
521 launcherApps.startShortcut(shortcut, null, bundle);
522 } catch (ActivityNotFoundException | NullPointerException
523 | IllegalStateException | SecurityException e) { /* Gracefully fail */ }
527 private static void prepareToStartActivity(Context context, boolean openInNewWindow, Runnable runnable) {
528 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_CONTEXT_MENU"));
530 if(!FreeformHackHelper.getInstance().isTouchAbsorberActive()
531 && shouldLaunchTouchAbsorber(context)) {
532 startTouchAbsorberActivity(context);
533 new Handler().postDelayed(runnable, 100);
534 } else if(openInNewWindow) {
535 Intent intent = new Intent(context, DummyActivity.class);
536 intent.putExtra("finish_on_pause", true);
537 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
538 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
539 startActivityLowerRight(context, intent);
541 new Handler().postDelayed(runnable, 100);
546 public static void startActivityMaximized(Context context, Intent intent) {
547 Bundle bundle = launchMode2(context, MAXIMIZED, ApplicationType.CONTEXT_MENU, null);
548 prepareToStartActivity(context, false, () -> context.startActivity(intent, bundle));
551 public static void startActivityLowerRight(Context context, Intent intent) {
552 DisplayInfo display = getDisplayInfo(context);
554 context.startActivity(intent,
555 getActivityOptionsBundle(context, ApplicationType.FREEFORM_HACK, null,
561 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
564 public static void startTouchAbsorberActivity(Context context) {
565 String position = getTaskbarPosition(context);
566 DisplayInfo display = getDisplayInfo(context);
570 int right = display.width;
571 int bottom = display.height;
573 int iconSize = context.getResources().getDimensionPixelSize(R.dimen.tb_icon_size);
575 if(position.contains("vertical_left"))
577 else if(position.contains("vertical_right"))
578 left = right - iconSize;
579 else if(position.contains("bottom"))
580 top = bottom - iconSize;
584 Intent intent = new Intent(context, TouchAbsorberActivity.class);
585 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
586 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
589 context.startActivity(intent,
590 getActivityOptionsBundle(context, ApplicationType.FREEFORM_HACK, null,
591 left, top, right, bottom));
592 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
595 public static void startContextMenuActivity(Context context, Bundle args) {
596 SharedPreferences pref = getSharedPreferences(context);
597 Intent intent = null;
599 switch(pref.getString("theme", "light")) {
601 intent = new Intent(context, ContextMenuActivity.class);
604 intent = new Intent(context, ContextMenuActivityDark.class);
609 intent.putExtra("args", args);
610 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
613 if(hasFreeformSupport(context) && FreeformHackHelper.getInstance().isInFreeformWorkspace()) {
614 DisplayInfo display = getDisplayInfo(context);
616 if(intent != null && hasBrokenSetLaunchBoundsApi())
617 intent.putExtra("context_menu_fix", true);
619 context.startActivity(intent,
620 getActivityOptionsBundle(context, ApplicationType.CONTEXT_MENU, null,
621 0, 0, display.width, display.height));
623 context.startActivity(intent);
626 public static void checkForUpdates(Context context) {
628 if(isPlayStoreRelease(context)) {
629 if(context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
630 && !isPlayStoreInstalled(context))
631 url = "https://github.com/farmerbb/Taskbar/releases";
633 url = "https://play.google.com/store/apps/details?id=" + context.getPackageName();
635 url = "https://f-droid.org/repository/browse/?fdid=" + context.getPackageName();
637 Intent intent = new Intent(Intent.ACTION_VIEW);
638 intent.setData(Uri.parse(url));
639 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
642 context.startActivity(intent);
643 } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
646 public static boolean launcherIsDefault(Context context) {
647 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
648 homeIntent.addCategory(Intent.CATEGORY_HOME);
649 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
651 return defaultLauncher.activityInfo.packageName.equals(context.getPackageName());
654 public static void setCachedRotation(int cachedRotation) {
655 U.cachedRotation = cachedRotation;
658 public static String getTaskbarPosition(Context context) {
659 SharedPreferences pref = getSharedPreferences(context);
660 String position = pref.getString("position", "bottom_left");
662 if(pref.getBoolean("anchor", false)) {
663 WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
664 int rotation = cachedRotation != null ? cachedRotation : windowManager.getDefaultDisplay().getRotation();
669 case Surface.ROTATION_0:
670 return "bottom_left";
671 case Surface.ROTATION_90:
672 return "bottom_vertical_right";
673 case Surface.ROTATION_180:
675 case Surface.ROTATION_270:
676 return "top_vertical_left";
679 case "bottom_vertical_left":
681 case Surface.ROTATION_0:
682 return "bottom_vertical_left";
683 case Surface.ROTATION_90:
684 return "bottom_right";
685 case Surface.ROTATION_180:
686 return "top_vertical_right";
687 case Surface.ROTATION_270:
693 case Surface.ROTATION_0:
694 return "bottom_right";
695 case Surface.ROTATION_90:
696 return "top_vertical_right";
697 case Surface.ROTATION_180:
699 case Surface.ROTATION_270:
700 return "bottom_vertical_left";
703 case "bottom_vertical_right":
705 case Surface.ROTATION_0:
706 return "bottom_vertical_right";
707 case Surface.ROTATION_90:
709 case Surface.ROTATION_180:
710 return "top_vertical_left";
711 case Surface.ROTATION_270:
712 return "bottom_left";
717 case Surface.ROTATION_0:
719 case Surface.ROTATION_90:
720 return "bottom_vertical_left";
721 case Surface.ROTATION_180:
722 return "bottom_right";
723 case Surface.ROTATION_270:
724 return "top_vertical_right";
727 case "top_vertical_left":
729 case Surface.ROTATION_0:
730 return "top_vertical_left";
731 case Surface.ROTATION_90:
732 return "bottom_left";
733 case Surface.ROTATION_180:
734 return "bottom_vertical_right";
735 case Surface.ROTATION_270:
741 case Surface.ROTATION_0:
743 case Surface.ROTATION_90:
744 return "top_vertical_left";
745 case Surface.ROTATION_180:
746 return "bottom_left";
747 case Surface.ROTATION_270:
748 return "bottom_vertical_right";
751 case "top_vertical_right":
753 case Surface.ROTATION_0:
754 return "top_vertical_right";
755 case Surface.ROTATION_90:
757 case Surface.ROTATION_180:
758 return "bottom_vertical_left";
759 case Surface.ROTATION_270:
760 return "bottom_right";
769 private static int getMaxNumOfColumns(Context context) {
770 SharedPreferences pref = getSharedPreferences(context);
771 DisplayInfo display = getDisplayInfo(context);
772 float density = display.density / 160;
773 float baseTaskbarSize = getBaseTaskbarSizeFloat(context) / density;
774 int numOfColumns = 0;
776 float maxScreenSize = getTaskbarPosition(context).contains("vertical")
777 ? (display.height - getStatusBarHeight(context)) / density
778 : display.width / density;
780 float iconSize = context.getResources().getDimension(R.dimen.tb_icon_size) / density;
782 int userMaxNumOfColumns = Integer.valueOf(pref.getString("max_num_of_recents", "10"));
784 while(baseTaskbarSize + iconSize < maxScreenSize
785 && numOfColumns < userMaxNumOfColumns) {
786 baseTaskbarSize = baseTaskbarSize + iconSize;
793 public static int getMaxNumOfEntries(Context context) {
794 SharedPreferences pref = getSharedPreferences(context);
795 return pref.getBoolean("disable_scrolling_list", false)
796 ? getMaxNumOfColumns(context)
797 : Integer.valueOf(pref.getString("max_num_of_recents", "10"));
800 public static int getStatusBarHeight(Context context) {
801 return getSystemDimen(context, "status_bar_height");
804 private static int getNavbarHeight(Context context) {
805 return getSystemDimen(context, "navigation_bar_height");
808 private static int getSystemDimen(Context context, String id) {
810 int resourceId = context.getResources().getIdentifier(id, "dimen", "android");
812 value = context.getResources().getDimensionPixelSize(resourceId);
817 public static void refreshPinnedIcons(Context context) {
818 IconCache.getInstance(context).clearCache();
820 PinnedBlockedApps pba = PinnedBlockedApps.getInstance(context);
821 List<AppEntry> pinnedAppsList = new ArrayList<>(pba.getPinnedApps());
822 List<AppEntry> blockedAppsList = new ArrayList<>(pba.getBlockedApps());
823 PackageManager pm = context.getPackageManager();
827 for(AppEntry entry : pinnedAppsList) {
828 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
829 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
831 final List<UserHandle> userHandles = userManager.getUserProfiles();
832 LauncherActivityInfo appInfo = null;
834 for(UserHandle handle : userHandles) {
835 List<LauncherActivityInfo> list = launcherApps.getActivityList(entry.getPackageName(), handle);
836 if(!list.isEmpty()) {
837 // Google App workaround
838 if(!entry.getPackageName().equals("com.google.android.googlequicksearchbox"))
839 appInfo = list.get(0);
841 boolean added = false;
842 for(LauncherActivityInfo info : list) {
843 if(info.getName().equals("com.google.android.googlequicksearchbox.SearchActivity")) {
849 if(!added) appInfo = list.get(0);
856 if(appInfo != null) {
857 AppEntry newEntry = new AppEntry(
858 entry.getPackageName(),
859 entry.getComponentName(),
861 IconCache.getInstance(context).getIcon(context, pm, appInfo),
864 newEntry.setUserId(entry.getUserId(context));
865 pba.addPinnedApp(context, newEntry);
869 for(AppEntry entry : blockedAppsList) {
870 pba.addBlockedApp(context, entry);
874 public static boolean canEnableFreeform() {
875 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
878 @TargetApi(Build.VERSION_CODES.N)
879 public static boolean hasFreeformSupport(Context context) {
880 return canEnableFreeform()
881 && (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
882 || Settings.Global.getInt(context.getContentResolver(), "enable_freeform_support", 0) != 0
883 || (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
884 && Settings.Global.getInt(context.getContentResolver(), "force_resizable_activities", 0) != 0));
887 public static boolean canBootToFreeform(Context context) {
888 return hasFreeformSupport(context) && !isOverridingFreeformHack(context);
891 public static boolean isSamsungDevice() {
892 return Build.MANUFACTURER.equalsIgnoreCase("Samsung");
895 private static boolean isNvidiaDevice() {
896 return Build.MANUFACTURER.equalsIgnoreCase("NVIDIA");
899 public static boolean isServiceRunning(Context context, Class<? extends Service> cls) {
900 if(LauncherHelper.getInstance().isOnSecondaryHomeScreen()
901 && (cls.equals(TaskbarService.class)
902 || cls.equals(StartMenuService.class)
903 || cls.equals(DashboardService.class)))
906 return isServiceRunning(context, cls.getName());
909 private static boolean isServiceRunning(Context context, String className) {
910 ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
911 for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
912 if(className.equals(service.service.getClassName()))
919 public static int getBackgroundTint(Context context) {
920 SharedPreferences pref = getSharedPreferences(context);
922 // Import old background tint preference
923 if(pref.contains("show_background")) {
924 SharedPreferences.Editor editor = pref.edit();
926 if(!pref.getBoolean("show_background", true))
927 editor.putInt("background_tint", Color.TRANSPARENT).apply();
929 editor.remove("show_background");
933 return pref.getInt("background_tint", context.getResources().getInteger(R.integer.tb_translucent_gray));
936 public static int getAccentColor(Context context) {
937 SharedPreferences pref = getSharedPreferences(context);
938 return pref.getInt("accent_color", context.getResources().getInteger(R.integer.tb_translucent_white));
941 public static boolean canDrawOverlays(Context context) {
942 return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context);
945 public static boolean isGame(Context context, String packageName) {
946 SharedPreferences pref = getSharedPreferences(context);
947 if(pref.getBoolean("launch_games_fullscreen", true)) {
948 PackageManager pm = context.getPackageManager();
951 ApplicationInfo info = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
952 return (info.flags & ApplicationInfo.FLAG_IS_GAME) != 0 || (info.metaData != null && info.metaData.getBoolean("isGame", false));
953 } catch (PackageManager.NameNotFoundException e) {
960 private static ActivityOptions getActivityOptions(View view) {
961 return getActivityOptions(null, null, view);
964 private static ActivityOptions getActivityOptions(Context context, ApplicationType applicationType, View view) {
965 ActivityOptions options;
967 options = ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight());
968 else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
969 options = ActivityOptions.makeBasic();
972 Constructor<ActivityOptions> constructor = ActivityOptions.class.getDeclaredConstructor();
973 constructor.setAccessible(true);
974 options = constructor.newInstance();
975 } catch (Exception e) {
980 if(applicationType == null)
985 switch(applicationType) {
988 if(FreeformHackHelper.getInstance().isFreeformHackActive())
989 stackId = getFreeformWindowModeId();
991 stackId = getFullscreenWindowModeId();
994 stackId = getFullscreenWindowModeId();
997 stackId = getFreeformWindowModeId();
1000 if(hasBrokenSetLaunchBoundsApi()
1001 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f))
1002 stackId = getFullscreenWindowModeId();
1007 Method method = ActivityOptions.class.getMethod(getWindowingModeMethodName(), int.class);
1008 method.invoke(options, stackId);
1009 } catch (Exception e) { /* Gracefully fail */ }
1011 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1012 int launchDisplayId = LauncherHelper.getInstance().getSecondaryDisplayId();
1013 if(launchDisplayId != -1)
1014 options.setLaunchDisplayId(launchDisplayId);
1020 private static int getFullscreenWindowModeId() {
1021 if(getCurrentApiVersion() >= 28.0f)
1022 return WINDOWING_MODE_FULLSCREEN;
1024 return FULLSCREEN_WORKSPACE_STACK_ID;
1027 private static int getFreeformWindowModeId() {
1028 if(getCurrentApiVersion() >= 28.0f)
1029 return WINDOWING_MODE_FREEFORM;
1031 return FREEFORM_WORKSPACE_STACK_ID;
1034 private static String getWindowingModeMethodName() {
1035 if(getCurrentApiVersion() >= 28.0f)
1036 return "setLaunchWindowingMode";
1038 return "setLaunchStackId";
1041 public static Bundle getActivityOptionsBundle(Context context, ApplicationType type, View view) {
1042 SharedPreferences pref = getSharedPreferences(context);
1044 return getActivityOptionsBundle(context, type, pref.getString("window_size", context.getString(R.string.tb_def_window_size)), view);
1047 private static Bundle getActivityOptionsBundle(Context context, ApplicationType type, String windowSize, View view) {
1048 SharedPreferences pref = getSharedPreferences(context);
1049 if(!canEnableFreeform() || !pref.getBoolean("freeform_hack", false))
1050 return getActivityOptions(view).toBundle();
1052 switch(windowSize) {
1054 return launchMode1(context, type, view);
1056 return launchMode2(context, MAXIMIZED, type, view);
1058 return launchMode2(context, LEFT, type, view);
1060 return launchMode2(context, RIGHT, type, view);
1062 return launchMode3(context, type, view);
1065 return getActivityOptions(context, type, view).toBundle();
1068 private static Bundle getActivityOptionsBundle(Context context, ApplicationType applicationType, View view,
1069 int left, int top, int right, int bottom) {
1070 ActivityOptions options = getActivityOptions(context, applicationType, view);
1074 if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
1075 return options.toBundle();
1077 return options.setLaunchBounds(new Rect(left, top, right, bottom)).toBundle();
1080 @SuppressLint("SwitchIntDef")
1081 private static ApplicationType getApplicationType(Context context, AppEntry entry) {
1082 if(isGame(context, entry.getPackageName()))
1083 return ApplicationType.GAME;
1086 ActivityInfo info = context.getPackageManager().getActivityInfo(
1087 ComponentName.unflattenFromString(entry.getComponentName()),
1091 switch(info.screenOrientation) {
1092 case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
1093 case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
1094 case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
1095 case ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE:
1096 return ApplicationType.APP_LANDSCAPE;
1098 } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
1100 return ApplicationType.APP_PORTRAIT;
1103 public static boolean isSystemApp(Context context) {
1105 ApplicationInfo info = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
1106 int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
1107 return (info.flags & mask) != 0;
1108 } catch (PackageManager.NameNotFoundException e) {
1113 public static boolean isChromeOs(Context context) {
1114 return context.getPackageManager().hasSystemFeature("org.chromium.arc");
1117 public static boolean isBlissOs(Context context) {
1118 boolean validBlissOsBuildProp = false;
1120 String blissVersion = getSystemProperty("ro.bliss.version");
1121 if(blissVersion != null && !blissVersion.isEmpty())
1122 validBlissOsBuildProp = true;
1124 String buildUser = getSystemProperty("ro.build.user");
1125 if(buildUser != null && buildUser.equals("electrikjesus"))
1126 validBlissOsBuildProp = true;
1128 return validBlissOsBuildProp
1129 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1130 && isSystemApp(context);
1133 public static boolean isLauncherPermanentlyEnabled(Context context) {
1134 if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
1137 PackageManager pm = context.getPackageManager();
1139 pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1140 return pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, context.getPackageName()) == PackageManager.SIGNATURE_MATCH
1141 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1142 && isSystemApp(context);
1143 } catch (PackageManager.NameNotFoundException e) {
1148 public static boolean hasSupportLibrary(Context context, int minVersion) {
1149 PackageManager pm = context.getPackageManager();
1151 PackageInfo pInfo = pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1152 return pInfo.versionCode >= minVersion
1153 && pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, context.getPackageName()) == PackageManager.SIGNATURE_MATCH
1154 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1155 && isSystemApp(context);
1156 } catch (PackageManager.NameNotFoundException e) {
1161 public static int getBaseTaskbarSize(Context context) {
1162 return Math.round(getBaseTaskbarSizeFloat(context));
1165 private static float getBaseTaskbarSizeFloat(Context context) {
1166 SharedPreferences pref = getSharedPreferences(context);
1167 float baseTaskbarSize = context.getResources().getDimension(R.dimen.tb_base_taskbar_size);
1168 boolean navbarButtonsEnabled = false;
1170 if(pref.getBoolean("dashboard", context.getResources().getBoolean(R.bool.tb_def_dashboard)))
1171 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_dashboard_button_size);
1173 if(pref.getBoolean("button_back", false)) {
1174 navbarButtonsEnabled = true;
1175 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1178 if(pref.getBoolean("button_home", false)) {
1179 navbarButtonsEnabled = true;
1180 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1183 if(pref.getBoolean("button_recents", false)) {
1184 navbarButtonsEnabled = true;
1185 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1188 if(navbarButtonsEnabled)
1189 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_navbar_buttons_margin);
1191 if(isSystemTrayEnabled(context))
1192 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_systray_size);
1194 return baseTaskbarSize;
1197 private static void startTaskbarService(Context context, boolean fullRestart) {
1198 context.startService(new Intent(context, TaskbarService.class));
1199 context.startService(new Intent(context, StartMenuService.class));
1200 context.startService(new Intent(context, DashboardService.class));
1201 if(fullRestart) context.startService(new Intent(context, NotificationService.class));
1204 private static void stopTaskbarService(Context context, boolean fullRestart) {
1205 context.stopService(new Intent(context, TaskbarService.class));
1206 context.stopService(new Intent(context, StartMenuService.class));
1207 context.stopService(new Intent(context, DashboardService.class));
1208 if(fullRestart) context.stopService(new Intent(context, NotificationService.class));
1211 public static void restartTaskbar(Context context) {
1212 SharedPreferences pref = getSharedPreferences(context);
1213 if(pref.getBoolean("taskbar_active", false) && !pref.getBoolean("is_hidden", false)) {
1215 .putBoolean("is_restarting", true)
1216 .putBoolean("skip_auto_hide_navbar", true)
1219 stopTaskbarService(context, true);
1220 startTaskbarService(context, true);
1221 } else if(isServiceRunning(context, StartMenuService.class)) {
1222 pref.edit().putBoolean("skip_auto_hide_navbar", true).apply();
1224 stopTaskbarService(context, false);
1225 startTaskbarService(context, false);
1228 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.RESTART"));
1231 public static void restartNotificationService(Context context) {
1232 if(isServiceRunning(context, NotificationService.class)) {
1233 SharedPreferences pref = getSharedPreferences(context);
1234 pref.edit().putBoolean("is_restarting", true).apply();
1236 Intent intent = new Intent(context, NotificationService.class);
1237 context.stopService(intent);
1238 context.startService(intent);
1242 public static void showHideNavigationBar(Context context, boolean show) {
1243 if(hasSupportLibrary(context, 7)) {
1244 Intent intent = new Intent(BuildConfig.SUPPORT_APPLICATION_ID + ".CHANGE_OVERSCAN");
1245 intent.setPackage(BuildConfig.SUPPORT_APPLICATION_ID);
1247 intent.putExtra("display_id", getDisplayID());
1248 intent.putExtra("value", show ? 0 : getNavbarHeight(context) * -1);
1250 context.sendBroadcast(intent);
1254 // Show or hide the system navigation bar on Bliss-x86
1256 if(getCurrentApiVersion() >= 28.0f)
1257 Settings.Secure.putInt(context.getContentResolver(), "navigation_bar_visible", show ? 1 : 0);
1259 Settings.System.putInt(context.getContentResolver(), "navigation_bar_show", show ? 1 : 0);
1260 } catch (Exception e) { /* Gracefully fail */ }
1263 public static void initPrefs(Context context) {
1264 // Enable freeform hack automatically on supported devices
1265 SharedPreferences pref = getSharedPreferences(context);
1266 if(canEnableFreeform()) {
1267 if(!pref.getBoolean("freeform_hack_override", false)) {
1269 .putBoolean("freeform_hack", hasFreeformSupport(context) && !isSamsungDevice())
1270 .putBoolean("save_window_sizes", false)
1271 .putBoolean("freeform_hack_override", true)
1273 } else if(!hasFreeformSupport(context)) {
1274 pref.edit().putBoolean("freeform_hack", false).apply();
1276 stopFreeformHack(context);
1279 boolean freeformWasEnabled = pref.getBoolean("freeform_hack", false)
1280 || pref.getBoolean("show_freeform_disabled_message", false);
1283 .putBoolean("freeform_hack", false)
1284 .putBoolean("show_freeform_disabled_message", freeformWasEnabled)
1287 SavedWindowSizes.getInstance(context).clear(context);
1288 stopFreeformHack(context);
1291 // Customizations for BlissOS
1292 if(isBlissOs(context) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1293 && !pref.getBoolean("bliss_os_prefs", false)) {
1294 SharedPreferences.Editor editor = pref.edit();
1296 if(hasFreeformSupport(context)) {
1297 editor.putBoolean("freeform_hack", true);
1300 editor.putString("recents_amount", "running_apps_only");
1301 editor.putString("refresh_frequency", "0");
1302 editor.putString("max_num_of_recents", "2147483647");
1303 editor.putString("sort_order", "true");
1304 editor.putString("start_button_image", "app_logo");
1305 editor.putBoolean("button_back", true);
1306 editor.putBoolean("button_home", true);
1307 editor.putBoolean("button_recents", true);
1308 editor.putBoolean("auto_hide_navbar", true);
1309 editor.putBoolean("shortcut_icon", false);
1310 editor.putBoolean("bliss_os_prefs", true);
1314 // Customizations for Android-x86 devices (non-Bliss)
1315 if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
1316 && isSystemApp(context)
1317 && !pref.getBoolean("android_x86_prefs", false)) {
1319 .putString("recents_amount", "running_apps_only")
1320 .putString("refresh_frequency", "0")
1321 .putString("max_num_of_recents", "2147483647")
1322 .putString("sort_order", "true")
1323 .putBoolean("shortcut_icon", false)
1324 .putBoolean("android_x86_prefs", true)
1329 public static DisplayInfo getDisplayInfo(Context context) {
1330 return getDisplayInfo(context, false);
1333 public static DisplayInfo getDisplayInfo(Context context, boolean fromTaskbar) {
1334 context = context.getApplicationContext();
1335 int displayID = getDisplayID();
1337 DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1338 Display currentDisplay = null;
1340 for(Display display : dm.getDisplays()) {
1341 if(display.getDisplayId() == displayID) {
1342 currentDisplay = display;
1347 if(currentDisplay == null)
1348 return new DisplayInfo(0, 0, 0);
1350 DisplayMetrics metrics = new DisplayMetrics();
1351 currentDisplay.getMetrics(metrics);
1353 DisplayMetrics realMetrics = new DisplayMetrics();
1354 currentDisplay.getRealMetrics(realMetrics);
1356 DisplayInfo info = new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);
1358 if(isChromeOs(context)) {
1359 SharedPreferences pref = getSharedPreferences(context);
1360 if(!pref.getBoolean("chrome_os_context_menu_fix", true)) {
1361 info.width = realMetrics.widthPixels;
1362 info.height = realMetrics.heightPixels;
1368 // Workaround for incorrect display size on devices with notches in landscape mode
1369 if(fromTaskbar && context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)
1372 boolean sameWidth = metrics.widthPixels == realMetrics.widthPixels;
1373 boolean sameHeight = metrics.heightPixels == realMetrics.heightPixels;
1375 if(sameWidth && !sameHeight) {
1376 info.width = realMetrics.widthPixels;
1377 info.height = realMetrics.heightPixels - getNavbarHeight(context);
1380 if(!sameWidth && sameHeight) {
1381 info.width = realMetrics.widthPixels - getNavbarHeight(context);
1382 info.height = realMetrics.heightPixels;
1388 private static int getDisplayID() {
1389 LauncherHelper helper = LauncherHelper.getInstance();
1391 if(helper.isOnSecondaryHomeScreen())
1392 return helper.getSecondaryDisplayId();
1394 return Display.DEFAULT_DISPLAY;
1397 public static void pinAppShortcut(Context context) {
1398 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1399 ShortcutManager mShortcutManager = context.getSystemService(ShortcutManager.class);
1401 if(mShortcutManager.isRequestPinShortcutSupported()) {
1402 ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder(context, "freeform_mode").build();
1404 mShortcutManager.requestPinShortcut(pinShortcutInfo, null);
1406 showToastLong(context, R.string.tb_pin_shortcut_not_supported);
1408 Intent intent = ShortcutUtils.getShortcutIntent(context);
1409 intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
1410 intent.putExtra("duplicate", false);
1412 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1413 homeIntent.addCategory(Intent.CATEGORY_HOME);
1414 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
1416 intent.setPackage(defaultLauncher.activityInfo.packageName);
1417 context.sendBroadcast(intent);
1419 showToast(context, R.string.tb_shortcut_created);
1423 public static boolean shouldCollapse(Context context, boolean pendingAppLaunch) {
1424 SharedPreferences pref = getSharedPreferences(context);
1425 if(pref.getBoolean("hide_taskbar", true)) {
1426 if(!pref.getBoolean("freeform_hack", false))
1428 else if(isOverridingFreeformHack(context, false))
1429 return !LauncherHelper.getInstance().isOnHomeScreen();
1431 FreeformHackHelper helper = FreeformHackHelper.getInstance();
1432 if(pendingAppLaunch)
1433 return !helper.isFreeformHackActive();
1435 return !helper.isInFreeformWorkspace();
1441 public static boolean isOverridingFreeformHack(Context context) {
1442 return isOverridingFreeformHack(context, true);
1445 public static boolean isOverridingFreeformHack(Context context, boolean checkPref) {
1446 SharedPreferences pref = getSharedPreferences(context);
1447 return (!checkPref || pref.getBoolean("freeform_hack", false))
1448 && ((isChromeOs(context) && pref.getBoolean("chrome_os_context_menu_fix", true))
1449 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f));
1452 public static boolean isPlayStoreInstalled(Context context) {
1454 context.getPackageManager().getPackageInfo("com.android.vending", 0);
1456 } catch (PackageManager.NameNotFoundException e) {
1461 private static float getCurrentApiVersion() {
1462 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1463 return Float.valueOf(Build.VERSION.SDK_INT + "." + Build.VERSION.PREVIEW_SDK_INT);
1465 return (float) Build.VERSION.SDK_INT;
1468 public static boolean hasBrokenSetLaunchBoundsApi() {
1469 return getCurrentApiVersion() >= 26.0f
1470 && getCurrentApiVersion() < 28.0f
1471 && !isSamsungDevice()
1472 && !isNvidiaDevice();
1475 public static String getSecondScreenPackageName(Context context) {
1476 return getInstalledPackage(context, Arrays.asList(
1477 "com.farmerbb.secondscreen.free",
1478 "com.farmerbb.secondscreen"));
1481 // Returns the name of an installed package from a list of package names, in order of preference
1482 private static String getInstalledPackage(Context context, List<String> packageNames) {
1483 if(packageNames == null || packageNames.isEmpty())
1486 List<String> packages = packageNames instanceof ArrayList ? packageNames : new ArrayList<>(packageNames);
1487 String packageName = packages.get(0);
1490 context.getPackageManager().getPackageInfo(packageName, 0);
1492 } catch (PackageManager.NameNotFoundException e) {
1494 return getInstalledPackage(context, packages);
1498 public static void showRecentAppsDialog(Context context) {
1499 showRecentAppsDialog(context, null, null);
1502 public static AlertDialog showRecentAppsDialog(Context context, Runnable onError, Runnable onFinish) {
1503 Runnable finalOnFinish = onFinish == null
1507 Runnable finalOnError = onError == null
1508 ? () -> showErrorDialog(context, "GET_USAGE_STATS", finalOnFinish)
1511 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isSystemApp(context)) {
1512 ApplicationInfo applicationInfo = null;
1514 applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
1515 } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
1517 if(applicationInfo != null) {
1518 AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
1519 int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
1521 if(mode != AppOpsManager.MODE_ALLOWED) {
1522 AlertDialog.Builder builder = new AlertDialog.Builder(context);
1523 builder.setTitle(R.string.tb_pref_header_recent_apps)
1524 .setMessage(R.string.tb_enable_recent_apps)
1525 .setPositiveButton(R.string.tb_action_ok, (dialog, which) -> {
1527 context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
1528 showToastLong(context, R.string.tb_usage_stats_message);
1530 finalOnFinish.run();
1531 } catch (ActivityNotFoundException e) {
1535 .setNegativeButton(R.string.tb_action_cancel, (dialog, which) -> finalOnFinish.run());
1537 AlertDialog dialog = builder.create();
1539 dialog.setCancelable(false);
1546 finalOnFinish.run();
1550 public static Context wrapContext(Context context) {
1551 SharedPreferences pref = getSharedPreferences(context);
1554 switch(pref.getString("theme", "light")) {
1556 theme = R.style.Taskbar;
1559 theme = R.style.Taskbar_Dark;
1563 return theme > -1 ? new ContextThemeWrapper(context, theme) : context;
1566 public static boolean isPlayStoreRelease(Context context) {
1567 return isPlayStoreRelease(context, context.getPackageName());
1570 @SuppressLint("PackageManagerGetSignatures")
1571 public static boolean isPlayStoreRelease(Context context, String packageName) {
1572 Signature playStoreSignature = new Signature(context.getString(R.string.tb_signature));
1574 PackageManager pm = context.getPackageManager();
1575 PackageInfo info = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
1576 for(Signature signature : info.signatures) {
1577 if(signature.equals(playStoreSignature))
1580 } catch (Exception e) { /* Gracefully fail */ }
1585 public static boolean isExternalAccessDisabled(Context context) {
1586 SharedPreferences pref = getSharedPreferences(context);
1587 return !pref.getBoolean("tasker_enabled", true);
1590 public static boolean enableFreeformModeShortcut(Context context) {
1591 return canEnableFreeform()
1592 && !isOverridingFreeformHack(context)
1593 && !isChromeOs(context);
1596 public static void startForegroundService(Context context, Intent intent) {
1597 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1598 if(Settings.canDrawOverlays(context))
1599 context.startForegroundService(intent);
1601 context.startService(intent);
1604 public static int getOverlayType() {
1605 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
1606 ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
1607 : WindowManager.LayoutParams.TYPE_PHONE;
1610 public static boolean isDelegatingHomeActivity(Context context) {
1611 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1612 homeIntent.addCategory(Intent.CATEGORY_HOME);
1614 final List<ResolveInfo> listOfLaunchers = context.getPackageManager().queryIntentActivities(homeIntent, 0);
1615 for(ResolveInfo launcher : listOfLaunchers) {
1616 if(launcher.activityInfo.packageName.equals(BuildConfig.SUPPORT_APPLICATION_ID))
1623 @SuppressLint("PrivateApi")
1624 private static String getSystemProperty(String key) {
1626 Class<?> cls = Class.forName("android.os.SystemProperties");
1627 return cls.getMethod("get", String.class).invoke(null, key).toString();
1628 } catch (Exception e) {
1633 @SuppressWarnings("ResultOfMethodCallIgnored")
1634 public static boolean importCustomStartButtonImage(Context context, Uri uri) {
1636 File imagesDir = new File(context.getFilesDir(), "tb_images");
1639 File importedFile = new File(imagesDir, "custom_image_new");
1640 if(importedFile.exists()) importedFile.delete();
1642 BufferedInputStream is = new BufferedInputStream(context.getContentResolver().openInputStream(uri));
1643 byte[] data = new byte[is.available()];
1645 if(data.length > 0) {
1646 BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(importedFile));
1653 File prevFile = new File(imagesDir, "custom_image");
1654 if(prevFile.exists()) prevFile.delete();
1656 importedFile.renameTo(prevFile);
1658 } catch (IOException e) {
1663 public static String getDefaultStartButtonImage(Context context) {
1664 SharedPreferences pref = getSharedPreferences(context);
1665 return pref.getBoolean("app_drawer_icon", false)
1670 private static boolean shouldLaunchTouchAbsorber(Context context) {
1671 return isOverridingFreeformHack(context) && !isChromeOs(context) && getCurrentApiVersion() < 29.0f;
1674 public static boolean isDesktopIconsEnabled(Context context) {
1675 return !canBootToFreeform(context) && !shouldLaunchTouchAbsorber(context);
1678 public static boolean isSystemTrayEnabled(Context context) {
1679 SharedPreferences pref = getSharedPreferences(context);
1681 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1682 && pref.getBoolean("sys_tray", context.getResources().getBoolean(R.bool.tb_def_sys_tray))
1683 && pref.getBoolean("full_length", context.getResources().getBoolean(R.bool.tb_def_full_length))
1684 && !getTaskbarPosition(context).contains("vertical");
1687 @SuppressWarnings("deprecation")
1688 public static boolean isLibrary(Context context) {
1689 return !context.getPackageName().equals(BuildConfig.APPLICATION_ID);
1692 public static boolean applyDisplayCutoutModeTo(WindowManager.LayoutParams params) {
1693 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
1694 params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;