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.annotation.SuppressLint;
20 import android.annotation.TargetApi;
21 import android.app.ActivityManager;
22 import android.app.ActivityOptions;
23 import android.app.AlertDialog;
24 import android.app.AppOpsManager;
25 import android.app.Service;
26 import android.content.ActivityNotFoundException;
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.SharedPreferences;
31 import android.content.pm.ActivityInfo;
32 import android.content.pm.ApplicationInfo;
33 import android.content.pm.LauncherActivityInfo;
34 import android.content.pm.LauncherApps;
35 import android.content.pm.PackageInfo;
36 import android.content.pm.PackageManager;
37 import android.content.pm.ResolveInfo;
38 import android.content.pm.ShortcutInfo;
39 import android.content.pm.ShortcutManager;
40 import android.content.pm.Signature;
41 import android.content.res.Configuration;
42 import android.graphics.Color;
43 import android.graphics.Rect;
44 import android.hardware.display.DisplayManager;
45 import android.net.Uri;
46 import android.os.Build;
47 import android.os.Bundle;
48 import android.os.Handler;
49 import android.os.Process;
50 import android.os.UserHandle;
51 import android.os.UserManager;
52 import android.provider.Settings;
53 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
54 import androidx.appcompat.view.ContextThemeWrapper;
55 import android.util.DisplayMetrics;
56 import android.view.Display;
57 import android.view.Surface;
58 import android.view.View;
59 import android.view.WindowManager;
60 import android.widget.Toast;
62 import com.farmerbb.taskbar.BuildConfig;
63 import com.farmerbb.taskbar.R;
64 import com.farmerbb.taskbar.activity.ContextMenuActivity;
65 import com.farmerbb.taskbar.activity.DummyActivity;
66 import com.farmerbb.taskbar.activity.InvisibleActivityFreeform;
67 import com.farmerbb.taskbar.activity.TouchAbsorberActivity;
68 import com.farmerbb.taskbar.activity.dark.ContextMenuActivityDark;
69 import com.farmerbb.taskbar.service.DashboardService;
70 import com.farmerbb.taskbar.service.NotificationService;
71 import com.farmerbb.taskbar.service.PowerMenuService;
72 import com.farmerbb.taskbar.service.StartMenuService;
73 import com.farmerbb.taskbar.service.TaskbarService;
75 import java.io.BufferedInputStream;
76 import java.io.BufferedOutputStream;
78 import java.io.FileOutputStream;
79 import java.io.IOException;
80 import java.lang.reflect.Method;
81 import java.util.ArrayList;
82 import java.util.Arrays;
83 import java.util.List;
89 private static Integer cachedRotation;
91 private static final int MAXIMIZED = 0;
92 private static final int LEFT = -1;
93 private static final int RIGHT = 1;
95 public static final int HIDDEN = 0;
96 public static final int TOP_APPS = 1;
98 // From android.app.ActivityManager.StackId
99 private static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
100 private static final int FREEFORM_WORKSPACE_STACK_ID = 2;
102 // From android.app.WindowConfiguration
103 private static final int WINDOWING_MODE_FULLSCREEN = 1;
104 private static final int WINDOWING_MODE_FREEFORM = 5;
106 @SuppressWarnings("deprecation")
107 public static SharedPreferences getSharedPreferences(Context context) {
108 return context.getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", Context.MODE_PRIVATE);
111 public static void showPermissionDialog(Context context) {
112 showPermissionDialog(context, null, null);
115 @TargetApi(Build.VERSION_CODES.M)
116 public static AlertDialog showPermissionDialog(Context context, Runnable onError, Runnable onFinish) {
117 Runnable finalOnFinish = onFinish == null
121 Runnable finalOnError = onError == null
122 ? () -> showErrorDialog(context, "SYSTEM_ALERT_WINDOW", finalOnFinish)
125 AlertDialog.Builder builder = new AlertDialog.Builder(context);
126 builder.setTitle(R.string.tb_permission_dialog_title)
127 .setMessage(R.string.tb_permission_dialog_message)
128 .setPositiveButton(R.string.tb_action_grant_permission, (dialog, which) -> {
130 context.startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
131 Uri.parse("package:" + context.getPackageName())));
134 } catch (ActivityNotFoundException e) {
139 AlertDialog dialog = builder.create();
141 dialog.setCancelable(false);
146 public static AlertDialog showErrorDialog(Context context, String appopCmd) {
147 return showErrorDialog(context, appopCmd, null);
150 private static AlertDialog showErrorDialog(Context context, String appopCmd, Runnable onFinish) {
151 Runnable finalOnFinish = onFinish == null
155 AlertDialog.Builder builder = new AlertDialog.Builder(context);
156 builder.setTitle(R.string.tb_error_dialog_title)
157 .setMessage(context.getString(R.string.tb_error_dialog_message, context.getPackageName(), appopCmd))
158 .setPositiveButton(R.string.tb_action_ok, (dialog, which) -> finalOnFinish.run());
160 AlertDialog dialog = builder.create();
162 dialog.setCancelable(false);
167 public static void sendAccessibilityAction(Context context, int action) {
168 sendAccessibilityAction(context, action, null);
171 public static void sendAccessibilityAction(Context context, int action, Runnable onComplete) {
172 ComponentName component = new ComponentName(context, PowerMenuService.class);
173 context.getPackageManager().setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
174 PackageManager.DONT_KILL_APP);
176 boolean isAccessibilityServiceEnabled = isAccessibilityServiceEnabled(context);
178 if(!isAccessibilityServiceEnabled
179 && hasWriteSecureSettingsPermission(context)) {
180 String services = Settings.Secure.getString(context.getContentResolver(),
181 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
183 String finalServices = services == null ? "" : services;
185 String powerMenuService = new ComponentName(context, PowerMenuService.class).flattenToString();
187 if(!finalServices.contains(powerMenuService)) {
189 Settings.Secure.putString(context.getContentResolver(),
190 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
191 finalServices.isEmpty()
193 : finalServices + ":" + powerMenuService);
194 } catch (Exception e) { /* Gracefully fail */ }
197 new Handler().postDelayed(() -> {
198 Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
199 intent.putExtra("action", action);
200 LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
203 Settings.Secure.putString(context.getContentResolver(),
204 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
206 } catch (Exception e) { /* Gracefully fail */ }
208 if(onComplete != null) onComplete.run();
210 } else if(isAccessibilityServiceEnabled) {
211 Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
212 intent.putExtra("action", action);
213 LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
215 if(onComplete != null) onComplete.run();
217 launchApp(context, () -> {
218 Intent intent = new Intent(context, DummyActivity.class);
219 intent.putExtra("accessibility", true);
220 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
223 context.startActivity(intent, getActivityOptionsBundle(context, ApplicationType.APPLICATION, null));
224 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
229 public static boolean isAccessibilityServiceEnabled(Context context) {
230 String accessibilityServices = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
231 ComponentName component = new ComponentName(context, PowerMenuService.class);
233 return accessibilityServices != null
234 && (accessibilityServices.contains(component.flattenToString())
235 || accessibilityServices.contains(component.flattenToShortString()));
238 public static boolean hasWriteSecureSettingsPermission(Context context) {
239 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
240 && context.checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) == PackageManager.PERMISSION_GRANTED;
243 public static void showToast(Context context, int message) {
244 showToast(context, context.getString(message), Toast.LENGTH_SHORT);
247 public static void showToastLong(Context context, int message) {
248 showToast(context, context.getString(message), Toast.LENGTH_LONG);
251 public static void showToast(Context context, String message, int length) {
254 ToastInterface toast = DependencyUtils.createToast(context.getApplicationContext(), message, length);
257 ToastHelper.getInstance().setLastToast(toast);
260 public static void cancelToast() {
261 ToastInterface toast = ToastHelper.getInstance().getLastToast();
262 if(toast != null) toast.cancel();
265 public static void startShortcut(Context context, AppEntry entry, ShortcutInfo shortcut, View view) {
275 public static void launchApp(final Context context,
276 final AppEntry entry,
277 final String windowSize,
278 final boolean launchedFromTaskbar,
279 final boolean openInNewWindow,
290 private static void launchApp(final Context context,
291 final AppEntry entry,
292 final String windowSize,
293 final boolean launchedFromTaskbar,
294 final boolean openInNewWindow,
295 final ShortcutInfo shortcut,
297 launchApp(context, launchedFromTaskbar, () -> continueLaunchingApp(context, entry,
298 windowSize, openInNewWindow, shortcut, view));
301 public static void launchApp(Context context, Runnable runnable) {
302 launchApp(context, true, runnable);
305 private static void launchApp(Context context, boolean launchedFromTaskbar, Runnable runnable) {
306 SharedPreferences pref = getSharedPreferences(context);
307 FreeformHackHelper helper = FreeformHackHelper.getInstance();
309 boolean specialLaunch = hasBrokenSetLaunchBoundsApi()
310 && helper.isInFreeformWorkspace()
311 && MenuHelper.getInstance().isContextMenuOpen();
313 boolean noAnimation = pref.getBoolean("disable_animations", false);
315 if(hasFreeformSupport(context)
316 && pref.getBoolean("freeform_hack", false)
317 && (!helper.isInFreeformWorkspace() || specialLaunch)) {
318 new Handler().postDelayed(() -> {
319 startFreeformHack(context, true);
321 new Handler().postDelayed(runnable, helper.isFreeformHackActive() ? 0 : 100);
322 }, launchedFromTaskbar ? 0 : 100);
324 new Handler().postDelayed(runnable, !launchedFromTaskbar && noAnimation ? 100 : 0);
327 public static void startFreeformHack(Context context) {
328 startFreeformHack(context, false);
331 @TargetApi(Build.VERSION_CODES.N)
332 public static void startFreeformHack(Context context, boolean checkMultiWindow) {
333 Intent freeformHackIntent = new Intent(context, InvisibleActivityFreeform.class);
334 freeformHackIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
335 | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
336 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
339 freeformHackIntent.putExtra("check_multiwindow", true);
341 if(canDrawOverlays(context))
342 startActivityLowerRight(context, freeformHackIntent);
345 public static void stopFreeformHack(Context context) {
346 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.FINISH_FREEFORM_ACTIVITY"));
348 if(isOverridingFreeformHack(context)) {
349 FreeformHackHelper helper = FreeformHackHelper.getInstance();
350 helper.setFreeformHackActive(false);
351 helper.setInFreeformWorkspace(false);
355 @TargetApi(Build.VERSION_CODES.N)
356 private static void continueLaunchingApp(Context context,
359 boolean openInNewWindow,
360 ShortcutInfo shortcut,
362 SharedPreferences pref = getSharedPreferences(context);
363 Intent intent = new Intent();
364 intent.setComponent(ComponentName.unflattenFromString(entry.getComponentName()));
365 intent.setAction(Intent.ACTION_MAIN);
366 intent.addCategory(Intent.CATEGORY_LAUNCHER);
367 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
368 intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
370 if(FreeformHackHelper.getInstance().isInFreeformWorkspace()
371 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1)
372 intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
374 if(pref.getBoolean("disable_animations", false))
375 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
377 boolean realOpenInNewWindow = openInNewWindow || pref.getBoolean("force_new_window", false);
378 if(realOpenInNewWindow) {
379 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
381 ActivityInfo activityInfo = intent.resolveActivityInfo(context.getPackageManager(), 0);
382 if(activityInfo != null) {
383 switch(activityInfo.launchMode) {
384 case ActivityInfo.LAUNCH_SINGLE_TASK:
385 case ActivityInfo.LAUNCH_SINGLE_INSTANCE:
386 intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
392 ApplicationType type = getApplicationType(context, entry.getPackageName());
394 if(windowSize == null)
395 windowSize = SavedWindowSizes.getInstance(context).getWindowSize(context, entry.getPackageName());
397 Bundle bundle = getActivityOptionsBundle(context, type, windowSize, view);
399 prepareToStartActivity(context, realOpenInNewWindow, () -> {
400 if(shortcut == null) {
401 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
402 if(entry.getUserId(context) == userManager.getSerialNumberForUser(Process.myUserHandle())) {
404 context.startActivity(intent, bundle);
405 } catch (ActivityNotFoundException e) {
406 launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context));
407 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
409 launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context));
411 launchShortcut(context, shortcut, bundle);
414 if(shouldCollapse(context, true))
415 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_TASKBAR"));
417 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_START_MENU"));
420 @TargetApi(Build.VERSION_CODES.N)
421 private static Bundle launchMode1(Context context, ApplicationType type, View view) {
422 DisplayInfo display = getDisplayInfo(context);
424 int width1 = display.width / 8;
425 int width2 = display.width - width1;
426 int height1 = display.height / 8;
427 int height2 = display.height - height1;
429 return getActivityOptions(context, type, view).setLaunchBounds(new Rect(
437 @TargetApi(Build.VERSION_CODES.N)
438 private static Bundle launchMode2(Context context, int launchType, ApplicationType type, View view) {
439 DisplayInfo display = getDisplayInfo(context);
441 int statusBarHeight = getStatusBarHeight(context);
442 String position = getTaskbarPosition(context);
444 boolean isPortrait = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
445 boolean isLandscape = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
448 int top = statusBarHeight;
449 int right = display.width;
450 int bottom = display.height;
452 int iconSize = isOverridingFreeformHack(context) && !LauncherHelper.getInstance().isOnHomeScreen()
454 : context.getResources().getDimensionPixelSize(R.dimen.tb_icon_size);
456 if(position.contains("vertical_left"))
457 left = left + iconSize;
458 else if(position.contains("vertical_right"))
459 right = right - iconSize;
460 else if(position.contains("bottom"))
461 bottom = bottom - iconSize;
463 top = top + iconSize;
465 int halfLandscape = (right / 2) + ((iconSize / 2) * (position.contains("vertical_left") ? 1 : 0));
466 int halfPortrait = (bottom / 2) + ((iconSize / 2) * ((position.equals("top_left") || position.equals("top_right")) ? 1 : 0));
468 if(launchType == RIGHT && isLandscape)
469 left = halfLandscape;
470 else if(launchType == RIGHT && isPortrait)
472 else if(launchType == LEFT && isLandscape)
473 right = halfLandscape;
474 else if(launchType == LEFT && isPortrait)
475 bottom = halfPortrait;
477 return getActivityOptions(context, type, view)
478 .setLaunchBounds(new Rect(left, top, right, bottom)).toBundle();
481 @TargetApi(Build.VERSION_CODES.N)
482 private static Bundle launchMode3(Context context, ApplicationType type, View view) {
483 DisplayInfo display = getDisplayInfo(context);
485 int width1 = display.width / 2;
486 int width2 = context.getResources().getDimensionPixelSize(R.dimen.tb_phone_size_width) / 2;
487 int height1 = display.height / 2;
488 int height2 = context.getResources().getDimensionPixelSize(R.dimen.tb_phone_size_height) / 2;
490 return getActivityOptions(context, type, view).setLaunchBounds(new Rect(
498 private static void launchAndroidForWork(Context context, ComponentName componentName, Bundle bundle, long userId) {
499 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
500 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
503 launcherApps.startMainActivity(componentName, userManager.getUserForSerialNumber(userId), null, bundle);
504 } catch (ActivityNotFoundException | NullPointerException e) { /* Gracefully fail */ }
507 @TargetApi(Build.VERSION_CODES.N_MR1)
508 private static void launchShortcut(Context context, ShortcutInfo shortcut, Bundle bundle) {
509 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
511 if(launcherApps.hasShortcutHostPermission()) {
513 launcherApps.startShortcut(shortcut, null, bundle);
514 } catch (ActivityNotFoundException | NullPointerException e) { /* Gracefully fail */ }
518 private static void prepareToStartActivity(Context context, boolean openInNewWindow, Runnable runnable) {
519 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_CONTEXT_MENU"));
521 if(!FreeformHackHelper.getInstance().isTouchAbsorberActive()
522 && shouldLaunchTouchAbsorber(context)) {
523 startTouchAbsorberActivity(context);
524 new Handler().postDelayed(runnable, 100);
525 } else if(openInNewWindow) {
526 Intent intent = new Intent(context, DummyActivity.class);
527 intent.putExtra("finish_on_pause", true);
528 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
529 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
530 startActivityLowerRight(context, intent);
532 new Handler().postDelayed(runnable, 100);
537 public static void startActivityMaximized(Context context, Intent intent) {
538 Bundle bundle = launchMode2(context, MAXIMIZED, ApplicationType.CONTEXT_MENU, null);
539 prepareToStartActivity(context, false, () -> context.startActivity(intent, bundle));
542 @TargetApi(Build.VERSION_CODES.N)
543 public static void startActivityLowerRight(Context context, Intent intent) {
544 DisplayInfo display = getDisplayInfo(context);
546 context.startActivity(intent,
547 getActivityOptions(context, ApplicationType.FREEFORM_HACK, null)
548 .setLaunchBounds(new Rect(
554 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
557 @TargetApi(Build.VERSION_CODES.N)
558 public static void startTouchAbsorberActivity(Context context) {
559 String position = getTaskbarPosition(context);
560 DisplayInfo display = getDisplayInfo(context);
564 int right = display.width;
565 int bottom = display.height;
567 int iconSize = context.getResources().getDimensionPixelSize(R.dimen.tb_icon_size);
569 if(position.contains("vertical_left"))
571 else if(position.contains("vertical_right"))
572 left = right - iconSize;
573 else if(position.contains("bottom"))
574 top = bottom - iconSize;
578 Intent intent = new Intent(context, TouchAbsorberActivity.class);
579 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
580 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
583 context.startActivity(intent,
584 getActivityOptions(context, ApplicationType.FREEFORM_HACK, null)
585 .setLaunchBounds(new Rect(left, top, right, bottom)).toBundle());
586 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
589 public static void startContextMenuActivity(Context context, Bundle args) {
590 SharedPreferences pref = getSharedPreferences(context);
591 Intent intent = null;
593 switch(pref.getString("theme", "light")) {
595 intent = new Intent(context, ContextMenuActivity.class);
598 intent = new Intent(context, ContextMenuActivityDark.class);
603 intent.putExtra("args", args);
604 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
607 if(hasFreeformSupport(context) && FreeformHackHelper.getInstance().isInFreeformWorkspace()) {
608 DisplayInfo display = getDisplayInfo(context);
610 if(intent != null && hasBrokenSetLaunchBoundsApi())
611 intent.putExtra("context_menu_fix", true);
613 context.startActivity(intent,
614 getActivityOptions(context, ApplicationType.CONTEXT_MENU, null)
616 new Rect(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 @TargetApi(Build.VERSION_CODES.M)
941 public static boolean canDrawOverlays(Context context) {
942 return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context);
945 public static boolean isGame(Context context, String packageName) {
946 SharedPreferences pref = getSharedPreferences(context);
947 if(pref.getBoolean("launch_games_fullscreen", true)) {
948 PackageManager pm = context.getPackageManager();
951 ApplicationInfo info = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
952 return (info.flags & ApplicationInfo.FLAG_IS_GAME) != 0 || (info.metaData != null && info.metaData.getBoolean("isGame", false));
953 } catch (PackageManager.NameNotFoundException e) {
960 private static ActivityOptions getActivityOptions(View view) {
961 return getActivityOptions(null, null, view);
964 @TargetApi(Build.VERSION_CODES.N)
965 private static ActivityOptions getActivityOptions(Context context, ApplicationType applicationType, View view) {
966 ActivityOptions options;
968 options = ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight());
970 options = ActivityOptions.makeBasic();
972 if(applicationType == null)
977 switch(applicationType) {
979 if(FreeformHackHelper.getInstance().isFreeformHackActive())
980 stackId = getFreeformWindowModeId();
982 stackId = getFullscreenWindowModeId();
985 stackId = getFullscreenWindowModeId();
988 stackId = getFreeformWindowModeId();
991 if(hasBrokenSetLaunchBoundsApi()
992 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f))
993 stackId = getFullscreenWindowModeId();
998 Method method = ActivityOptions.class.getMethod(getWindowingModeMethodName(), int.class);
999 method.invoke(options, stackId);
1000 } catch (Exception e) { /* Gracefully fail */ }
1002 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1003 int launchDisplayId = LauncherHelper.getInstance().getSecondaryDisplayId();
1004 if(launchDisplayId != -1)
1005 options.setLaunchDisplayId(launchDisplayId);
1011 private static int getFullscreenWindowModeId() {
1012 if(getCurrentApiVersion() >= 28.0f)
1013 return WINDOWING_MODE_FULLSCREEN;
1015 return FULLSCREEN_WORKSPACE_STACK_ID;
1018 private static int getFreeformWindowModeId() {
1019 if(getCurrentApiVersion() >= 28.0f)
1020 return WINDOWING_MODE_FREEFORM;
1022 return FREEFORM_WORKSPACE_STACK_ID;
1025 private static String getWindowingModeMethodName() {
1026 if(getCurrentApiVersion() >= 28.0f)
1027 return "setLaunchWindowingMode";
1029 return "setLaunchStackId";
1032 public static Bundle getActivityOptionsBundle(Context context, ApplicationType type, View view) {
1033 SharedPreferences pref = getSharedPreferences(context);
1035 return getActivityOptionsBundle(context, type, pref.getString("window_size", context.getString(R.string.tb_def_window_size)), view);
1038 private static Bundle getActivityOptionsBundle(Context context, ApplicationType type, String windowSize, View view) {
1039 SharedPreferences pref = getSharedPreferences(context);
1040 if(!canEnableFreeform() || !pref.getBoolean("freeform_hack", false))
1041 return getActivityOptions(view).toBundle();
1043 switch(windowSize) {
1045 return launchMode1(context, type, view);
1047 return launchMode2(context, MAXIMIZED, type, view);
1049 return launchMode2(context, LEFT, type, view);
1051 return launchMode2(context, RIGHT, type, view);
1053 return launchMode3(context, type, view);
1056 return getActivityOptions(context, type, view).toBundle();
1059 private static ApplicationType getApplicationType(Context context, String packageName) {
1060 return isGame(context, packageName) ? ApplicationType.GAME : ApplicationType.APPLICATION;
1063 public static boolean isSystemApp(Context context) {
1065 ApplicationInfo info = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
1066 int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
1067 return (info.flags & mask) != 0;
1068 } catch (PackageManager.NameNotFoundException e) {
1073 public static boolean isChromeOs(Context context) {
1074 return context.getPackageManager().hasSystemFeature("org.chromium.arc");
1077 public static boolean isBlissOs(Context context) {
1078 boolean validBlissOsBuildProp = false;
1080 String blissVersion = getSystemProperty("ro.bliss.version");
1081 if(blissVersion != null && !blissVersion.isEmpty())
1082 validBlissOsBuildProp = true;
1084 String buildUser = getSystemProperty("ro.build.user");
1085 if(buildUser != null && buildUser.equals("electrikjesus"))
1086 validBlissOsBuildProp = true;
1088 return validBlissOsBuildProp
1089 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1090 && isSystemApp(context);
1093 public static boolean isLauncherPermanentlyEnabled(Context context) {
1094 if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
1097 PackageManager pm = context.getPackageManager();
1099 pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1100 return pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, context.getPackageName()) == PackageManager.SIGNATURE_MATCH
1101 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1102 && isSystemApp(context);
1103 } catch (PackageManager.NameNotFoundException e) {
1108 public static boolean hasSupportLibrary(Context context, int minVersion) {
1109 PackageManager pm = context.getPackageManager();
1111 PackageInfo pInfo = pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1112 return pInfo.versionCode >= minVersion
1113 && pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, context.getPackageName()) == PackageManager.SIGNATURE_MATCH
1114 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1115 && isSystemApp(context);
1116 } catch (PackageManager.NameNotFoundException e) {
1121 public static int getBaseTaskbarSize(Context context) {
1122 return Math.round(getBaseTaskbarSizeFloat(context));
1125 private static float getBaseTaskbarSizeFloat(Context context) {
1126 SharedPreferences pref = getSharedPreferences(context);
1127 float baseTaskbarSize = context.getResources().getDimension(R.dimen.tb_base_taskbar_size);
1128 boolean navbarButtonsEnabled = false;
1130 if(pref.getBoolean("dashboard", context.getResources().getBoolean(R.bool.tb_def_dashboard)))
1131 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_dashboard_button_size);
1133 if(pref.getBoolean("button_back", false)) {
1134 navbarButtonsEnabled = true;
1135 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1138 if(pref.getBoolean("button_home", false)) {
1139 navbarButtonsEnabled = true;
1140 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1143 if(pref.getBoolean("button_recents", false)) {
1144 navbarButtonsEnabled = true;
1145 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1148 if(navbarButtonsEnabled)
1149 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_navbar_buttons_margin);
1151 if(isSystemTrayEnabled(context))
1152 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_systray_size);
1154 return baseTaskbarSize;
1157 private static void startTaskbarService(Context context, boolean fullRestart) {
1158 context.startService(new Intent(context, TaskbarService.class));
1159 context.startService(new Intent(context, StartMenuService.class));
1160 context.startService(new Intent(context, DashboardService.class));
1161 if(fullRestart) context.startService(new Intent(context, NotificationService.class));
1164 private static void stopTaskbarService(Context context, boolean fullRestart) {
1165 context.stopService(new Intent(context, TaskbarService.class));
1166 context.stopService(new Intent(context, StartMenuService.class));
1167 context.stopService(new Intent(context, DashboardService.class));
1168 if(fullRestart) context.stopService(new Intent(context, NotificationService.class));
1171 public static void restartTaskbar(Context context) {
1172 SharedPreferences pref = getSharedPreferences(context);
1173 if(pref.getBoolean("taskbar_active", false) && !pref.getBoolean("is_hidden", false)) {
1175 .putBoolean("is_restarting", true)
1176 .putBoolean("skip_auto_hide_navbar", true)
1179 stopTaskbarService(context, true);
1180 startTaskbarService(context, true);
1181 } else if(isServiceRunning(context, StartMenuService.class)) {
1182 pref.edit().putBoolean("skip_auto_hide_navbar", true).apply();
1184 stopTaskbarService(context, false);
1185 startTaskbarService(context, false);
1188 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.RESTART"));
1191 public static void restartNotificationService(Context context) {
1192 if(isServiceRunning(context, NotificationService.class)) {
1193 SharedPreferences pref = getSharedPreferences(context);
1194 pref.edit().putBoolean("is_restarting", true).apply();
1196 Intent intent = new Intent(context, NotificationService.class);
1197 context.stopService(intent);
1198 context.startService(intent);
1202 public static void showHideNavigationBar(Context context, boolean show) {
1203 if(hasSupportLibrary(context, 7)) {
1204 Intent intent = new Intent(BuildConfig.SUPPORT_APPLICATION_ID + ".CHANGE_OVERSCAN");
1205 intent.setPackage(BuildConfig.SUPPORT_APPLICATION_ID);
1207 intent.putExtra("display_id", getDisplayID());
1208 intent.putExtra("value", show ? 0 : getNavbarHeight(context) * -1);
1210 context.sendBroadcast(intent);
1214 // Show or hide the system navigation bar on Bliss-x86
1216 if(getCurrentApiVersion() >= 28.0f)
1217 Settings.Secure.putInt(context.getContentResolver(), "navigation_bar_visible", show ? 1 : 0);
1219 Settings.System.putInt(context.getContentResolver(), "navigation_bar_show", show ? 1 : 0);
1220 } catch (Exception e) { /* Gracefully fail */ }
1223 public static void initPrefs(Context context) {
1224 // Enable freeform hack automatically on supported devices
1225 SharedPreferences pref = getSharedPreferences(context);
1226 if(canEnableFreeform()) {
1227 if(!pref.getBoolean("freeform_hack_override", false)) {
1229 .putBoolean("freeform_hack", hasFreeformSupport(context) && !isSamsungDevice())
1230 .putBoolean("save_window_sizes", false)
1231 .putBoolean("freeform_hack_override", true)
1233 } else if(!hasFreeformSupport(context)) {
1234 pref.edit().putBoolean("freeform_hack", false).apply();
1236 stopFreeformHack(context);
1239 boolean freeformWasEnabled = pref.getBoolean("freeform_hack", false)
1240 || pref.getBoolean("show_freeform_disabled_message", false);
1243 .putBoolean("freeform_hack", false)
1244 .putBoolean("show_freeform_disabled_message", freeformWasEnabled)
1247 SavedWindowSizes.getInstance(context).clear(context);
1248 stopFreeformHack(context);
1251 // Customizations for BlissOS
1252 if(isBlissOs(context) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1253 && !pref.getBoolean("bliss_os_prefs", false)) {
1254 SharedPreferences.Editor editor = pref.edit();
1256 if(hasFreeformSupport(context)) {
1257 editor.putBoolean("freeform_hack", true);
1260 editor.putString("recents_amount", "running_apps_only");
1261 editor.putString("refresh_frequency", "0");
1262 editor.putString("max_num_of_recents", "2147483647");
1263 editor.putString("sort_order", "true");
1264 editor.putString("start_button_image", "app_logo");
1265 editor.putBoolean("button_back", true);
1266 editor.putBoolean("button_home", true);
1267 editor.putBoolean("button_recents", true);
1268 editor.putBoolean("auto_hide_navbar", true);
1269 editor.putBoolean("shortcut_icon", false);
1270 editor.putBoolean("bliss_os_prefs", true);
1274 // Customizations for Android-x86 devices (non-Bliss)
1275 if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
1276 && isSystemApp(context)
1277 && !pref.getBoolean("android_x86_prefs", false)) {
1279 .putString("recents_amount", "running_apps_only")
1280 .putString("refresh_frequency", "0")
1281 .putString("max_num_of_recents", "2147483647")
1282 .putString("sort_order", "true")
1283 .putBoolean("shortcut_icon", false)
1284 .putBoolean("android_x86_prefs", true)
1289 public static DisplayInfo getDisplayInfo(Context context) {
1290 return getDisplayInfo(context, false);
1293 public static DisplayInfo getDisplayInfo(Context context, boolean fromTaskbar) {
1294 context = context.getApplicationContext();
1295 int displayID = getDisplayID();
1297 DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1298 Display currentDisplay = null;
1300 for(Display display : dm.getDisplays()) {
1301 if(display.getDisplayId() == displayID) {
1302 currentDisplay = display;
1307 if(currentDisplay == null)
1308 return new DisplayInfo(0, 0, 0);
1310 DisplayMetrics metrics = new DisplayMetrics();
1311 currentDisplay.getMetrics(metrics);
1313 DisplayMetrics realMetrics = new DisplayMetrics();
1314 currentDisplay.getRealMetrics(realMetrics);
1316 DisplayInfo info = new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);
1318 if(isChromeOs(context)) {
1319 SharedPreferences pref = getSharedPreferences(context);
1320 if(!pref.getBoolean("chrome_os_context_menu_fix", true)) {
1321 info.width = realMetrics.widthPixels;
1322 info.height = realMetrics.heightPixels;
1328 // Workaround for incorrect display size on devices with notches in landscape mode
1329 if(fromTaskbar && context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)
1332 boolean sameWidth = metrics.widthPixels == realMetrics.widthPixels;
1333 boolean sameHeight = metrics.heightPixels == realMetrics.heightPixels;
1335 if(sameWidth && !sameHeight) {
1336 info.width = realMetrics.widthPixels;
1337 info.height = realMetrics.heightPixels - getNavbarHeight(context);
1340 if(!sameWidth && sameHeight) {
1341 info.width = realMetrics.widthPixels - getNavbarHeight(context);
1342 info.height = realMetrics.heightPixels;
1348 private static int getDisplayID() {
1349 LauncherHelper helper = LauncherHelper.getInstance();
1351 if(helper.isOnSecondaryHomeScreen())
1352 return helper.getSecondaryDisplayId();
1354 return Display.DEFAULT_DISPLAY;
1357 public static void pinAppShortcut(Context context) {
1358 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1359 ShortcutManager mShortcutManager = context.getSystemService(ShortcutManager.class);
1361 if(mShortcutManager.isRequestPinShortcutSupported()) {
1362 ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder(context, "freeform_mode").build();
1364 mShortcutManager.requestPinShortcut(pinShortcutInfo, null);
1366 showToastLong(context, R.string.tb_pin_shortcut_not_supported);
1368 Intent intent = ShortcutUtils.getShortcutIntent(context);
1369 intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
1370 intent.putExtra("duplicate", false);
1372 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1373 homeIntent.addCategory(Intent.CATEGORY_HOME);
1374 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
1376 intent.setPackage(defaultLauncher.activityInfo.packageName);
1377 context.sendBroadcast(intent);
1379 showToast(context, R.string.tb_shortcut_created);
1383 public static boolean shouldCollapse(Context context, boolean pendingAppLaunch) {
1384 SharedPreferences pref = getSharedPreferences(context);
1385 if(pref.getBoolean("hide_taskbar", true)) {
1386 if(isOverridingFreeformHack(context))
1387 return !LauncherHelper.getInstance().isOnHomeScreen();
1389 FreeformHackHelper helper = FreeformHackHelper.getInstance();
1390 if(pendingAppLaunch)
1391 return !helper.isFreeformHackActive();
1393 return !helper.isInFreeformWorkspace();
1399 public static boolean isOverridingFreeformHack(Context context) {
1400 SharedPreferences pref = getSharedPreferences(context);
1401 return pref.getBoolean("freeform_hack", false)
1402 && ((isChromeOs(context) && pref.getBoolean("chrome_os_context_menu_fix", true))
1403 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f));
1406 public static boolean isPlayStoreInstalled(Context context) {
1408 context.getPackageManager().getPackageInfo("com.android.vending", 0);
1410 } catch (PackageManager.NameNotFoundException e) {
1415 private static float getCurrentApiVersion() {
1416 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1417 return Float.valueOf(Build.VERSION.SDK_INT + "." + Build.VERSION.PREVIEW_SDK_INT);
1419 return (float) Build.VERSION.SDK_INT;
1422 public static boolean hasBrokenSetLaunchBoundsApi() {
1423 return getCurrentApiVersion() >= 26.0f
1424 && getCurrentApiVersion() < 28.0f
1425 && !isSamsungDevice()
1426 && !isNvidiaDevice();
1429 public static String getSecondScreenPackageName(Context context) {
1430 return getInstalledPackage(context, Arrays.asList(
1431 "com.farmerbb.secondscreen.free",
1432 "com.farmerbb.secondscreen"));
1435 // Returns the name of an installed package from a list of package names, in order of preference
1436 private static String getInstalledPackage(Context context, List<String> packageNames) {
1437 if(packageNames == null || packageNames.isEmpty())
1440 List<String> packages = packageNames instanceof ArrayList ? packageNames : new ArrayList<>(packageNames);
1441 String packageName = packages.get(0);
1444 context.getPackageManager().getPackageInfo(packageName, 0);
1446 } catch (PackageManager.NameNotFoundException e) {
1448 return getInstalledPackage(context, packages);
1452 public static boolean visualFeedbackEnabled(Context context) {
1453 SharedPreferences pref = getSharedPreferences(context);
1454 return (getCurrentApiVersion() < 26.0f || getCurrentApiVersion() >= 28.0f)
1455 && pref.getBoolean("visual_feedback", true)
1456 && !isNvidiaDevice();
1459 public static void showRecentAppsDialog(Context context) {
1460 showRecentAppsDialog(context, null, null);
1463 public static AlertDialog showRecentAppsDialog(Context context, Runnable onError, Runnable onFinish) {
1464 Runnable finalOnFinish = onFinish == null
1468 Runnable finalOnError = onError == null
1469 ? () -> showErrorDialog(context, "GET_USAGE_STATS", finalOnFinish)
1472 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isSystemApp(context)) {
1473 ApplicationInfo applicationInfo = null;
1475 applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
1476 } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
1478 if(applicationInfo != null) {
1479 AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
1480 int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
1482 if(mode != AppOpsManager.MODE_ALLOWED) {
1483 AlertDialog.Builder builder = new AlertDialog.Builder(context);
1484 builder.setTitle(R.string.tb_pref_header_recent_apps)
1485 .setMessage(R.string.tb_enable_recent_apps)
1486 .setPositiveButton(R.string.tb_action_ok, (dialog, which) -> {
1488 context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
1489 showToastLong(context, R.string.tb_usage_stats_message);
1491 finalOnFinish.run();
1492 } catch (ActivityNotFoundException e) {
1496 .setNegativeButton(R.string.tb_action_cancel, (dialog, which) -> finalOnFinish.run());
1498 AlertDialog dialog = builder.create();
1500 dialog.setCancelable(false);
1507 finalOnFinish.run();
1511 public static Context wrapContext(Context context) {
1512 SharedPreferences pref = getSharedPreferences(context);
1515 switch(pref.getString("theme", "light")) {
1517 theme = R.style.Taskbar;
1520 theme = R.style.Taskbar_Dark;
1524 return theme > -1 ? new ContextThemeWrapper(context, theme) : context;
1527 public static boolean isPlayStoreRelease(Context context) {
1528 return isPlayStoreRelease(context, context.getPackageName());
1531 @SuppressLint("PackageManagerGetSignatures")
1532 public static boolean isPlayStoreRelease(Context context, String packageName) {
1533 Signature playStoreSignature = new Signature(context.getString(R.string.tb_signature));
1535 PackageManager pm = context.getPackageManager();
1536 PackageInfo info = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
1537 for(Signature signature : info.signatures) {
1538 if(signature.equals(playStoreSignature))
1541 } catch (Exception e) { /* Gracefully fail */ }
1546 public static boolean isExternalAccessDisabled(Context context) {
1547 SharedPreferences pref = getSharedPreferences(context);
1548 return !pref.getBoolean("tasker_enabled", true);
1551 public static boolean enableFreeformModeShortcut(Context context) {
1552 return canEnableFreeform()
1553 && !isOverridingFreeformHack(context)
1554 && !isChromeOs(context);
1557 public static void startForegroundService(Context context, Intent intent) {
1558 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1559 if(Settings.canDrawOverlays(context))
1560 context.startForegroundService(intent);
1562 context.startService(intent);
1565 public static int getOverlayType() {
1566 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
1567 ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
1568 : WindowManager.LayoutParams.TYPE_PHONE;
1571 public static boolean isDelegatingHomeActivity(Context context) {
1572 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1573 homeIntent.addCategory(Intent.CATEGORY_HOME);
1575 final List<ResolveInfo> listOfLaunchers = context.getPackageManager().queryIntentActivities(homeIntent, 0);
1576 for(ResolveInfo launcher : listOfLaunchers) {
1577 if(launcher.activityInfo.packageName.equals(BuildConfig.SUPPORT_APPLICATION_ID))
1584 @SuppressLint("PrivateApi")
1585 private static String getSystemProperty(String key) {
1587 Class<?> cls = Class.forName("android.os.SystemProperties");
1588 return cls.getMethod("get", String.class).invoke(null, key).toString();
1589 } catch (Exception e) {
1594 @SuppressWarnings("ResultOfMethodCallIgnored")
1595 public static boolean importCustomStartButtonImage(Context context, Uri uri) {
1597 File imagesDir = new File(context.getFilesDir(), "tb_images");
1600 File importedFile = new File(imagesDir, "custom_image_new");
1601 if(importedFile.exists()) importedFile.delete();
1603 BufferedInputStream is = new BufferedInputStream(context.getContentResolver().openInputStream(uri));
1604 byte[] data = new byte[is.available()];
1606 if(data.length > 0) {
1607 BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(importedFile));
1614 File prevFile = new File(imagesDir, "custom_image");
1615 if(prevFile.exists()) prevFile.delete();
1617 importedFile.renameTo(prevFile);
1619 } catch (IOException e) {
1624 public static String getDefaultStartButtonImage(Context context) {
1625 SharedPreferences pref = getSharedPreferences(context);
1626 return pref.getBoolean("app_drawer_icon", false)
1631 private static boolean shouldLaunchTouchAbsorber(Context context) {
1632 return isOverridingFreeformHack(context) && !isChromeOs(context) && getCurrentApiVersion() < 29.0f;
1635 public static boolean isDesktopIconsEnabled(Context context) {
1636 return !canBootToFreeform(context) && !shouldLaunchTouchAbsorber(context);
1639 public static boolean isSystemTrayEnabled(Context context) {
1640 SharedPreferences pref = getSharedPreferences(context);
1642 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1643 && pref.getBoolean("sys_tray", context.getResources().getBoolean(R.bool.tb_def_sys_tray))
1644 && pref.getBoolean("full_length", context.getResources().getBoolean(R.bool.tb_def_full_length))
1645 && !getTaskbarPosition(context).contains("vertical");
1648 @SuppressWarnings("deprecation")
1649 public static boolean isLibrary(Context context) {
1650 return !context.getPackageName().equals(BuildConfig.APPLICATION_ID);
1653 public static boolean applyDisplayCutoutModeTo(WindowManager.LayoutParams params) {
1654 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
1655 params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;