1 /* Copyright 2016 Braden Farmer
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
16 package com.farmerbb.taskbar.util;
18 import android.Manifest;
19 import android.accessibilityservice.AccessibilityService;
20 import android.annotation.SuppressLint;
21 import android.annotation.TargetApi;
22 import android.app.Activity;
23 import android.app.ActivityManager;
24 import android.app.ActivityOptions;
25 import android.app.AlertDialog;
26 import android.app.AppOpsManager;
27 import android.app.Service;
28 import android.content.ActivityNotFoundException;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.SharedPreferences;
33 import android.content.pm.ActivityInfo;
34 import android.content.pm.ApplicationInfo;
35 import android.content.pm.LauncherActivityInfo;
36 import android.content.pm.LauncherApps;
37 import android.content.pm.PackageInfo;
38 import android.content.pm.PackageManager;
39 import android.content.pm.ResolveInfo;
40 import android.content.pm.ShortcutInfo;
41 import android.content.pm.ShortcutManager;
42 import android.content.pm.Signature;
43 import android.content.res.Configuration;
44 import android.graphics.Color;
45 import android.graphics.Rect;
46 import android.hardware.display.DisplayManager;
47 import android.net.Uri;
48 import android.os.Build;
49 import android.os.Bundle;
50 import android.os.Handler;
51 import android.os.Process;
52 import android.os.UserHandle;
53 import android.os.UserManager;
54 import android.provider.Settings;
55 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
56 import androidx.appcompat.view.ContextThemeWrapper;
57 import android.util.DisplayMetrics;
58 import android.view.Display;
59 import android.view.Surface;
60 import android.view.View;
61 import android.view.WindowManager;
62 import android.widget.Toast;
64 import com.farmerbb.taskbar.BuildConfig;
65 import com.farmerbb.taskbar.R;
66 import com.farmerbb.taskbar.activity.ContextMenuActivity;
67 import com.farmerbb.taskbar.activity.DummyActivity;
68 import com.farmerbb.taskbar.activity.InvisibleActivityFreeform;
69 import com.farmerbb.taskbar.activity.MainActivity;
70 import com.farmerbb.taskbar.activity.TouchAbsorberActivity;
71 import com.farmerbb.taskbar.activity.dark.ContextMenuActivityDark;
72 import com.farmerbb.taskbar.service.DashboardService;
73 import com.farmerbb.taskbar.service.NotificationService;
74 import com.farmerbb.taskbar.service.PowerMenuService;
75 import com.farmerbb.taskbar.service.StartMenuService;
76 import com.farmerbb.taskbar.service.TaskbarService;
78 import java.io.BufferedInputStream;
79 import java.io.BufferedOutputStream;
81 import java.io.FileOutputStream;
82 import java.io.IOException;
83 import java.lang.reflect.Constructor;
84 import java.lang.reflect.Method;
85 import java.util.ArrayList;
86 import java.util.Arrays;
87 import java.util.List;
93 private static Integer cachedRotation;
95 private static final int MAXIMIZED = 0;
96 private static final int LEFT = -1;
97 private static final int RIGHT = 1;
99 public static final int HIDDEN = 0;
100 public static final int TOP_APPS = 1;
102 // From android.app.ActivityManager.StackId
103 private static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
104 private static final int FREEFORM_WORKSPACE_STACK_ID = 2;
106 // From android.app.WindowConfiguration
107 private static final int WINDOWING_MODE_FULLSCREEN = 1;
108 private static final int WINDOWING_MODE_FREEFORM = 5;
110 public static final int EXPORT = 123;
111 public static final int IMPORT = 456;
113 @SuppressWarnings("deprecation")
114 public static SharedPreferences getSharedPreferences(Context context) {
115 return context.getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", Context.MODE_PRIVATE);
118 public static void showPermissionDialog(Context context) {
119 showPermissionDialog(context, null, null);
122 public static AlertDialog showPermissionDialog(Context context, Runnable onError, Runnable onFinish) {
123 Runnable finalOnFinish = onFinish == null
127 Runnable finalOnError = onError == null
128 ? () -> showErrorDialog(context, "SYSTEM_ALERT_WINDOW", finalOnFinish)
131 AlertDialog.Builder builder;
132 if(hasAndroidTVSettings(context))
133 builder = buildPermissionDialogAndroidTV(context, finalOnError, finalOnFinish);
135 builder = buildPermissionDialogStandard(context, finalOnError, finalOnFinish);
137 AlertDialog dialog = builder.create();
139 dialog.setCancelable(false);
144 @TargetApi(Build.VERSION_CODES.M)
145 private static AlertDialog.Builder buildPermissionDialogStandard(Context context, Runnable onError, Runnable onFinish) {
146 return new AlertDialog.Builder(context)
147 .setTitle(R.string.tb_permission_dialog_title)
148 .setMessage(R.string.tb_permission_dialog_message)
149 .setPositiveButton(R.string.tb_action_grant_permission, (dialog, which) -> {
151 context.startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
152 Uri.parse("package:" + context.getPackageName())));
155 } catch (ActivityNotFoundException e) {
161 private static AlertDialog.Builder buildPermissionDialogAndroidTV(Context context, Runnable onError, Runnable onFinish) {
162 return new AlertDialog.Builder(context)
163 .setTitle(R.string.tb_permission_dialog_title)
164 .setMessage(R.string.tb_permission_dialog_message_alt)
165 .setPositiveButton(R.string.tb_action_open_settings, (dialog, which) -> {
167 context.startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));
169 } catch (ActivityNotFoundException e) {
175 public static AlertDialog showErrorDialog(Context context, String appopCmd) {
176 return showErrorDialog(context, appopCmd, null);
179 private static AlertDialog showErrorDialog(Context context, String appopCmd, Runnable onFinish) {
180 Runnable finalOnFinish = onFinish == null
184 AlertDialog.Builder builder = new AlertDialog.Builder(context);
185 builder.setTitle(R.string.tb_error_dialog_title)
186 .setMessage(context.getString(R.string.tb_error_dialog_message, context.getPackageName(), appopCmd))
187 .setPositiveButton(R.string.tb_action_ok, (dialog, which) -> finalOnFinish.run());
189 AlertDialog dialog = builder.create();
191 dialog.setCancelable(false);
196 @TargetApi(Build.VERSION_CODES.P)
197 public static void lockDevice(Context context) {
198 sendAccessibilityAction(context, AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN);
201 public static void sendAccessibilityAction(Context context, int action) {
202 sendAccessibilityAction(context, action, null);
205 public static void sendAccessibilityAction(Context context, int action, Runnable onComplete) {
206 ComponentName component = new ComponentName(context, PowerMenuService.class);
207 context.getPackageManager().setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
208 PackageManager.DONT_KILL_APP);
210 boolean isAccessibilityServiceEnabled = isAccessibilityServiceEnabled(context);
212 if(!isAccessibilityServiceEnabled
213 && hasWriteSecureSettingsPermission(context)) {
214 String services = Settings.Secure.getString(context.getContentResolver(),
215 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
217 String finalServices = services == null ? "" : services;
219 String powerMenuService = new ComponentName(context, PowerMenuService.class).flattenToString();
221 if(!finalServices.contains(powerMenuService)) {
223 Settings.Secure.putString(context.getContentResolver(),
224 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
225 finalServices.isEmpty()
227 : finalServices + ":" + powerMenuService);
228 } catch (Exception e) { /* Gracefully fail */ }
231 new Handler().postDelayed(() -> {
232 Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
233 intent.putExtra("action", action);
234 LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
237 Settings.Secure.putString(context.getContentResolver(),
238 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
240 } catch (Exception e) { /* Gracefully fail */ }
242 if(onComplete != null) onComplete.run();
244 } else if(isAccessibilityServiceEnabled) {
245 Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
246 intent.putExtra("action", action);
247 LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
249 if(onComplete != null) onComplete.run();
251 launchApp(context, () -> {
252 Intent intent = new Intent(context, DummyActivity.class);
253 intent.putExtra("accessibility", true);
254 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
257 context.startActivity(intent, getActivityOptionsBundle(context, ApplicationType.APP_PORTRAIT, null));
258 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
263 public static boolean isAccessibilityServiceEnabled(Context context) {
264 String accessibilityServices = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
265 ComponentName component = new ComponentName(context, PowerMenuService.class);
267 return accessibilityServices != null
268 && (accessibilityServices.contains(component.flattenToString())
269 || accessibilityServices.contains(component.flattenToShortString()));
272 public static boolean hasWriteSecureSettingsPermission(Context context) {
273 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
274 && context.checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) == PackageManager.PERMISSION_GRANTED;
277 public static void showToast(Context context, int message) {
278 showToast(context, context.getString(message), Toast.LENGTH_SHORT);
281 public static void showToastLong(Context context, int message) {
282 showToast(context, context.getString(message), Toast.LENGTH_LONG);
285 public static void showToast(Context context, String message, int length) {
288 ToastInterface toast = DependencyUtils.createToast(context.getApplicationContext(), message, length);
291 ToastHelper.getInstance().setLastToast(toast);
294 public static void cancelToast() {
295 ToastInterface toast = ToastHelper.getInstance().getLastToast();
296 if(toast != null) toast.cancel();
299 public static void startShortcut(Context context, AppEntry entry, ShortcutInfo shortcut, View view) {
311 public static void launchApp(final Context context,
312 final AppEntry entry,
313 final String windowSize,
314 final boolean launchedFromTaskbar,
315 final boolean openInNewWindow,
328 public static void launchApp(final Context context,
329 final AppEntry entry,
330 final String windowSize,
331 final Runnable onError) {
343 private static void launchApp(final Context context,
344 final AppEntry entry,
345 final String windowSize,
346 final boolean launchedFromTaskbar,
347 final boolean isPersistentShortcut,
348 final boolean openInNewWindow,
349 final ShortcutInfo shortcut,
351 final Runnable onError) {
352 launchApp(context, launchedFromTaskbar, isPersistentShortcut, () ->
353 continueLaunchingApp(context, entry, windowSize, openInNewWindow, shortcut, view, onError)
357 public static void launchApp(Context context, Runnable runnable) {
358 launchApp(context, true, false, runnable);
361 private static void launchApp(Context context, boolean launchedFromTaskbar, boolean isPersistentShortcut, Runnable runnable) {
362 SharedPreferences pref = getSharedPreferences(context);
363 FreeformHackHelper helper = FreeformHackHelper.getInstance();
365 boolean specialLaunch = hasBrokenSetLaunchBoundsApi()
366 && helper.isInFreeformWorkspace()
367 && MenuHelper.getInstance().isContextMenuOpen();
369 boolean noAnimation = pref.getBoolean("disable_animations", false);
371 if(hasFreeformSupport(context)
372 && (pref.getBoolean("freeform_hack", false) || isPersistentShortcut)
373 && (!helper.isInFreeformWorkspace() || specialLaunch)) {
374 new Handler().postDelayed(() -> {
375 startFreeformHack(context, true);
377 new Handler().postDelayed(runnable, helper.isFreeformHackActive() ? 0 : 100);
378 }, launchedFromTaskbar ? 0 : 100);
380 new Handler().postDelayed(runnable, !launchedFromTaskbar && noAnimation ? 100 : 0);
383 public static void startFreeformHack(Context context) {
384 startFreeformHack(context, false);
387 @TargetApi(Build.VERSION_CODES.N)
388 public static void startFreeformHack(Context context, boolean checkMultiWindow) {
389 Intent freeformHackIntent = new Intent(context, InvisibleActivityFreeform.class);
390 freeformHackIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
391 | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
392 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
395 freeformHackIntent.putExtra("check_multiwindow", true);
397 if(canDrawOverlays(context))
398 startActivityLowerRight(context, freeformHackIntent);
401 public static void stopFreeformHack(Context context) {
402 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.FINISH_FREEFORM_ACTIVITY"));
404 if(isOverridingFreeformHack(context, false)) {
405 FreeformHackHelper helper = FreeformHackHelper.getInstance();
406 helper.setFreeformHackActive(false);
407 helper.setInFreeformWorkspace(false);
411 @TargetApi(Build.VERSION_CODES.N)
412 private static void continueLaunchingApp(Context context,
415 boolean openInNewWindow,
416 ShortcutInfo shortcut,
419 SharedPreferences pref = getSharedPreferences(context);
420 Intent intent = new Intent();
421 intent.setComponent(ComponentName.unflattenFromString(entry.getComponentName()));
422 intent.setAction(Intent.ACTION_MAIN);
423 intent.addCategory(Intent.CATEGORY_LAUNCHER);
424 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
425 intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
427 if(FreeformHackHelper.getInstance().isInFreeformWorkspace()
428 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1)
429 intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
431 if(pref.getBoolean("disable_animations", false))
432 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
434 boolean realOpenInNewWindow = openInNewWindow || pref.getBoolean("force_new_window", false);
435 if(realOpenInNewWindow) {
436 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
438 ActivityInfo activityInfo = intent.resolveActivityInfo(context.getPackageManager(), 0);
439 if(activityInfo != null) {
440 switch(activityInfo.launchMode) {
441 case ActivityInfo.LAUNCH_SINGLE_TASK:
442 case ActivityInfo.LAUNCH_SINGLE_INSTANCE:
443 intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
449 ApplicationType type = getApplicationType(context, entry);
451 if(windowSize == null)
452 windowSize = SavedWindowSizes.getInstance(context).getWindowSize(context, entry.getPackageName());
454 Bundle bundle = getActivityOptionsBundle(context, type, windowSize, view);
456 prepareToStartActivity(context, realOpenInNewWindow, () -> {
457 if(shortcut == null) {
458 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
459 if(entry.getUserId(context) == userManager.getSerialNumberForUser(Process.myUserHandle())) {
461 context.startActivity(intent, bundle);
462 } catch (ActivityNotFoundException e) {
463 launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context), onError);
464 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
466 launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context), onError);
468 launchShortcut(context, shortcut, bundle, onError);
471 if(shouldCollapse(context, true))
472 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_TASKBAR"));
474 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_START_MENU"));
477 private static Bundle launchMode1(Context context, ApplicationType type, View view, int factor) {
478 DisplayInfo display = getDisplayInfo(context);
480 int width1 = display.width / factor;
481 int width2 = display.width - width1;
482 int height1 = display.height / factor;
483 int height2 = display.height - height1;
485 return getActivityOptionsBundle(context, type, view,
493 private static Bundle launchMode2(Context context, int launchType, ApplicationType type, View view) {
494 DisplayInfo display = getDisplayInfo(context);
496 int statusBarHeight = getStatusBarHeight(context);
497 String position = getTaskbarPosition(context);
499 boolean isPortrait = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
500 boolean isLandscape = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
503 int top = statusBarHeight;
504 int right = display.width;
505 int bottom = display.height;
507 int iconSize = isOverridingFreeformHack(context) && !LauncherHelper.getInstance().isOnHomeScreen()
509 : context.getResources().getDimensionPixelSize(R.dimen.tb_icon_size);
511 if(position.contains("vertical_left"))
512 left = left + iconSize;
513 else if(position.contains("vertical_right"))
514 right = right - iconSize;
515 else if(position.contains("bottom"))
516 bottom = bottom - iconSize;
518 top = top + iconSize;
520 int halfLandscape = (right / 2) + ((iconSize / 2) * (position.contains("vertical_left") ? 1 : 0));
521 int halfPortrait = (bottom / 2) + ((iconSize / 2) * ((position.equals("top_left") || position.equals("top_right")) ? 1 : 0));
523 if(launchType == RIGHT && isLandscape)
524 left = halfLandscape;
525 else if(launchType == RIGHT && isPortrait)
527 else if(launchType == LEFT && isLandscape)
528 right = halfLandscape;
529 else if(launchType == LEFT && isPortrait)
530 bottom = halfPortrait;
532 return getActivityOptionsBundle(context, type, view, left, top, right, bottom);
535 private static Bundle launchMode3(Context context, ApplicationType type, View view) {
536 DisplayInfo display = getDisplayInfo(context);
538 boolean isLandscape = type == ApplicationType.APP_LANDSCAPE;
539 int widthDimen = isLandscape ? R.dimen.tb_phone_size_height : R.dimen.tb_phone_size_width;
540 int heightDimen = isLandscape ? R.dimen.tb_phone_size_width : R.dimen.tb_phone_size_height;
542 int width1 = display.width / 2;
543 int width2 = context.getResources().getDimensionPixelSize(widthDimen) / 2;
544 int height1 = display.height / 2;
545 int height2 = context.getResources().getDimensionPixelSize(heightDimen) / 2;
547 return getActivityOptionsBundle(context, type, view,
555 private static void launchAndroidForWork(Context context, ComponentName componentName, Bundle bundle, long userId, Runnable onError) {
556 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
557 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
560 launcherApps.startMainActivity(componentName, userManager.getUserForSerialNumber(userId), null, bundle);
561 } catch (ActivityNotFoundException | NullPointerException
562 | IllegalStateException | SecurityException e) {
563 if(onError != null) launchApp(context, onError);
567 @TargetApi(Build.VERSION_CODES.N_MR1)
568 private static void launchShortcut(Context context, ShortcutInfo shortcut, Bundle bundle, Runnable onError) {
569 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
571 if(launcherApps.hasShortcutHostPermission()) {
573 launcherApps.startShortcut(shortcut, null, bundle);
574 } catch (ActivityNotFoundException | NullPointerException
575 | IllegalStateException | SecurityException e) {
576 if(onError != null) launchApp(context, onError);
581 private static void prepareToStartActivity(Context context, boolean openInNewWindow, Runnable runnable) {
582 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_CONTEXT_MENU"));
584 if(!FreeformHackHelper.getInstance().isTouchAbsorberActive()
585 && shouldLaunchTouchAbsorber(context)) {
586 startTouchAbsorberActivity(context);
587 new Handler().postDelayed(runnable, 100);
588 } else if(openInNewWindow) {
589 Intent intent = new Intent(context, DummyActivity.class);
590 intent.putExtra("finish_on_pause", true);
591 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
592 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
593 startActivityLowerRight(context, intent);
595 new Handler().postDelayed(runnable, 100);
600 public static void startActivityMaximized(Context context, Intent intent) {
601 Bundle bundle = launchMode2(context, MAXIMIZED, ApplicationType.CONTEXT_MENU, null);
602 prepareToStartActivity(context, false, () -> context.startActivity(intent, bundle));
605 public static void startActivityLowerRight(Context context, Intent intent) {
606 DisplayInfo display = getDisplayInfo(context);
608 context.startActivity(intent,
609 getActivityOptionsBundle(context, ApplicationType.FREEFORM_HACK, null,
615 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
618 public static void startTouchAbsorberActivity(Context context) {
619 String position = getTaskbarPosition(context);
620 DisplayInfo display = getDisplayInfo(context);
624 int right = display.width;
625 int bottom = display.height;
627 int iconSize = context.getResources().getDimensionPixelSize(R.dimen.tb_icon_size);
629 if(position.contains("vertical_left"))
631 else if(position.contains("vertical_right"))
632 left = right - iconSize;
633 else if(position.contains("bottom"))
634 top = bottom - iconSize;
638 Intent intent = new Intent(context, TouchAbsorberActivity.class);
639 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
640 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
643 context.startActivity(intent,
644 getActivityOptionsBundle(context, ApplicationType.FREEFORM_HACK, null,
645 left, top, right, bottom));
646 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
649 public static void startContextMenuActivity(Context context, Bundle args) {
650 SharedPreferences pref = getSharedPreferences(context);
651 Intent intent = null;
653 switch(pref.getString("theme", "light")) {
655 intent = new Intent(context, ContextMenuActivity.class);
658 intent = new Intent(context, ContextMenuActivityDark.class);
663 intent.putExtra("args", args);
664 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
667 if(hasFreeformSupport(context) && FreeformHackHelper.getInstance().isInFreeformWorkspace()) {
668 DisplayInfo display = getDisplayInfo(context);
670 if(intent != null && hasBrokenSetLaunchBoundsApi())
671 intent.putExtra("context_menu_fix", true);
673 context.startActivity(intent,
674 getActivityOptionsBundle(context, ApplicationType.CONTEXT_MENU, null,
675 0, 0, display.width, display.height));
677 context.startActivity(intent);
680 public static void checkForUpdates(Context context) {
682 if(isPlayStoreRelease(context)) {
683 if(context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
684 && !isPlayStoreInstalled(context))
685 url = "https://github.com/farmerbb/Taskbar/releases";
687 url = "https://play.google.com/store/apps/details?id=" + context.getPackageName();
689 url = "https://f-droid.org/repository/browse/?fdid=" + context.getPackageName();
691 Intent intent = new Intent(Intent.ACTION_VIEW);
692 intent.setData(Uri.parse(url));
693 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
696 context.startActivity(intent);
697 } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
700 public static boolean launcherIsDefault(Context context) {
701 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
702 homeIntent.addCategory(Intent.CATEGORY_HOME);
703 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
705 return defaultLauncher.activityInfo.packageName.equals(context.getPackageName());
708 public static void setCachedRotation(int cachedRotation) {
709 U.cachedRotation = cachedRotation;
712 public static String getTaskbarPosition(Context context) {
713 SharedPreferences pref = getSharedPreferences(context);
714 String position = pref.getString("position", "bottom_left");
716 if(pref.getBoolean("anchor", false)) {
717 WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
718 int rotation = cachedRotation != null ? cachedRotation : windowManager.getDefaultDisplay().getRotation();
723 case Surface.ROTATION_0:
724 return "bottom_left";
725 case Surface.ROTATION_90:
726 return "bottom_vertical_right";
727 case Surface.ROTATION_180:
729 case Surface.ROTATION_270:
730 return "top_vertical_left";
733 case "bottom_vertical_left":
735 case Surface.ROTATION_0:
736 return "bottom_vertical_left";
737 case Surface.ROTATION_90:
738 return "bottom_right";
739 case Surface.ROTATION_180:
740 return "top_vertical_right";
741 case Surface.ROTATION_270:
747 case Surface.ROTATION_0:
748 return "bottom_right";
749 case Surface.ROTATION_90:
750 return "top_vertical_right";
751 case Surface.ROTATION_180:
753 case Surface.ROTATION_270:
754 return "bottom_vertical_left";
757 case "bottom_vertical_right":
759 case Surface.ROTATION_0:
760 return "bottom_vertical_right";
761 case Surface.ROTATION_90:
763 case Surface.ROTATION_180:
764 return "top_vertical_left";
765 case Surface.ROTATION_270:
766 return "bottom_left";
771 case Surface.ROTATION_0:
773 case Surface.ROTATION_90:
774 return "bottom_vertical_left";
775 case Surface.ROTATION_180:
776 return "bottom_right";
777 case Surface.ROTATION_270:
778 return "top_vertical_right";
781 case "top_vertical_left":
783 case Surface.ROTATION_0:
784 return "top_vertical_left";
785 case Surface.ROTATION_90:
786 return "bottom_left";
787 case Surface.ROTATION_180:
788 return "bottom_vertical_right";
789 case Surface.ROTATION_270:
795 case Surface.ROTATION_0:
797 case Surface.ROTATION_90:
798 return "top_vertical_left";
799 case Surface.ROTATION_180:
800 return "bottom_left";
801 case Surface.ROTATION_270:
802 return "bottom_vertical_right";
805 case "top_vertical_right":
807 case Surface.ROTATION_0:
808 return "top_vertical_right";
809 case Surface.ROTATION_90:
811 case Surface.ROTATION_180:
812 return "bottom_vertical_left";
813 case Surface.ROTATION_270:
814 return "bottom_right";
823 private static int getMaxNumOfColumns(Context context) {
824 SharedPreferences pref = getSharedPreferences(context);
825 DisplayInfo display = getDisplayInfo(context);
826 float density = display.density / 160;
827 float baseTaskbarSize = getBaseTaskbarSizeFloat(context) / density;
828 int numOfColumns = 0;
830 float maxScreenSize = getTaskbarPosition(context).contains("vertical")
831 ? (display.height - getStatusBarHeight(context)) / density
832 : display.width / density;
834 float iconSize = context.getResources().getDimension(R.dimen.tb_icon_size) / density;
836 int userMaxNumOfColumns = Integer.valueOf(pref.getString("max_num_of_recents", "10"));
838 while(baseTaskbarSize + iconSize < maxScreenSize
839 && numOfColumns < userMaxNumOfColumns) {
840 baseTaskbarSize = baseTaskbarSize + iconSize;
847 public static int getMaxNumOfEntries(Context context) {
848 SharedPreferences pref = getSharedPreferences(context);
849 return pref.getBoolean("disable_scrolling_list", false)
850 ? getMaxNumOfColumns(context)
851 : Integer.valueOf(pref.getString("max_num_of_recents", "10"));
854 public static int getStatusBarHeight(Context context) {
855 return getSystemDimen(context, "status_bar_height");
858 private static int getNavbarHeight(Context context) {
859 return getSystemDimen(context, "navigation_bar_height");
862 private static int getSystemDimen(Context context, String id) {
864 int resourceId = context.getResources().getIdentifier(id, "dimen", "android");
866 value = context.getResources().getDimensionPixelSize(resourceId);
871 public static void refreshPinnedIcons(Context context) {
872 IconCache.getInstance(context).clearCache();
874 PinnedBlockedApps pba = PinnedBlockedApps.getInstance(context);
875 List<AppEntry> pinnedAppsList = new ArrayList<>(pba.getPinnedApps());
876 List<AppEntry> blockedAppsList = new ArrayList<>(pba.getBlockedApps());
877 PackageManager pm = context.getPackageManager();
881 for(AppEntry entry : pinnedAppsList) {
882 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
883 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
885 final List<UserHandle> userHandles = userManager.getUserProfiles();
886 LauncherActivityInfo appInfo = null;
888 for(UserHandle handle : userHandles) {
889 List<LauncherActivityInfo> list = launcherApps.getActivityList(entry.getPackageName(), handle);
890 if(!list.isEmpty()) {
891 // Google App workaround
892 if(!entry.getPackageName().equals("com.google.android.googlequicksearchbox"))
893 appInfo = list.get(0);
895 boolean added = false;
896 for(LauncherActivityInfo info : list) {
897 if(info.getName().equals("com.google.android.googlequicksearchbox.SearchActivity")) {
903 if(!added) appInfo = list.get(0);
910 if(appInfo != null) {
911 AppEntry newEntry = new AppEntry(
912 entry.getPackageName(),
913 entry.getComponentName(),
915 IconCache.getInstance(context).getIcon(context, pm, appInfo),
918 newEntry.setUserId(entry.getUserId(context));
919 pba.addPinnedApp(context, newEntry);
923 for(AppEntry entry : blockedAppsList) {
924 pba.addBlockedApp(context, entry);
928 public static boolean canEnableFreeform() {
929 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
932 @TargetApi(Build.VERSION_CODES.N)
933 public static boolean hasFreeformSupport(Context context) {
934 return canEnableFreeform()
935 && (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
936 || Settings.Global.getInt(context.getContentResolver(), "enable_freeform_support", 0) != 0
937 || (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
938 && Settings.Global.getInt(context.getContentResolver(), "force_resizable_activities", 0) != 0));
941 public static boolean canBootToFreeform(Context context) {
942 return hasFreeformSupport(context) && !isOverridingFreeformHack(context);
945 public static boolean isSamsungDevice() {
946 return Build.MANUFACTURER.equalsIgnoreCase("Samsung");
949 private static boolean isNvidiaDevice() {
950 return Build.MANUFACTURER.equalsIgnoreCase("NVIDIA");
953 public static boolean isServiceRunning(Context context, Class<? extends Service> cls) {
954 if(LauncherHelper.getInstance().isOnSecondaryHomeScreen()
955 && (cls.equals(TaskbarService.class)
956 || cls.equals(StartMenuService.class)
957 || cls.equals(DashboardService.class)))
960 return isServiceRunning(context, cls.getName());
963 private static boolean isServiceRunning(Context context, String className) {
964 ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
965 for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
966 if(className.equals(service.service.getClassName()))
973 public static int getBackgroundTint(Context context) {
974 SharedPreferences pref = getSharedPreferences(context);
976 // Import old background tint preference
977 if(pref.contains("show_background")) {
978 SharedPreferences.Editor editor = pref.edit();
980 if(!pref.getBoolean("show_background", true))
981 editor.putInt("background_tint", Color.TRANSPARENT).apply();
983 editor.remove("show_background");
987 return pref.getInt("background_tint", context.getResources().getInteger(R.integer.tb_translucent_gray));
990 public static int getAccentColor(Context context) {
991 SharedPreferences pref = getSharedPreferences(context);
992 return pref.getInt("accent_color", context.getResources().getInteger(R.integer.tb_translucent_white));
995 public static boolean canDrawOverlays(Context context) {
996 return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context);
999 public static boolean isGame(Context context, String packageName) {
1000 SharedPreferences pref = getSharedPreferences(context);
1001 if(pref.getBoolean("launch_games_fullscreen", true)) {
1002 PackageManager pm = context.getPackageManager();
1005 ApplicationInfo info = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
1006 return (info.flags & ApplicationInfo.FLAG_IS_GAME) != 0 || (info.metaData != null && info.metaData.getBoolean("isGame", false));
1007 } catch (PackageManager.NameNotFoundException e) {
1014 private static ActivityOptions getActivityOptions(View view) {
1015 return getActivityOptions(null, null, view);
1018 public static ActivityOptions getActivityOptions(Context context, ApplicationType applicationType, View view) {
1019 ActivityOptions options;
1021 options = ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight());
1022 else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1023 options = ActivityOptions.makeBasic();
1026 Constructor<ActivityOptions> constructor = ActivityOptions.class.getDeclaredConstructor();
1027 constructor.setAccessible(true);
1028 options = constructor.newInstance();
1029 } catch (Exception e) {
1034 if(applicationType == null)
1039 switch(applicationType) {
1042 if(FreeformHackHelper.getInstance().isFreeformHackActive())
1043 stackId = getFreeformWindowModeId();
1045 stackId = getFullscreenWindowModeId();
1048 stackId = getFullscreenWindowModeId();
1051 stackId = getFreeformWindowModeId();
1054 if(hasBrokenSetLaunchBoundsApi()
1055 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f))
1056 stackId = getFullscreenWindowModeId();
1061 Method method = ActivityOptions.class.getMethod(getWindowingModeMethodName(), int.class);
1062 method.invoke(options, stackId);
1063 } catch (Exception e) { /* Gracefully fail */ }
1065 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1066 int launchDisplayId = LauncherHelper.getInstance().getSecondaryDisplayId();
1067 if(launchDisplayId != -1)
1068 options.setLaunchDisplayId(launchDisplayId);
1074 private static int getFullscreenWindowModeId() {
1075 if(getCurrentApiVersion() >= 28.0f)
1076 return WINDOWING_MODE_FULLSCREEN;
1078 return FULLSCREEN_WORKSPACE_STACK_ID;
1081 private static int getFreeformWindowModeId() {
1082 if(getCurrentApiVersion() >= 28.0f)
1083 return WINDOWING_MODE_FREEFORM;
1085 return FREEFORM_WORKSPACE_STACK_ID;
1088 private static String getWindowingModeMethodName() {
1089 if(getCurrentApiVersion() >= 28.0f)
1090 return "setLaunchWindowingMode";
1092 return "setLaunchStackId";
1095 public static Bundle getActivityOptionsBundle(Context context, ApplicationType type, View view) {
1096 SharedPreferences pref = getSharedPreferences(context);
1098 return getActivityOptionsBundle(context, type, pref.getString("window_size", context.getString(R.string.tb_def_window_size)), view);
1101 private static Bundle getActivityOptionsBundle(Context context, ApplicationType type, String windowSize, View view) {
1102 SharedPreferences pref = getSharedPreferences(context);
1103 if(!canEnableFreeform() || !pref.getBoolean("freeform_hack", false))
1104 return getActivityOptions(view).toBundle();
1106 switch(windowSize) {
1108 if(getCurrentApiVersion() > 29.0f)
1109 return launchMode1(context, type, view, 4);
1112 return launchMode1(context, type, view, 8);
1114 return launchMode2(context, MAXIMIZED, type, view);
1116 return launchMode2(context, LEFT, type, view);
1118 return launchMode2(context, RIGHT, type, view);
1120 return launchMode3(context, type, view);
1123 return getActivityOptions(context, type, view).toBundle();
1126 private static Bundle getActivityOptionsBundle(Context context, ApplicationType applicationType, View view,
1127 int left, int top, int right, int bottom) {
1128 ActivityOptions options = getActivityOptions(context, applicationType, view);
1132 if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
1133 return options.toBundle();
1135 return options.setLaunchBounds(new Rect(left, top, right, bottom)).toBundle();
1138 @SuppressLint("SwitchIntDef")
1139 private static ApplicationType getApplicationType(Context context, AppEntry entry) {
1140 if(isGame(context, entry.getPackageName()))
1141 return ApplicationType.GAME;
1144 ActivityInfo info = context.getPackageManager().getActivityInfo(
1145 ComponentName.unflattenFromString(entry.getComponentName()),
1149 switch(info.screenOrientation) {
1150 case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
1151 case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
1152 case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
1153 case ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE:
1154 return ApplicationType.APP_LANDSCAPE;
1156 case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
1157 case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:
1158 case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:
1159 case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT:
1160 return ApplicationType.APP_PORTRAIT;
1162 } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
1164 return context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
1165 ? ApplicationType.APP_LANDSCAPE
1166 : ApplicationType.APP_PORTRAIT;
1169 public static boolean isSystemApp(Context context) {
1171 ApplicationInfo info = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
1172 int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
1173 return (info.flags & mask) != 0;
1174 } catch (PackageManager.NameNotFoundException e) {
1179 public static boolean isChromeOs(Context context) {
1180 return context.getPackageManager().hasSystemFeature("org.chromium.arc");
1183 public static boolean isBlissOs(Context context) {
1184 boolean validBlissOsBuildProp = false;
1186 String blissVersion = getSystemProperty("ro.bliss.version");
1187 if(blissVersion != null && !blissVersion.isEmpty())
1188 validBlissOsBuildProp = true;
1190 String buildUser = getSystemProperty("ro.build.user");
1191 if(buildUser != null && buildUser.equals("electrikjesus"))
1192 validBlissOsBuildProp = true;
1194 return validBlissOsBuildProp
1195 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1196 && isSystemApp(context);
1199 public static boolean isLauncherPermanentlyEnabled(Context context) {
1200 if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
1203 PackageManager pm = context.getPackageManager();
1205 pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1206 return pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, context.getPackageName()) == PackageManager.SIGNATURE_MATCH
1207 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1208 && isSystemApp(context);
1209 } catch (PackageManager.NameNotFoundException e) {
1214 public static boolean hasSupportLibrary(Context context, int minVersion) {
1215 PackageManager pm = context.getPackageManager();
1217 PackageInfo pInfo = pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1218 return pInfo.versionCode >= minVersion
1219 && pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, context.getPackageName()) == PackageManager.SIGNATURE_MATCH
1220 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1221 && isSystemApp(context);
1222 } catch (PackageManager.NameNotFoundException e) {
1227 public static int getBaseTaskbarSize(Context context) {
1228 return Math.round(getBaseTaskbarSizeFloat(context));
1231 private static float getBaseTaskbarSizeFloat(Context context) {
1232 SharedPreferences pref = getSharedPreferences(context);
1233 float baseTaskbarSize = context.getResources().getDimension(R.dimen.tb_base_taskbar_size);
1234 boolean navbarButtonsEnabled = false;
1236 if(pref.getBoolean("dashboard", context.getResources().getBoolean(R.bool.tb_def_dashboard)))
1237 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_dashboard_button_size);
1239 if(pref.getBoolean("button_back", false)) {
1240 navbarButtonsEnabled = true;
1241 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1244 if(pref.getBoolean("button_home", false)) {
1245 navbarButtonsEnabled = true;
1246 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1249 if(pref.getBoolean("button_recents", false)) {
1250 navbarButtonsEnabled = true;
1251 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1254 if(navbarButtonsEnabled)
1255 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_navbar_buttons_margin);
1257 if(isSystemTrayEnabled(context))
1258 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_systray_size);
1260 return baseTaskbarSize;
1263 private static void startTaskbarService(Context context, boolean fullRestart) {
1264 context.startService(new Intent(context, TaskbarService.class));
1265 context.startService(new Intent(context, StartMenuService.class));
1266 context.startService(new Intent(context, DashboardService.class));
1267 if(fullRestart) context.startService(new Intent(context, NotificationService.class));
1270 private static void stopTaskbarService(Context context, boolean fullRestart) {
1271 context.stopService(new Intent(context, TaskbarService.class));
1272 context.stopService(new Intent(context, StartMenuService.class));
1273 context.stopService(new Intent(context, DashboardService.class));
1274 if(fullRestart) context.stopService(new Intent(context, NotificationService.class));
1277 public static void restartTaskbar(Context context) {
1278 SharedPreferences pref = getSharedPreferences(context);
1279 if(pref.getBoolean("taskbar_active", false) && !pref.getBoolean("is_hidden", false)) {
1281 .putBoolean("is_restarting", true)
1282 .putBoolean("skip_auto_hide_navbar", true)
1285 stopTaskbarService(context, true);
1286 startTaskbarService(context, true);
1287 } else if(isServiceRunning(context, StartMenuService.class)) {
1288 pref.edit().putBoolean("skip_auto_hide_navbar", true).apply();
1290 stopTaskbarService(context, false);
1291 startTaskbarService(context, false);
1294 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.RESTART"));
1297 public static void restartNotificationService(Context context) {
1298 if(isServiceRunning(context, NotificationService.class)) {
1299 SharedPreferences pref = getSharedPreferences(context);
1300 pref.edit().putBoolean("is_restarting", true).apply();
1302 Intent intent = new Intent(context, NotificationService.class);
1303 context.stopService(intent);
1304 context.startService(intent);
1308 public static void showHideNavigationBar(Context context, boolean show) {
1309 if(hasSupportLibrary(context, 7)) {
1310 Intent intent = new Intent(BuildConfig.SUPPORT_APPLICATION_ID + ".CHANGE_OVERSCAN");
1311 intent.setPackage(BuildConfig.SUPPORT_APPLICATION_ID);
1313 intent.putExtra("display_id", getDisplayID());
1314 intent.putExtra("value", show ? 0 : getNavbarHeight(context) * -1);
1316 context.sendBroadcast(intent);
1320 // Show or hide the system navigation bar on Bliss-x86
1322 if(getCurrentApiVersion() >= 28.0f)
1323 Settings.Secure.putInt(context.getContentResolver(), "navigation_bar_visible", show ? 1 : 0);
1325 Settings.System.putInt(context.getContentResolver(), "navigation_bar_show", show ? 1 : 0);
1326 } catch (Exception e) { /* Gracefully fail */ }
1329 public static void initPrefs(Context context) {
1330 // Enable freeform hack automatically on supported devices
1331 SharedPreferences pref = getSharedPreferences(context);
1332 if(canEnableFreeform()) {
1333 if(!pref.getBoolean("freeform_hack_override", false)) {
1335 .putBoolean("freeform_hack", hasFreeformSupport(context) && !isSamsungDevice())
1336 .putBoolean("save_window_sizes", false)
1337 .putBoolean("freeform_hack_override", true)
1339 } else if(!hasFreeformSupport(context)) {
1340 pref.edit().putBoolean("freeform_hack", false).apply();
1342 stopFreeformHack(context);
1345 boolean freeformWasEnabled = pref.getBoolean("freeform_hack", false)
1346 || pref.getBoolean("show_freeform_disabled_message", false);
1349 .putBoolean("freeform_hack", false)
1350 .putBoolean("show_freeform_disabled_message", freeformWasEnabled)
1353 SavedWindowSizes.getInstance(context).clear(context);
1354 stopFreeformHack(context);
1357 // Customizations for BlissOS
1358 if(isBlissOs(context) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1359 && !pref.getBoolean("bliss_os_prefs", false)) {
1360 SharedPreferences.Editor editor = pref.edit();
1362 if(hasFreeformSupport(context)) {
1363 editor.putBoolean("freeform_hack", true);
1366 editor.putString("recents_amount", "running_apps_only");
1367 editor.putString("refresh_frequency", "0");
1368 editor.putString("max_num_of_recents", "2147483647");
1369 editor.putString("sort_order", "true");
1370 editor.putString("start_button_image", "app_logo");
1371 editor.putBoolean("button_back", true);
1372 editor.putBoolean("button_home", true);
1373 editor.putBoolean("button_recents", true);
1374 editor.putBoolean("auto_hide_navbar", true);
1375 editor.putBoolean("shortcut_icon", false);
1376 editor.putBoolean("bliss_os_prefs", true);
1380 // Customizations for Android-x86 devices (non-Bliss)
1381 if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
1382 && isSystemApp(context)
1383 && !pref.getBoolean("android_x86_prefs", false)) {
1385 .putString("recents_amount", "running_apps_only")
1386 .putString("refresh_frequency", "0")
1387 .putString("max_num_of_recents", "2147483647")
1388 .putString("sort_order", "true")
1389 .putBoolean("shortcut_icon", false)
1390 .putBoolean("android_x86_prefs", true)
1395 public static DisplayInfo getDisplayInfo(Context context) {
1396 return getDisplayInfo(context, false);
1399 public static DisplayInfo getDisplayInfo(Context context, boolean fromTaskbar) {
1400 context = context.getApplicationContext();
1401 int displayID = getDisplayID();
1403 DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1404 Display currentDisplay = null;
1406 for(Display display : dm.getDisplays()) {
1407 if(display.getDisplayId() == displayID) {
1408 currentDisplay = display;
1413 if(currentDisplay == null)
1414 return new DisplayInfo(0, 0, 0);
1416 DisplayMetrics metrics = new DisplayMetrics();
1417 currentDisplay.getMetrics(metrics);
1419 DisplayMetrics realMetrics = new DisplayMetrics();
1420 currentDisplay.getRealMetrics(realMetrics);
1422 DisplayInfo info = new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);
1424 if(isChromeOs(context)) {
1425 SharedPreferences pref = getSharedPreferences(context);
1426 if(!pref.getBoolean("chrome_os_context_menu_fix", true)) {
1427 info.width = realMetrics.widthPixels;
1428 info.height = realMetrics.heightPixels;
1434 // Workaround for incorrect display size on devices with notches in landscape mode
1435 if(fromTaskbar && context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)
1438 boolean sameWidth = metrics.widthPixels == realMetrics.widthPixels;
1439 boolean sameHeight = metrics.heightPixels == realMetrics.heightPixels;
1441 if(sameWidth && !sameHeight) {
1442 info.width = realMetrics.widthPixels;
1443 info.height = realMetrics.heightPixels - getNavbarHeight(context);
1446 if(!sameWidth && sameHeight) {
1447 info.width = realMetrics.widthPixels - getNavbarHeight(context);
1448 info.height = realMetrics.heightPixels;
1454 private static int getDisplayID() {
1455 LauncherHelper helper = LauncherHelper.getInstance();
1457 if(helper.isOnSecondaryHomeScreen())
1458 return helper.getSecondaryDisplayId();
1460 return Display.DEFAULT_DISPLAY;
1463 public static void pinAppShortcut(Context context) {
1464 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1465 ShortcutManager mShortcutManager = context.getSystemService(ShortcutManager.class);
1467 if(mShortcutManager.isRequestPinShortcutSupported()) {
1468 ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder(context, "freeform_mode").build();
1470 mShortcutManager.requestPinShortcut(pinShortcutInfo, null);
1472 showToastLong(context, R.string.tb_pin_shortcut_not_supported);
1474 Intent intent = ShortcutUtils.getShortcutIntent(context);
1475 intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
1476 intent.putExtra("duplicate", false);
1478 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1479 homeIntent.addCategory(Intent.CATEGORY_HOME);
1480 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
1482 intent.setPackage(defaultLauncher.activityInfo.packageName);
1483 context.sendBroadcast(intent);
1485 showToast(context, R.string.tb_shortcut_created);
1489 public static boolean shouldCollapse(Context context, boolean pendingAppLaunch) {
1490 SharedPreferences pref = getSharedPreferences(context);
1491 if(pref.getBoolean("hide_taskbar", true)) {
1492 if(!pref.getBoolean("freeform_hack", false)
1493 || isOverridingFreeformHack(context, false))
1494 return !LauncherHelper.getInstance().isOnHomeScreen();
1496 FreeformHackHelper helper = FreeformHackHelper.getInstance();
1497 if(pendingAppLaunch)
1498 return !helper.isFreeformHackActive();
1500 return !helper.isInFreeformWorkspace();
1506 public static boolean isOverridingFreeformHack(Context context) {
1507 return isOverridingFreeformHack(context, true);
1510 public static boolean isOverridingFreeformHack(Context context, boolean checkPref) {
1511 SharedPreferences pref = getSharedPreferences(context);
1512 return (!checkPref || pref.getBoolean("freeform_hack", false))
1513 && ((isChromeOs(context) && pref.getBoolean("chrome_os_context_menu_fix", true))
1514 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f));
1517 public static boolean isPlayStoreInstalled(Context context) {
1519 context.getPackageManager().getPackageInfo("com.android.vending", 0);
1521 } catch (PackageManager.NameNotFoundException e) {
1526 public static float getCurrentApiVersion() {
1527 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1528 return Float.valueOf(Build.VERSION.SDK_INT + "." + Build.VERSION.PREVIEW_SDK_INT);
1530 return (float) Build.VERSION.SDK_INT;
1533 public static boolean hasBrokenSetLaunchBoundsApi() {
1534 return getCurrentApiVersion() >= 26.0f
1535 && getCurrentApiVersion() < 28.0f
1536 && !isSamsungDevice()
1537 && !isNvidiaDevice();
1540 public static String getSecondScreenPackageName(Context context) {
1541 return getInstalledPackage(context,
1542 "com.farmerbb.secondscreen.free",
1543 "com.farmerbb.secondscreen");
1546 // Returns the name of an installed package from a list of package names, in order of preference
1547 private static String getInstalledPackage(Context context, String... packageNames) {
1548 return getInstalledPackage(context, Arrays.asList(packageNames));
1551 private static String getInstalledPackage(Context context, List<String> packageNames) {
1552 if(packageNames == null || packageNames.isEmpty())
1555 List<String> packages = packageNames instanceof ArrayList ? packageNames : new ArrayList<>(packageNames);
1556 String packageName = packages.get(0);
1559 context.getPackageManager().getPackageInfo(packageName, 0);
1561 } catch (PackageManager.NameNotFoundException e) {
1563 return getInstalledPackage(context, packages);
1567 public static void showRecentAppsDialog(Context context) {
1568 showRecentAppsDialog(context, null, null);
1571 public static AlertDialog showRecentAppsDialog(Context context, Runnable onError, Runnable onFinish) {
1572 Runnable finalOnFinish = onFinish == null
1576 Runnable finalOnError = onError == null
1577 ? () -> showErrorDialog(context, "GET_USAGE_STATS", finalOnFinish)
1580 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isSystemApp(context)) {
1581 ApplicationInfo applicationInfo = null;
1583 applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
1584 } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
1586 if(applicationInfo != null) {
1587 AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
1588 int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
1590 if(mode != AppOpsManager.MODE_ALLOWED) {
1591 AlertDialog.Builder builder;
1592 if(hasAndroidTVSettings(context))
1593 builder = buildRecentAppsDialogAndroidTV(context, finalOnError, finalOnFinish);
1595 builder = buildRecentAppsDialogStandard(context, finalOnError, finalOnFinish);
1597 AlertDialog dialog = builder.create();
1599 dialog.setCancelable(false);
1606 finalOnFinish.run();
1610 private static AlertDialog.Builder buildRecentAppsDialogStandard(Context context, Runnable onError, Runnable onFinish) {
1611 return new AlertDialog.Builder(context)
1612 .setTitle(R.string.tb_pref_header_recent_apps)
1613 .setMessage(R.string.tb_enable_recent_apps)
1614 .setPositiveButton(R.string.tb_action_ok, (dialog, which) -> {
1616 context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
1617 showToastLong(context, R.string.tb_usage_stats_message);
1620 } catch (ActivityNotFoundException e) {
1624 .setNegativeButton(R.string.tb_action_cancel, (dialog, which) -> onFinish.run());
1627 private static AlertDialog.Builder buildRecentAppsDialogAndroidTV(Context context, Runnable onError, Runnable onFinish) {
1628 return new AlertDialog.Builder(context)
1629 .setTitle(R.string.tb_pref_header_recent_apps)
1630 .setMessage(R.string.tb_enable_recent_apps_alt)
1631 .setPositiveButton(R.string.tb_action_open_settings, (dialog, which) -> {
1633 context.startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));
1635 } catch (ActivityNotFoundException e) {
1639 .setNegativeButton(R.string.tb_action_cancel, (dialog, which) -> onFinish.run());
1642 public static Context wrapContext(Context context) {
1643 SharedPreferences pref = getSharedPreferences(context);
1646 switch(pref.getString("theme", "light")) {
1648 theme = R.style.Taskbar;
1651 theme = R.style.Taskbar_Dark;
1655 return theme > -1 ? new ContextThemeWrapper(context, theme) : context;
1658 public static boolean isPlayStoreRelease(Context context) {
1659 return isPlayStoreRelease(context, context.getPackageName());
1662 @SuppressLint("PackageManagerGetSignatures")
1663 public static boolean isPlayStoreRelease(Context context, String packageName) {
1664 Signature playStoreSignature = new Signature(context.getString(R.string.tb_signature));
1666 PackageManager pm = context.getPackageManager();
1667 PackageInfo info = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
1668 for(Signature signature : info.signatures) {
1669 if(signature.equals(playStoreSignature))
1672 } catch (Exception e) { /* Gracefully fail */ }
1677 public static boolean isExternalAccessDisabled(Context context) {
1678 if(isLibrary(context)) return true;
1680 SharedPreferences pref = getSharedPreferences(context);
1681 return !pref.getBoolean("tasker_enabled", true);
1684 public static boolean enableFreeformModeShortcut(Context context) {
1685 return canEnableFreeform()
1686 && !isOverridingFreeformHack(context)
1687 && !isChromeOs(context);
1690 public static void startForegroundService(Context context, Intent intent) {
1691 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1692 if(Settings.canDrawOverlays(context))
1693 context.startForegroundService(intent);
1695 context.startService(intent);
1698 public static int getOverlayType() {
1699 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
1700 ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
1701 : WindowManager.LayoutParams.TYPE_PHONE;
1704 public static boolean isDelegatingHomeActivity(Context context) {
1705 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1706 homeIntent.addCategory(Intent.CATEGORY_HOME);
1708 final List<ResolveInfo> listOfLaunchers = context.getPackageManager().queryIntentActivities(homeIntent, 0);
1709 for(ResolveInfo launcher : listOfLaunchers) {
1710 if(launcher.activityInfo.packageName.equals(BuildConfig.SUPPORT_APPLICATION_ID))
1717 @SuppressLint("PrivateApi")
1718 private static String getSystemProperty(String key) {
1720 Class<?> cls = Class.forName("android.os.SystemProperties");
1721 return cls.getMethod("get", String.class).invoke(null, key).toString();
1722 } catch (Exception e) {
1727 @SuppressWarnings("ResultOfMethodCallIgnored")
1728 public static boolean importCustomStartButtonImage(Context context, Uri uri) {
1730 File imagesDir = new File(context.getFilesDir(), "tb_images");
1733 File importedFile = new File(imagesDir, "custom_image_new");
1734 if(importedFile.exists()) importedFile.delete();
1736 BufferedInputStream is = new BufferedInputStream(context.getContentResolver().openInputStream(uri));
1737 byte[] data = new byte[is.available()];
1739 if(data.length > 0) {
1740 BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(importedFile));
1747 File prevFile = new File(imagesDir, "custom_image");
1748 if(prevFile.exists()) prevFile.delete();
1750 importedFile.renameTo(prevFile);
1752 } catch (IOException e) {
1757 public static String getDefaultStartButtonImage(Context context) {
1758 SharedPreferences pref = getSharedPreferences(context);
1759 return pref.getBoolean("app_drawer_icon", false)
1764 private static boolean shouldLaunchTouchAbsorber(Context context) {
1765 return isOverridingFreeformHack(context) && !isChromeOs(context) && getCurrentApiVersion() < 29.0f;
1768 public static boolean isDesktopIconsEnabled(Context context) {
1769 return !canBootToFreeform(context) && !shouldLaunchTouchAbsorber(context);
1772 public static boolean isSystemTrayEnabled(Context context) {
1773 SharedPreferences pref = getSharedPreferences(context);
1775 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1776 && pref.getBoolean("sys_tray", context.getResources().getBoolean(R.bool.tb_def_sys_tray))
1777 && pref.getBoolean("full_length", context.getResources().getBoolean(R.bool.tb_def_full_length))
1778 && !getTaskbarPosition(context).contains("vertical");
1781 @SuppressWarnings("deprecation")
1782 public static boolean isLibrary(Context context) {
1783 return !context.getPackageName().equals(BuildConfig.APPLICATION_ID);
1786 public static boolean applyDisplayCutoutModeTo(WindowManager.LayoutParams params) {
1787 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
1788 params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
1795 private static boolean hasAndroidTVSettings(Context context) {
1796 return getInstalledPackage(context, "com.android.tv.settings") != null
1797 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
1800 public static void restartApp(Activity activity, boolean shouldFade) {
1801 Intent restartIntent = new Intent(activity, MainActivity.class);
1802 restartIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
1803 activity.startActivity(restartIntent);
1805 activity.overridePendingTransition(
1806 shouldFade ? android.R.anim.fade_in : 0,
1807 shouldFade ? android.R.anim.fade_out : 0
1813 public static boolean isDesktopModeSupported(Context context) {
1814 return Build.VERSION.SDK_INT > Build.VERSION_CODES.P
1815 && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
1818 public static boolean isDesktopModeActive(Context context) {
1819 if(!isDesktopModeSupported(context)) return false;
1821 boolean desktopModePrefEnabled;
1824 desktopModePrefEnabled = Settings.Global.getInt(context.getContentResolver(), "force_desktop_mode_on_external_displays") == 1;
1825 } catch (Settings.SettingNotFoundException e) {
1826 desktopModePrefEnabled = false;
1829 return desktopModePrefEnabled && getExternalDisplayID(context) != Display.DEFAULT_DISPLAY;
1832 public static int getExternalDisplayID(Context context) {
1833 DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1834 Display[] displays = dm.getDisplays();
1836 return displays[displays.length - 1].getDisplayId();