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 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 int width1 = display.width / 2;
489 int width2 = context.getResources().getDimensionPixelSize(R.dimen.tb_phone_size_width) / 2;
490 int height1 = display.height / 2;
491 int height2 = context.getResources().getDimensionPixelSize(R.dimen.tb_phone_size_height) / 2;
493 return getActivityOptionsBundle(context, type, view,
501 private static void launchAndroidForWork(Context context, ComponentName componentName, Bundle bundle, long userId) {
502 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
503 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
506 launcherApps.startMainActivity(componentName, userManager.getUserForSerialNumber(userId), null, bundle);
507 } catch (ActivityNotFoundException | NullPointerException
508 | IllegalStateException | SecurityException e) { /* Gracefully fail */ }
511 @TargetApi(Build.VERSION_CODES.N_MR1)
512 private static void launchShortcut(Context context, ShortcutInfo shortcut, Bundle bundle) {
513 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
515 if(launcherApps.hasShortcutHostPermission()) {
517 launcherApps.startShortcut(shortcut, null, bundle);
518 } catch (ActivityNotFoundException | NullPointerException
519 | IllegalStateException | SecurityException e) { /* Gracefully fail */ }
523 private static void prepareToStartActivity(Context context, boolean openInNewWindow, Runnable runnable) {
524 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_CONTEXT_MENU"));
526 if(!FreeformHackHelper.getInstance().isTouchAbsorberActive()
527 && shouldLaunchTouchAbsorber(context)) {
528 startTouchAbsorberActivity(context);
529 new Handler().postDelayed(runnable, 100);
530 } else if(openInNewWindow) {
531 Intent intent = new Intent(context, DummyActivity.class);
532 intent.putExtra("finish_on_pause", true);
533 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
534 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
535 startActivityLowerRight(context, intent);
537 new Handler().postDelayed(runnable, 100);
542 public static void startActivityMaximized(Context context, Intent intent) {
543 Bundle bundle = launchMode2(context, MAXIMIZED, ApplicationType.CONTEXT_MENU, null);
544 prepareToStartActivity(context, false, () -> context.startActivity(intent, bundle));
547 public static void startActivityLowerRight(Context context, Intent intent) {
548 DisplayInfo display = getDisplayInfo(context);
550 context.startActivity(intent,
551 getActivityOptionsBundle(context, ApplicationType.FREEFORM_HACK, null,
557 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
560 public static void startTouchAbsorberActivity(Context context) {
561 String position = getTaskbarPosition(context);
562 DisplayInfo display = getDisplayInfo(context);
566 int right = display.width;
567 int bottom = display.height;
569 int iconSize = context.getResources().getDimensionPixelSize(R.dimen.tb_icon_size);
571 if(position.contains("vertical_left"))
573 else if(position.contains("vertical_right"))
574 left = right - iconSize;
575 else if(position.contains("bottom"))
576 top = bottom - iconSize;
580 Intent intent = new Intent(context, TouchAbsorberActivity.class);
581 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
582 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
585 context.startActivity(intent,
586 getActivityOptionsBundle(context, ApplicationType.FREEFORM_HACK, null,
587 left, top, right, bottom));
588 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
591 public static void startContextMenuActivity(Context context, Bundle args) {
592 SharedPreferences pref = getSharedPreferences(context);
593 Intent intent = null;
595 switch(pref.getString("theme", "light")) {
597 intent = new Intent(context, ContextMenuActivity.class);
600 intent = new Intent(context, ContextMenuActivityDark.class);
605 intent.putExtra("args", args);
606 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
609 if(hasFreeformSupport(context) && FreeformHackHelper.getInstance().isInFreeformWorkspace()) {
610 DisplayInfo display = getDisplayInfo(context);
612 if(intent != null && hasBrokenSetLaunchBoundsApi())
613 intent.putExtra("context_menu_fix", true);
615 context.startActivity(intent,
616 getActivityOptionsBundle(context, ApplicationType.CONTEXT_MENU, null,
617 0, 0, display.width, display.height));
619 context.startActivity(intent);
622 public static void checkForUpdates(Context context) {
624 if(isPlayStoreRelease(context)) {
625 if(context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
626 && !isPlayStoreInstalled(context))
627 url = "https://github.com/farmerbb/Taskbar/releases";
629 url = "https://play.google.com/store/apps/details?id=" + context.getPackageName();
631 url = "https://f-droid.org/repository/browse/?fdid=" + context.getPackageName();
633 Intent intent = new Intent(Intent.ACTION_VIEW);
634 intent.setData(Uri.parse(url));
635 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
638 context.startActivity(intent);
639 } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
642 public static boolean launcherIsDefault(Context context) {
643 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
644 homeIntent.addCategory(Intent.CATEGORY_HOME);
645 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
647 return defaultLauncher.activityInfo.packageName.equals(context.getPackageName());
650 public static void setCachedRotation(int cachedRotation) {
651 U.cachedRotation = cachedRotation;
654 public static String getTaskbarPosition(Context context) {
655 SharedPreferences pref = getSharedPreferences(context);
656 String position = pref.getString("position", "bottom_left");
658 if(pref.getBoolean("anchor", false)) {
659 WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
660 int rotation = cachedRotation != null ? cachedRotation : windowManager.getDefaultDisplay().getRotation();
665 case Surface.ROTATION_0:
666 return "bottom_left";
667 case Surface.ROTATION_90:
668 return "bottom_vertical_right";
669 case Surface.ROTATION_180:
671 case Surface.ROTATION_270:
672 return "top_vertical_left";
675 case "bottom_vertical_left":
677 case Surface.ROTATION_0:
678 return "bottom_vertical_left";
679 case Surface.ROTATION_90:
680 return "bottom_right";
681 case Surface.ROTATION_180:
682 return "top_vertical_right";
683 case Surface.ROTATION_270:
689 case Surface.ROTATION_0:
690 return "bottom_right";
691 case Surface.ROTATION_90:
692 return "top_vertical_right";
693 case Surface.ROTATION_180:
695 case Surface.ROTATION_270:
696 return "bottom_vertical_left";
699 case "bottom_vertical_right":
701 case Surface.ROTATION_0:
702 return "bottom_vertical_right";
703 case Surface.ROTATION_90:
705 case Surface.ROTATION_180:
706 return "top_vertical_left";
707 case Surface.ROTATION_270:
708 return "bottom_left";
713 case Surface.ROTATION_0:
715 case Surface.ROTATION_90:
716 return "bottom_vertical_left";
717 case Surface.ROTATION_180:
718 return "bottom_right";
719 case Surface.ROTATION_270:
720 return "top_vertical_right";
723 case "top_vertical_left":
725 case Surface.ROTATION_0:
726 return "top_vertical_left";
727 case Surface.ROTATION_90:
728 return "bottom_left";
729 case Surface.ROTATION_180:
730 return "bottom_vertical_right";
731 case Surface.ROTATION_270:
737 case Surface.ROTATION_0:
739 case Surface.ROTATION_90:
740 return "top_vertical_left";
741 case Surface.ROTATION_180:
742 return "bottom_left";
743 case Surface.ROTATION_270:
744 return "bottom_vertical_right";
747 case "top_vertical_right":
749 case Surface.ROTATION_0:
750 return "top_vertical_right";
751 case Surface.ROTATION_90:
753 case Surface.ROTATION_180:
754 return "bottom_vertical_left";
755 case Surface.ROTATION_270:
756 return "bottom_right";
765 private static int getMaxNumOfColumns(Context context) {
766 SharedPreferences pref = getSharedPreferences(context);
767 DisplayInfo display = getDisplayInfo(context);
768 float density = display.density / 160;
769 float baseTaskbarSize = getBaseTaskbarSizeFloat(context) / density;
770 int numOfColumns = 0;
772 float maxScreenSize = getTaskbarPosition(context).contains("vertical")
773 ? (display.height - getStatusBarHeight(context)) / density
774 : display.width / density;
776 float iconSize = context.getResources().getDimension(R.dimen.tb_icon_size) / density;
778 int userMaxNumOfColumns = Integer.valueOf(pref.getString("max_num_of_recents", "10"));
780 while(baseTaskbarSize + iconSize < maxScreenSize
781 && numOfColumns < userMaxNumOfColumns) {
782 baseTaskbarSize = baseTaskbarSize + iconSize;
789 public static int getMaxNumOfEntries(Context context) {
790 SharedPreferences pref = getSharedPreferences(context);
791 return pref.getBoolean("disable_scrolling_list", false)
792 ? getMaxNumOfColumns(context)
793 : Integer.valueOf(pref.getString("max_num_of_recents", "10"));
796 public static int getStatusBarHeight(Context context) {
797 return getSystemDimen(context, "status_bar_height");
800 private static int getNavbarHeight(Context context) {
801 return getSystemDimen(context, "navigation_bar_height");
804 private static int getSystemDimen(Context context, String id) {
806 int resourceId = context.getResources().getIdentifier(id, "dimen", "android");
808 value = context.getResources().getDimensionPixelSize(resourceId);
813 public static void refreshPinnedIcons(Context context) {
814 IconCache.getInstance(context).clearCache();
816 PinnedBlockedApps pba = PinnedBlockedApps.getInstance(context);
817 List<AppEntry> pinnedAppsList = new ArrayList<>(pba.getPinnedApps());
818 List<AppEntry> blockedAppsList = new ArrayList<>(pba.getBlockedApps());
819 PackageManager pm = context.getPackageManager();
823 for(AppEntry entry : pinnedAppsList) {
824 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
825 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
827 final List<UserHandle> userHandles = userManager.getUserProfiles();
828 LauncherActivityInfo appInfo = null;
830 for(UserHandle handle : userHandles) {
831 List<LauncherActivityInfo> list = launcherApps.getActivityList(entry.getPackageName(), handle);
832 if(!list.isEmpty()) {
833 // Google App workaround
834 if(!entry.getPackageName().equals("com.google.android.googlequicksearchbox"))
835 appInfo = list.get(0);
837 boolean added = false;
838 for(LauncherActivityInfo info : list) {
839 if(info.getName().equals("com.google.android.googlequicksearchbox.SearchActivity")) {
845 if(!added) appInfo = list.get(0);
852 if(appInfo != null) {
853 AppEntry newEntry = new AppEntry(
854 entry.getPackageName(),
855 entry.getComponentName(),
857 IconCache.getInstance(context).getIcon(context, pm, appInfo),
860 newEntry.setUserId(entry.getUserId(context));
861 pba.addPinnedApp(context, newEntry);
865 for(AppEntry entry : blockedAppsList) {
866 pba.addBlockedApp(context, entry);
870 public static boolean canEnableFreeform() {
871 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
874 @TargetApi(Build.VERSION_CODES.N)
875 public static boolean hasFreeformSupport(Context context) {
876 return canEnableFreeform()
877 && (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
878 || Settings.Global.getInt(context.getContentResolver(), "enable_freeform_support", 0) != 0
879 || (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
880 && Settings.Global.getInt(context.getContentResolver(), "force_resizable_activities", 0) != 0));
883 public static boolean canBootToFreeform(Context context) {
884 SharedPreferences pref = getSharedPreferences(context);
885 return hasFreeformSupport(context)
886 && pref.getBoolean("freeform_hack", false)
887 && !isOverridingFreeformHack(context);
890 public static boolean isSamsungDevice() {
891 return Build.MANUFACTURER.equalsIgnoreCase("Samsung");
894 private static boolean isNvidiaDevice() {
895 return Build.MANUFACTURER.equalsIgnoreCase("NVIDIA");
898 public static boolean isServiceRunning(Context context, Class<? extends Service> cls) {
899 if(LauncherHelper.getInstance().isOnSecondaryHomeScreen()
900 && (cls.equals(TaskbarService.class)
901 || cls.equals(StartMenuService.class)
902 || cls.equals(DashboardService.class)))
905 return isServiceRunning(context, cls.getName());
908 private static boolean isServiceRunning(Context context, String className) {
909 ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
910 for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
911 if(className.equals(service.service.getClassName()))
918 public static int getBackgroundTint(Context context) {
919 SharedPreferences pref = getSharedPreferences(context);
921 // Import old background tint preference
922 if(pref.contains("show_background")) {
923 SharedPreferences.Editor editor = pref.edit();
925 if(!pref.getBoolean("show_background", true))
926 editor.putInt("background_tint", Color.TRANSPARENT).apply();
928 editor.remove("show_background");
932 return pref.getInt("background_tint", context.getResources().getInteger(R.integer.tb_translucent_gray));
935 public static int getAccentColor(Context context) {
936 SharedPreferences pref = getSharedPreferences(context);
937 return pref.getInt("accent_color", context.getResources().getInteger(R.integer.tb_translucent_white));
940 public static boolean canDrawOverlays(Context context) {
941 return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context);
944 public static boolean isGame(Context context, String packageName) {
945 SharedPreferences pref = getSharedPreferences(context);
946 if(pref.getBoolean("launch_games_fullscreen", true)) {
947 PackageManager pm = context.getPackageManager();
950 ApplicationInfo info = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
951 return (info.flags & ApplicationInfo.FLAG_IS_GAME) != 0 || (info.metaData != null && info.metaData.getBoolean("isGame", false));
952 } catch (PackageManager.NameNotFoundException e) {
959 private static ActivityOptions getActivityOptions(View view) {
960 return getActivityOptions(null, null, view);
963 private static ActivityOptions getActivityOptions(Context context, ApplicationType applicationType, View view) {
964 ActivityOptions options;
966 options = ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight());
967 else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
968 options = ActivityOptions.makeBasic();
971 Constructor<ActivityOptions> constructor = ActivityOptions.class.getDeclaredConstructor();
972 constructor.setAccessible(true);
973 options = constructor.newInstance();
974 } catch (Exception e) {
979 if(applicationType == null)
984 switch(applicationType) {
986 if(FreeformHackHelper.getInstance().isFreeformHackActive())
987 stackId = getFreeformWindowModeId();
989 stackId = getFullscreenWindowModeId();
992 stackId = getFullscreenWindowModeId();
995 stackId = getFreeformWindowModeId();
998 if(hasBrokenSetLaunchBoundsApi()
999 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f))
1000 stackId = getFullscreenWindowModeId();
1005 Method method = ActivityOptions.class.getMethod(getWindowingModeMethodName(), int.class);
1006 method.invoke(options, stackId);
1007 } catch (Exception e) { /* Gracefully fail */ }
1009 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1010 int launchDisplayId = LauncherHelper.getInstance().getSecondaryDisplayId();
1011 if(launchDisplayId != -1)
1012 options.setLaunchDisplayId(launchDisplayId);
1018 private static int getFullscreenWindowModeId() {
1019 if(getCurrentApiVersion() >= 28.0f)
1020 return WINDOWING_MODE_FULLSCREEN;
1022 return FULLSCREEN_WORKSPACE_STACK_ID;
1025 private static int getFreeformWindowModeId() {
1026 if(getCurrentApiVersion() >= 28.0f)
1027 return WINDOWING_MODE_FREEFORM;
1029 return FREEFORM_WORKSPACE_STACK_ID;
1032 private static String getWindowingModeMethodName() {
1033 if(getCurrentApiVersion() >= 28.0f)
1034 return "setLaunchWindowingMode";
1036 return "setLaunchStackId";
1039 public static Bundle getActivityOptionsBundle(Context context, ApplicationType type, View view) {
1040 SharedPreferences pref = getSharedPreferences(context);
1042 return getActivityOptionsBundle(context, type, pref.getString("window_size", context.getString(R.string.tb_def_window_size)), view);
1045 private static Bundle getActivityOptionsBundle(Context context, ApplicationType type, String windowSize, View view) {
1046 SharedPreferences pref = getSharedPreferences(context);
1047 if(!canEnableFreeform() || !pref.getBoolean("freeform_hack", false))
1048 return getActivityOptions(view).toBundle();
1050 switch(windowSize) {
1052 return launchMode1(context, type, view);
1054 return launchMode2(context, MAXIMIZED, type, view);
1056 return launchMode2(context, LEFT, type, view);
1058 return launchMode2(context, RIGHT, type, view);
1060 return launchMode3(context, type, view);
1063 return getActivityOptions(context, type, view).toBundle();
1066 private static Bundle getActivityOptionsBundle(Context context, ApplicationType applicationType, View view,
1067 int left, int top, int right, int bottom) {
1068 ActivityOptions options = getActivityOptions(context, applicationType, view);
1072 if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
1073 return options.toBundle();
1075 return options.setLaunchBounds(new Rect(left, top, right, bottom)).toBundle();
1078 private static ApplicationType getApplicationType(Context context, String packageName) {
1079 return isGame(context, packageName) ? ApplicationType.GAME : ApplicationType.APPLICATION;
1082 public static boolean isSystemApp(Context context) {
1084 ApplicationInfo info = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
1085 int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
1086 return (info.flags & mask) != 0;
1087 } catch (PackageManager.NameNotFoundException e) {
1092 public static boolean isChromeOs(Context context) {
1093 return context.getPackageManager().hasSystemFeature("org.chromium.arc");
1096 public static boolean isBlissOs(Context context) {
1097 boolean validBlissOsBuildProp = false;
1099 String blissVersion = getSystemProperty("ro.bliss.version");
1100 if(blissVersion != null && !blissVersion.isEmpty())
1101 validBlissOsBuildProp = true;
1103 String buildUser = getSystemProperty("ro.build.user");
1104 if(buildUser != null && buildUser.equals("electrikjesus"))
1105 validBlissOsBuildProp = true;
1107 return validBlissOsBuildProp
1108 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1109 && isSystemApp(context);
1112 public static boolean isLauncherPermanentlyEnabled(Context context) {
1113 if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
1116 PackageManager pm = context.getPackageManager();
1118 pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1119 return pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, context.getPackageName()) == PackageManager.SIGNATURE_MATCH
1120 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1121 && isSystemApp(context);
1122 } catch (PackageManager.NameNotFoundException e) {
1127 public static boolean hasSupportLibrary(Context context, int minVersion) {
1128 PackageManager pm = context.getPackageManager();
1130 PackageInfo pInfo = pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1131 return pInfo.versionCode >= minVersion
1132 && pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, context.getPackageName()) == PackageManager.SIGNATURE_MATCH
1133 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1134 && isSystemApp(context);
1135 } catch (PackageManager.NameNotFoundException e) {
1140 public static int getBaseTaskbarSize(Context context) {
1141 return Math.round(getBaseTaskbarSizeFloat(context));
1144 private static float getBaseTaskbarSizeFloat(Context context) {
1145 SharedPreferences pref = getSharedPreferences(context);
1146 float baseTaskbarSize = context.getResources().getDimension(R.dimen.tb_base_taskbar_size);
1147 boolean navbarButtonsEnabled = false;
1149 if(pref.getBoolean("dashboard", context.getResources().getBoolean(R.bool.tb_def_dashboard)))
1150 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_dashboard_button_size);
1152 if(pref.getBoolean("button_back", false)) {
1153 navbarButtonsEnabled = true;
1154 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1157 if(pref.getBoolean("button_home", false)) {
1158 navbarButtonsEnabled = true;
1159 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1162 if(pref.getBoolean("button_recents", false)) {
1163 navbarButtonsEnabled = true;
1164 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1167 if(navbarButtonsEnabled)
1168 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_navbar_buttons_margin);
1170 if(isSystemTrayEnabled(context))
1171 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_systray_size);
1173 return baseTaskbarSize;
1176 private static void startTaskbarService(Context context, boolean fullRestart) {
1177 context.startService(new Intent(context, TaskbarService.class));
1178 context.startService(new Intent(context, StartMenuService.class));
1179 context.startService(new Intent(context, DashboardService.class));
1180 if(fullRestart) context.startService(new Intent(context, NotificationService.class));
1183 private static void stopTaskbarService(Context context, boolean fullRestart) {
1184 context.stopService(new Intent(context, TaskbarService.class));
1185 context.stopService(new Intent(context, StartMenuService.class));
1186 context.stopService(new Intent(context, DashboardService.class));
1187 if(fullRestart) context.stopService(new Intent(context, NotificationService.class));
1190 public static void restartTaskbar(Context context) {
1191 SharedPreferences pref = getSharedPreferences(context);
1192 if(pref.getBoolean("taskbar_active", false) && !pref.getBoolean("is_hidden", false)) {
1194 .putBoolean("is_restarting", true)
1195 .putBoolean("skip_auto_hide_navbar", true)
1198 stopTaskbarService(context, true);
1199 startTaskbarService(context, true);
1200 } else if(isServiceRunning(context, StartMenuService.class)) {
1201 pref.edit().putBoolean("skip_auto_hide_navbar", true).apply();
1203 stopTaskbarService(context, false);
1204 startTaskbarService(context, false);
1207 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.RESTART"));
1210 public static void restartNotificationService(Context context) {
1211 if(isServiceRunning(context, NotificationService.class)) {
1212 SharedPreferences pref = getSharedPreferences(context);
1213 pref.edit().putBoolean("is_restarting", true).apply();
1215 Intent intent = new Intent(context, NotificationService.class);
1216 context.stopService(intent);
1217 context.startService(intent);
1221 public static void showHideNavigationBar(Context context, boolean show) {
1222 if(hasSupportLibrary(context, 7)) {
1223 Intent intent = new Intent(BuildConfig.SUPPORT_APPLICATION_ID + ".CHANGE_OVERSCAN");
1224 intent.setPackage(BuildConfig.SUPPORT_APPLICATION_ID);
1226 intent.putExtra("display_id", getDisplayID());
1227 intent.putExtra("value", show ? 0 : getNavbarHeight(context) * -1);
1229 context.sendBroadcast(intent);
1233 // Show or hide the system navigation bar on Bliss-x86
1235 if(getCurrentApiVersion() >= 28.0f)
1236 Settings.Secure.putInt(context.getContentResolver(), "navigation_bar_visible", show ? 1 : 0);
1238 Settings.System.putInt(context.getContentResolver(), "navigation_bar_show", show ? 1 : 0);
1239 } catch (Exception e) { /* Gracefully fail */ }
1242 public static void initPrefs(Context context) {
1243 // Enable freeform hack automatically on supported devices
1244 SharedPreferences pref = getSharedPreferences(context);
1245 if(canEnableFreeform()) {
1246 if(!pref.getBoolean("freeform_hack_override", false)) {
1248 .putBoolean("freeform_hack", hasFreeformSupport(context) && !isSamsungDevice())
1249 .putBoolean("save_window_sizes", false)
1250 .putBoolean("freeform_hack_override", true)
1252 } else if(!hasFreeformSupport(context)) {
1253 pref.edit().putBoolean("freeform_hack", false).apply();
1255 stopFreeformHack(context);
1258 boolean freeformWasEnabled = pref.getBoolean("freeform_hack", false)
1259 || pref.getBoolean("show_freeform_disabled_message", false);
1262 .putBoolean("freeform_hack", false)
1263 .putBoolean("show_freeform_disabled_message", freeformWasEnabled)
1266 SavedWindowSizes.getInstance(context).clear(context);
1267 stopFreeformHack(context);
1270 // Customizations for BlissOS
1271 if(isBlissOs(context) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1272 && !pref.getBoolean("bliss_os_prefs", false)) {
1273 SharedPreferences.Editor editor = pref.edit();
1275 if(hasFreeformSupport(context)) {
1276 editor.putBoolean("freeform_hack", true);
1279 editor.putString("recents_amount", "running_apps_only");
1280 editor.putString("refresh_frequency", "0");
1281 editor.putString("max_num_of_recents", "2147483647");
1282 editor.putString("sort_order", "true");
1283 editor.putString("start_button_image", "app_logo");
1284 editor.putBoolean("button_back", true);
1285 editor.putBoolean("button_home", true);
1286 editor.putBoolean("button_recents", true);
1287 editor.putBoolean("auto_hide_navbar", true);
1288 editor.putBoolean("shortcut_icon", false);
1289 editor.putBoolean("bliss_os_prefs", true);
1293 // Customizations for Android-x86 devices (non-Bliss)
1294 if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
1295 && isSystemApp(context)
1296 && !pref.getBoolean("android_x86_prefs", false)) {
1298 .putString("recents_amount", "running_apps_only")
1299 .putString("refresh_frequency", "0")
1300 .putString("max_num_of_recents", "2147483647")
1301 .putString("sort_order", "true")
1302 .putBoolean("shortcut_icon", false)
1303 .putBoolean("android_x86_prefs", true)
1308 public static DisplayInfo getDisplayInfo(Context context) {
1309 return getDisplayInfo(context, false);
1312 public static DisplayInfo getDisplayInfo(Context context, boolean fromTaskbar) {
1313 context = context.getApplicationContext();
1314 int displayID = getDisplayID();
1316 DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1317 Display currentDisplay = null;
1319 for(Display display : dm.getDisplays()) {
1320 if(display.getDisplayId() == displayID) {
1321 currentDisplay = display;
1326 if(currentDisplay == null)
1327 return new DisplayInfo(0, 0, 0);
1329 DisplayMetrics metrics = new DisplayMetrics();
1330 currentDisplay.getMetrics(metrics);
1332 DisplayMetrics realMetrics = new DisplayMetrics();
1333 currentDisplay.getRealMetrics(realMetrics);
1335 DisplayInfo info = new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);
1337 if(isChromeOs(context)) {
1338 SharedPreferences pref = getSharedPreferences(context);
1339 if(!pref.getBoolean("chrome_os_context_menu_fix", true)) {
1340 info.width = realMetrics.widthPixels;
1341 info.height = realMetrics.heightPixels;
1347 // Workaround for incorrect display size on devices with notches in landscape mode
1348 if(fromTaskbar && context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)
1351 boolean sameWidth = metrics.widthPixels == realMetrics.widthPixels;
1352 boolean sameHeight = metrics.heightPixels == realMetrics.heightPixels;
1354 if(sameWidth && !sameHeight) {
1355 info.width = realMetrics.widthPixels;
1356 info.height = realMetrics.heightPixels - getNavbarHeight(context);
1359 if(!sameWidth && sameHeight) {
1360 info.width = realMetrics.widthPixels - getNavbarHeight(context);
1361 info.height = realMetrics.heightPixels;
1367 private static int getDisplayID() {
1368 LauncherHelper helper = LauncherHelper.getInstance();
1370 if(helper.isOnSecondaryHomeScreen())
1371 return helper.getSecondaryDisplayId();
1373 return Display.DEFAULT_DISPLAY;
1376 public static void pinAppShortcut(Context context) {
1377 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1378 ShortcutManager mShortcutManager = context.getSystemService(ShortcutManager.class);
1380 if(mShortcutManager.isRequestPinShortcutSupported()) {
1381 ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder(context, "freeform_mode").build();
1383 mShortcutManager.requestPinShortcut(pinShortcutInfo, null);
1385 showToastLong(context, R.string.tb_pin_shortcut_not_supported);
1387 Intent intent = ShortcutUtils.getShortcutIntent(context);
1388 intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
1389 intent.putExtra("duplicate", false);
1391 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1392 homeIntent.addCategory(Intent.CATEGORY_HOME);
1393 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
1395 intent.setPackage(defaultLauncher.activityInfo.packageName);
1396 context.sendBroadcast(intent);
1398 showToast(context, R.string.tb_shortcut_created);
1402 public static boolean shouldCollapse(Context context, boolean pendingAppLaunch) {
1403 SharedPreferences pref = getSharedPreferences(context);
1404 if(pref.getBoolean("hide_taskbar", true)) {
1405 if(isOverridingFreeformHack(context))
1406 return !LauncherHelper.getInstance().isOnHomeScreen();
1408 FreeformHackHelper helper = FreeformHackHelper.getInstance();
1409 if(pendingAppLaunch)
1410 return !helper.isFreeformHackActive();
1412 return !helper.isInFreeformWorkspace();
1418 public static boolean isOverridingFreeformHack(Context context) {
1419 SharedPreferences pref = getSharedPreferences(context);
1420 return pref.getBoolean("freeform_hack", false)
1421 && ((isChromeOs(context) && pref.getBoolean("chrome_os_context_menu_fix", true))
1422 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f));
1425 public static boolean isPlayStoreInstalled(Context context) {
1427 context.getPackageManager().getPackageInfo("com.android.vending", 0);
1429 } catch (PackageManager.NameNotFoundException e) {
1434 private static float getCurrentApiVersion() {
1435 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1436 return Float.valueOf(Build.VERSION.SDK_INT + "." + Build.VERSION.PREVIEW_SDK_INT);
1438 return (float) Build.VERSION.SDK_INT;
1441 public static boolean hasBrokenSetLaunchBoundsApi() {
1442 return getCurrentApiVersion() >= 26.0f
1443 && getCurrentApiVersion() < 28.0f
1444 && !isSamsungDevice()
1445 && !isNvidiaDevice();
1448 public static String getSecondScreenPackageName(Context context) {
1449 return getInstalledPackage(context, Arrays.asList(
1450 "com.farmerbb.secondscreen.free",
1451 "com.farmerbb.secondscreen"));
1454 // Returns the name of an installed package from a list of package names, in order of preference
1455 private static String getInstalledPackage(Context context, List<String> packageNames) {
1456 if(packageNames == null || packageNames.isEmpty())
1459 List<String> packages = packageNames instanceof ArrayList ? packageNames : new ArrayList<>(packageNames);
1460 String packageName = packages.get(0);
1463 context.getPackageManager().getPackageInfo(packageName, 0);
1465 } catch (PackageManager.NameNotFoundException e) {
1467 return getInstalledPackage(context, packages);
1471 public static void showRecentAppsDialog(Context context) {
1472 showRecentAppsDialog(context, null, null);
1475 public static AlertDialog showRecentAppsDialog(Context context, Runnable onError, Runnable onFinish) {
1476 Runnable finalOnFinish = onFinish == null
1480 Runnable finalOnError = onError == null
1481 ? () -> showErrorDialog(context, "GET_USAGE_STATS", finalOnFinish)
1484 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isSystemApp(context)) {
1485 ApplicationInfo applicationInfo = null;
1487 applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
1488 } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
1490 if(applicationInfo != null) {
1491 AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
1492 int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
1494 if(mode != AppOpsManager.MODE_ALLOWED) {
1495 AlertDialog.Builder builder = new AlertDialog.Builder(context);
1496 builder.setTitle(R.string.tb_pref_header_recent_apps)
1497 .setMessage(R.string.tb_enable_recent_apps)
1498 .setPositiveButton(R.string.tb_action_ok, (dialog, which) -> {
1500 context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
1501 showToastLong(context, R.string.tb_usage_stats_message);
1503 finalOnFinish.run();
1504 } catch (ActivityNotFoundException e) {
1508 .setNegativeButton(R.string.tb_action_cancel, (dialog, which) -> finalOnFinish.run());
1510 AlertDialog dialog = builder.create();
1512 dialog.setCancelable(false);
1519 finalOnFinish.run();
1523 public static Context wrapContext(Context context) {
1524 SharedPreferences pref = getSharedPreferences(context);
1527 switch(pref.getString("theme", "light")) {
1529 theme = R.style.Taskbar;
1532 theme = R.style.Taskbar_Dark;
1536 return theme > -1 ? new ContextThemeWrapper(context, theme) : context;
1539 public static boolean isPlayStoreRelease(Context context) {
1540 return isPlayStoreRelease(context, context.getPackageName());
1543 @SuppressLint("PackageManagerGetSignatures")
1544 public static boolean isPlayStoreRelease(Context context, String packageName) {
1545 Signature playStoreSignature = new Signature(context.getString(R.string.tb_signature));
1547 PackageManager pm = context.getPackageManager();
1548 PackageInfo info = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
1549 for(Signature signature : info.signatures) {
1550 if(signature.equals(playStoreSignature))
1553 } catch (Exception e) { /* Gracefully fail */ }
1558 public static boolean isExternalAccessDisabled(Context context) {
1559 SharedPreferences pref = getSharedPreferences(context);
1560 return !pref.getBoolean("tasker_enabled", true);
1563 public static boolean enableFreeformModeShortcut(Context context) {
1564 return canEnableFreeform()
1565 && !isOverridingFreeformHack(context)
1566 && !isChromeOs(context);
1569 public static void startForegroundService(Context context, Intent intent) {
1570 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1571 if(Settings.canDrawOverlays(context))
1572 context.startForegroundService(intent);
1574 context.startService(intent);
1577 public static int getOverlayType() {
1578 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
1579 ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
1580 : WindowManager.LayoutParams.TYPE_PHONE;
1583 public static boolean isDelegatingHomeActivity(Context context) {
1584 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1585 homeIntent.addCategory(Intent.CATEGORY_HOME);
1587 final List<ResolveInfo> listOfLaunchers = context.getPackageManager().queryIntentActivities(homeIntent, 0);
1588 for(ResolveInfo launcher : listOfLaunchers) {
1589 if(launcher.activityInfo.packageName.equals(BuildConfig.SUPPORT_APPLICATION_ID))
1596 @SuppressLint("PrivateApi")
1597 private static String getSystemProperty(String key) {
1599 Class<?> cls = Class.forName("android.os.SystemProperties");
1600 return cls.getMethod("get", String.class).invoke(null, key).toString();
1601 } catch (Exception e) {
1606 @SuppressWarnings("ResultOfMethodCallIgnored")
1607 public static boolean importCustomStartButtonImage(Context context, Uri uri) {
1609 File imagesDir = new File(context.getFilesDir(), "tb_images");
1612 File importedFile = new File(imagesDir, "custom_image_new");
1613 if(importedFile.exists()) importedFile.delete();
1615 BufferedInputStream is = new BufferedInputStream(context.getContentResolver().openInputStream(uri));
1616 byte[] data = new byte[is.available()];
1618 if(data.length > 0) {
1619 BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(importedFile));
1626 File prevFile = new File(imagesDir, "custom_image");
1627 if(prevFile.exists()) prevFile.delete();
1629 importedFile.renameTo(prevFile);
1631 } catch (IOException e) {
1636 public static String getDefaultStartButtonImage(Context context) {
1637 SharedPreferences pref = getSharedPreferences(context);
1638 return pref.getBoolean("app_drawer_icon", false)
1643 private static boolean shouldLaunchTouchAbsorber(Context context) {
1644 return isOverridingFreeformHack(context) && !isChromeOs(context) && getCurrentApiVersion() < 29.0f;
1647 public static boolean isDesktopIconsEnabled(Context context) {
1648 return !canBootToFreeform(context) && !shouldLaunchTouchAbsorber(context);
1651 public static boolean isSystemTrayEnabled(Context context) {
1652 SharedPreferences pref = getSharedPreferences(context);
1654 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1655 && pref.getBoolean("sys_tray", context.getResources().getBoolean(R.bool.tb_def_sys_tray))
1656 && pref.getBoolean("full_length", context.getResources().getBoolean(R.bool.tb_def_full_length))
1657 && !getTaskbarPosition(context).contains("vertical");
1660 @SuppressWarnings("deprecation")
1661 public static boolean isLibrary(Context context) {
1662 return !context.getPackageName().equals(BuildConfig.APPLICATION_ID);
1665 public static boolean applyDisplayCutoutModeTo(WindowManager.LayoutParams params) {
1666 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
1667 params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;