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) {
113 showPermissionDialog(context, null, null);
116 @TargetApi(Build.VERSION_CODES.M)
117 public static AlertDialog showPermissionDialog(Context context, Runnable onError, Runnable onFinish) {
118 Runnable finalOnFinish = onFinish == null
122 Runnable finalOnError = onError == null
123 ? () -> showErrorDialog(context, "SYSTEM_ALERT_WINDOW", finalOnFinish)
126 AlertDialog.Builder builder = new AlertDialog.Builder(context);
127 builder.setTitle(R.string.tb_permission_dialog_title)
128 .setMessage(R.string.tb_permission_dialog_message)
129 .setPositiveButton(R.string.tb_action_grant_permission, (dialog, which) -> {
131 context.startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
132 Uri.parse("package:" + context.getPackageName())));
135 } catch (ActivityNotFoundException e) {
140 AlertDialog dialog = builder.create();
142 dialog.setCancelable(false);
147 public static AlertDialog showErrorDialog(Context context, String appopCmd) {
148 return showErrorDialog(context, appopCmd, null);
151 private static AlertDialog showErrorDialog(Context context, String appopCmd, Runnable onFinish) {
152 Runnable finalOnFinish = onFinish == null
156 AlertDialog.Builder builder = new AlertDialog.Builder(context);
157 builder.setTitle(R.string.tb_error_dialog_title)
158 .setMessage(context.getString(R.string.tb_error_dialog_message, context.getPackageName(), appopCmd))
159 .setPositiveButton(R.string.tb_action_ok, (dialog, which) -> finalOnFinish.run());
161 AlertDialog dialog = builder.create();
163 dialog.setCancelable(false);
168 public static void sendAccessibilityAction(Context context, int action) {
169 sendAccessibilityAction(context, action, null);
172 public static void sendAccessibilityAction(Context context, int action, Runnable onComplete) {
173 ComponentName component = new ComponentName(context, PowerMenuService.class);
174 context.getPackageManager().setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
175 PackageManager.DONT_KILL_APP);
177 boolean isAccessibilityServiceEnabled = isAccessibilityServiceEnabled(context);
179 if(!isAccessibilityServiceEnabled
180 && hasWriteSecureSettingsPermission(context)) {
181 String services = Settings.Secure.getString(context.getContentResolver(),
182 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
184 String finalServices = services == null ? "" : services;
186 String powerMenuService = new ComponentName(context, PowerMenuService.class).flattenToString();
188 if(!finalServices.contains(powerMenuService)) {
190 Settings.Secure.putString(context.getContentResolver(),
191 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
192 finalServices.isEmpty()
194 : finalServices + ":" + powerMenuService);
195 } catch (Exception e) { /* Gracefully fail */ }
198 new Handler().postDelayed(() -> {
199 Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
200 intent.putExtra("action", action);
201 LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
204 Settings.Secure.putString(context.getContentResolver(),
205 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
207 } catch (Exception e) { /* Gracefully fail */ }
209 if(onComplete != null) onComplete.run();
211 } else if(isAccessibilityServiceEnabled) {
212 Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
213 intent.putExtra("action", action);
214 LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
216 if(onComplete != null) onComplete.run();
218 launchApp(context, () -> {
219 Intent intent = new Intent(context, DummyActivity.class);
220 intent.putExtra("accessibility", true);
221 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
224 context.startActivity(intent, getActivityOptionsBundle(context, ApplicationType.APPLICATION, null));
225 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
230 public static boolean isAccessibilityServiceEnabled(Context context) {
231 String accessibilityServices = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
232 ComponentName component = new ComponentName(context, PowerMenuService.class);
234 return accessibilityServices != null
235 && (accessibilityServices.contains(component.flattenToString())
236 || accessibilityServices.contains(component.flattenToShortString()));
239 public static boolean hasWriteSecureSettingsPermission(Context context) {
240 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
241 && context.checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) == PackageManager.PERMISSION_GRANTED;
244 public static void showToast(Context context, int message) {
245 showToast(context, context.getString(message), Toast.LENGTH_SHORT);
248 public static void showToastLong(Context context, int message) {
249 showToast(context, context.getString(message), Toast.LENGTH_LONG);
252 public static void showToast(Context context, String message, int length) {
255 ToastInterface toast = DependencyUtils.createToast(context.getApplicationContext(), message, length);
258 ToastHelper.getInstance().setLastToast(toast);
261 public static void cancelToast() {
262 ToastInterface toast = ToastHelper.getInstance().getLastToast();
263 if(toast != null) toast.cancel();
266 public static void startShortcut(Context context, AppEntry entry, ShortcutInfo shortcut, View view) {
276 public static void launchApp(final Context context,
277 final AppEntry entry,
278 final String windowSize,
279 final boolean launchedFromTaskbar,
280 final boolean openInNewWindow,
291 private static void launchApp(final Context context,
292 final AppEntry entry,
293 final String windowSize,
294 final boolean launchedFromTaskbar,
295 final boolean openInNewWindow,
296 final ShortcutInfo shortcut,
298 launchApp(context, launchedFromTaskbar, () -> continueLaunchingApp(context, entry,
299 windowSize, openInNewWindow, shortcut, view));
302 public static void launchApp(Context context, Runnable runnable) {
303 launchApp(context, true, runnable);
306 private static void launchApp(Context context, boolean launchedFromTaskbar, Runnable runnable) {
307 SharedPreferences pref = getSharedPreferences(context);
308 FreeformHackHelper helper = FreeformHackHelper.getInstance();
310 boolean specialLaunch = hasBrokenSetLaunchBoundsApi()
311 && helper.isInFreeformWorkspace()
312 && MenuHelper.getInstance().isContextMenuOpen();
314 boolean noAnimation = pref.getBoolean("disable_animations", false);
316 if(hasFreeformSupport(context)
317 && pref.getBoolean("freeform_hack", false)
318 && (!helper.isInFreeformWorkspace() || specialLaunch)) {
319 new Handler().postDelayed(() -> {
320 startFreeformHack(context, true);
322 new Handler().postDelayed(runnable, helper.isFreeformHackActive() ? 0 : 100);
323 }, launchedFromTaskbar ? 0 : 100);
325 new Handler().postDelayed(runnable, !launchedFromTaskbar && noAnimation ? 100 : 0);
328 public static void startFreeformHack(Context context) {
329 startFreeformHack(context, false);
332 @TargetApi(Build.VERSION_CODES.N)
333 public static void startFreeformHack(Context context, boolean checkMultiWindow) {
334 Intent freeformHackIntent = new Intent(context, InvisibleActivityFreeform.class);
335 freeformHackIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
336 | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
337 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
340 freeformHackIntent.putExtra("check_multiwindow", true);
342 if(canDrawOverlays(context))
343 startActivityLowerRight(context, freeformHackIntent);
346 public static void stopFreeformHack(Context context) {
347 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.FINISH_FREEFORM_ACTIVITY"));
349 if(isOverridingFreeformHack(context)) {
350 FreeformHackHelper helper = FreeformHackHelper.getInstance();
351 helper.setFreeformHackActive(false);
352 helper.setInFreeformWorkspace(false);
356 @TargetApi(Build.VERSION_CODES.N)
357 private static void continueLaunchingApp(Context context,
360 boolean openInNewWindow,
361 ShortcutInfo shortcut,
363 SharedPreferences pref = getSharedPreferences(context);
364 Intent intent = new Intent();
365 intent.setComponent(ComponentName.unflattenFromString(entry.getComponentName()));
366 intent.setAction(Intent.ACTION_MAIN);
367 intent.addCategory(Intent.CATEGORY_LAUNCHER);
368 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
369 intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
371 if(FreeformHackHelper.getInstance().isInFreeformWorkspace()
372 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1)
373 intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
375 if(pref.getBoolean("disable_animations", false))
376 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
378 boolean realOpenInNewWindow = openInNewWindow || pref.getBoolean("force_new_window", false);
379 if(realOpenInNewWindow) {
380 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
382 ActivityInfo activityInfo = intent.resolveActivityInfo(context.getPackageManager(), 0);
383 if(activityInfo != null) {
384 switch(activityInfo.launchMode) {
385 case ActivityInfo.LAUNCH_SINGLE_TASK:
386 case ActivityInfo.LAUNCH_SINGLE_INSTANCE:
387 intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
393 ApplicationType type = getApplicationType(context, entry.getPackageName());
395 if(windowSize == null)
396 windowSize = SavedWindowSizes.getInstance(context).getWindowSize(context, entry.getPackageName());
398 Bundle bundle = getActivityOptionsBundle(context, type, windowSize, view);
400 prepareToStartActivity(context, realOpenInNewWindow, () -> {
401 if(shortcut == null) {
402 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
403 if(entry.getUserId(context) == userManager.getSerialNumberForUser(Process.myUserHandle())) {
405 context.startActivity(intent, bundle);
406 } catch (ActivityNotFoundException e) {
407 launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context));
408 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
410 launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context));
412 launchShortcut(context, shortcut, bundle);
415 if(shouldCollapse(context, true))
416 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_TASKBAR"));
418 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_START_MENU"));
421 @TargetApi(Build.VERSION_CODES.N)
422 private static Bundle launchMode1(Context context, ApplicationType type, View view) {
423 DisplayInfo display = getDisplayInfo(context);
425 int width1 = display.width / 8;
426 int width2 = display.width - width1;
427 int height1 = display.height / 8;
428 int height2 = display.height - height1;
430 return getActivityOptions(context, type, view).setLaunchBounds(new Rect(
438 @TargetApi(Build.VERSION_CODES.N)
439 private static Bundle launchMode2(Context context, int launchType, ApplicationType type, View view) {
440 DisplayInfo display = getDisplayInfo(context);
442 int statusBarHeight = getStatusBarHeight(context);
443 String position = getTaskbarPosition(context);
445 boolean isPortrait = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
446 boolean isLandscape = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
449 int top = statusBarHeight;
450 int right = display.width;
451 int bottom = display.height;
453 int iconSize = isOverridingFreeformHack(context) && !LauncherHelper.getInstance().isOnHomeScreen()
455 : context.getResources().getDimensionPixelSize(R.dimen.tb_icon_size);
457 if(position.contains("vertical_left"))
458 left = left + iconSize;
459 else if(position.contains("vertical_right"))
460 right = right - iconSize;
461 else if(position.contains("bottom"))
462 bottom = bottom - iconSize;
464 top = top + iconSize;
466 int halfLandscape = (right / 2) + ((iconSize / 2) * (position.contains("vertical_left") ? 1 : 0));
467 int halfPortrait = (bottom / 2) + ((iconSize / 2) * ((position.equals("top_left") || position.equals("top_right")) ? 1 : 0));
469 if(launchType == RIGHT && isLandscape)
470 left = halfLandscape;
471 else if(launchType == RIGHT && isPortrait)
473 else if(launchType == LEFT && isLandscape)
474 right = halfLandscape;
475 else if(launchType == LEFT && isPortrait)
476 bottom = halfPortrait;
478 return getActivityOptions(context, type, view)
479 .setLaunchBounds(new Rect(left, top, right, bottom)).toBundle();
482 @TargetApi(Build.VERSION_CODES.N)
483 private static Bundle launchMode3(Context context, ApplicationType type, View view) {
484 DisplayInfo display = getDisplayInfo(context);
486 int width1 = display.width / 2;
487 int width2 = context.getResources().getDimensionPixelSize(R.dimen.tb_phone_size_width) / 2;
488 int height1 = display.height / 2;
489 int height2 = context.getResources().getDimensionPixelSize(R.dimen.tb_phone_size_height) / 2;
491 return getActivityOptions(context, type, view).setLaunchBounds(new Rect(
499 private static void launchAndroidForWork(Context context, ComponentName componentName, Bundle bundle, long userId) {
500 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
501 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
504 launcherApps.startMainActivity(componentName, userManager.getUserForSerialNumber(userId), null, bundle);
505 } catch (ActivityNotFoundException | NullPointerException e) { /* Gracefully fail */ }
508 @TargetApi(Build.VERSION_CODES.N_MR1)
509 private static void launchShortcut(Context context, ShortcutInfo shortcut, Bundle bundle) {
510 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
512 if(launcherApps.hasShortcutHostPermission()) {
514 launcherApps.startShortcut(shortcut, null, bundle);
515 } catch (ActivityNotFoundException | NullPointerException e) { /* Gracefully fail */ }
519 private static void prepareToStartActivity(Context context, boolean openInNewWindow, Runnable runnable) {
520 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_CONTEXT_MENU"));
522 if(!FreeformHackHelper.getInstance().isTouchAbsorberActive()
523 && shouldLaunchTouchAbsorber(context)) {
524 startTouchAbsorberActivity(context);
525 new Handler().postDelayed(runnable, 100);
526 } else if(openInNewWindow) {
527 Intent intent = new Intent(context, DummyActivity.class);
528 intent.putExtra("finish_on_pause", true);
529 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
530 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
531 startActivityLowerRight(context, intent);
533 new Handler().postDelayed(runnable, 100);
538 public static void startActivityMaximized(Context context, Intent intent) {
539 Bundle bundle = launchMode2(context, MAXIMIZED, ApplicationType.CONTEXT_MENU, null);
540 prepareToStartActivity(context, false, () -> context.startActivity(intent, bundle));
543 @TargetApi(Build.VERSION_CODES.N)
544 public static void startActivityLowerRight(Context context, Intent intent) {
545 DisplayInfo display = getDisplayInfo(context);
547 context.startActivity(intent,
548 getActivityOptions(context, ApplicationType.FREEFORM_HACK, null)
549 .setLaunchBounds(new Rect(
555 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
558 @TargetApi(Build.VERSION_CODES.N)
559 public static void startTouchAbsorberActivity(Context context) {
560 String position = getTaskbarPosition(context);
561 DisplayInfo display = getDisplayInfo(context);
565 int right = display.width;
566 int bottom = display.height;
568 int iconSize = context.getResources().getDimensionPixelSize(R.dimen.tb_icon_size);
570 if(position.contains("vertical_left"))
572 else if(position.contains("vertical_right"))
573 left = right - iconSize;
574 else if(position.contains("bottom"))
575 top = bottom - iconSize;
579 Intent intent = new Intent(context, TouchAbsorberActivity.class);
580 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
581 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
584 context.startActivity(intent,
585 getActivityOptions(context, ApplicationType.FREEFORM_HACK, null)
586 .setLaunchBounds(new Rect(left, top, right, bottom)).toBundle());
587 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
590 public static void startContextMenuActivity(Context context, Bundle args) {
591 SharedPreferences pref = getSharedPreferences(context);
592 Intent intent = null;
594 switch(pref.getString("theme", "light")) {
596 intent = new Intent(context, ContextMenuActivity.class);
599 intent = new Intent(context, ContextMenuActivityDark.class);
604 intent.putExtra("args", args);
605 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
608 if(hasFreeformSupport(context) && FreeformHackHelper.getInstance().isInFreeformWorkspace()) {
609 DisplayInfo display = getDisplayInfo(context);
611 if(intent != null && hasBrokenSetLaunchBoundsApi())
612 intent.putExtra("context_menu_fix", true);
614 context.startActivity(intent,
615 getActivityOptions(context, ApplicationType.CONTEXT_MENU, null)
617 new Rect(0, 0, display.width, display.height)
620 context.startActivity(intent);
623 public static void checkForUpdates(Context context) {
625 if(isPlayStoreRelease(context)) {
626 if(context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
627 && !isPlayStoreInstalled(context))
628 url = "https://github.com/farmerbb/Taskbar/releases";
630 url = "https://play.google.com/store/apps/details?id=" + context.getPackageName();
632 url = "https://f-droid.org/repository/browse/?fdid=" + context.getPackageName();
634 Intent intent = new Intent(Intent.ACTION_VIEW);
635 intent.setData(Uri.parse(url));
636 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
639 context.startActivity(intent);
640 } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
643 public static boolean launcherIsDefault(Context context) {
644 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
645 homeIntent.addCategory(Intent.CATEGORY_HOME);
646 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
648 return defaultLauncher.activityInfo.packageName.equals(context.getPackageName());
651 public static void setCachedRotation(int cachedRotation) {
652 U.cachedRotation = cachedRotation;
655 public static String getTaskbarPosition(Context context) {
656 SharedPreferences pref = getSharedPreferences(context);
657 String position = pref.getString("position", "bottom_left");
659 if(pref.getBoolean("anchor", false)) {
660 WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
661 int rotation = cachedRotation != null ? cachedRotation : windowManager.getDefaultDisplay().getRotation();
666 case Surface.ROTATION_0:
667 return "bottom_left";
668 case Surface.ROTATION_90:
669 return "bottom_vertical_right";
670 case Surface.ROTATION_180:
672 case Surface.ROTATION_270:
673 return "top_vertical_left";
676 case "bottom_vertical_left":
678 case Surface.ROTATION_0:
679 return "bottom_vertical_left";
680 case Surface.ROTATION_90:
681 return "bottom_right";
682 case Surface.ROTATION_180:
683 return "top_vertical_right";
684 case Surface.ROTATION_270:
690 case Surface.ROTATION_0:
691 return "bottom_right";
692 case Surface.ROTATION_90:
693 return "top_vertical_right";
694 case Surface.ROTATION_180:
696 case Surface.ROTATION_270:
697 return "bottom_vertical_left";
700 case "bottom_vertical_right":
702 case Surface.ROTATION_0:
703 return "bottom_vertical_right";
704 case Surface.ROTATION_90:
706 case Surface.ROTATION_180:
707 return "top_vertical_left";
708 case Surface.ROTATION_270:
709 return "bottom_left";
714 case Surface.ROTATION_0:
716 case Surface.ROTATION_90:
717 return "bottom_vertical_left";
718 case Surface.ROTATION_180:
719 return "bottom_right";
720 case Surface.ROTATION_270:
721 return "top_vertical_right";
724 case "top_vertical_left":
726 case Surface.ROTATION_0:
727 return "top_vertical_left";
728 case Surface.ROTATION_90:
729 return "bottom_left";
730 case Surface.ROTATION_180:
731 return "bottom_vertical_right";
732 case Surface.ROTATION_270:
738 case Surface.ROTATION_0:
740 case Surface.ROTATION_90:
741 return "top_vertical_left";
742 case Surface.ROTATION_180:
743 return "bottom_left";
744 case Surface.ROTATION_270:
745 return "bottom_vertical_right";
748 case "top_vertical_right":
750 case Surface.ROTATION_0:
751 return "top_vertical_right";
752 case Surface.ROTATION_90:
754 case Surface.ROTATION_180:
755 return "bottom_vertical_left";
756 case Surface.ROTATION_270:
757 return "bottom_right";
766 private static int getMaxNumOfColumns(Context context) {
767 SharedPreferences pref = getSharedPreferences(context);
768 DisplayInfo display = getDisplayInfo(context);
769 float density = display.density / 160;
770 float baseTaskbarSize = getBaseTaskbarSizeFloat(context) / density;
771 int numOfColumns = 0;
773 float maxScreenSize = getTaskbarPosition(context).contains("vertical")
774 ? (display.height - getStatusBarHeight(context)) / density
775 : display.width / density;
777 float iconSize = context.getResources().getDimension(R.dimen.tb_icon_size) / density;
779 int userMaxNumOfColumns = Integer.valueOf(pref.getString("max_num_of_recents", "10"));
781 while(baseTaskbarSize + iconSize < maxScreenSize
782 && numOfColumns < userMaxNumOfColumns) {
783 baseTaskbarSize = baseTaskbarSize + iconSize;
790 public static int getMaxNumOfEntries(Context context) {
791 SharedPreferences pref = getSharedPreferences(context);
792 return pref.getBoolean("disable_scrolling_list", false)
793 ? getMaxNumOfColumns(context)
794 : Integer.valueOf(pref.getString("max_num_of_recents", "10"));
797 public static int getStatusBarHeight(Context context) {
798 return getSystemDimen(context, "status_bar_height");
801 private static int getNavbarHeight(Context context) {
802 return getSystemDimen(context, "navigation_bar_height");
805 private static int getSystemDimen(Context context, String id) {
807 int resourceId = context.getResources().getIdentifier(id, "dimen", "android");
809 value = context.getResources().getDimensionPixelSize(resourceId);
814 public static void refreshPinnedIcons(Context context) {
815 IconCache.getInstance(context).clearCache();
817 PinnedBlockedApps pba = PinnedBlockedApps.getInstance(context);
818 List<AppEntry> pinnedAppsList = new ArrayList<>(pba.getPinnedApps());
819 List<AppEntry> blockedAppsList = new ArrayList<>(pba.getBlockedApps());
820 PackageManager pm = context.getPackageManager();
824 for(AppEntry entry : pinnedAppsList) {
825 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
826 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
828 final List<UserHandle> userHandles = userManager.getUserProfiles();
829 LauncherActivityInfo appInfo = null;
831 for(UserHandle handle : userHandles) {
832 List<LauncherActivityInfo> list = launcherApps.getActivityList(entry.getPackageName(), handle);
833 if(!list.isEmpty()) {
834 // Google App workaround
835 if(!entry.getPackageName().equals("com.google.android.googlequicksearchbox"))
836 appInfo = list.get(0);
838 boolean added = false;
839 for(LauncherActivityInfo info : list) {
840 if(info.getName().equals("com.google.android.googlequicksearchbox.SearchActivity")) {
846 if(!added) appInfo = list.get(0);
853 if(appInfo != null) {
854 AppEntry newEntry = new AppEntry(
855 entry.getPackageName(),
856 entry.getComponentName(),
858 IconCache.getInstance(context).getIcon(context, pm, appInfo),
861 newEntry.setUserId(entry.getUserId(context));
862 pba.addPinnedApp(context, newEntry);
866 for(AppEntry entry : blockedAppsList) {
867 pba.addBlockedApp(context, entry);
871 public static boolean canEnableFreeform() {
872 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
875 @TargetApi(Build.VERSION_CODES.N)
876 public static boolean hasFreeformSupport(Context context) {
877 return canEnableFreeform()
878 && (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
879 || Settings.Global.getInt(context.getContentResolver(), "enable_freeform_support", 0) != 0
880 || (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
881 && Settings.Global.getInt(context.getContentResolver(), "force_resizable_activities", 0) != 0));
884 public static boolean canBootToFreeform(Context context) {
885 SharedPreferences pref = getSharedPreferences(context);
886 return hasFreeformSupport(context)
887 && pref.getBoolean("freeform_hack", false)
888 && !isOverridingFreeformHack(context);
891 public static boolean isSamsungDevice() {
892 return Build.MANUFACTURER.equalsIgnoreCase("Samsung");
895 private static boolean isNvidiaDevice() {
896 return Build.MANUFACTURER.equalsIgnoreCase("NVIDIA");
899 public static boolean isServiceRunning(Context context, Class<? extends Service> cls) {
900 if(LauncherHelper.getInstance().isOnSecondaryHomeScreen()
901 && (cls.equals(TaskbarService.class)
902 || cls.equals(StartMenuService.class)
903 || cls.equals(DashboardService.class)))
906 return isServiceRunning(context, cls.getName());
909 private static boolean isServiceRunning(Context context, String className) {
910 ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
911 for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
912 if(className.equals(service.service.getClassName()))
919 public static int getBackgroundTint(Context context) {
920 SharedPreferences pref = getSharedPreferences(context);
922 // Import old background tint preference
923 if(pref.contains("show_background")) {
924 SharedPreferences.Editor editor = pref.edit();
926 if(!pref.getBoolean("show_background", true))
927 editor.putInt("background_tint", Color.TRANSPARENT).apply();
929 editor.remove("show_background");
933 return pref.getInt("background_tint", context.getResources().getInteger(R.integer.tb_translucent_gray));
936 public static int getAccentColor(Context context) {
937 SharedPreferences pref = getSharedPreferences(context);
938 return pref.getInt("accent_color", context.getResources().getInteger(R.integer.tb_translucent_white));
941 @TargetApi(Build.VERSION_CODES.M)
942 public static boolean canDrawOverlays(Context context) {
943 return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context);
946 public static boolean isGame(Context context, String packageName) {
947 SharedPreferences pref = getSharedPreferences(context);
948 if(pref.getBoolean("launch_games_fullscreen", true)) {
949 PackageManager pm = context.getPackageManager();
952 ApplicationInfo info = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
953 return (info.flags & ApplicationInfo.FLAG_IS_GAME) != 0 || (info.metaData != null && info.metaData.getBoolean("isGame", false));
954 } catch (PackageManager.NameNotFoundException e) {
961 private static ActivityOptions getActivityOptions(View view) {
962 return getActivityOptions(null, null, view);
965 @TargetApi(Build.VERSION_CODES.N)
966 private static ActivityOptions getActivityOptions(Context context, ApplicationType applicationType, View view) {
967 ActivityOptions options;
969 options = ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight());
971 options = ActivityOptions.makeBasic();
973 if(applicationType == null)
978 switch(applicationType) {
980 if(FreeformHackHelper.getInstance().isFreeformHackActive())
981 stackId = getFreeformWindowModeId();
983 stackId = getFullscreenWindowModeId();
986 stackId = getFullscreenWindowModeId();
989 stackId = getFreeformWindowModeId();
992 if(hasBrokenSetLaunchBoundsApi()
993 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f))
994 stackId = getFullscreenWindowModeId();
999 Method method = ActivityOptions.class.getMethod(getWindowingModeMethodName(), int.class);
1000 method.invoke(options, stackId);
1001 } catch (Exception e) { /* Gracefully fail */ }
1003 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1004 int launchDisplayId = LauncherHelper.getInstance().getSecondaryDisplayId();
1005 if(launchDisplayId != -1)
1006 options.setLaunchDisplayId(launchDisplayId);
1012 private static int getFullscreenWindowModeId() {
1013 if(getCurrentApiVersion() >= 28.0f)
1014 return WINDOWING_MODE_FULLSCREEN;
1016 return FULLSCREEN_WORKSPACE_STACK_ID;
1019 private static int getFreeformWindowModeId() {
1020 if(getCurrentApiVersion() >= 28.0f)
1021 return WINDOWING_MODE_FREEFORM;
1023 return FREEFORM_WORKSPACE_STACK_ID;
1026 private static String getWindowingModeMethodName() {
1027 if(getCurrentApiVersion() >= 28.0f)
1028 return "setLaunchWindowingMode";
1030 return "setLaunchStackId";
1033 public static Bundle getActivityOptionsBundle(Context context, ApplicationType type, View view) {
1034 SharedPreferences pref = getSharedPreferences(context);
1036 return getActivityOptionsBundle(context, type, pref.getString("window_size", context.getString(R.string.tb_def_window_size)), view);
1039 private static Bundle getActivityOptionsBundle(Context context, ApplicationType type, String windowSize, View view) {
1040 SharedPreferences pref = getSharedPreferences(context);
1041 if(!canEnableFreeform() || !pref.getBoolean("freeform_hack", false))
1042 return getActivityOptions(view).toBundle();
1044 switch(windowSize) {
1046 return launchMode1(context, type, view);
1048 return launchMode2(context, MAXIMIZED, type, view);
1050 return launchMode2(context, LEFT, type, view);
1052 return launchMode2(context, RIGHT, type, view);
1054 return launchMode3(context, type, view);
1057 return getActivityOptions(context, type, view).toBundle();
1060 private static ApplicationType getApplicationType(Context context, String packageName) {
1061 return isGame(context, packageName) ? ApplicationType.GAME : ApplicationType.APPLICATION;
1064 public static boolean isSystemApp(Context context) {
1066 ApplicationInfo info = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
1067 int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
1068 return (info.flags & mask) != 0;
1069 } catch (PackageManager.NameNotFoundException e) {
1074 public static boolean isChromeOs(Context context) {
1075 return context.getPackageManager().hasSystemFeature("org.chromium.arc");
1078 public static boolean isBlissOs(Context context) {
1079 boolean validBlissOsBuildProp = false;
1081 String blissVersion = getSystemProperty("ro.bliss.version");
1082 if(blissVersion != null && !blissVersion.isEmpty())
1083 validBlissOsBuildProp = true;
1085 String buildUser = getSystemProperty("ro.build.user");
1086 if(buildUser != null && buildUser.equals("electrikjesus"))
1087 validBlissOsBuildProp = true;
1089 return validBlissOsBuildProp
1090 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1091 && isSystemApp(context);
1094 public static boolean isLauncherPermanentlyEnabled(Context context) {
1095 if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
1098 PackageManager pm = context.getPackageManager();
1100 pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1101 return pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, context.getPackageName()) == PackageManager.SIGNATURE_MATCH
1102 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1103 && isSystemApp(context);
1104 } catch (PackageManager.NameNotFoundException e) {
1109 public static int getBaseTaskbarSize(Context context) {
1110 return Math.round(getBaseTaskbarSizeFloat(context));
1113 private static float getBaseTaskbarSizeFloat(Context context) {
1114 SharedPreferences pref = getSharedPreferences(context);
1115 float baseTaskbarSize = context.getResources().getDimension(R.dimen.tb_base_taskbar_size);
1116 boolean navbarButtonsEnabled = false;
1118 if(pref.getBoolean("dashboard", context.getResources().getBoolean(R.bool.tb_def_dashboard)))
1119 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_dashboard_button_size);
1121 if(pref.getBoolean("button_back", false)) {
1122 navbarButtonsEnabled = true;
1123 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1126 if(pref.getBoolean("button_home", false)) {
1127 navbarButtonsEnabled = true;
1128 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1131 if(pref.getBoolean("button_recents", false)) {
1132 navbarButtonsEnabled = true;
1133 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1136 if(navbarButtonsEnabled)
1137 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_navbar_buttons_margin);
1139 if(isSystemTrayEnabled(context))
1140 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_systray_size);
1142 return baseTaskbarSize;
1145 private static void startTaskbarService(Context context, boolean fullRestart) {
1146 context.startService(new Intent(context, TaskbarService.class));
1147 context.startService(new Intent(context, StartMenuService.class));
1148 context.startService(new Intent(context, DashboardService.class));
1149 if(fullRestart) context.startService(new Intent(context, NotificationService.class));
1152 private static void stopTaskbarService(Context context, boolean fullRestart) {
1153 context.stopService(new Intent(context, TaskbarService.class));
1154 context.stopService(new Intent(context, StartMenuService.class));
1155 context.stopService(new Intent(context, DashboardService.class));
1156 if(fullRestart) context.stopService(new Intent(context, NotificationService.class));
1159 public static void restartTaskbar(Context context) {
1160 SharedPreferences pref = getSharedPreferences(context);
1161 if(pref.getBoolean("taskbar_active", false) && !pref.getBoolean("is_hidden", false)) {
1163 .putBoolean("is_restarting", true)
1164 .putBoolean("skip_auto_hide_navbar", true)
1167 stopTaskbarService(context, true);
1168 startTaskbarService(context, true);
1169 } else if(isServiceRunning(context, StartMenuService.class)) {
1170 pref.edit().putBoolean("skip_auto_hide_navbar", true).apply();
1172 stopTaskbarService(context, false);
1173 startTaskbarService(context, false);
1176 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.RESTART"));
1179 public static void restartNotificationService(Context context) {
1180 if(isServiceRunning(context, NotificationService.class)) {
1181 SharedPreferences pref = getSharedPreferences(context);
1182 pref.edit().putBoolean("is_restarting", true).apply();
1184 Intent intent = new Intent(context, NotificationService.class);
1185 context.stopService(intent);
1186 context.startService(intent);
1190 public static void showHideNavigationBar(Context context, boolean show) {
1191 // Show or hide the system navigation bar on Bliss-x86
1193 if(getCurrentApiVersion() >= 28.0f)
1194 Settings.Secure.putInt(context.getContentResolver(), "navigation_bar_visible", show ? 1 : 0);
1196 Settings.System.putInt(context.getContentResolver(), "navigation_bar_show", show ? 1 : 0);
1197 } catch (Exception e) { /* Gracefully fail */ }
1200 public static void initPrefs(Context context) {
1201 // Enable freeform hack automatically on supported devices
1202 SharedPreferences pref = getSharedPreferences(context);
1203 if(canEnableFreeform()) {
1204 if(!pref.getBoolean("freeform_hack_override", false)) {
1206 .putBoolean("freeform_hack", hasFreeformSupport(context) && !isSamsungDevice())
1207 .putBoolean("save_window_sizes", false)
1208 .putBoolean("freeform_hack_override", true)
1210 } else if(!hasFreeformSupport(context)) {
1211 pref.edit().putBoolean("freeform_hack", false).apply();
1213 stopFreeformHack(context);
1216 boolean freeformWasEnabled = pref.getBoolean("freeform_hack", false)
1217 || pref.getBoolean("show_freeform_disabled_message", false);
1220 .putBoolean("freeform_hack", false)
1221 .putBoolean("show_freeform_disabled_message", freeformWasEnabled)
1224 SavedWindowSizes.getInstance(context).clear(context);
1225 stopFreeformHack(context);
1228 // Customizations for BlissOS
1229 if(isBlissOs(context) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1230 && !pref.getBoolean("bliss_os_prefs", false)) {
1231 SharedPreferences.Editor editor = pref.edit();
1233 if(hasFreeformSupport(context)) {
1234 editor.putBoolean("freeform_hack", true);
1237 editor.putString("recents_amount", "running_apps_only");
1238 editor.putString("refresh_frequency", "0");
1239 editor.putString("max_num_of_recents", "2147483647");
1240 editor.putString("sort_order", "true");
1241 editor.putString("start_button_image", "app_logo");
1242 editor.putBoolean("button_back", true);
1243 editor.putBoolean("button_home", true);
1244 editor.putBoolean("button_recents", true);
1245 // editor.putBoolean("auto_hide_navbar", true);
1246 // editor.putBoolean("shortcut_icon", false);
1247 editor.putBoolean("bliss_os_prefs", true);
1251 // Customizations for Android-x86 devices (non-Bliss)
1252 if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
1253 && isSystemApp(context)
1254 && !pref.getBoolean("android_x86_prefs", false)) {
1256 .putString("recents_amount", "running_apps_only")
1257 .putString("refresh_frequency", "0")
1258 .putString("max_num_of_recents", "2147483647")
1259 .putString("sort_order", "true")
1260 .putBoolean("shortcut_icon", false)
1261 .putBoolean("android_x86_prefs", true)
1266 public static DisplayInfo getDisplayInfo(Context context) {
1267 return getDisplayInfo(context, false);
1270 public static DisplayInfo getDisplayInfo(Context context, boolean fromTaskbar) {
1271 context = context.getApplicationContext();
1273 LauncherHelper helper = LauncherHelper.getInstance();
1276 if(helper.isOnSecondaryHomeScreen())
1277 displayID = helper.getSecondaryDisplayId();
1279 displayID = Display.DEFAULT_DISPLAY;
1281 DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1282 Display currentDisplay = null;
1284 for(Display display : dm.getDisplays()) {
1285 if(display.getDisplayId() == displayID) {
1286 currentDisplay = display;
1291 if(currentDisplay == null)
1292 return new DisplayInfo(0, 0, 0);
1294 DisplayMetrics metrics = new DisplayMetrics();
1295 currentDisplay.getMetrics(metrics);
1297 DisplayMetrics realMetrics = new DisplayMetrics();
1298 currentDisplay.getRealMetrics(realMetrics);
1300 DisplayInfo info = new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);
1302 if(isChromeOs(context)) {
1303 SharedPreferences pref = getSharedPreferences(context);
1304 if(!pref.getBoolean("chrome_os_context_menu_fix", true)) {
1305 info.width = realMetrics.widthPixels;
1306 info.height = realMetrics.heightPixels;
1312 // Workaround for incorrect display size on devices with notches in landscape mode
1313 if(fromTaskbar && context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)
1316 boolean sameWidth = metrics.widthPixels == realMetrics.widthPixels;
1317 boolean sameHeight = metrics.heightPixels == realMetrics.heightPixels;
1319 if(sameWidth && !sameHeight) {
1320 info.width = realMetrics.widthPixels;
1321 info.height = realMetrics.heightPixels - getNavbarHeight(context);
1324 if(!sameWidth && sameHeight) {
1325 info.width = realMetrics.widthPixels - getNavbarHeight(context);
1326 info.height = realMetrics.heightPixels;
1332 public static void pinAppShortcut(Context context) {
1333 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1334 ShortcutManager mShortcutManager = context.getSystemService(ShortcutManager.class);
1336 if(mShortcutManager.isRequestPinShortcutSupported()) {
1337 ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder(context, "freeform_mode").build();
1339 mShortcutManager.requestPinShortcut(pinShortcutInfo, null);
1341 showToastLong(context, R.string.tb_pin_shortcut_not_supported);
1343 Intent intent = ShortcutUtils.getShortcutIntent(context);
1344 intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
1345 intent.putExtra("duplicate", false);
1347 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1348 homeIntent.addCategory(Intent.CATEGORY_HOME);
1349 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
1351 intent.setPackage(defaultLauncher.activityInfo.packageName);
1352 context.sendBroadcast(intent);
1354 showToast(context, R.string.tb_shortcut_created);
1358 public static boolean shouldCollapse(Context context, boolean pendingAppLaunch) {
1359 SharedPreferences pref = getSharedPreferences(context);
1360 if(pref.getBoolean("hide_taskbar", true)) {
1361 if(isOverridingFreeformHack(context))
1362 return !LauncherHelper.getInstance().isOnHomeScreen();
1364 FreeformHackHelper helper = FreeformHackHelper.getInstance();
1365 if(pendingAppLaunch)
1366 return !helper.isFreeformHackActive();
1368 return !helper.isInFreeformWorkspace();
1374 public static boolean isOverridingFreeformHack(Context context) {
1375 SharedPreferences pref = getSharedPreferences(context);
1376 return pref.getBoolean("freeform_hack", false)
1377 && ((isChromeOs(context) && pref.getBoolean("chrome_os_context_menu_fix", true))
1378 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f));
1381 public static boolean isPlayStoreInstalled(Context context) {
1383 context.getPackageManager().getPackageInfo("com.android.vending", 0);
1385 } catch (PackageManager.NameNotFoundException e) {
1390 public static float getCurrentApiVersion() {
1391 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1392 return Float.valueOf(Build.VERSION.SDK_INT + "." + Build.VERSION.PREVIEW_SDK_INT);
1394 return (float) Build.VERSION.SDK_INT;
1397 public static boolean hasBrokenSetLaunchBoundsApi() {
1398 return getCurrentApiVersion() >= 26.0f
1399 && getCurrentApiVersion() < 28.0f
1400 && !isSamsungDevice()
1401 && !isNvidiaDevice();
1404 public static String getSecondScreenPackageName(Context context) {
1405 return getInstalledPackage(context, Arrays.asList(
1406 "com.farmerbb.secondscreen.free",
1407 "com.farmerbb.secondscreen"));
1410 // Returns the name of an installed package from a list of package names, in order of preference
1411 private static String getInstalledPackage(Context context, List<String> packageNames) {
1412 if(packageNames == null || packageNames.isEmpty())
1415 List<String> packages = packageNames instanceof ArrayList ? packageNames : new ArrayList<>(packageNames);
1416 String packageName = packages.get(0);
1419 context.getPackageManager().getPackageInfo(packageName, 0);
1421 } catch (PackageManager.NameNotFoundException e) {
1423 return getInstalledPackage(context, packages);
1427 public static boolean visualFeedbackEnabled(Context context) {
1428 SharedPreferences pref = getSharedPreferences(context);
1429 return (getCurrentApiVersion() < 26.0f || getCurrentApiVersion() >= 28.0f)
1430 && pref.getBoolean("visual_feedback", true)
1431 && !isNvidiaDevice();
1434 public static void showRecentAppsDialog(Context context) {
1435 showRecentAppsDialog(context, null, null);
1438 public static AlertDialog showRecentAppsDialog(Context context, Runnable onError, Runnable onFinish) {
1439 Runnable finalOnFinish = onFinish == null
1443 Runnable finalOnError = onError == null
1444 ? () -> showErrorDialog(context, "GET_USAGE_STATS", finalOnFinish)
1447 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isSystemApp(context)) {
1448 ApplicationInfo applicationInfo = null;
1450 applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
1451 } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
1453 if(applicationInfo != null) {
1454 AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
1455 int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
1457 if(mode != AppOpsManager.MODE_ALLOWED) {
1458 AlertDialog.Builder builder = new AlertDialog.Builder(context);
1459 builder.setTitle(R.string.tb_pref_header_recent_apps)
1460 .setMessage(R.string.tb_enable_recent_apps)
1461 .setPositiveButton(R.string.tb_action_ok, (dialog, which) -> {
1463 context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
1464 showToastLong(context, R.string.tb_usage_stats_message);
1466 finalOnFinish.run();
1467 } catch (ActivityNotFoundException e) {
1471 .setNegativeButton(R.string.tb_action_cancel, (dialog, which) -> finalOnFinish.run());
1473 AlertDialog dialog = builder.create();
1475 dialog.setCancelable(false);
1482 finalOnFinish.run();
1486 public static Context wrapContext(Context context) {
1487 SharedPreferences pref = getSharedPreferences(context);
1490 switch(pref.getString("theme", "light")) {
1492 theme = R.style.Taskbar;
1495 theme = R.style.Taskbar_Dark;
1499 return theme > -1 ? new ContextThemeWrapper(context, theme) : context;
1502 public static boolean isPlayStoreRelease(Context context) {
1503 return isPlayStoreRelease(context, context.getPackageName());
1506 @SuppressLint("PackageManagerGetSignatures")
1507 public static boolean isPlayStoreRelease(Context context, String packageName) {
1508 Signature playStoreSignature = new Signature(context.getString(R.string.tb_signature));
1510 PackageManager pm = context.getPackageManager();
1511 PackageInfo info = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
1512 for(Signature signature : info.signatures) {
1513 if(signature.equals(playStoreSignature))
1516 } catch (Exception e) { /* Gracefully fail */ }
1521 public static boolean isExternalAccessDisabled(Context context) {
1522 SharedPreferences pref = getSharedPreferences(context);
1523 return !pref.getBoolean("tasker_enabled", true);
1526 public static boolean enableFreeformModeShortcut(Context context) {
1527 return canEnableFreeform()
1528 && !isOverridingFreeformHack(context)
1529 && !isChromeOs(context);
1532 public static void startForegroundService(Context context, Intent intent) {
1533 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1534 if(Settings.canDrawOverlays(context))
1535 context.startForegroundService(intent);
1537 context.startService(intent);
1540 public static int getOverlayType() {
1541 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
1542 ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
1543 : WindowManager.LayoutParams.TYPE_PHONE;
1546 public static boolean isDelegatingHomeActivity(Context context) {
1547 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1548 homeIntent.addCategory(Intent.CATEGORY_HOME);
1550 final List<ResolveInfo> listOfLaunchers = context.getPackageManager().queryIntentActivities(homeIntent, 0);
1551 for(ResolveInfo launcher : listOfLaunchers) {
1552 if(launcher.activityInfo.packageName.equals(BuildConfig.SUPPORT_APPLICATION_ID))
1559 @SuppressLint("PrivateApi")
1560 private static String getSystemProperty(String key) {
1562 Class<?> cls = Class.forName("android.os.SystemProperties");
1563 return cls.getMethod("get", String.class).invoke(null, key).toString();
1564 } catch (Exception e) {
1569 @SuppressWarnings("ResultOfMethodCallIgnored")
1570 public static boolean importCustomStartButtonImage(Context context, Uri uri) {
1572 File imagesDir = new File(context.getFilesDir(), "tb_images");
1575 File importedFile = new File(imagesDir, "custom_image_new");
1576 if(importedFile.exists()) importedFile.delete();
1578 BufferedInputStream is = new BufferedInputStream(context.getContentResolver().openInputStream(uri));
1579 byte[] data = new byte[is.available()];
1581 if(data.length > 0) {
1582 BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(importedFile));
1589 File prevFile = new File(imagesDir, "custom_image");
1590 if(prevFile.exists()) prevFile.delete();
1592 importedFile.renameTo(prevFile);
1594 } catch (IOException e) {
1599 public static String getDefaultStartButtonImage(Context context) {
1600 SharedPreferences pref = getSharedPreferences(context);
1601 return pref.getBoolean("app_drawer_icon", false)
1606 private static boolean shouldLaunchTouchAbsorber(Context context) {
1607 return isOverridingFreeformHack(context) && !isChromeOs(context) && getCurrentApiVersion() < 29.0f;
1610 public static boolean isDesktopIconsEnabled(Context context) {
1611 return !canBootToFreeform(context) && !shouldLaunchTouchAbsorber(context);
1614 public static boolean isSystemTrayEnabled(Context context) {
1615 SharedPreferences pref = getSharedPreferences(context);
1617 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1618 && pref.getBoolean("sys_tray", context.getResources().getBoolean(R.bool.tb_def_sys_tray))
1619 && pref.getBoolean("full_length", context.getResources().getBoolean(R.bool.tb_def_full_length))
1620 && !getTaskbarPosition(context).contains("vertical");
1623 @SuppressWarnings("deprecation")
1624 public static boolean isLibrary(Context context) {
1625 return !context.getPackageName().equals(BuildConfig.APPLICATION_ID);
1628 public static boolean applyDisplayCutoutModeTo(WindowManager.LayoutParams params) {
1629 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
1630 params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;