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.content.TaskbarIntent;
73 import com.farmerbb.taskbar.service.DashboardService;
74 import com.farmerbb.taskbar.service.NotificationService;
75 import com.farmerbb.taskbar.service.PowerMenuService;
76 import com.farmerbb.taskbar.service.StartMenuService;
77 import com.farmerbb.taskbar.service.TaskbarService;
79 import java.io.BufferedInputStream;
80 import java.io.BufferedOutputStream;
82 import java.io.FileOutputStream;
83 import java.io.IOException;
84 import java.lang.reflect.Constructor;
85 import java.lang.reflect.Method;
86 import java.util.ArrayList;
87 import java.util.Arrays;
88 import java.util.List;
94 private static Integer cachedRotation;
96 private static final int MAXIMIZED = 0;
97 private static final int LEFT = -1;
98 private static final int RIGHT = 1;
100 public static final int HIDDEN = 0;
101 public static final int TOP_APPS = 1;
103 // From android.app.ActivityManager.StackId
104 private static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
105 private static final int FREEFORM_WORKSPACE_STACK_ID = 2;
107 // From android.app.WindowConfiguration
108 private static final int WINDOWING_MODE_FULLSCREEN = 1;
109 private static final int WINDOWING_MODE_FREEFORM = 5;
111 public static final int EXPORT = 123;
112 public static final int IMPORT = 456;
114 @SuppressWarnings("deprecation")
115 public static SharedPreferences getSharedPreferences(Context context) {
116 return context.getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", Context.MODE_PRIVATE);
119 public static void showPermissionDialog(Context context) {
120 showPermissionDialog(context, null, null);
123 public static AlertDialog showPermissionDialog(Context context, Runnable onError, Runnable onFinish) {
124 Runnable finalOnFinish = onFinish == null
128 Runnable finalOnError = onError == null
129 ? () -> showErrorDialog(context, "SYSTEM_ALERT_WINDOW", finalOnFinish)
132 AlertDialog.Builder builder;
133 if(hasAndroidTVSettings(context))
134 builder = buildPermissionDialogAndroidTV(context, finalOnError, finalOnFinish);
136 builder = buildPermissionDialogStandard(context, finalOnError, finalOnFinish);
138 AlertDialog dialog = builder.create();
140 dialog.setCancelable(false);
145 @TargetApi(Build.VERSION_CODES.M)
146 private static AlertDialog.Builder buildPermissionDialogStandard(Context context, Runnable onError, Runnable onFinish) {
147 return new AlertDialog.Builder(context)
148 .setTitle(R.string.tb_permission_dialog_title)
149 .setMessage(R.string.tb_permission_dialog_message)
150 .setPositiveButton(R.string.tb_action_grant_permission, (dialog, which) -> {
152 context.startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
153 Uri.parse("package:" + context.getPackageName())));
156 } catch (ActivityNotFoundException e) {
162 private static AlertDialog.Builder buildPermissionDialogAndroidTV(Context context, Runnable onError, Runnable onFinish) {
163 return new AlertDialog.Builder(context)
164 .setTitle(R.string.tb_permission_dialog_title)
165 .setMessage(R.string.tb_permission_dialog_message_alt)
166 .setPositiveButton(R.string.tb_action_open_settings, (dialog, which) -> {
168 context.startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));
170 } catch (ActivityNotFoundException e) {
176 public static AlertDialog showErrorDialog(Context context, String appopCmd) {
177 return showErrorDialog(context, appopCmd, null);
180 private static AlertDialog showErrorDialog(Context context, String appopCmd, Runnable onFinish) {
181 Runnable finalOnFinish = onFinish == null
185 AlertDialog.Builder builder = new AlertDialog.Builder(context);
186 builder.setTitle(R.string.tb_error_dialog_title)
187 .setMessage(context.getString(R.string.tb_error_dialog_message, context.getPackageName(), appopCmd))
188 .setPositiveButton(R.string.tb_action_ok, (dialog, which) -> finalOnFinish.run());
190 AlertDialog dialog = builder.create();
192 dialog.setCancelable(false);
197 @TargetApi(Build.VERSION_CODES.P)
198 public static void lockDevice(Context context) {
199 sendAccessibilityAction(context, AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN);
202 public static void sendAccessibilityAction(Context context, int action) {
203 sendAccessibilityAction(context, action, null);
206 public static void sendAccessibilityAction(Context context, int action, Runnable onComplete) {
207 ComponentName component = new ComponentName(context, PowerMenuService.class);
208 context.getPackageManager().setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
209 PackageManager.DONT_KILL_APP);
211 boolean isAccessibilityServiceEnabled = isAccessibilityServiceEnabled(context);
213 if(!isAccessibilityServiceEnabled
214 && hasWriteSecureSettingsPermission(context)) {
215 String services = Settings.Secure.getString(context.getContentResolver(),
216 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
218 String finalServices = services == null ? "" : services;
220 String powerMenuService = new ComponentName(context, PowerMenuService.class).flattenToString();
222 if(!finalServices.contains(powerMenuService)) {
224 Settings.Secure.putString(context.getContentResolver(),
225 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
226 finalServices.isEmpty()
228 : finalServices + ":" + powerMenuService);
229 } catch (Exception e) { /* Gracefully fail */ }
232 new Handler().postDelayed(() -> {
233 Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
234 intent.putExtra("action", action);
235 LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
238 Settings.Secure.putString(context.getContentResolver(),
239 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
241 } catch (Exception e) { /* Gracefully fail */ }
243 if(onComplete != null) onComplete.run();
245 } else if(isAccessibilityServiceEnabled) {
246 Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
247 intent.putExtra("action", action);
248 LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
250 if(onComplete != null) onComplete.run();
252 launchApp(context, () -> {
253 Intent intent = new Intent(context, DummyActivity.class);
254 intent.putExtra("accessibility", true);
255 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
258 context.startActivity(intent, getActivityOptionsBundle(context, ApplicationType.APP_PORTRAIT, null));
259 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
264 public static boolean isAccessibilityServiceEnabled(Context context) {
265 String accessibilityServices = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
266 ComponentName component = new ComponentName(context, PowerMenuService.class);
268 return accessibilityServices != null
269 && (accessibilityServices.contains(component.flattenToString())
270 || accessibilityServices.contains(component.flattenToShortString()));
273 public static boolean hasWriteSecureSettingsPermission(Context context) {
274 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
275 && context.checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) == PackageManager.PERMISSION_GRANTED;
278 public static void showToast(Context context, int message) {
279 showToast(context, context.getString(message), Toast.LENGTH_SHORT);
282 public static void showToastLong(Context context, int message) {
283 showToast(context, context.getString(message), Toast.LENGTH_LONG);
286 public static void showToast(Context context, String message, int length) {
289 ToastInterface toast = DependencyUtils.createToast(context.getApplicationContext(), message, length);
292 ToastHelper.getInstance().setLastToast(toast);
295 public static void cancelToast() {
296 ToastInterface toast = ToastHelper.getInstance().getLastToast();
297 if(toast != null) toast.cancel();
300 public static void startShortcut(Context context, AppEntry entry, ShortcutInfo shortcut, View view) {
312 public static void launchApp(final Context context,
313 final AppEntry entry,
314 final String windowSize,
315 final boolean launchedFromTaskbar,
316 final boolean openInNewWindow,
329 public static void launchApp(final Context context,
330 final AppEntry entry,
331 final String windowSize,
332 final Runnable onError) {
344 private static void launchApp(final Context context,
345 final AppEntry entry,
346 final String windowSize,
347 final boolean launchedFromTaskbar,
348 final boolean isPersistentShortcut,
349 final boolean openInNewWindow,
350 final ShortcutInfo shortcut,
352 final Runnable onError) {
353 launchApp(context, launchedFromTaskbar, isPersistentShortcut, () ->
354 continueLaunchingApp(context, entry, windowSize, openInNewWindow, shortcut, view, onError)
358 public static void launchApp(Context context, Runnable runnable) {
359 launchApp(context, true, false, runnable);
362 private static void launchApp(Context context, boolean launchedFromTaskbar, boolean isPersistentShortcut, Runnable runnable) {
363 SharedPreferences pref = getSharedPreferences(context);
364 FreeformHackHelper helper = FreeformHackHelper.getInstance();
366 boolean specialLaunch = hasBrokenSetLaunchBoundsApi()
367 && helper.isInFreeformWorkspace()
368 && MenuHelper.getInstance().isContextMenuOpen();
370 boolean noAnimation = pref.getBoolean("disable_animations", false);
372 if(hasFreeformSupport(context)
373 && (pref.getBoolean("freeform_hack", false) || isPersistentShortcut)
374 && (!helper.isInFreeformWorkspace() || specialLaunch)) {
375 new Handler().postDelayed(() -> {
376 startFreeformHack(context, true);
378 new Handler().postDelayed(runnable, helper.isFreeformHackActive() ? 0 : 100);
379 }, launchedFromTaskbar ? 0 : 100);
381 new Handler().postDelayed(runnable, !launchedFromTaskbar && noAnimation ? 100 : 0);
384 public static void startFreeformHack(Context context) {
385 startFreeformHack(context, false);
388 @TargetApi(Build.VERSION_CODES.N)
389 public static void startFreeformHack(Context context, boolean checkMultiWindow) {
390 Intent freeformHackIntent = new Intent(context, InvisibleActivityFreeform.class);
391 freeformHackIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
392 | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
393 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
396 freeformHackIntent.putExtra("check_multiwindow", true);
398 if(canDrawOverlays(context))
399 startActivityLowerRight(context, freeformHackIntent);
402 public static void stopFreeformHack(Context context) {
403 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.FINISH_FREEFORM_ACTIVITY"));
405 if(isOverridingFreeformHack(context, false)) {
406 FreeformHackHelper helper = FreeformHackHelper.getInstance();
407 helper.setFreeformHackActive(false);
408 helper.setInFreeformWorkspace(false);
412 @TargetApi(Build.VERSION_CODES.N)
413 private static void continueLaunchingApp(Context context,
416 boolean openInNewWindow,
417 ShortcutInfo shortcut,
420 SharedPreferences pref = getSharedPreferences(context);
421 Intent intent = new Intent();
422 intent.setComponent(ComponentName.unflattenFromString(entry.getComponentName()));
423 intent.setAction(Intent.ACTION_MAIN);
424 intent.addCategory(Intent.CATEGORY_LAUNCHER);
425 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
426 intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
428 if(FreeformHackHelper.getInstance().isInFreeformWorkspace()
429 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1)
430 intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
432 if(pref.getBoolean("disable_animations", false))
433 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
435 boolean realOpenInNewWindow = openInNewWindow || pref.getBoolean("force_new_window", false);
436 if(realOpenInNewWindow) {
437 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
439 ActivityInfo activityInfo = intent.resolveActivityInfo(context.getPackageManager(), 0);
440 if(activityInfo != null) {
441 switch(activityInfo.launchMode) {
442 case ActivityInfo.LAUNCH_SINGLE_TASK:
443 case ActivityInfo.LAUNCH_SINGLE_INSTANCE:
444 intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
450 ApplicationType type = getApplicationType(context, entry);
452 if(windowSize == null)
453 windowSize = SavedWindowSizes.getInstance(context).getWindowSize(context, entry.getPackageName());
455 Bundle bundle = getActivityOptionsBundle(context, type, windowSize, view);
457 prepareToStartActivity(context, realOpenInNewWindow, () -> {
458 if(shortcut == null) {
459 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
460 if(entry.getUserId(context) == userManager.getSerialNumberForUser(Process.myUserHandle())) {
462 context.startActivity(intent, bundle);
463 } catch (ActivityNotFoundException e) {
464 launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context), onError);
465 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
467 launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context), onError);
469 launchShortcut(context, shortcut, bundle, onError);
472 if(shouldCollapse(context, true)) {
473 LocalBroadcastManager
474 .getInstance(context)
475 .sendBroadcast(new Intent(TaskbarIntent.ACTION_HIDE_TASKBAR));
477 LocalBroadcastManager
478 .getInstance(context)
479 .sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_START_MENU"));
483 private static Bundle launchMode1(Context context, ApplicationType type, View view, int factor) {
484 DisplayInfo display = getDisplayInfo(context);
486 int width1 = display.width / factor;
487 int width2 = display.width - width1;
488 int height1 = display.height / factor;
489 int height2 = display.height - height1;
491 return getActivityOptionsBundle(context, type, view,
499 private static Bundle launchMode2(Context context, int launchType, ApplicationType type, View view) {
500 DisplayInfo display = getDisplayInfo(context);
502 int statusBarHeight = getStatusBarHeight(context);
503 String position = getTaskbarPosition(context);
505 boolean isPortrait = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
506 boolean isLandscape = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
509 int top = statusBarHeight;
510 int right = display.width;
511 int bottom = display.height;
513 int iconSize = isOverridingFreeformHack(context) && !LauncherHelper.getInstance().isOnHomeScreen()
515 : context.getResources().getDimensionPixelSize(R.dimen.tb_icon_size);
517 if(position.contains("vertical_left"))
518 left = left + iconSize;
519 else if(position.contains("vertical_right"))
520 right = right - iconSize;
521 else if(position.contains("bottom"))
522 bottom = bottom - iconSize;
524 top = top + iconSize;
526 int halfLandscape = (right / 2) + ((iconSize / 2) * (position.contains("vertical_left") ? 1 : 0));
527 int halfPortrait = (bottom / 2) + ((iconSize / 2) * ((position.equals("top_left") || position.equals("top_right")) ? 1 : 0));
529 if(launchType == RIGHT && isLandscape)
530 left = halfLandscape;
531 else if(launchType == RIGHT && isPortrait)
533 else if(launchType == LEFT && isLandscape)
534 right = halfLandscape;
535 else if(launchType == LEFT && isPortrait)
536 bottom = halfPortrait;
538 return getActivityOptionsBundle(context, type, view, left, top, right, bottom);
541 private static Bundle launchMode3(Context context, ApplicationType type, View view) {
542 DisplayInfo display = getDisplayInfo(context);
544 boolean isLandscape = type == ApplicationType.APP_LANDSCAPE;
545 int widthDimen = isLandscape ? R.dimen.tb_phone_size_height : R.dimen.tb_phone_size_width;
546 int heightDimen = isLandscape ? R.dimen.tb_phone_size_width : R.dimen.tb_phone_size_height;
548 int width1 = display.width / 2;
549 int width2 = context.getResources().getDimensionPixelSize(widthDimen) / 2;
550 int height1 = display.height / 2;
551 int height2 = context.getResources().getDimensionPixelSize(heightDimen) / 2;
553 return getActivityOptionsBundle(context, type, view,
561 private static void launchAndroidForWork(Context context, ComponentName componentName, Bundle bundle, long userId, Runnable onError) {
562 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
563 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
566 launcherApps.startMainActivity(componentName, userManager.getUserForSerialNumber(userId), null, bundle);
567 } catch (ActivityNotFoundException | NullPointerException
568 | IllegalStateException | SecurityException e) {
569 if(onError != null) launchApp(context, onError);
573 @TargetApi(Build.VERSION_CODES.N_MR1)
574 private static void launchShortcut(Context context, ShortcutInfo shortcut, Bundle bundle, Runnable onError) {
575 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
577 if(launcherApps.hasShortcutHostPermission()) {
579 launcherApps.startShortcut(shortcut, null, bundle);
580 } catch (ActivityNotFoundException | NullPointerException
581 | IllegalStateException | SecurityException e) {
582 if(onError != null) launchApp(context, onError);
587 private static void prepareToStartActivity(Context context, boolean openInNewWindow, Runnable runnable) {
588 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_CONTEXT_MENU"));
590 if(!FreeformHackHelper.getInstance().isTouchAbsorberActive()
591 && shouldLaunchTouchAbsorber(context)) {
592 startTouchAbsorberActivity(context);
593 new Handler().postDelayed(runnable, 100);
594 } else if(openInNewWindow) {
595 Intent intent = new Intent(context, DummyActivity.class);
596 intent.putExtra("finish_on_pause", true);
597 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
598 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
599 startActivityLowerRight(context, intent);
601 new Handler().postDelayed(runnable, 100);
606 public static void startActivityMaximized(Context context, Intent intent) {
607 Bundle bundle = launchMode2(context, MAXIMIZED, ApplicationType.CONTEXT_MENU, null);
608 prepareToStartActivity(context, false, () -> context.startActivity(intent, bundle));
611 public static void startActivityLowerRight(Context context, Intent intent) {
612 DisplayInfo display = getDisplayInfo(context);
614 context.startActivity(intent,
615 getActivityOptionsBundle(context, ApplicationType.FREEFORM_HACK, null,
621 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
624 public static void startTouchAbsorberActivity(Context context) {
625 String position = getTaskbarPosition(context);
626 DisplayInfo display = getDisplayInfo(context);
630 int right = display.width;
631 int bottom = display.height;
633 int iconSize = context.getResources().getDimensionPixelSize(R.dimen.tb_icon_size);
635 if(position.contains("vertical_left"))
637 else if(position.contains("vertical_right"))
638 left = right - iconSize;
639 else if(position.contains("bottom"))
640 top = bottom - iconSize;
644 Intent intent = new Intent(context, TouchAbsorberActivity.class);
645 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
646 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
649 context.startActivity(intent,
650 getActivityOptionsBundle(context, ApplicationType.FREEFORM_HACK, null,
651 left, top, right, bottom));
652 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
655 public static void startContextMenuActivity(Context context, Bundle args) {
656 SharedPreferences pref = getSharedPreferences(context);
657 Intent intent = null;
659 switch(pref.getString("theme", "light")) {
661 intent = new Intent(context, ContextMenuActivity.class);
664 intent = new Intent(context, ContextMenuActivityDark.class);
669 intent.putExtra("args", args);
670 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
673 if(hasFreeformSupport(context) && FreeformHackHelper.getInstance().isInFreeformWorkspace()) {
674 DisplayInfo display = getDisplayInfo(context);
676 if(intent != null && hasBrokenSetLaunchBoundsApi())
677 intent.putExtra("context_menu_fix", true);
679 context.startActivity(intent,
680 getActivityOptionsBundle(context, ApplicationType.CONTEXT_MENU, null,
681 0, 0, display.width, display.height));
683 context.startActivity(intent);
686 public static void checkForUpdates(Context context) {
688 if(isPlayStoreRelease(context)) {
689 if(context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
690 && !isPlayStoreInstalled(context))
691 url = "https://github.com/farmerbb/Taskbar/releases";
693 url = "https://play.google.com/store/apps/details?id=" + context.getPackageName();
695 url = "https://f-droid.org/repository/browse/?fdid=" + context.getPackageName();
697 Intent intent = new Intent(Intent.ACTION_VIEW);
698 intent.setData(Uri.parse(url));
699 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
702 context.startActivity(intent);
703 } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
706 public static boolean launcherIsDefault(Context context) {
707 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
708 homeIntent.addCategory(Intent.CATEGORY_HOME);
709 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
711 return defaultLauncher.activityInfo.packageName.equals(context.getPackageName());
714 public static void setCachedRotation(int cachedRotation) {
715 U.cachedRotation = cachedRotation;
718 public static String getTaskbarPosition(Context context) {
719 SharedPreferences pref = getSharedPreferences(context);
720 String position = pref.getString("position", "bottom_left");
722 if(pref.getBoolean("anchor", false)) {
723 WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
724 int rotation = cachedRotation != null ? cachedRotation : windowManager.getDefaultDisplay().getRotation();
729 case Surface.ROTATION_0:
730 return "bottom_left";
731 case Surface.ROTATION_90:
732 return "bottom_vertical_right";
733 case Surface.ROTATION_180:
735 case Surface.ROTATION_270:
736 return "top_vertical_left";
739 case "bottom_vertical_left":
741 case Surface.ROTATION_0:
742 return "bottom_vertical_left";
743 case Surface.ROTATION_90:
744 return "bottom_right";
745 case Surface.ROTATION_180:
746 return "top_vertical_right";
747 case Surface.ROTATION_270:
753 case Surface.ROTATION_0:
754 return "bottom_right";
755 case Surface.ROTATION_90:
756 return "top_vertical_right";
757 case Surface.ROTATION_180:
759 case Surface.ROTATION_270:
760 return "bottom_vertical_left";
763 case "bottom_vertical_right":
765 case Surface.ROTATION_0:
766 return "bottom_vertical_right";
767 case Surface.ROTATION_90:
769 case Surface.ROTATION_180:
770 return "top_vertical_left";
771 case Surface.ROTATION_270:
772 return "bottom_left";
777 case Surface.ROTATION_0:
779 case Surface.ROTATION_90:
780 return "bottom_vertical_left";
781 case Surface.ROTATION_180:
782 return "bottom_right";
783 case Surface.ROTATION_270:
784 return "top_vertical_right";
787 case "top_vertical_left":
789 case Surface.ROTATION_0:
790 return "top_vertical_left";
791 case Surface.ROTATION_90:
792 return "bottom_left";
793 case Surface.ROTATION_180:
794 return "bottom_vertical_right";
795 case Surface.ROTATION_270:
801 case Surface.ROTATION_0:
803 case Surface.ROTATION_90:
804 return "top_vertical_left";
805 case Surface.ROTATION_180:
806 return "bottom_left";
807 case Surface.ROTATION_270:
808 return "bottom_vertical_right";
811 case "top_vertical_right":
813 case Surface.ROTATION_0:
814 return "top_vertical_right";
815 case Surface.ROTATION_90:
817 case Surface.ROTATION_180:
818 return "bottom_vertical_left";
819 case Surface.ROTATION_270:
820 return "bottom_right";
829 private static int getMaxNumOfColumns(Context context) {
830 SharedPreferences pref = getSharedPreferences(context);
831 DisplayInfo display = getDisplayInfo(context);
832 float density = display.density / 160;
833 float baseTaskbarSize = getBaseTaskbarSizeFloat(context) / density;
834 int numOfColumns = 0;
836 float maxScreenSize = getTaskbarPosition(context).contains("vertical")
837 ? (display.height - getStatusBarHeight(context)) / density
838 : display.width / density;
840 float iconSize = context.getResources().getDimension(R.dimen.tb_icon_size) / density;
842 int userMaxNumOfColumns = Integer.valueOf(pref.getString("max_num_of_recents", "10"));
844 while(baseTaskbarSize + iconSize < maxScreenSize
845 && numOfColumns < userMaxNumOfColumns) {
846 baseTaskbarSize = baseTaskbarSize + iconSize;
853 public static int getMaxNumOfEntries(Context context) {
854 SharedPreferences pref = getSharedPreferences(context);
855 return pref.getBoolean("disable_scrolling_list", false)
856 ? getMaxNumOfColumns(context)
857 : Integer.valueOf(pref.getString("max_num_of_recents", "10"));
860 public static int getStatusBarHeight(Context context) {
861 return getSystemDimen(context, "status_bar_height");
864 private static int getNavbarHeight(Context context) {
865 return getSystemDimen(context, "navigation_bar_height");
868 private static int getSystemDimen(Context context, String id) {
870 int resourceId = context.getResources().getIdentifier(id, "dimen", "android");
872 value = context.getResources().getDimensionPixelSize(resourceId);
877 public static void refreshPinnedIcons(Context context) {
878 IconCache.getInstance(context).clearCache();
880 PinnedBlockedApps pba = PinnedBlockedApps.getInstance(context);
881 List<AppEntry> pinnedAppsList = new ArrayList<>(pba.getPinnedApps());
882 List<AppEntry> blockedAppsList = new ArrayList<>(pba.getBlockedApps());
883 PackageManager pm = context.getPackageManager();
887 for(AppEntry entry : pinnedAppsList) {
888 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
889 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
891 final List<UserHandle> userHandles = userManager.getUserProfiles();
892 LauncherActivityInfo appInfo = null;
894 for(UserHandle handle : userHandles) {
895 List<LauncherActivityInfo> list = launcherApps.getActivityList(entry.getPackageName(), handle);
896 if(!list.isEmpty()) {
897 // Google App workaround
898 if(!entry.getPackageName().equals("com.google.android.googlequicksearchbox"))
899 appInfo = list.get(0);
901 boolean added = false;
902 for(LauncherActivityInfo info : list) {
903 if(info.getName().equals("com.google.android.googlequicksearchbox.SearchActivity")) {
909 if(!added) appInfo = list.get(0);
916 if(appInfo != null) {
917 AppEntry newEntry = new AppEntry(
918 entry.getPackageName(),
919 entry.getComponentName(),
921 IconCache.getInstance(context).getIcon(context, pm, appInfo),
924 newEntry.setUserId(entry.getUserId(context));
925 pba.addPinnedApp(context, newEntry);
929 for(AppEntry entry : blockedAppsList) {
930 pba.addBlockedApp(context, entry);
934 public static boolean canEnableFreeform() {
935 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
938 @TargetApi(Build.VERSION_CODES.N)
939 public static boolean hasFreeformSupport(Context context) {
940 return canEnableFreeform()
941 && (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
942 || Settings.Global.getInt(context.getContentResolver(), "enable_freeform_support", 0) != 0
943 || (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
944 && Settings.Global.getInt(context.getContentResolver(), "force_resizable_activities", 0) != 0));
947 public static boolean canBootToFreeform(Context context) {
948 return canBootToFreeform(context, true);
951 private static boolean canBootToFreeform(Context context, boolean checkPref) {
952 return hasFreeformSupport(context) && !isOverridingFreeformHack(context, checkPref);
955 public static boolean isSamsungDevice() {
956 return Build.MANUFACTURER.equalsIgnoreCase("Samsung");
959 private static boolean isNvidiaDevice() {
960 return Build.MANUFACTURER.equalsIgnoreCase("NVIDIA");
963 public static boolean isServiceRunning(Context context, Class<? extends Service> cls) {
964 if(LauncherHelper.getInstance().isOnSecondaryHomeScreen()
965 && (cls.equals(TaskbarService.class)
966 || cls.equals(StartMenuService.class)
967 || cls.equals(DashboardService.class)))
970 return isServiceRunning(context, cls.getName());
973 private static boolean isServiceRunning(Context context, String className) {
974 ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
975 for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
976 if(className.equals(service.service.getClassName()))
983 public static int getBackgroundTint(Context context) {
984 SharedPreferences pref = getSharedPreferences(context);
986 // Import old background tint preference
987 if(pref.contains("show_background")) {
988 SharedPreferences.Editor editor = pref.edit();
990 if(!pref.getBoolean("show_background", true))
991 editor.putInt("background_tint", Color.TRANSPARENT).apply();
993 editor.remove("show_background");
997 return pref.getInt("background_tint", context.getResources().getInteger(R.integer.tb_translucent_gray));
1000 public static int getAccentColor(Context context) {
1001 SharedPreferences pref = getSharedPreferences(context);
1002 return pref.getInt("accent_color", context.getResources().getInteger(R.integer.tb_translucent_white));
1005 public static boolean canDrawOverlays(Context context) {
1006 return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context);
1009 public static boolean isGame(Context context, String packageName) {
1010 SharedPreferences pref = getSharedPreferences(context);
1011 if(pref.getBoolean("launch_games_fullscreen", true)) {
1012 PackageManager pm = context.getPackageManager();
1015 ApplicationInfo info = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
1016 return (info.flags & ApplicationInfo.FLAG_IS_GAME) != 0 || (info.metaData != null && info.metaData.getBoolean("isGame", false));
1017 } catch (PackageManager.NameNotFoundException e) {
1024 private static ActivityOptions getActivityOptions(View view) {
1025 return getActivityOptions(null, null, view);
1028 public static ActivityOptions getActivityOptions(Context context, ApplicationType applicationType, View view) {
1029 ActivityOptions options;
1031 options = ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight());
1032 else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1033 options = ActivityOptions.makeBasic();
1036 Constructor<ActivityOptions> constructor = ActivityOptions.class.getDeclaredConstructor();
1037 constructor.setAccessible(true);
1038 options = constructor.newInstance();
1039 } catch (Exception e) {
1044 if(applicationType == null)
1049 switch(applicationType) {
1052 if(FreeformHackHelper.getInstance().isFreeformHackActive())
1053 stackId = getFreeformWindowModeId();
1055 stackId = getFullscreenWindowModeId();
1057 case APP_FULLSCREEN:
1058 stackId = getFullscreenWindowModeId();
1061 stackId = getFreeformWindowModeId();
1064 if(hasBrokenSetLaunchBoundsApi()
1065 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f))
1066 stackId = getFullscreenWindowModeId();
1071 Method method = ActivityOptions.class.getMethod(getWindowingModeMethodName(), int.class);
1072 method.invoke(options, stackId);
1073 } catch (Exception e) { /* Gracefully fail */ }
1075 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1076 int launchDisplayId = LauncherHelper.getInstance().getSecondaryDisplayId();
1077 if(launchDisplayId != -1)
1078 options.setLaunchDisplayId(launchDisplayId);
1084 private static int getFullscreenWindowModeId() {
1085 if(getCurrentApiVersion() >= 28.0f)
1086 return WINDOWING_MODE_FULLSCREEN;
1088 return FULLSCREEN_WORKSPACE_STACK_ID;
1091 private static int getFreeformWindowModeId() {
1092 if(getCurrentApiVersion() >= 28.0f)
1093 return WINDOWING_MODE_FREEFORM;
1095 return FREEFORM_WORKSPACE_STACK_ID;
1098 private static String getWindowingModeMethodName() {
1099 if(getCurrentApiVersion() >= 28.0f)
1100 return "setLaunchWindowingMode";
1102 return "setLaunchStackId";
1105 public static Bundle getActivityOptionsBundle(Context context, ApplicationType type, View view) {
1106 SharedPreferences pref = getSharedPreferences(context);
1108 return getActivityOptionsBundle(context, type, pref.getString("window_size", context.getString(R.string.tb_def_window_size)), view);
1111 private static Bundle getActivityOptionsBundle(Context context, ApplicationType type, String windowSize, View view) {
1112 SharedPreferences pref = getSharedPreferences(context);
1113 if(!canEnableFreeform() || !pref.getBoolean("freeform_hack", false))
1114 return getActivityOptions(view).toBundle();
1116 switch(windowSize) {
1118 if(getCurrentApiVersion() > 29.0f)
1119 return launchMode1(context, type, view, 4);
1122 return launchMode1(context, type, view, 8);
1124 return launchMode2(context, MAXIMIZED, type, view);
1126 return launchMode2(context, LEFT, type, view);
1128 return launchMode2(context, RIGHT, type, view);
1130 return launchMode3(context, type, view);
1133 return getActivityOptions(context, type, view).toBundle();
1136 private static Bundle getActivityOptionsBundle(Context context, ApplicationType applicationType, View view,
1137 int left, int top, int right, int bottom) {
1138 ActivityOptions options = getActivityOptions(context, applicationType, view);
1142 if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
1143 return options.toBundle();
1145 return options.setLaunchBounds(new Rect(left, top, right, bottom)).toBundle();
1148 @SuppressLint("SwitchIntDef")
1149 private static ApplicationType getApplicationType(Context context, AppEntry entry) {
1150 if(isGame(context, entry.getPackageName()))
1151 return ApplicationType.APP_FULLSCREEN;
1154 ActivityInfo info = context.getPackageManager().getActivityInfo(
1155 ComponentName.unflattenFromString(entry.getComponentName()),
1159 switch(info.screenOrientation) {
1160 case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
1161 case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
1162 case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
1163 case ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE:
1164 return ApplicationType.APP_LANDSCAPE;
1166 case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
1167 case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:
1168 case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:
1169 case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT:
1170 return ApplicationType.APP_PORTRAIT;
1172 } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
1174 return context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
1175 ? ApplicationType.APP_LANDSCAPE
1176 : ApplicationType.APP_PORTRAIT;
1179 public static boolean isSystemApp(Context context) {
1181 ApplicationInfo info = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
1182 int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
1183 return (info.flags & mask) != 0;
1184 } catch (PackageManager.NameNotFoundException e) {
1189 public static boolean isChromeOs(Context context) {
1190 return context.getPackageManager().hasSystemFeature("org.chromium.arc");
1193 public static boolean isBlissOs(Context context) {
1194 boolean validBlissOsBuildProp = false;
1196 String blissVersion = getSystemProperty("ro.bliss.version");
1197 if(blissVersion != null && !blissVersion.isEmpty())
1198 validBlissOsBuildProp = true;
1200 String buildUser = getSystemProperty("ro.build.user");
1201 if(buildUser != null && buildUser.equals("electrikjesus"))
1202 validBlissOsBuildProp = true;
1204 return validBlissOsBuildProp
1205 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1206 && isSystemApp(context);
1209 public static boolean isLauncherPermanentlyEnabled(Context context) {
1210 if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
1213 PackageManager pm = context.getPackageManager();
1215 pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1216 return pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, context.getPackageName()) == PackageManager.SIGNATURE_MATCH
1217 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1218 && isSystemApp(context);
1219 } catch (PackageManager.NameNotFoundException e) {
1224 public static boolean hasSupportLibrary(Context context, int minVersion) {
1225 PackageManager pm = context.getPackageManager();
1227 PackageInfo pInfo = pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1228 return pInfo.versionCode >= minVersion
1229 && pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, context.getPackageName()) == PackageManager.SIGNATURE_MATCH
1230 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1231 && isSystemApp(context);
1232 } catch (PackageManager.NameNotFoundException e) {
1237 public static int getBaseTaskbarSize(Context context) {
1238 return Math.round(getBaseTaskbarSizeFloat(context));
1241 private static float getBaseTaskbarSizeFloat(Context context) {
1242 SharedPreferences pref = getSharedPreferences(context);
1243 float baseTaskbarSize = context.getResources().getDimension(R.dimen.tb_base_taskbar_size);
1244 boolean navbarButtonsEnabled = false;
1246 if(pref.getBoolean("dashboard", context.getResources().getBoolean(R.bool.tb_def_dashboard)))
1247 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_dashboard_button_size);
1249 if(pref.getBoolean("button_back", false)) {
1250 navbarButtonsEnabled = true;
1251 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1254 if(pref.getBoolean("button_home", false)) {
1255 navbarButtonsEnabled = true;
1256 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1259 if(pref.getBoolean("button_recents", false)) {
1260 navbarButtonsEnabled = true;
1261 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1264 if(navbarButtonsEnabled)
1265 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_navbar_buttons_margin);
1267 if(isSystemTrayEnabled(context))
1268 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_systray_size);
1270 return baseTaskbarSize;
1273 private static void startTaskbarService(Context context, boolean fullRestart) {
1274 context.startService(new Intent(context, TaskbarService.class));
1275 context.startService(new Intent(context, StartMenuService.class));
1276 context.startService(new Intent(context, DashboardService.class));
1277 if(fullRestart) context.startService(new Intent(context, NotificationService.class));
1280 private static void stopTaskbarService(Context context, boolean fullRestart) {
1281 context.stopService(new Intent(context, TaskbarService.class));
1282 context.stopService(new Intent(context, StartMenuService.class));
1283 context.stopService(new Intent(context, DashboardService.class));
1284 if(fullRestart) context.stopService(new Intent(context, NotificationService.class));
1287 public static void restartTaskbar(Context context) {
1288 SharedPreferences pref = getSharedPreferences(context);
1289 if(pref.getBoolean("taskbar_active", false) && !pref.getBoolean("is_hidden", false)) {
1291 .putBoolean("is_restarting", true)
1292 .putBoolean("skip_auto_hide_navbar", true)
1295 stopTaskbarService(context, true);
1296 startTaskbarService(context, true);
1297 } else if(isServiceRunning(context, StartMenuService.class)) {
1298 pref.edit().putBoolean("skip_auto_hide_navbar", true).apply();
1300 stopTaskbarService(context, false);
1301 startTaskbarService(context, false);
1304 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.RESTART"));
1307 public static void restartNotificationService(Context context) {
1308 if(isServiceRunning(context, NotificationService.class)) {
1309 SharedPreferences pref = getSharedPreferences(context);
1310 pref.edit().putBoolean("is_restarting", true).apply();
1312 Intent intent = new Intent(context, NotificationService.class);
1313 context.stopService(intent);
1314 context.startService(intent);
1318 public static void showHideNavigationBar(Context context, boolean show) {
1319 if(hasSupportLibrary(context, 7)) {
1320 Intent intent = new Intent(BuildConfig.SUPPORT_APPLICATION_ID + ".CHANGE_OVERSCAN");
1321 intent.setPackage(BuildConfig.SUPPORT_APPLICATION_ID);
1323 intent.putExtra("display_id", getDisplayID());
1324 intent.putExtra("value", show ? 0 : getNavbarHeight(context) * -1);
1326 context.sendBroadcast(intent);
1330 // Show or hide the system navigation bar on Bliss-x86
1332 if(getCurrentApiVersion() >= 28.0f)
1333 Settings.Secure.putInt(context.getContentResolver(), "navigation_bar_visible", show ? 1 : 0);
1335 Settings.System.putInt(context.getContentResolver(), "navigation_bar_show", show ? 1 : 0);
1336 } catch (Exception e) { /* Gracefully fail */ }
1339 public static void initPrefs(Context context) {
1340 // Enable freeform hack automatically on supported devices
1341 SharedPreferences pref = getSharedPreferences(context);
1342 if(canEnableFreeform()) {
1343 if(!pref.getBoolean("freeform_hack_override", false)) {
1345 .putBoolean("freeform_hack", hasFreeformSupport(context) && !isSamsungDevice())
1346 .putBoolean("save_window_sizes", false)
1347 .putBoolean("freeform_hack_override", true)
1349 } else if(!hasFreeformSupport(context)) {
1350 pref.edit().putBoolean("freeform_hack", false).apply();
1352 stopFreeformHack(context);
1355 boolean freeformWasEnabled = pref.getBoolean("freeform_hack", false)
1356 || pref.getBoolean("show_freeform_disabled_message", false);
1359 .putBoolean("freeform_hack", false)
1360 .putBoolean("show_freeform_disabled_message", freeformWasEnabled)
1363 SavedWindowSizes.getInstance(context).clear(context);
1364 stopFreeformHack(context);
1367 // Customizations for BlissOS
1368 if(isBlissOs(context) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1369 && !pref.getBoolean("bliss_os_prefs", false)) {
1370 SharedPreferences.Editor editor = pref.edit();
1372 if(hasFreeformSupport(context)) {
1373 editor.putBoolean("freeform_hack", true);
1376 editor.putString("recents_amount", "running_apps_only");
1377 editor.putString("refresh_frequency", "0");
1378 editor.putString("max_num_of_recents", "2147483647");
1379 editor.putString("sort_order", "true");
1380 editor.putString("start_button_image", "app_logo");
1381 editor.putBoolean("button_back", true);
1382 editor.putBoolean("button_home", true);
1383 editor.putBoolean("button_recents", true);
1384 editor.putBoolean("auto_hide_navbar", true);
1385 editor.putBoolean("shortcut_icon", false);
1386 editor.putBoolean("bliss_os_prefs", true);
1390 // Customizations for Android-x86 devices (non-Bliss)
1391 if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
1392 && isSystemApp(context)
1393 && !pref.getBoolean("android_x86_prefs", false)) {
1395 .putString("recents_amount", "running_apps_only")
1396 .putString("refresh_frequency", "0")
1397 .putString("max_num_of_recents", "2147483647")
1398 .putString("sort_order", "true")
1399 .putBoolean("shortcut_icon", false)
1400 .putBoolean("android_x86_prefs", true)
1405 public static DisplayInfo getDisplayInfo(Context context) {
1406 return getDisplayInfo(context, false);
1409 public static DisplayInfo getDisplayInfo(Context context, boolean fromTaskbar) {
1410 context = context.getApplicationContext();
1411 int displayID = getDisplayID();
1413 DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1414 Display currentDisplay = null;
1416 for(Display display : dm.getDisplays()) {
1417 if(display.getDisplayId() == displayID) {
1418 currentDisplay = display;
1423 if(currentDisplay == null)
1424 return new DisplayInfo(0, 0, 0);
1426 DisplayMetrics metrics = new DisplayMetrics();
1427 currentDisplay.getMetrics(metrics);
1429 DisplayMetrics realMetrics = new DisplayMetrics();
1430 currentDisplay.getRealMetrics(realMetrics);
1432 DisplayInfo info = new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);
1434 if(isChromeOs(context)) {
1435 SharedPreferences pref = getSharedPreferences(context);
1436 if(!pref.getBoolean("chrome_os_context_menu_fix", true)) {
1437 info.width = realMetrics.widthPixels;
1438 info.height = realMetrics.heightPixels;
1444 // Workaround for incorrect display size on devices with notches in landscape mode
1445 if(fromTaskbar && context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)
1448 boolean sameWidth = metrics.widthPixels == realMetrics.widthPixels;
1449 boolean sameHeight = metrics.heightPixels == realMetrics.heightPixels;
1451 if(sameWidth && !sameHeight) {
1452 info.width = realMetrics.widthPixels;
1453 info.height = realMetrics.heightPixels - getNavbarHeight(context);
1456 if(!sameWidth && sameHeight) {
1457 info.width = realMetrics.widthPixels - getNavbarHeight(context);
1458 info.height = realMetrics.heightPixels;
1464 private static int getDisplayID() {
1465 LauncherHelper helper = LauncherHelper.getInstance();
1467 if(helper.isOnSecondaryHomeScreen())
1468 return helper.getSecondaryDisplayId();
1470 return Display.DEFAULT_DISPLAY;
1473 public static void pinAppShortcut(Context context) {
1474 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1475 ShortcutManager mShortcutManager = context.getSystemService(ShortcutManager.class);
1477 if(mShortcutManager.isRequestPinShortcutSupported()) {
1478 ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder(context, "freeform_mode").build();
1480 mShortcutManager.requestPinShortcut(pinShortcutInfo, null);
1482 showToastLong(context, R.string.tb_pin_shortcut_not_supported);
1484 Intent intent = ShortcutUtils.getShortcutIntent(context);
1485 intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
1486 intent.putExtra("duplicate", false);
1488 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1489 homeIntent.addCategory(Intent.CATEGORY_HOME);
1490 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
1492 intent.setPackage(defaultLauncher.activityInfo.packageName);
1493 context.sendBroadcast(intent);
1495 showToast(context, R.string.tb_shortcut_created);
1499 public static boolean shouldCollapse(Context context, boolean pendingAppLaunch) {
1500 SharedPreferences pref = getSharedPreferences(context);
1501 if(pref.getBoolean("hide_taskbar", true)) {
1502 if(!pref.getBoolean("freeform_hack", false)
1503 || isOverridingFreeformHack(context, false))
1504 return !LauncherHelper.getInstance().isOnHomeScreen();
1506 FreeformHackHelper helper = FreeformHackHelper.getInstance();
1507 if(pendingAppLaunch)
1508 return !helper.isFreeformHackActive();
1510 return !helper.isInFreeformWorkspace();
1516 public static boolean isOverridingFreeformHack(Context context) {
1517 return isOverridingFreeformHack(context, true);
1520 public static boolean isOverridingFreeformHack(Context context, boolean checkPref) {
1521 SharedPreferences pref = getSharedPreferences(context);
1522 return (!checkPref || pref.getBoolean("freeform_hack", false))
1523 && ((isChromeOs(context) && pref.getBoolean("chrome_os_context_menu_fix", true))
1524 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f));
1527 public static boolean isPlayStoreInstalled(Context context) {
1529 context.getPackageManager().getPackageInfo("com.android.vending", 0);
1531 } catch (PackageManager.NameNotFoundException e) {
1536 public static float getCurrentApiVersion() {
1537 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1538 return Float.valueOf(Build.VERSION.SDK_INT + "." + Build.VERSION.PREVIEW_SDK_INT);
1540 return (float) Build.VERSION.SDK_INT;
1543 public static boolean hasBrokenSetLaunchBoundsApi() {
1544 return getCurrentApiVersion() >= 26.0f
1545 && getCurrentApiVersion() < 28.0f
1546 && !isSamsungDevice()
1547 && !isNvidiaDevice();
1550 public static String getSecondScreenPackageName(Context context) {
1551 return getInstalledPackage(context,
1552 "com.farmerbb.secondscreen.free",
1553 "com.farmerbb.secondscreen");
1556 // Returns the name of an installed package from a list of package names, in order of preference
1557 private static String getInstalledPackage(Context context, String... packageNames) {
1558 return getInstalledPackage(context, Arrays.asList(packageNames));
1561 private static String getInstalledPackage(Context context, List<String> packageNames) {
1562 if(packageNames == null || packageNames.isEmpty())
1565 List<String> packages = packageNames instanceof ArrayList ? packageNames : new ArrayList<>(packageNames);
1566 String packageName = packages.get(0);
1569 context.getPackageManager().getPackageInfo(packageName, 0);
1571 } catch (PackageManager.NameNotFoundException e) {
1573 return getInstalledPackage(context, packages);
1577 public static void showRecentAppsDialog(Context context) {
1578 showRecentAppsDialog(context, null, null);
1581 public static AlertDialog showRecentAppsDialog(Context context, Runnable onError, Runnable onFinish) {
1582 Runnable finalOnFinish = onFinish == null
1586 Runnable finalOnError = onError == null
1587 ? () -> showErrorDialog(context, "GET_USAGE_STATS", finalOnFinish)
1590 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isSystemApp(context)) {
1591 ApplicationInfo applicationInfo = null;
1593 applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
1594 } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
1596 if(applicationInfo != null) {
1597 AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
1598 int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
1600 if(mode != AppOpsManager.MODE_ALLOWED) {
1601 AlertDialog.Builder builder;
1602 if(hasAndroidTVSettings(context))
1603 builder = buildRecentAppsDialogAndroidTV(context, finalOnError, finalOnFinish);
1605 builder = buildRecentAppsDialogStandard(context, finalOnError, finalOnFinish);
1607 AlertDialog dialog = builder.create();
1609 dialog.setCancelable(false);
1616 finalOnFinish.run();
1620 private static AlertDialog.Builder buildRecentAppsDialogStandard(Context context, Runnable onError, Runnable onFinish) {
1621 return new AlertDialog.Builder(context)
1622 .setTitle(R.string.tb_pref_header_recent_apps)
1623 .setMessage(R.string.tb_enable_recent_apps)
1624 .setPositiveButton(R.string.tb_action_ok, (dialog, which) -> {
1626 context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
1627 showToastLong(context, R.string.tb_usage_stats_message);
1630 } catch (ActivityNotFoundException e) {
1634 .setNegativeButton(R.string.tb_action_cancel, (dialog, which) -> onFinish.run());
1637 private static AlertDialog.Builder buildRecentAppsDialogAndroidTV(Context context, Runnable onError, Runnable onFinish) {
1638 return new AlertDialog.Builder(context)
1639 .setTitle(R.string.tb_pref_header_recent_apps)
1640 .setMessage(R.string.tb_enable_recent_apps_alt)
1641 .setPositiveButton(R.string.tb_action_open_settings, (dialog, which) -> {
1643 context.startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));
1645 } catch (ActivityNotFoundException e) {
1649 .setNegativeButton(R.string.tb_action_cancel, (dialog, which) -> onFinish.run());
1652 public static Context wrapContext(Context context) {
1653 SharedPreferences pref = getSharedPreferences(context);
1656 switch(pref.getString("theme", "light")) {
1658 theme = R.style.Taskbar;
1661 theme = R.style.Taskbar_Dark;
1665 return theme > -1 ? new ContextThemeWrapper(context, theme) : context;
1668 public static boolean isPlayStoreRelease(Context context) {
1669 return isPlayStoreRelease(context, context.getPackageName());
1672 @SuppressLint("PackageManagerGetSignatures")
1673 public static boolean isPlayStoreRelease(Context context, String packageName) {
1674 Signature playStoreSignature = new Signature(context.getString(R.string.tb_signature));
1676 PackageManager pm = context.getPackageManager();
1677 PackageInfo info = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
1678 for(Signature signature : info.signatures) {
1679 if(signature.equals(playStoreSignature))
1682 } catch (Exception e) { /* Gracefully fail */ }
1687 public static boolean isExternalAccessDisabled(Context context) {
1688 if(isLibrary(context)) return true;
1690 SharedPreferences pref = getSharedPreferences(context);
1691 return !pref.getBoolean("tasker_enabled", true);
1694 public static boolean enableFreeformModeShortcut(Context context) {
1695 return canEnableFreeform()
1696 && !isOverridingFreeformHack(context, false)
1697 && !isChromeOs(context);
1700 public static void startForegroundService(Context context, Intent intent) {
1701 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1702 if(Settings.canDrawOverlays(context))
1703 context.startForegroundService(intent);
1705 context.startService(intent);
1708 public static int getOverlayType() {
1709 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
1710 ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
1711 : WindowManager.LayoutParams.TYPE_PHONE;
1714 public static boolean isDelegatingHomeActivity(Context context) {
1715 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1716 homeIntent.addCategory(Intent.CATEGORY_HOME);
1718 final List<ResolveInfo> listOfLaunchers = context.getPackageManager().queryIntentActivities(homeIntent, 0);
1719 for(ResolveInfo launcher : listOfLaunchers) {
1720 if(launcher.activityInfo.packageName.equals(BuildConfig.SUPPORT_APPLICATION_ID))
1727 @SuppressLint("PrivateApi")
1728 private static String getSystemProperty(String key) {
1730 Class<?> cls = Class.forName("android.os.SystemProperties");
1731 return cls.getMethod("get", String.class).invoke(null, key).toString();
1732 } catch (Exception e) {
1737 @SuppressWarnings("ResultOfMethodCallIgnored")
1738 public static boolean importCustomStartButtonImage(Context context, Uri uri) {
1740 File imagesDir = new File(context.getFilesDir(), "tb_images");
1743 File importedFile = new File(imagesDir, "custom_image_new");
1744 if(importedFile.exists()) importedFile.delete();
1746 BufferedInputStream is = new BufferedInputStream(context.getContentResolver().openInputStream(uri));
1747 byte[] data = new byte[is.available()];
1749 if(data.length > 0) {
1750 BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(importedFile));
1757 File prevFile = new File(imagesDir, "custom_image");
1758 if(prevFile.exists()) prevFile.delete();
1760 importedFile.renameTo(prevFile);
1762 } catch (IOException e) {
1767 public static String getDefaultStartButtonImage(Context context) {
1768 SharedPreferences pref = getSharedPreferences(context);
1769 return pref.getBoolean("app_drawer_icon", false)
1774 private static boolean shouldLaunchTouchAbsorber(Context context) {
1775 return isOverridingFreeformHack(context) && !isChromeOs(context) && getCurrentApiVersion() < 29.0f;
1778 public static boolean isDesktopIconsEnabled(Context context) {
1779 return !canBootToFreeform(context, false) && !shouldLaunchTouchAbsorber(context);
1782 public static boolean isSystemTrayEnabled(Context context) {
1783 SharedPreferences pref = getSharedPreferences(context);
1785 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1786 && pref.getBoolean("sys_tray", context.getResources().getBoolean(R.bool.tb_def_sys_tray))
1787 && pref.getBoolean("full_length", context.getResources().getBoolean(R.bool.tb_def_full_length))
1788 && !getTaskbarPosition(context).contains("vertical");
1791 @SuppressWarnings("deprecation")
1792 public static boolean isLibrary(Context context) {
1793 return !context.getPackageName().equals(BuildConfig.APPLICATION_ID);
1796 public static boolean applyDisplayCutoutModeTo(WindowManager.LayoutParams params) {
1797 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
1798 params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
1805 private static boolean hasAndroidTVSettings(Context context) {
1806 return getInstalledPackage(context, "com.android.tv.settings") != null
1807 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
1810 public static void restartApp(Activity activity, boolean shouldFade) {
1811 Intent restartIntent = new Intent(activity, MainActivity.class);
1812 restartIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
1813 activity.startActivity(restartIntent);
1815 activity.overridePendingTransition(
1816 shouldFade ? android.R.anim.fade_in : 0,
1817 shouldFade ? android.R.anim.fade_out : 0
1823 public static boolean isDesktopModeSupported(Context context) {
1824 if(isLauncherPermanentlyEnabled(context)
1825 || isLibrary(context)
1826 || !BuildConfig.DEBUG // TODO remove this line
1827 || isChromeOs(context))
1830 return Build.VERSION.SDK_INT > Build.VERSION_CODES.P
1831 && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
1834 public static boolean isDesktopModeActive(Context context) {
1835 if(!isDesktopModeSupported(context)) return false;
1837 boolean desktopModePrefEnabled;
1840 desktopModePrefEnabled = Settings.Global.getInt(context.getContentResolver(), "force_desktop_mode_on_external_displays") == 1;
1841 } catch (Settings.SettingNotFoundException e) {
1842 desktopModePrefEnabled = false;
1845 return desktopModePrefEnabled && getExternalDisplayID(context) != Display.DEFAULT_DISPLAY;
1848 public static boolean shouldStartDesktopMode(Context context) {
1849 SharedPreferences pref = getSharedPreferences(context);
1851 return isDesktopModeSupported(context)
1852 && pref.getBoolean("desktop_mode", false)
1853 && !pref.getBoolean("launcher", false);
1856 public static int getExternalDisplayID(Context context) {
1857 DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1858 Display[] displays = dm.getDisplays();
1860 return displays[displays.length - 1].getDisplayId();