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.APPLICATION, 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)) {
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.getPackageName());
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 @TargetApi(Build.VERSION_CODES.N)
428 private static Bundle launchMode1(Context context, ApplicationType type, View view) {
429 DisplayInfo display = getDisplayInfo(context);
431 int width1 = display.width / 8;
432 int width2 = display.width - width1;
433 int height1 = display.height / 8;
434 int height2 = display.height - height1;
436 return getActivityOptions(context, type, view).setLaunchBounds(new Rect(
444 @TargetApi(Build.VERSION_CODES.N)
445 private static Bundle launchMode2(Context context, int launchType, ApplicationType type, View view) {
446 DisplayInfo display = getDisplayInfo(context);
448 int statusBarHeight = getStatusBarHeight(context);
449 String position = getTaskbarPosition(context);
451 boolean isPortrait = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
452 boolean isLandscape = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
455 int top = statusBarHeight;
456 int right = display.width;
457 int bottom = display.height;
459 int iconSize = isOverridingFreeformHack(context) && !LauncherHelper.getInstance().isOnHomeScreen()
461 : context.getResources().getDimensionPixelSize(R.dimen.tb_icon_size);
463 if(position.contains("vertical_left"))
464 left = left + iconSize;
465 else if(position.contains("vertical_right"))
466 right = right - iconSize;
467 else if(position.contains("bottom"))
468 bottom = bottom - iconSize;
470 top = top + iconSize;
472 int halfLandscape = (right / 2) + ((iconSize / 2) * (position.contains("vertical_left") ? 1 : 0));
473 int halfPortrait = (bottom / 2) + ((iconSize / 2) * ((position.equals("top_left") || position.equals("top_right")) ? 1 : 0));
475 if(launchType == RIGHT && isLandscape)
476 left = halfLandscape;
477 else if(launchType == RIGHT && isPortrait)
479 else if(launchType == LEFT && isLandscape)
480 right = halfLandscape;
481 else if(launchType == LEFT && isPortrait)
482 bottom = halfPortrait;
484 return getActivityOptions(context, type, view)
485 .setLaunchBounds(new Rect(left, top, right, bottom)).toBundle();
488 @TargetApi(Build.VERSION_CODES.N)
489 private static Bundle launchMode3(Context context, ApplicationType type, View view) {
490 DisplayInfo display = getDisplayInfo(context);
492 int width1 = display.width / 2;
493 int width2 = context.getResources().getDimensionPixelSize(R.dimen.tb_phone_size_width) / 2;
494 int height1 = display.height / 2;
495 int height2 = context.getResources().getDimensionPixelSize(R.dimen.tb_phone_size_height) / 2;
497 return getActivityOptions(context, type, view).setLaunchBounds(new Rect(
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 | SecurityException e) { /* Gracefully fail */ }
514 @TargetApi(Build.VERSION_CODES.N_MR1)
515 private static void launchShortcut(Context context, ShortcutInfo shortcut, Bundle bundle) {
516 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
518 if(launcherApps.hasShortcutHostPermission()) {
520 launcherApps.startShortcut(shortcut, null, bundle);
521 } catch (ActivityNotFoundException | NullPointerException | SecurityException e) { /* Gracefully fail */ }
525 private static void prepareToStartActivity(Context context, boolean openInNewWindow, Runnable runnable) {
526 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_CONTEXT_MENU"));
528 if(!FreeformHackHelper.getInstance().isTouchAbsorberActive()
529 && shouldLaunchTouchAbsorber(context)) {
530 startTouchAbsorberActivity(context);
531 new Handler().postDelayed(runnable, 100);
532 } else if(openInNewWindow) {
533 Intent intent = new Intent(context, DummyActivity.class);
534 intent.putExtra("finish_on_pause", true);
535 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
536 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
537 startActivityLowerRight(context, intent);
539 new Handler().postDelayed(runnable, 100);
544 public static void startActivityMaximized(Context context, Intent intent) {
545 Bundle bundle = launchMode2(context, MAXIMIZED, ApplicationType.CONTEXT_MENU, null);
546 prepareToStartActivity(context, false, () -> context.startActivity(intent, bundle));
549 @TargetApi(Build.VERSION_CODES.N)
550 public static void startActivityLowerRight(Context context, Intent intent) {
551 DisplayInfo display = getDisplayInfo(context);
553 context.startActivity(intent,
554 getActivityOptions(context, ApplicationType.FREEFORM_HACK, null)
555 .setLaunchBounds(new Rect(
561 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
564 @TargetApi(Build.VERSION_CODES.N)
565 public static void startTouchAbsorberActivity(Context context) {
566 String position = getTaskbarPosition(context);
567 DisplayInfo display = getDisplayInfo(context);
571 int right = display.width;
572 int bottom = display.height;
574 int iconSize = context.getResources().getDimensionPixelSize(R.dimen.tb_icon_size);
576 if(position.contains("vertical_left"))
578 else if(position.contains("vertical_right"))
579 left = right - iconSize;
580 else if(position.contains("bottom"))
581 top = bottom - iconSize;
585 Intent intent = new Intent(context, TouchAbsorberActivity.class);
586 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
587 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
590 context.startActivity(intent,
591 getActivityOptions(context, ApplicationType.FREEFORM_HACK, null)
592 .setLaunchBounds(new Rect(left, top, right, bottom)).toBundle());
593 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
596 public static void startContextMenuActivity(Context context, Bundle args) {
597 SharedPreferences pref = getSharedPreferences(context);
598 Intent intent = null;
600 switch(pref.getString("theme", "light")) {
602 intent = new Intent(context, ContextMenuActivity.class);
605 intent = new Intent(context, ContextMenuActivityDark.class);
610 intent.putExtra("args", args);
611 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
614 if(hasFreeformSupport(context) && FreeformHackHelper.getInstance().isInFreeformWorkspace()) {
615 DisplayInfo display = getDisplayInfo(context);
617 if(intent != null && hasBrokenSetLaunchBoundsApi())
618 intent.putExtra("context_menu_fix", true);
620 context.startActivity(intent,
621 getActivityOptions(context, ApplicationType.CONTEXT_MENU, null)
623 new Rect(0, 0, display.width, display.height)
626 context.startActivity(intent);
629 public static void checkForUpdates(Context context) {
631 if(isPlayStoreRelease(context)) {
632 if(context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
633 && !isPlayStoreInstalled(context))
634 url = "https://github.com/farmerbb/Taskbar/releases";
636 url = "https://play.google.com/store/apps/details?id=" + context.getPackageName();
638 url = "https://f-droid.org/repository/browse/?fdid=" + context.getPackageName();
640 Intent intent = new Intent(Intent.ACTION_VIEW);
641 intent.setData(Uri.parse(url));
642 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
645 context.startActivity(intent);
646 } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
649 public static boolean launcherIsDefault(Context context) {
650 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
651 homeIntent.addCategory(Intent.CATEGORY_HOME);
652 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
654 return defaultLauncher.activityInfo.packageName.equals(context.getPackageName());
657 public static void setCachedRotation(int cachedRotation) {
658 U.cachedRotation = cachedRotation;
661 public static String getTaskbarPosition(Context context) {
662 SharedPreferences pref = getSharedPreferences(context);
663 String position = pref.getString("position", "bottom_left");
665 if(pref.getBoolean("anchor", false)) {
666 WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
667 int rotation = cachedRotation != null ? cachedRotation : windowManager.getDefaultDisplay().getRotation();
672 case Surface.ROTATION_0:
673 return "bottom_left";
674 case Surface.ROTATION_90:
675 return "bottom_vertical_right";
676 case Surface.ROTATION_180:
678 case Surface.ROTATION_270:
679 return "top_vertical_left";
682 case "bottom_vertical_left":
684 case Surface.ROTATION_0:
685 return "bottom_vertical_left";
686 case Surface.ROTATION_90:
687 return "bottom_right";
688 case Surface.ROTATION_180:
689 return "top_vertical_right";
690 case Surface.ROTATION_270:
696 case Surface.ROTATION_0:
697 return "bottom_right";
698 case Surface.ROTATION_90:
699 return "top_vertical_right";
700 case Surface.ROTATION_180:
702 case Surface.ROTATION_270:
703 return "bottom_vertical_left";
706 case "bottom_vertical_right":
708 case Surface.ROTATION_0:
709 return "bottom_vertical_right";
710 case Surface.ROTATION_90:
712 case Surface.ROTATION_180:
713 return "top_vertical_left";
714 case Surface.ROTATION_270:
715 return "bottom_left";
720 case Surface.ROTATION_0:
722 case Surface.ROTATION_90:
723 return "bottom_vertical_left";
724 case Surface.ROTATION_180:
725 return "bottom_right";
726 case Surface.ROTATION_270:
727 return "top_vertical_right";
730 case "top_vertical_left":
732 case Surface.ROTATION_0:
733 return "top_vertical_left";
734 case Surface.ROTATION_90:
735 return "bottom_left";
736 case Surface.ROTATION_180:
737 return "bottom_vertical_right";
738 case Surface.ROTATION_270:
744 case Surface.ROTATION_0:
746 case Surface.ROTATION_90:
747 return "top_vertical_left";
748 case Surface.ROTATION_180:
749 return "bottom_left";
750 case Surface.ROTATION_270:
751 return "bottom_vertical_right";
754 case "top_vertical_right":
756 case Surface.ROTATION_0:
757 return "top_vertical_right";
758 case Surface.ROTATION_90:
760 case Surface.ROTATION_180:
761 return "bottom_vertical_left";
762 case Surface.ROTATION_270:
763 return "bottom_right";
772 private static int getMaxNumOfColumns(Context context) {
773 SharedPreferences pref = getSharedPreferences(context);
774 DisplayInfo display = getDisplayInfo(context);
775 float density = display.density / 160;
776 float baseTaskbarSize = getBaseTaskbarSizeFloat(context) / density;
777 int numOfColumns = 0;
779 float maxScreenSize = getTaskbarPosition(context).contains("vertical")
780 ? (display.height - getStatusBarHeight(context)) / density
781 : display.width / density;
783 float iconSize = context.getResources().getDimension(R.dimen.tb_icon_size) / density;
785 int userMaxNumOfColumns = Integer.valueOf(pref.getString("max_num_of_recents", "10"));
787 while(baseTaskbarSize + iconSize < maxScreenSize
788 && numOfColumns < userMaxNumOfColumns) {
789 baseTaskbarSize = baseTaskbarSize + iconSize;
796 public static int getMaxNumOfEntries(Context context) {
797 SharedPreferences pref = getSharedPreferences(context);
798 return pref.getBoolean("disable_scrolling_list", false)
799 ? getMaxNumOfColumns(context)
800 : Integer.valueOf(pref.getString("max_num_of_recents", "10"));
803 public static int getStatusBarHeight(Context context) {
804 return getSystemDimen(context, "status_bar_height");
807 private static int getNavbarHeight(Context context) {
808 return getSystemDimen(context, "navigation_bar_height");
811 private static int getSystemDimen(Context context, String id) {
813 int resourceId = context.getResources().getIdentifier(id, "dimen", "android");
815 value = context.getResources().getDimensionPixelSize(resourceId);
820 public static void refreshPinnedIcons(Context context) {
821 IconCache.getInstance(context).clearCache();
823 PinnedBlockedApps pba = PinnedBlockedApps.getInstance(context);
824 List<AppEntry> pinnedAppsList = new ArrayList<>(pba.getPinnedApps());
825 List<AppEntry> blockedAppsList = new ArrayList<>(pba.getBlockedApps());
826 PackageManager pm = context.getPackageManager();
830 for(AppEntry entry : pinnedAppsList) {
831 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
832 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
834 final List<UserHandle> userHandles = userManager.getUserProfiles();
835 LauncherActivityInfo appInfo = null;
837 for(UserHandle handle : userHandles) {
838 List<LauncherActivityInfo> list = launcherApps.getActivityList(entry.getPackageName(), handle);
839 if(!list.isEmpty()) {
840 // Google App workaround
841 if(!entry.getPackageName().equals("com.google.android.googlequicksearchbox"))
842 appInfo = list.get(0);
844 boolean added = false;
845 for(LauncherActivityInfo info : list) {
846 if(info.getName().equals("com.google.android.googlequicksearchbox.SearchActivity")) {
852 if(!added) appInfo = list.get(0);
859 if(appInfo != null) {
860 AppEntry newEntry = new AppEntry(
861 entry.getPackageName(),
862 entry.getComponentName(),
864 IconCache.getInstance(context).getIcon(context, pm, appInfo),
867 newEntry.setUserId(entry.getUserId(context));
868 pba.addPinnedApp(context, newEntry);
872 for(AppEntry entry : blockedAppsList) {
873 pba.addBlockedApp(context, entry);
877 public static boolean canEnableFreeform() {
878 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
881 @TargetApi(Build.VERSION_CODES.N)
882 public static boolean hasFreeformSupport(Context context) {
883 return canEnableFreeform()
884 && (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
885 || Settings.Global.getInt(context.getContentResolver(), "enable_freeform_support", 0) != 0
886 || (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
887 && Settings.Global.getInt(context.getContentResolver(), "force_resizable_activities", 0) != 0));
890 public static boolean canBootToFreeform(Context context) {
891 SharedPreferences pref = getSharedPreferences(context);
892 return hasFreeformSupport(context)
893 && pref.getBoolean("freeform_hack", false)
894 && !isOverridingFreeformHack(context);
897 public static boolean isSamsungDevice() {
898 return Build.MANUFACTURER.equalsIgnoreCase("Samsung");
901 private static boolean isNvidiaDevice() {
902 return Build.MANUFACTURER.equalsIgnoreCase("NVIDIA");
905 public static boolean isServiceRunning(Context context, Class<? extends Service> cls) {
906 if(LauncherHelper.getInstance().isOnSecondaryHomeScreen()
907 && (cls.equals(TaskbarService.class)
908 || cls.equals(StartMenuService.class)
909 || cls.equals(DashboardService.class)))
912 return isServiceRunning(context, cls.getName());
915 private static boolean isServiceRunning(Context context, String className) {
916 ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
917 for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
918 if(className.equals(service.service.getClassName()))
925 public static int getBackgroundTint(Context context) {
926 SharedPreferences pref = getSharedPreferences(context);
928 // Import old background tint preference
929 if(pref.contains("show_background")) {
930 SharedPreferences.Editor editor = pref.edit();
932 if(!pref.getBoolean("show_background", true))
933 editor.putInt("background_tint", Color.TRANSPARENT).apply();
935 editor.remove("show_background");
939 return pref.getInt("background_tint", context.getResources().getInteger(R.integer.tb_translucent_gray));
942 public static int getAccentColor(Context context) {
943 SharedPreferences pref = getSharedPreferences(context);
944 return pref.getInt("accent_color", context.getResources().getInteger(R.integer.tb_translucent_white));
947 @TargetApi(Build.VERSION_CODES.M)
948 public static boolean canDrawOverlays(Context context) {
949 return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context);
952 public static boolean isGame(Context context, String packageName) {
953 SharedPreferences pref = getSharedPreferences(context);
954 if(pref.getBoolean("launch_games_fullscreen", true)) {
955 PackageManager pm = context.getPackageManager();
958 ApplicationInfo info = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
959 return (info.flags & ApplicationInfo.FLAG_IS_GAME) != 0 || (info.metaData != null && info.metaData.getBoolean("isGame", false));
960 } catch (PackageManager.NameNotFoundException e) {
967 private static ActivityOptions getActivityOptions(View view) {
968 return getActivityOptions(null, null, view);
971 private static ActivityOptions getActivityOptions(Context context, ApplicationType applicationType, View view) {
972 ActivityOptions options;
974 options = ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight());
975 else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
976 options = ActivityOptions.makeBasic();
979 Constructor<ActivityOptions> constructor = ActivityOptions.class.getDeclaredConstructor();
980 constructor.setAccessible(true);
981 options = constructor.newInstance();
982 } catch (Exception e) {
983 // If this ever happens, the app will likely crash at this point due to NPE
988 if(applicationType == null)
993 switch(applicationType) {
995 if(FreeformHackHelper.getInstance().isFreeformHackActive())
996 stackId = getFreeformWindowModeId();
998 stackId = getFullscreenWindowModeId();
1001 stackId = getFullscreenWindowModeId();
1004 stackId = getFreeformWindowModeId();
1007 if(hasBrokenSetLaunchBoundsApi()
1008 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f))
1009 stackId = getFullscreenWindowModeId();
1014 Method method = ActivityOptions.class.getMethod(getWindowingModeMethodName(), int.class);
1015 method.invoke(options, stackId);
1016 } catch (Exception e) { /* Gracefully fail */ }
1018 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1019 int launchDisplayId = LauncherHelper.getInstance().getSecondaryDisplayId();
1020 if(launchDisplayId != -1)
1021 options.setLaunchDisplayId(launchDisplayId);
1027 private static int getFullscreenWindowModeId() {
1028 if(getCurrentApiVersion() >= 28.0f)
1029 return WINDOWING_MODE_FULLSCREEN;
1031 return FULLSCREEN_WORKSPACE_STACK_ID;
1034 private static int getFreeformWindowModeId() {
1035 if(getCurrentApiVersion() >= 28.0f)
1036 return WINDOWING_MODE_FREEFORM;
1038 return FREEFORM_WORKSPACE_STACK_ID;
1041 private static String getWindowingModeMethodName() {
1042 if(getCurrentApiVersion() >= 28.0f)
1043 return "setLaunchWindowingMode";
1045 return "setLaunchStackId";
1048 public static Bundle getActivityOptionsBundle(Context context, ApplicationType type, View view) {
1049 SharedPreferences pref = getSharedPreferences(context);
1051 return getActivityOptionsBundle(context, type, pref.getString("window_size", context.getString(R.string.tb_def_window_size)), view);
1054 private static Bundle getActivityOptionsBundle(Context context, ApplicationType type, String windowSize, View view) {
1055 SharedPreferences pref = getSharedPreferences(context);
1056 if(!canEnableFreeform() || !pref.getBoolean("freeform_hack", false))
1057 return getActivityOptions(view).toBundle();
1059 switch(windowSize) {
1061 return launchMode1(context, type, view);
1063 return launchMode2(context, MAXIMIZED, type, view);
1065 return launchMode2(context, LEFT, type, view);
1067 return launchMode2(context, RIGHT, type, view);
1069 return launchMode3(context, type, view);
1072 return getActivityOptions(context, type, view).toBundle();
1075 private static ApplicationType getApplicationType(Context context, String packageName) {
1076 return isGame(context, packageName) ? ApplicationType.GAME : ApplicationType.APPLICATION;
1079 public static boolean isSystemApp(Context context) {
1081 ApplicationInfo info = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
1082 int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
1083 return (info.flags & mask) != 0;
1084 } catch (PackageManager.NameNotFoundException e) {
1089 public static boolean isChromeOs(Context context) {
1090 return context.getPackageManager().hasSystemFeature("org.chromium.arc");
1093 public static boolean isBlissOs(Context context) {
1094 boolean validBlissOsBuildProp = false;
1096 String blissVersion = getSystemProperty("ro.bliss.version");
1097 if(blissVersion != null && !blissVersion.isEmpty())
1098 validBlissOsBuildProp = true;
1100 String buildUser = getSystemProperty("ro.build.user");
1101 if(buildUser != null && buildUser.equals("electrikjesus"))
1102 validBlissOsBuildProp = true;
1104 return validBlissOsBuildProp
1105 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1106 && isSystemApp(context);
1109 public static boolean isLauncherPermanentlyEnabled(Context context) {
1110 if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
1113 PackageManager pm = context.getPackageManager();
1115 pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1116 return pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, context.getPackageName()) == PackageManager.SIGNATURE_MATCH
1117 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1118 && isSystemApp(context);
1119 } catch (PackageManager.NameNotFoundException e) {
1124 public static boolean hasSupportLibrary(Context context, int minVersion) {
1125 PackageManager pm = context.getPackageManager();
1127 PackageInfo pInfo = pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1128 return pInfo.versionCode >= minVersion
1129 && pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, context.getPackageName()) == PackageManager.SIGNATURE_MATCH
1130 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1131 && isSystemApp(context);
1132 } catch (PackageManager.NameNotFoundException e) {
1137 public static int getBaseTaskbarSize(Context context) {
1138 return Math.round(getBaseTaskbarSizeFloat(context));
1141 private static float getBaseTaskbarSizeFloat(Context context) {
1142 SharedPreferences pref = getSharedPreferences(context);
1143 float baseTaskbarSize = context.getResources().getDimension(R.dimen.tb_base_taskbar_size);
1144 boolean navbarButtonsEnabled = false;
1146 if(pref.getBoolean("dashboard", context.getResources().getBoolean(R.bool.tb_def_dashboard)))
1147 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_dashboard_button_size);
1149 if(pref.getBoolean("button_back", false)) {
1150 navbarButtonsEnabled = true;
1151 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1154 if(pref.getBoolean("button_home", false)) {
1155 navbarButtonsEnabled = true;
1156 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1159 if(pref.getBoolean("button_recents", false)) {
1160 navbarButtonsEnabled = true;
1161 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1164 if(navbarButtonsEnabled)
1165 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_navbar_buttons_margin);
1167 if(isSystemTrayEnabled(context))
1168 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_systray_size);
1170 return baseTaskbarSize;
1173 private static void startTaskbarService(Context context, boolean fullRestart) {
1174 context.startService(new Intent(context, TaskbarService.class));
1175 context.startService(new Intent(context, StartMenuService.class));
1176 context.startService(new Intent(context, DashboardService.class));
1177 if(fullRestart) context.startService(new Intent(context, NotificationService.class));
1180 private static void stopTaskbarService(Context context, boolean fullRestart) {
1181 context.stopService(new Intent(context, TaskbarService.class));
1182 context.stopService(new Intent(context, StartMenuService.class));
1183 context.stopService(new Intent(context, DashboardService.class));
1184 if(fullRestart) context.stopService(new Intent(context, NotificationService.class));
1187 public static void restartTaskbar(Context context) {
1188 SharedPreferences pref = getSharedPreferences(context);
1189 if(pref.getBoolean("taskbar_active", false) && !pref.getBoolean("is_hidden", false)) {
1191 .putBoolean("is_restarting", true)
1192 .putBoolean("skip_auto_hide_navbar", true)
1195 stopTaskbarService(context, true);
1196 startTaskbarService(context, true);
1197 } else if(isServiceRunning(context, StartMenuService.class)) {
1198 pref.edit().putBoolean("skip_auto_hide_navbar", true).apply();
1200 stopTaskbarService(context, false);
1201 startTaskbarService(context, false);
1204 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.RESTART"));
1207 public static void restartNotificationService(Context context) {
1208 if(isServiceRunning(context, NotificationService.class)) {
1209 SharedPreferences pref = getSharedPreferences(context);
1210 pref.edit().putBoolean("is_restarting", true).apply();
1212 Intent intent = new Intent(context, NotificationService.class);
1213 context.stopService(intent);
1214 context.startService(intent);
1218 public static void showHideNavigationBar(Context context, boolean show) {
1219 if(hasSupportLibrary(context, 7)) {
1220 Intent intent = new Intent(BuildConfig.SUPPORT_APPLICATION_ID + ".CHANGE_OVERSCAN");
1221 intent.setPackage(BuildConfig.SUPPORT_APPLICATION_ID);
1223 intent.putExtra("display_id", getDisplayID());
1224 intent.putExtra("value", show ? 0 : getNavbarHeight(context) * -1);
1226 context.sendBroadcast(intent);
1230 // Show or hide the system navigation bar on Bliss-x86
1232 if(getCurrentApiVersion() >= 28.0f)
1233 Settings.Secure.putInt(context.getContentResolver(), "navigation_bar_visible", show ? 1 : 0);
1235 Settings.System.putInt(context.getContentResolver(), "navigation_bar_show", show ? 1 : 0);
1236 } catch (Exception e) { /* Gracefully fail */ }
1239 public static void initPrefs(Context context) {
1240 // Enable freeform hack automatically on supported devices
1241 SharedPreferences pref = getSharedPreferences(context);
1242 if(canEnableFreeform()) {
1243 if(!pref.getBoolean("freeform_hack_override", false)) {
1245 .putBoolean("freeform_hack", hasFreeformSupport(context) && !isSamsungDevice())
1246 .putBoolean("save_window_sizes", false)
1247 .putBoolean("freeform_hack_override", true)
1249 } else if(!hasFreeformSupport(context)) {
1250 pref.edit().putBoolean("freeform_hack", false).apply();
1252 stopFreeformHack(context);
1255 boolean freeformWasEnabled = pref.getBoolean("freeform_hack", false)
1256 || pref.getBoolean("show_freeform_disabled_message", false);
1259 .putBoolean("freeform_hack", false)
1260 .putBoolean("show_freeform_disabled_message", freeformWasEnabled)
1263 SavedWindowSizes.getInstance(context).clear(context);
1264 stopFreeformHack(context);
1267 // Customizations for BlissOS
1268 if(isBlissOs(context) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1269 && !pref.getBoolean("bliss_os_prefs", false)) {
1270 SharedPreferences.Editor editor = pref.edit();
1272 if(hasFreeformSupport(context)) {
1273 editor.putBoolean("freeform_hack", true);
1276 editor.putString("recents_amount", "running_apps_only");
1277 editor.putString("refresh_frequency", "0");
1278 editor.putString("max_num_of_recents", "2147483647");
1279 editor.putString("sort_order", "true");
1280 editor.putString("start_button_image", "app_logo");
1281 editor.putBoolean("button_back", true);
1282 editor.putBoolean("button_home", true);
1283 editor.putBoolean("button_recents", true);
1284 editor.putBoolean("auto_hide_navbar", true);
1285 editor.putBoolean("shortcut_icon", false);
1286 editor.putBoolean("bliss_os_prefs", true);
1290 // Customizations for Android-x86 devices (non-Bliss)
1291 if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
1292 && isSystemApp(context)
1293 && !pref.getBoolean("android_x86_prefs", false)) {
1295 .putString("recents_amount", "running_apps_only")
1296 .putString("refresh_frequency", "0")
1297 .putString("max_num_of_recents", "2147483647")
1298 .putString("sort_order", "true")
1299 .putBoolean("shortcut_icon", false)
1300 .putBoolean("android_x86_prefs", true)
1305 public static DisplayInfo getDisplayInfo(Context context) {
1306 return getDisplayInfo(context, false);
1309 public static DisplayInfo getDisplayInfo(Context context, boolean fromTaskbar) {
1310 context = context.getApplicationContext();
1311 int displayID = getDisplayID();
1313 DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1314 Display currentDisplay = null;
1316 for(Display display : dm.getDisplays()) {
1317 if(display.getDisplayId() == displayID) {
1318 currentDisplay = display;
1323 if(currentDisplay == null)
1324 return new DisplayInfo(0, 0, 0);
1326 DisplayMetrics metrics = new DisplayMetrics();
1327 currentDisplay.getMetrics(metrics);
1329 DisplayMetrics realMetrics = new DisplayMetrics();
1330 currentDisplay.getRealMetrics(realMetrics);
1332 DisplayInfo info = new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);
1334 if(isChromeOs(context)) {
1335 SharedPreferences pref = getSharedPreferences(context);
1336 if(!pref.getBoolean("chrome_os_context_menu_fix", true)) {
1337 info.width = realMetrics.widthPixels;
1338 info.height = realMetrics.heightPixels;
1344 // Workaround for incorrect display size on devices with notches in landscape mode
1345 if(fromTaskbar && context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)
1348 boolean sameWidth = metrics.widthPixels == realMetrics.widthPixels;
1349 boolean sameHeight = metrics.heightPixels == realMetrics.heightPixels;
1351 if(sameWidth && !sameHeight) {
1352 info.width = realMetrics.widthPixels;
1353 info.height = realMetrics.heightPixels - getNavbarHeight(context);
1356 if(!sameWidth && sameHeight) {
1357 info.width = realMetrics.widthPixels - getNavbarHeight(context);
1358 info.height = realMetrics.heightPixels;
1364 private static int getDisplayID() {
1365 LauncherHelper helper = LauncherHelper.getInstance();
1367 if(helper.isOnSecondaryHomeScreen())
1368 return helper.getSecondaryDisplayId();
1370 return Display.DEFAULT_DISPLAY;
1373 public static void pinAppShortcut(Context context) {
1374 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1375 ShortcutManager mShortcutManager = context.getSystemService(ShortcutManager.class);
1377 if(mShortcutManager.isRequestPinShortcutSupported()) {
1378 ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder(context, "freeform_mode").build();
1380 mShortcutManager.requestPinShortcut(pinShortcutInfo, null);
1382 showToastLong(context, R.string.tb_pin_shortcut_not_supported);
1384 Intent intent = ShortcutUtils.getShortcutIntent(context);
1385 intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
1386 intent.putExtra("duplicate", false);
1388 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1389 homeIntent.addCategory(Intent.CATEGORY_HOME);
1390 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
1392 intent.setPackage(defaultLauncher.activityInfo.packageName);
1393 context.sendBroadcast(intent);
1395 showToast(context, R.string.tb_shortcut_created);
1399 public static boolean shouldCollapse(Context context, boolean pendingAppLaunch) {
1400 SharedPreferences pref = getSharedPreferences(context);
1401 if(pref.getBoolean("hide_taskbar", true)) {
1402 if(isOverridingFreeformHack(context))
1403 return !LauncherHelper.getInstance().isOnHomeScreen();
1405 FreeformHackHelper helper = FreeformHackHelper.getInstance();
1406 if(pendingAppLaunch)
1407 return !helper.isFreeformHackActive();
1409 return !helper.isInFreeformWorkspace();
1415 public static boolean isOverridingFreeformHack(Context context) {
1416 SharedPreferences pref = getSharedPreferences(context);
1417 return pref.getBoolean("freeform_hack", false)
1418 && ((isChromeOs(context) && pref.getBoolean("chrome_os_context_menu_fix", true))
1419 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f));
1422 public static boolean isPlayStoreInstalled(Context context) {
1424 context.getPackageManager().getPackageInfo("com.android.vending", 0);
1426 } catch (PackageManager.NameNotFoundException e) {
1431 private static float getCurrentApiVersion() {
1432 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1433 return Float.valueOf(Build.VERSION.SDK_INT + "." + Build.VERSION.PREVIEW_SDK_INT);
1435 return (float) Build.VERSION.SDK_INT;
1438 public static boolean hasBrokenSetLaunchBoundsApi() {
1439 return getCurrentApiVersion() >= 26.0f
1440 && getCurrentApiVersion() < 28.0f
1441 && !isSamsungDevice()
1442 && !isNvidiaDevice();
1445 public static String getSecondScreenPackageName(Context context) {
1446 return getInstalledPackage(context, Arrays.asList(
1447 "com.farmerbb.secondscreen.free",
1448 "com.farmerbb.secondscreen"));
1451 // Returns the name of an installed package from a list of package names, in order of preference
1452 private static String getInstalledPackage(Context context, List<String> packageNames) {
1453 if(packageNames == null || packageNames.isEmpty())
1456 List<String> packages = packageNames instanceof ArrayList ? packageNames : new ArrayList<>(packageNames);
1457 String packageName = packages.get(0);
1460 context.getPackageManager().getPackageInfo(packageName, 0);
1462 } catch (PackageManager.NameNotFoundException e) {
1464 return getInstalledPackage(context, packages);
1468 public static void showRecentAppsDialog(Context context) {
1469 showRecentAppsDialog(context, null, null);
1472 public static AlertDialog showRecentAppsDialog(Context context, Runnable onError, Runnable onFinish) {
1473 Runnable finalOnFinish = onFinish == null
1477 Runnable finalOnError = onError == null
1478 ? () -> showErrorDialog(context, "GET_USAGE_STATS", finalOnFinish)
1481 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isSystemApp(context)) {
1482 ApplicationInfo applicationInfo = null;
1484 applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
1485 } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
1487 if(applicationInfo != null) {
1488 AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
1489 int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
1491 if(mode != AppOpsManager.MODE_ALLOWED) {
1492 AlertDialog.Builder builder = new AlertDialog.Builder(context);
1493 builder.setTitle(R.string.tb_pref_header_recent_apps)
1494 .setMessage(R.string.tb_enable_recent_apps)
1495 .setPositiveButton(R.string.tb_action_ok, (dialog, which) -> {
1497 context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
1498 showToastLong(context, R.string.tb_usage_stats_message);
1500 finalOnFinish.run();
1501 } catch (ActivityNotFoundException e) {
1505 .setNegativeButton(R.string.tb_action_cancel, (dialog, which) -> finalOnFinish.run());
1507 AlertDialog dialog = builder.create();
1509 dialog.setCancelable(false);
1516 finalOnFinish.run();
1520 public static Context wrapContext(Context context) {
1521 SharedPreferences pref = getSharedPreferences(context);
1524 switch(pref.getString("theme", "light")) {
1526 theme = R.style.Taskbar;
1529 theme = R.style.Taskbar_Dark;
1533 return theme > -1 ? new ContextThemeWrapper(context, theme) : context;
1536 public static boolean isPlayStoreRelease(Context context) {
1537 return isPlayStoreRelease(context, context.getPackageName());
1540 @SuppressLint("PackageManagerGetSignatures")
1541 public static boolean isPlayStoreRelease(Context context, String packageName) {
1542 Signature playStoreSignature = new Signature(context.getString(R.string.tb_signature));
1544 PackageManager pm = context.getPackageManager();
1545 PackageInfo info = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
1546 for(Signature signature : info.signatures) {
1547 if(signature.equals(playStoreSignature))
1550 } catch (Exception e) { /* Gracefully fail */ }
1555 public static boolean isExternalAccessDisabled(Context context) {
1556 SharedPreferences pref = getSharedPreferences(context);
1557 return !pref.getBoolean("tasker_enabled", true);
1560 public static boolean enableFreeformModeShortcut(Context context) {
1561 return canEnableFreeform()
1562 && !isOverridingFreeformHack(context)
1563 && !isChromeOs(context);
1566 public static void startForegroundService(Context context, Intent intent) {
1567 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1568 if(Settings.canDrawOverlays(context))
1569 context.startForegroundService(intent);
1571 context.startService(intent);
1574 public static int getOverlayType() {
1575 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
1576 ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
1577 : WindowManager.LayoutParams.TYPE_PHONE;
1580 public static boolean isDelegatingHomeActivity(Context context) {
1581 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1582 homeIntent.addCategory(Intent.CATEGORY_HOME);
1584 final List<ResolveInfo> listOfLaunchers = context.getPackageManager().queryIntentActivities(homeIntent, 0);
1585 for(ResolveInfo launcher : listOfLaunchers) {
1586 if(launcher.activityInfo.packageName.equals(BuildConfig.SUPPORT_APPLICATION_ID))
1593 @SuppressLint("PrivateApi")
1594 private static String getSystemProperty(String key) {
1596 Class<?> cls = Class.forName("android.os.SystemProperties");
1597 return cls.getMethod("get", String.class).invoke(null, key).toString();
1598 } catch (Exception e) {
1603 @SuppressWarnings("ResultOfMethodCallIgnored")
1604 public static boolean importCustomStartButtonImage(Context context, Uri uri) {
1606 File imagesDir = new File(context.getFilesDir(), "tb_images");
1609 File importedFile = new File(imagesDir, "custom_image_new");
1610 if(importedFile.exists()) importedFile.delete();
1612 BufferedInputStream is = new BufferedInputStream(context.getContentResolver().openInputStream(uri));
1613 byte[] data = new byte[is.available()];
1615 if(data.length > 0) {
1616 BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(importedFile));
1623 File prevFile = new File(imagesDir, "custom_image");
1624 if(prevFile.exists()) prevFile.delete();
1626 importedFile.renameTo(prevFile);
1628 } catch (IOException e) {
1633 public static String getDefaultStartButtonImage(Context context) {
1634 SharedPreferences pref = getSharedPreferences(context);
1635 return pref.getBoolean("app_drawer_icon", false)
1640 private static boolean shouldLaunchTouchAbsorber(Context context) {
1641 return isOverridingFreeformHack(context) && !isChromeOs(context) && getCurrentApiVersion() < 29.0f;
1644 public static boolean isDesktopIconsEnabled(Context context) {
1645 return !canBootToFreeform(context) && !shouldLaunchTouchAbsorber(context);
1648 public static boolean isSystemTrayEnabled(Context context) {
1649 SharedPreferences pref = getSharedPreferences(context);
1651 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1652 && pref.getBoolean("sys_tray", context.getResources().getBoolean(R.bool.tb_def_sys_tray))
1653 && pref.getBoolean("full_length", context.getResources().getBoolean(R.bool.tb_def_full_length))
1654 && !getTaskbarPosition(context).contains("vertical");
1657 @SuppressWarnings("deprecation")
1658 public static boolean isLibrary(Context context) {
1659 return !context.getPackageName().equals(BuildConfig.APPLICATION_ID);
1662 public static boolean applyDisplayCutoutModeTo(WindowManager.LayoutParams params) {
1663 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
1664 params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;