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.BroadcastReceiver;
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.IntentFilter;
34 import android.content.SharedPreferences;
35 import android.content.pm.ActivityInfo;
36 import android.content.pm.ApplicationInfo;
37 import android.content.pm.LauncherActivityInfo;
38 import android.content.pm.LauncherApps;
39 import android.content.pm.PackageInfo;
40 import android.content.pm.PackageManager;
41 import android.content.pm.ResolveInfo;
42 import android.content.pm.ShortcutInfo;
43 import android.content.pm.ShortcutManager;
44 import android.content.pm.Signature;
45 import android.content.res.Configuration;
46 import android.graphics.Color;
47 import android.graphics.Rect;
48 import android.hardware.display.DisplayManager;
49 import android.net.Uri;
50 import android.os.Build;
51 import android.os.Bundle;
52 import android.os.Handler;
53 import android.os.Process;
54 import android.os.UserHandle;
55 import android.os.UserManager;
56 import android.provider.Settings;
57 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
58 import androidx.appcompat.view.ContextThemeWrapper;
59 import android.util.DisplayMetrics;
60 import android.view.Display;
61 import android.view.Surface;
62 import android.view.View;
63 import android.view.WindowManager;
64 import android.widget.Toast;
66 import com.farmerbb.taskbar.BuildConfig;
67 import com.farmerbb.taskbar.R;
68 import com.farmerbb.taskbar.activity.ContextMenuActivity;
69 import com.farmerbb.taskbar.activity.DummyActivity;
70 import com.farmerbb.taskbar.activity.InvisibleActivityFreeform;
71 import com.farmerbb.taskbar.activity.MainActivity;
72 import com.farmerbb.taskbar.activity.TouchAbsorberActivity;
73 import com.farmerbb.taskbar.activity.dark.ContextMenuActivityDark;
74 import com.farmerbb.taskbar.content.TaskbarIntent;
75 import com.farmerbb.taskbar.service.DashboardService;
76 import com.farmerbb.taskbar.service.NotificationService;
77 import com.farmerbb.taskbar.service.PowerMenuService;
78 import com.farmerbb.taskbar.service.StartMenuService;
79 import com.farmerbb.taskbar.service.TaskbarService;
81 import java.io.BufferedInputStream;
82 import java.io.BufferedOutputStream;
84 import java.io.FileOutputStream;
85 import java.io.IOException;
86 import java.lang.reflect.Constructor;
87 import java.lang.reflect.Method;
88 import java.util.ArrayList;
89 import java.util.Arrays;
90 import java.util.List;
96 private static Integer cachedRotation;
98 private static final int MAXIMIZED = 0;
99 private static final int LEFT = -1;
100 private static final int RIGHT = 1;
102 public static final int HIDDEN = 0;
103 public static final int TOP_APPS = 1;
105 // From android.app.ActivityManager.StackId
106 private static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
107 private static final int FREEFORM_WORKSPACE_STACK_ID = 2;
109 // From android.app.WindowConfiguration
110 private static final int WINDOWING_MODE_FULLSCREEN = 1;
111 private static final int WINDOWING_MODE_FREEFORM = 5;
113 public static final int EXPORT = 123;
114 public static final int IMPORT = 456;
116 @SuppressWarnings("deprecation")
117 public static SharedPreferences getSharedPreferences(Context context) {
118 return context.getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", Context.MODE_PRIVATE);
121 public static void showPermissionDialog(Context context) {
122 showPermissionDialog(context, null, null);
125 public static AlertDialog showPermissionDialog(Context context, Runnable onError, Runnable onFinish) {
126 Runnable finalOnFinish = onFinish == null
130 Runnable finalOnError = onError == null
131 ? () -> showErrorDialog(context, "SYSTEM_ALERT_WINDOW", finalOnFinish)
134 AlertDialog.Builder builder;
135 if(hasAndroidTVSettings(context))
136 builder = buildPermissionDialogAndroidTV(context, finalOnError, finalOnFinish);
138 builder = buildPermissionDialogStandard(context, finalOnError, finalOnFinish);
140 AlertDialog dialog = builder.create();
142 dialog.setCancelable(false);
147 @TargetApi(Build.VERSION_CODES.M)
148 private static AlertDialog.Builder buildPermissionDialogStandard(Context context, Runnable onError, Runnable onFinish) {
149 return new AlertDialog.Builder(context)
150 .setTitle(R.string.tb_permission_dialog_title)
151 .setMessage(R.string.tb_permission_dialog_message)
152 .setPositiveButton(R.string.tb_action_grant_permission, (dialog, which) -> {
154 context.startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
155 Uri.parse("package:" + context.getPackageName())));
158 } catch (ActivityNotFoundException e) {
164 private static AlertDialog.Builder buildPermissionDialogAndroidTV(Context context, Runnable onError, Runnable onFinish) {
165 return new AlertDialog.Builder(context)
166 .setTitle(R.string.tb_permission_dialog_title)
167 .setMessage(R.string.tb_permission_dialog_message_alt)
168 .setPositiveButton(R.string.tb_action_open_settings, (dialog, which) -> {
170 context.startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));
172 } catch (ActivityNotFoundException e) {
178 public static AlertDialog showErrorDialog(Context context, String appopCmd) {
179 return showErrorDialog(context, appopCmd, null);
182 private static AlertDialog showErrorDialog(Context context, String appopCmd, Runnable onFinish) {
183 Runnable finalOnFinish = onFinish == null
187 AlertDialog.Builder builder = new AlertDialog.Builder(context);
188 builder.setTitle(R.string.tb_error_dialog_title)
189 .setMessage(context.getString(R.string.tb_error_dialog_message, context.getPackageName(), appopCmd))
190 .setPositiveButton(R.string.tb_action_ok, (dialog, which) -> finalOnFinish.run());
192 AlertDialog dialog = builder.create();
194 dialog.setCancelable(false);
199 @TargetApi(Build.VERSION_CODES.P)
200 public static void lockDevice(Context context) {
201 sendAccessibilityAction(context, AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN);
204 public static void sendAccessibilityAction(Context context, int action) {
205 sendAccessibilityAction(context, action, null);
208 public static void sendAccessibilityAction(Context context, int action, Runnable onComplete) {
209 setComponentEnabled(context, PowerMenuService.class, true);
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(TaskbarIntent.ACTION_ACCESSIBILITY_ACTION);
234 intent.putExtra("action", action);
235 sendBroadcast(context, 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(TaskbarIntent.ACTION_ACCESSIBILITY_ACTION);
247 intent.putExtra("action", action);
248 sendBroadcast(context, 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 sendBroadcast(context, TaskbarIntent.ACTION_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 sendBroadcast(context, TaskbarIntent.ACTION_HIDE_TASKBAR);
475 sendBroadcast(context, TaskbarIntent.ACTION_HIDE_START_MENU);
479 private static Bundle launchMode1(Context context, ApplicationType type, View view, int factor) {
480 DisplayInfo display = getDisplayInfo(context);
482 int width1 = display.width / factor;
483 int width2 = display.width - width1;
484 int height1 = display.height / factor;
485 int height2 = display.height - height1;
487 return getActivityOptionsBundle(context, type, view,
495 private static Bundle launchMode2(Context context, int launchType, ApplicationType type, View view) {
496 DisplayInfo display = getDisplayInfo(context);
498 int statusBarHeight = getStatusBarHeight(context);
499 String position = getTaskbarPosition(context);
501 boolean isPortrait = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
502 boolean isLandscape = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
505 int top = statusBarHeight;
506 int right = display.width;
507 int bottom = display.height;
509 int iconSize = isOverridingFreeformHack(context) && !LauncherHelper.getInstance().isOnHomeScreen()
511 : context.getResources().getDimensionPixelSize(R.dimen.tb_icon_size);
513 if(position.contains("vertical_left"))
514 left = left + iconSize;
515 else if(position.contains("vertical_right"))
516 right = right - iconSize;
517 else if(position.contains("bottom"))
518 bottom = bottom - iconSize;
520 top = top + iconSize;
522 int halfLandscape = (right / 2) + ((iconSize / 2) * (position.contains("vertical_left") ? 1 : 0));
523 int halfPortrait = (bottom / 2) + ((iconSize / 2) * ((position.equals("top_left") || position.equals("top_right")) ? 1 : 0));
525 if(launchType == RIGHT && isLandscape)
526 left = halfLandscape;
527 else if(launchType == RIGHT && isPortrait)
529 else if(launchType == LEFT && isLandscape)
530 right = halfLandscape;
531 else if(launchType == LEFT && isPortrait)
532 bottom = halfPortrait;
534 return getActivityOptionsBundle(context, type, view, left, top, right, bottom);
537 private static Bundle launchMode3(Context context, ApplicationType type, View view) {
538 DisplayInfo display = getDisplayInfo(context);
540 boolean isLandscape = type == ApplicationType.APP_LANDSCAPE;
541 int widthDimen = isLandscape ? R.dimen.tb_phone_size_height : R.dimen.tb_phone_size_width;
542 int heightDimen = isLandscape ? R.dimen.tb_phone_size_width : R.dimen.tb_phone_size_height;
544 int width1 = display.width / 2;
545 int width2 = context.getResources().getDimensionPixelSize(widthDimen) / 2;
546 int height1 = display.height / 2;
547 int height2 = context.getResources().getDimensionPixelSize(heightDimen) / 2;
549 return getActivityOptionsBundle(context, type, view,
557 private static void launchAndroidForWork(Context context, ComponentName componentName, Bundle bundle, long userId, Runnable onError) {
558 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
559 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
562 launcherApps.startMainActivity(componentName, userManager.getUserForSerialNumber(userId), null, bundle);
563 } catch (ActivityNotFoundException | NullPointerException
564 | IllegalStateException | SecurityException e) {
565 if(onError != null) launchApp(context, onError);
569 @TargetApi(Build.VERSION_CODES.N_MR1)
570 private static void launchShortcut(Context context, ShortcutInfo shortcut, Bundle bundle, Runnable onError) {
571 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
573 if(launcherApps.hasShortcutHostPermission()) {
575 launcherApps.startShortcut(shortcut, null, bundle);
576 } catch (ActivityNotFoundException | NullPointerException
577 | IllegalStateException | SecurityException e) {
578 if(onError != null) launchApp(context, onError);
583 private static void prepareToStartActivity(Context context, boolean openInNewWindow, Runnable runnable) {
584 sendBroadcast(context, TaskbarIntent.ACTION_HIDE_CONTEXT_MENU);
586 if(!FreeformHackHelper.getInstance().isTouchAbsorberActive()
587 && shouldLaunchTouchAbsorber(context)) {
588 startTouchAbsorberActivity(context);
589 new Handler().postDelayed(runnable, 100);
590 } else if(openInNewWindow) {
591 Intent intent = new Intent(context, DummyActivity.class);
592 intent.putExtra("finish_on_pause", true);
593 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
594 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
595 startActivityLowerRight(context, intent);
597 new Handler().postDelayed(runnable, 100);
602 public static void startActivityMaximized(Context context, Intent intent) {
603 Bundle bundle = launchMode2(context, MAXIMIZED, ApplicationType.CONTEXT_MENU, null);
604 prepareToStartActivity(context, false, () -> context.startActivity(intent, bundle));
607 public static void startActivityLowerRight(Context context, Intent intent) {
608 DisplayInfo display = getDisplayInfo(context);
610 context.startActivity(intent,
611 getActivityOptionsBundle(context, ApplicationType.FREEFORM_HACK, null,
617 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
620 public static void startTouchAbsorberActivity(Context context) {
621 String position = getTaskbarPosition(context);
622 DisplayInfo display = getDisplayInfo(context);
626 int right = display.width;
627 int bottom = display.height;
629 int iconSize = context.getResources().getDimensionPixelSize(R.dimen.tb_icon_size);
631 if(position.contains("vertical_left"))
633 else if(position.contains("vertical_right"))
634 left = right - iconSize;
635 else if(position.contains("bottom"))
636 top = bottom - iconSize;
640 Intent intent = new Intent(context, TouchAbsorberActivity.class);
641 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
642 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
645 context.startActivity(intent,
646 getActivityOptionsBundle(context, ApplicationType.FREEFORM_HACK, null,
647 left, top, right, bottom));
648 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
651 public static void startContextMenuActivity(Context context, Bundle args) {
652 SharedPreferences pref = getSharedPreferences(context);
653 Intent intent = null;
655 switch(pref.getString("theme", "light")) {
657 intent = new Intent(context, ContextMenuActivity.class);
660 intent = new Intent(context, ContextMenuActivityDark.class);
665 intent.putExtra("args", args);
666 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
669 if(hasFreeformSupport(context) && FreeformHackHelper.getInstance().isInFreeformWorkspace()) {
670 DisplayInfo display = getDisplayInfo(context);
672 if(intent != null && hasBrokenSetLaunchBoundsApi())
673 intent.putExtra("context_menu_fix", true);
675 context.startActivity(intent,
676 getActivityOptionsBundle(context, ApplicationType.CONTEXT_MENU, null,
677 0, 0, display.width, display.height));
679 context.startActivity(intent);
682 public static void checkForUpdates(Context context) {
684 if(isPlayStoreRelease(context)) {
685 if(context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
686 && !isPlayStoreInstalled(context))
687 url = "https://github.com/farmerbb/Taskbar/releases";
689 url = "https://play.google.com/store/apps/details?id=" + context.getPackageName();
691 url = "https://f-droid.org/repository/browse/?fdid=" + context.getPackageName();
693 Intent intent = new Intent(Intent.ACTION_VIEW);
694 intent.setData(Uri.parse(url));
695 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
698 context.startActivity(intent);
699 } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
702 public static boolean launcherIsDefault(Context context) {
703 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
704 homeIntent.addCategory(Intent.CATEGORY_HOME);
705 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
707 return defaultLauncher.activityInfo.packageName.equals(context.getPackageName());
710 public static void setCachedRotation(int cachedRotation) {
711 U.cachedRotation = cachedRotation;
714 public static String getTaskbarPosition(Context context) {
715 SharedPreferences pref = getSharedPreferences(context);
716 String position = pref.getString("position", "bottom_left");
718 if(pref.getBoolean("anchor", false)) {
719 WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
720 int rotation = cachedRotation != null ? cachedRotation : windowManager.getDefaultDisplay().getRotation();
725 case Surface.ROTATION_0:
726 return "bottom_left";
727 case Surface.ROTATION_90:
728 return "bottom_vertical_right";
729 case Surface.ROTATION_180:
731 case Surface.ROTATION_270:
732 return "top_vertical_left";
735 case "bottom_vertical_left":
737 case Surface.ROTATION_0:
738 return "bottom_vertical_left";
739 case Surface.ROTATION_90:
740 return "bottom_right";
741 case Surface.ROTATION_180:
742 return "top_vertical_right";
743 case Surface.ROTATION_270:
749 case Surface.ROTATION_0:
750 return "bottom_right";
751 case Surface.ROTATION_90:
752 return "top_vertical_right";
753 case Surface.ROTATION_180:
755 case Surface.ROTATION_270:
756 return "bottom_vertical_left";
759 case "bottom_vertical_right":
761 case Surface.ROTATION_0:
762 return "bottom_vertical_right";
763 case Surface.ROTATION_90:
765 case Surface.ROTATION_180:
766 return "top_vertical_left";
767 case Surface.ROTATION_270:
768 return "bottom_left";
773 case Surface.ROTATION_0:
775 case Surface.ROTATION_90:
776 return "bottom_vertical_left";
777 case Surface.ROTATION_180:
778 return "bottom_right";
779 case Surface.ROTATION_270:
780 return "top_vertical_right";
783 case "top_vertical_left":
785 case Surface.ROTATION_0:
786 return "top_vertical_left";
787 case Surface.ROTATION_90:
788 return "bottom_left";
789 case Surface.ROTATION_180:
790 return "bottom_vertical_right";
791 case Surface.ROTATION_270:
797 case Surface.ROTATION_0:
799 case Surface.ROTATION_90:
800 return "top_vertical_left";
801 case Surface.ROTATION_180:
802 return "bottom_left";
803 case Surface.ROTATION_270:
804 return "bottom_vertical_right";
807 case "top_vertical_right":
809 case Surface.ROTATION_0:
810 return "top_vertical_right";
811 case Surface.ROTATION_90:
813 case Surface.ROTATION_180:
814 return "bottom_vertical_left";
815 case Surface.ROTATION_270:
816 return "bottom_right";
825 private static int getMaxNumOfColumns(Context context) {
826 SharedPreferences pref = getSharedPreferences(context);
827 DisplayInfo display = getDisplayInfo(context);
828 float density = display.density / 160;
829 float baseTaskbarSize = getBaseTaskbarSizeFloat(context) / density;
830 int numOfColumns = 0;
832 float maxScreenSize = getTaskbarPosition(context).contains("vertical")
833 ? (display.height - getStatusBarHeight(context)) / density
834 : display.width / density;
836 float iconSize = context.getResources().getDimension(R.dimen.tb_icon_size) / density;
838 int userMaxNumOfColumns = Integer.valueOf(pref.getString("max_num_of_recents", "10"));
840 while(baseTaskbarSize + iconSize < maxScreenSize
841 && numOfColumns < userMaxNumOfColumns) {
842 baseTaskbarSize = baseTaskbarSize + iconSize;
849 public static int getMaxNumOfEntries(Context context) {
850 SharedPreferences pref = getSharedPreferences(context);
851 return pref.getBoolean("disable_scrolling_list", false)
852 ? getMaxNumOfColumns(context)
853 : Integer.valueOf(pref.getString("max_num_of_recents", "10"));
856 public static int getStatusBarHeight(Context context) {
857 return getSystemDimen(context, "status_bar_height");
860 private static int getNavbarHeight(Context context) {
861 return getSystemDimen(context, "navigation_bar_height");
864 private static int getSystemDimen(Context context, String id) {
866 int resourceId = context.getResources().getIdentifier(id, "dimen", "android");
868 value = context.getResources().getDimensionPixelSize(resourceId);
873 public static void refreshPinnedIcons(Context context) {
874 IconCache.getInstance(context).clearCache();
876 PinnedBlockedApps pba = PinnedBlockedApps.getInstance(context);
877 List<AppEntry> pinnedAppsList = new ArrayList<>(pba.getPinnedApps());
878 List<AppEntry> blockedAppsList = new ArrayList<>(pba.getBlockedApps());
879 PackageManager pm = context.getPackageManager();
883 for(AppEntry entry : pinnedAppsList) {
884 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
885 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
887 final List<UserHandle> userHandles = userManager.getUserProfiles();
888 LauncherActivityInfo appInfo = null;
890 for(UserHandle handle : userHandles) {
891 List<LauncherActivityInfo> list = launcherApps.getActivityList(entry.getPackageName(), handle);
892 if(!list.isEmpty()) {
893 // Google App workaround
894 if(!entry.getPackageName().equals("com.google.android.googlequicksearchbox"))
895 appInfo = list.get(0);
897 boolean added = false;
898 for(LauncherActivityInfo info : list) {
899 if(info.getName().equals("com.google.android.googlequicksearchbox.SearchActivity")) {
905 if(!added) appInfo = list.get(0);
912 if(appInfo != null) {
913 AppEntry newEntry = new AppEntry(
914 entry.getPackageName(),
915 entry.getComponentName(),
917 IconCache.getInstance(context).getIcon(context, pm, appInfo),
920 newEntry.setUserId(entry.getUserId(context));
921 pba.addPinnedApp(context, newEntry);
925 for(AppEntry entry : blockedAppsList) {
926 pba.addBlockedApp(context, entry);
930 public static boolean canEnableFreeform() {
931 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
934 @TargetApi(Build.VERSION_CODES.N)
935 public static boolean hasFreeformSupport(Context context) {
936 return canEnableFreeform()
937 && (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
938 || Settings.Global.getInt(context.getContentResolver(), "enable_freeform_support", 0) != 0
939 || (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
940 && Settings.Global.getInt(context.getContentResolver(), "force_resizable_activities", 0) != 0));
943 public static boolean canBootToFreeform(Context context) {
944 return canBootToFreeform(context, true);
947 private static boolean canBootToFreeform(Context context, boolean checkPref) {
948 return hasFreeformSupport(context) && !isOverridingFreeformHack(context, checkPref);
951 public static boolean isSamsungDevice() {
952 return Build.MANUFACTURER.equalsIgnoreCase("Samsung");
955 private static boolean isNvidiaDevice() {
956 return Build.MANUFACTURER.equalsIgnoreCase("NVIDIA");
959 public static boolean isServiceRunning(Context context, Class<? extends Service> cls) {
960 if(LauncherHelper.getInstance().isOnSecondaryHomeScreen()
961 && (cls.equals(TaskbarService.class)
962 || cls.equals(StartMenuService.class)
963 || cls.equals(DashboardService.class)))
966 return isServiceRunning(context, cls.getName());
969 private static boolean isServiceRunning(Context context, String className) {
970 ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
971 for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
972 if(className.equals(service.service.getClassName()))
979 public static int getBackgroundTint(Context context) {
980 SharedPreferences pref = getSharedPreferences(context);
982 // Import old background tint preference
983 if(pref.contains("show_background")) {
984 SharedPreferences.Editor editor = pref.edit();
986 if(!pref.getBoolean("show_background", true))
987 editor.putInt("background_tint", Color.TRANSPARENT).apply();
989 editor.remove("show_background");
993 return pref.getInt("background_tint", context.getResources().getInteger(R.integer.tb_translucent_gray));
996 public static int getAccentColor(Context context) {
997 SharedPreferences pref = getSharedPreferences(context);
998 return pref.getInt("accent_color", context.getResources().getInteger(R.integer.tb_translucent_white));
1001 public static boolean canDrawOverlays(Context context) {
1002 return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context);
1005 public static boolean isGame(Context context, String packageName) {
1006 SharedPreferences pref = getSharedPreferences(context);
1007 if(pref.getBoolean("launch_games_fullscreen", true)) {
1008 PackageManager pm = context.getPackageManager();
1011 ApplicationInfo info = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
1012 return (info.flags & ApplicationInfo.FLAG_IS_GAME) != 0 || (info.metaData != null && info.metaData.getBoolean("isGame", false));
1013 } catch (PackageManager.NameNotFoundException e) {
1020 private static ActivityOptions getActivityOptions(View view) {
1021 return getActivityOptions(null, null, view);
1024 public static ActivityOptions getActivityOptions(Context context, ApplicationType applicationType, View view) {
1025 ActivityOptions options;
1027 options = ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight());
1028 else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1029 options = ActivityOptions.makeBasic();
1032 Constructor<ActivityOptions> constructor = ActivityOptions.class.getDeclaredConstructor();
1033 constructor.setAccessible(true);
1034 options = constructor.newInstance();
1035 } catch (Exception e) {
1040 if(applicationType == null)
1045 switch(applicationType) {
1048 if(FreeformHackHelper.getInstance().isFreeformHackActive())
1049 stackId = getFreeformWindowModeId();
1051 stackId = getFullscreenWindowModeId();
1053 case APP_FULLSCREEN:
1054 stackId = getFullscreenWindowModeId();
1057 stackId = getFreeformWindowModeId();
1060 if(hasBrokenSetLaunchBoundsApi()
1061 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f))
1062 stackId = getFullscreenWindowModeId();
1067 Method method = ActivityOptions.class.getMethod(getWindowingModeMethodName(), int.class);
1068 method.invoke(options, stackId);
1069 } catch (Exception e) { /* Gracefully fail */ }
1071 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1072 int launchDisplayId = LauncherHelper.getInstance().getSecondaryDisplayId();
1073 if(launchDisplayId != -1)
1074 options.setLaunchDisplayId(launchDisplayId);
1080 private static int getFullscreenWindowModeId() {
1081 if(getCurrentApiVersion() >= 28.0f)
1082 return WINDOWING_MODE_FULLSCREEN;
1084 return FULLSCREEN_WORKSPACE_STACK_ID;
1087 private static int getFreeformWindowModeId() {
1088 if(getCurrentApiVersion() >= 28.0f)
1089 return WINDOWING_MODE_FREEFORM;
1091 return FREEFORM_WORKSPACE_STACK_ID;
1094 private static String getWindowingModeMethodName() {
1095 if(getCurrentApiVersion() >= 28.0f)
1096 return "setLaunchWindowingMode";
1098 return "setLaunchStackId";
1101 public static Bundle getActivityOptionsBundle(Context context, ApplicationType type, View view) {
1102 SharedPreferences pref = getSharedPreferences(context);
1104 return getActivityOptionsBundle(context, type, pref.getString("window_size", context.getString(R.string.tb_def_window_size)), view);
1107 private static Bundle getActivityOptionsBundle(Context context, ApplicationType type, String windowSize, View view) {
1108 SharedPreferences pref = getSharedPreferences(context);
1109 if(!canEnableFreeform() || !pref.getBoolean("freeform_hack", false))
1110 return getActivityOptions(view).toBundle();
1112 switch(windowSize) {
1114 if(getCurrentApiVersion() > 29.0f)
1115 return launchMode1(context, type, view, 4);
1118 return launchMode1(context, type, view, 8);
1120 return launchMode2(context, MAXIMIZED, type, view);
1122 return launchMode2(context, LEFT, type, view);
1124 return launchMode2(context, RIGHT, type, view);
1126 return launchMode3(context, type, view);
1129 return getActivityOptions(context, type, view).toBundle();
1132 private static Bundle getActivityOptionsBundle(Context context, ApplicationType applicationType, View view,
1133 int left, int top, int right, int bottom) {
1134 ActivityOptions options = getActivityOptions(context, applicationType, view);
1138 if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
1139 return options.toBundle();
1141 return options.setLaunchBounds(new Rect(left, top, right, bottom)).toBundle();
1144 @SuppressLint("SwitchIntDef")
1145 private static ApplicationType getApplicationType(Context context, AppEntry entry) {
1146 if(isGame(context, entry.getPackageName()))
1147 return ApplicationType.APP_FULLSCREEN;
1150 ActivityInfo info = context.getPackageManager().getActivityInfo(
1151 ComponentName.unflattenFromString(entry.getComponentName()),
1155 switch(info.screenOrientation) {
1156 case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
1157 case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
1158 case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
1159 case ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE:
1160 return ApplicationType.APP_LANDSCAPE;
1162 case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
1163 case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:
1164 case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:
1165 case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT:
1166 return ApplicationType.APP_PORTRAIT;
1168 } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
1170 return context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
1171 ? ApplicationType.APP_LANDSCAPE
1172 : ApplicationType.APP_PORTRAIT;
1175 public static boolean isSystemApp(Context context) {
1177 ApplicationInfo info = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
1178 int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
1179 return (info.flags & mask) != 0;
1180 } catch (PackageManager.NameNotFoundException e) {
1185 public static boolean isChromeOs(Context context) {
1186 return context.getPackageManager().hasSystemFeature("org.chromium.arc");
1189 public static boolean isBlissOs(Context context) {
1190 boolean validBlissOsBuildProp = false;
1192 String blissVersion = getSystemProperty("ro.bliss.version");
1193 if(blissVersion != null && !blissVersion.isEmpty())
1194 validBlissOsBuildProp = true;
1196 String buildUser = getSystemProperty("ro.build.user");
1197 if(buildUser != null && buildUser.equals("electrikjesus"))
1198 validBlissOsBuildProp = true;
1200 return validBlissOsBuildProp
1201 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1202 && isSystemApp(context);
1205 public static boolean isLauncherPermanentlyEnabled(Context context) {
1206 if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
1209 return hasSupportLibrary(context, 0);
1212 public static boolean hasSupportLibrary(Context context, int minVersion) {
1213 PackageManager pm = context.getPackageManager();
1215 PackageInfo pInfo = pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1216 return pInfo.versionCode >= minVersion
1217 && pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, context.getPackageName()) == PackageManager.SIGNATURE_MATCH
1218 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1219 && isSystemApp(context);
1220 } catch (PackageManager.NameNotFoundException e) {
1225 public static int getBaseTaskbarSize(Context context) {
1226 return Math.round(getBaseTaskbarSizeFloat(context));
1229 private static float getBaseTaskbarSizeFloat(Context context) {
1230 SharedPreferences pref = getSharedPreferences(context);
1231 float baseTaskbarSize = context.getResources().getDimension(R.dimen.tb_base_taskbar_size);
1232 boolean navbarButtonsEnabled = false;
1234 if(pref.getBoolean("dashboard", context.getResources().getBoolean(R.bool.tb_def_dashboard)))
1235 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_dashboard_button_size);
1237 if(pref.getBoolean("button_back", false)) {
1238 navbarButtonsEnabled = true;
1239 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1242 if(pref.getBoolean("button_home", false)) {
1243 navbarButtonsEnabled = true;
1244 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1247 if(pref.getBoolean("button_recents", false)) {
1248 navbarButtonsEnabled = true;
1249 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1252 if(navbarButtonsEnabled)
1253 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_navbar_buttons_margin);
1255 if(isSystemTrayEnabled(context))
1256 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_systray_size);
1258 return baseTaskbarSize;
1261 private static void startTaskbarService(Context context, boolean fullRestart) {
1262 context.startService(new Intent(context, TaskbarService.class));
1263 context.startService(new Intent(context, StartMenuService.class));
1264 context.startService(new Intent(context, DashboardService.class));
1265 if(fullRestart) context.startService(new Intent(context, NotificationService.class));
1268 private static void stopTaskbarService(Context context, boolean fullRestart) {
1269 context.stopService(new Intent(context, TaskbarService.class));
1270 context.stopService(new Intent(context, StartMenuService.class));
1271 context.stopService(new Intent(context, DashboardService.class));
1272 if(fullRestart) context.stopService(new Intent(context, NotificationService.class));
1275 public static void restartTaskbar(Context context) {
1276 SharedPreferences pref = getSharedPreferences(context);
1277 if(pref.getBoolean("taskbar_active", false) && !pref.getBoolean("is_hidden", false)) {
1279 .putBoolean("is_restarting", true)
1280 .putBoolean("skip_auto_hide_navbar", true)
1283 stopTaskbarService(context, true);
1284 startTaskbarService(context, true);
1285 } else if(isServiceRunning(context, StartMenuService.class)) {
1286 pref.edit().putBoolean("skip_auto_hide_navbar", true).apply();
1288 stopTaskbarService(context, false);
1289 startTaskbarService(context, false);
1292 sendBroadcast(context, TaskbarIntent.ACTION_RESTART);
1295 public static void restartNotificationService(Context context) {
1296 if(isServiceRunning(context, NotificationService.class)) {
1297 SharedPreferences pref = getSharedPreferences(context);
1298 pref.edit().putBoolean("is_restarting", true).apply();
1300 Intent intent = new Intent(context, NotificationService.class);
1301 context.stopService(intent);
1302 context.startService(intent);
1306 public static void showHideNavigationBar(Context context, boolean show) {
1307 if(!isDesktopModeActive(context)
1308 && !isBlissOs(context)
1309 && !hasSupportLibrary(context, 7)) {
1313 int displayID = getDisplayID();
1314 int value = show ? 0 : getNavbarHeight(context) * -1;
1316 if(hasWriteSecureSettingsPermission(context)) {
1318 setOverscan(displayID, value);
1320 } catch (Exception e) {
1321 // Fallback to next method
1325 if(hasSupportLibrary(context, 7)) {
1326 Intent intent = new Intent(BuildConfig.SUPPORT_APPLICATION_ID + ".CHANGE_OVERSCAN");
1327 intent.setPackage(BuildConfig.SUPPORT_APPLICATION_ID);
1329 intent.putExtra("display_id", displayID);
1330 intent.putExtra("value", value);
1332 context.sendBroadcast(intent);
1336 // Show or hide the system navigation bar on Bliss-x86
1337 if(!isBlissOs(context)) return;
1340 if(getCurrentApiVersion() >= 28.0f)
1341 Settings.Secure.putInt(context.getContentResolver(), "navigation_bar_visible", show ? 1 : 0);
1343 Settings.System.putInt(context.getContentResolver(), "navigation_bar_show", show ? 1 : 0);
1344 } catch (Exception e) { /* Gracefully fail */ }
1347 public static void initPrefs(Context context) {
1348 // Enable freeform hack automatically on supported devices
1349 SharedPreferences pref = getSharedPreferences(context);
1350 if(canEnableFreeform()) {
1351 if(!pref.getBoolean("freeform_hack_override", false)) {
1353 .putBoolean("freeform_hack", hasFreeformSupport(context) && !isSamsungDevice())
1354 .putBoolean("save_window_sizes", false)
1355 .putBoolean("freeform_hack_override", true)
1357 } else if(!hasFreeformSupport(context)) {
1358 pref.edit().putBoolean("freeform_hack", false).apply();
1360 stopFreeformHack(context);
1363 boolean freeformWasEnabled = pref.getBoolean("freeform_hack", false)
1364 || pref.getBoolean("show_freeform_disabled_message", false);
1367 .putBoolean("freeform_hack", false)
1368 .putBoolean("show_freeform_disabled_message", freeformWasEnabled)
1371 SavedWindowSizes.getInstance(context).clear(context);
1372 stopFreeformHack(context);
1375 // Customizations for BlissOS
1376 if(isBlissOs(context) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1377 && !pref.getBoolean("bliss_os_prefs", false)) {
1378 SharedPreferences.Editor editor = pref.edit();
1380 if(hasFreeformSupport(context)) {
1381 editor.putBoolean("freeform_hack", true);
1384 editor.putString("recents_amount", "running_apps_only");
1385 editor.putString("refresh_frequency", "0");
1386 editor.putString("max_num_of_recents", "2147483647");
1387 editor.putString("sort_order", "true");
1388 editor.putString("start_button_image", "app_logo");
1389 editor.putBoolean("button_back", true);
1390 editor.putBoolean("button_home", true);
1391 editor.putBoolean("button_recents", true);
1392 editor.putBoolean("auto_hide_navbar", true);
1393 editor.putBoolean("shortcut_icon", false);
1394 editor.putBoolean("bliss_os_prefs", true);
1398 // Customizations for Android-x86 devices (non-Bliss)
1399 if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
1400 && isSystemApp(context)
1401 && !pref.getBoolean("android_x86_prefs", false)) {
1403 .putString("recents_amount", "running_apps_only")
1404 .putString("refresh_frequency", "0")
1405 .putString("max_num_of_recents", "2147483647")
1406 .putString("sort_order", "true")
1407 .putBoolean("shortcut_icon", false)
1408 .putBoolean("android_x86_prefs", true)
1413 public static DisplayInfo getDisplayInfo(Context context) {
1414 return getDisplayInfo(context, false);
1417 public static DisplayInfo getDisplayInfo(Context context, boolean fromTaskbar) {
1418 context = context.getApplicationContext();
1419 int displayID = getDisplayID();
1421 DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1422 Display currentDisplay = null;
1424 for(Display display : dm.getDisplays()) {
1425 if(display.getDisplayId() == displayID) {
1426 currentDisplay = display;
1431 if(currentDisplay == null)
1432 return new DisplayInfo(0, 0, 0);
1434 DisplayMetrics metrics = new DisplayMetrics();
1435 currentDisplay.getMetrics(metrics);
1437 DisplayMetrics realMetrics = new DisplayMetrics();
1438 currentDisplay.getRealMetrics(realMetrics);
1440 DisplayInfo info = new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);
1442 if(isChromeOs(context)) {
1443 SharedPreferences pref = getSharedPreferences(context);
1444 if(!pref.getBoolean("chrome_os_context_menu_fix", true)) {
1445 info.width = realMetrics.widthPixels;
1446 info.height = realMetrics.heightPixels;
1452 // Workaround for incorrect display size on devices with notches in landscape mode
1453 if(fromTaskbar && context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)
1456 boolean sameWidth = metrics.widthPixels == realMetrics.widthPixels;
1457 boolean sameHeight = metrics.heightPixels == realMetrics.heightPixels;
1459 if(sameWidth && !sameHeight) {
1460 info.width = realMetrics.widthPixels;
1461 info.height = realMetrics.heightPixels - getNavbarHeight(context);
1464 if(!sameWidth && sameHeight) {
1465 info.width = realMetrics.widthPixels - getNavbarHeight(context);
1466 info.height = realMetrics.heightPixels;
1472 private static int getDisplayID() {
1473 LauncherHelper helper = LauncherHelper.getInstance();
1475 if(helper.isOnSecondaryHomeScreen())
1476 return helper.getSecondaryDisplayId();
1478 return Display.DEFAULT_DISPLAY;
1481 public static void pinAppShortcut(Context context) {
1482 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1483 ShortcutManager mShortcutManager = context.getSystemService(ShortcutManager.class);
1485 if(mShortcutManager.isRequestPinShortcutSupported()) {
1486 ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder(context, "freeform_mode").build();
1488 mShortcutManager.requestPinShortcut(pinShortcutInfo, null);
1490 showToastLong(context, R.string.tb_pin_shortcut_not_supported);
1492 Intent intent = ShortcutUtils.getShortcutIntent(context);
1493 intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
1494 intent.putExtra("duplicate", false);
1496 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1497 homeIntent.addCategory(Intent.CATEGORY_HOME);
1498 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
1500 intent.setPackage(defaultLauncher.activityInfo.packageName);
1501 context.sendBroadcast(intent);
1503 showToast(context, R.string.tb_shortcut_created);
1507 public static boolean shouldCollapse(Context context, boolean pendingAppLaunch) {
1508 SharedPreferences pref = getSharedPreferences(context);
1509 if(pref.getBoolean("hide_taskbar", true)) {
1510 if(!pref.getBoolean("freeform_hack", false)
1511 || isOverridingFreeformHack(context, false))
1512 return !LauncherHelper.getInstance().isOnHomeScreen();
1514 FreeformHackHelper helper = FreeformHackHelper.getInstance();
1515 if(pendingAppLaunch)
1516 return !helper.isFreeformHackActive();
1518 return !helper.isInFreeformWorkspace();
1524 public static boolean isOverridingFreeformHack(Context context) {
1525 return isOverridingFreeformHack(context, true);
1528 public static boolean isOverridingFreeformHack(Context context, boolean checkPref) {
1529 SharedPreferences pref = getSharedPreferences(context);
1530 return (!checkPref || pref.getBoolean("freeform_hack", false))
1531 && ((isChromeOs(context) && pref.getBoolean("chrome_os_context_menu_fix", true))
1532 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f));
1535 public static boolean isPlayStoreInstalled(Context context) {
1537 context.getPackageManager().getPackageInfo("com.android.vending", 0);
1539 } catch (PackageManager.NameNotFoundException e) {
1544 public static float getCurrentApiVersion() {
1545 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1546 return Float.valueOf(Build.VERSION.SDK_INT + "." + Build.VERSION.PREVIEW_SDK_INT);
1548 return (float) Build.VERSION.SDK_INT;
1551 public static boolean hasBrokenSetLaunchBoundsApi() {
1552 return getCurrentApiVersion() >= 26.0f
1553 && getCurrentApiVersion() < 28.0f
1554 && !isSamsungDevice()
1555 && !isNvidiaDevice();
1558 public static String getSecondScreenPackageName(Context context) {
1559 return getInstalledPackage(context,
1560 "com.farmerbb.secondscreen.free",
1561 "com.farmerbb.secondscreen");
1564 // Returns the name of an installed package from a list of package names, in order of preference
1565 private static String getInstalledPackage(Context context, String... packageNames) {
1566 return getInstalledPackage(context, Arrays.asList(packageNames));
1569 private static String getInstalledPackage(Context context, List<String> packageNames) {
1570 if(packageNames == null || packageNames.isEmpty())
1573 List<String> packages = packageNames instanceof ArrayList ? packageNames : new ArrayList<>(packageNames);
1574 String packageName = packages.get(0);
1577 context.getPackageManager().getPackageInfo(packageName, 0);
1579 } catch (PackageManager.NameNotFoundException e) {
1581 return getInstalledPackage(context, packages);
1585 public static void showRecentAppsDialog(Context context) {
1586 showRecentAppsDialog(context, null, null);
1589 public static AlertDialog showRecentAppsDialog(Context context, Runnable onError, Runnable onFinish) {
1590 Runnable finalOnFinish = onFinish == null
1594 Runnable finalOnError = onError == null
1595 ? () -> showErrorDialog(context, "GET_USAGE_STATS", finalOnFinish)
1598 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isSystemApp(context)) {
1599 ApplicationInfo applicationInfo = null;
1601 applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
1602 } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
1604 if(applicationInfo != null) {
1605 AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
1606 int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
1608 if(mode != AppOpsManager.MODE_ALLOWED) {
1609 AlertDialog.Builder builder;
1610 if(hasAndroidTVSettings(context))
1611 builder = buildRecentAppsDialogAndroidTV(context, finalOnError, finalOnFinish);
1613 builder = buildRecentAppsDialogStandard(context, finalOnError, finalOnFinish);
1615 AlertDialog dialog = builder.create();
1617 dialog.setCancelable(false);
1624 finalOnFinish.run();
1628 private static AlertDialog.Builder buildRecentAppsDialogStandard(Context context, Runnable onError, Runnable onFinish) {
1629 return new AlertDialog.Builder(context)
1630 .setTitle(R.string.tb_pref_header_recent_apps)
1631 .setMessage(R.string.tb_enable_recent_apps)
1632 .setPositiveButton(R.string.tb_action_ok, (dialog, which) -> {
1634 context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
1635 showToastLong(context, R.string.tb_usage_stats_message);
1638 } catch (ActivityNotFoundException e) {
1642 .setNegativeButton(R.string.tb_action_cancel, (dialog, which) -> onFinish.run());
1645 private static AlertDialog.Builder buildRecentAppsDialogAndroidTV(Context context, Runnable onError, Runnable onFinish) {
1646 return new AlertDialog.Builder(context)
1647 .setTitle(R.string.tb_pref_header_recent_apps)
1648 .setMessage(R.string.tb_enable_recent_apps_alt)
1649 .setPositiveButton(R.string.tb_action_open_settings, (dialog, which) -> {
1651 context.startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));
1653 } catch (ActivityNotFoundException e) {
1657 .setNegativeButton(R.string.tb_action_cancel, (dialog, which) -> onFinish.run());
1660 public static Context wrapContext(Context context) {
1661 SharedPreferences pref = getSharedPreferences(context);
1664 switch(pref.getString("theme", "light")) {
1666 theme = R.style.Taskbar;
1669 theme = R.style.Taskbar_Dark;
1673 return theme > -1 ? new ContextThemeWrapper(context, theme) : context;
1676 public static boolean isPlayStoreRelease(Context context) {
1677 return isPlayStoreRelease(context, context.getPackageName());
1680 @SuppressLint("PackageManagerGetSignatures")
1681 public static boolean isPlayStoreRelease(Context context, String packageName) {
1682 Signature playStoreSignature = new Signature(context.getString(R.string.tb_signature));
1684 PackageManager pm = context.getPackageManager();
1685 PackageInfo info = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
1686 for(Signature signature : info.signatures) {
1687 if(signature.equals(playStoreSignature))
1690 } catch (Exception e) { /* Gracefully fail */ }
1695 public static boolean isExternalAccessDisabled(Context context) {
1696 if(isLibrary(context)) return true;
1698 SharedPreferences pref = getSharedPreferences(context);
1699 return !pref.getBoolean("tasker_enabled", true);
1702 public static boolean enableFreeformModeShortcut(Context context) {
1703 return canEnableFreeform()
1704 && !isOverridingFreeformHack(context, false)
1705 && !isChromeOs(context);
1708 public static void startForegroundService(Context context, Intent intent) {
1709 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1710 if(Settings.canDrawOverlays(context))
1711 context.startForegroundService(intent);
1713 context.startService(intent);
1716 public static int getOverlayType() {
1717 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
1718 ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
1719 : WindowManager.LayoutParams.TYPE_PHONE;
1722 public static boolean isDelegatingHomeActivity(Context context) {
1723 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1724 homeIntent.addCategory(Intent.CATEGORY_HOME);
1726 final List<ResolveInfo> listOfLaunchers = context.getPackageManager().queryIntentActivities(homeIntent, 0);
1727 for(ResolveInfo launcher : listOfLaunchers) {
1728 if(launcher.activityInfo.packageName.equals(BuildConfig.SUPPORT_APPLICATION_ID))
1735 @SuppressLint("PrivateApi")
1736 private static String getSystemProperty(String key) {
1738 Class<?> cls = Class.forName("android.os.SystemProperties");
1739 return cls.getMethod("get", String.class).invoke(null, key).toString();
1740 } catch (Exception e) {
1745 @SuppressWarnings("ResultOfMethodCallIgnored")
1746 public static boolean importCustomStartButtonImage(Context context, Uri uri) {
1748 File imagesDir = new File(context.getFilesDir(), "tb_images");
1751 File importedFile = new File(imagesDir, "custom_image_new");
1752 if(importedFile.exists()) importedFile.delete();
1754 BufferedInputStream is = new BufferedInputStream(context.getContentResolver().openInputStream(uri));
1755 byte[] data = new byte[is.available()];
1757 if(data.length > 0) {
1758 BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(importedFile));
1765 File prevFile = new File(imagesDir, "custom_image");
1766 if(prevFile.exists()) prevFile.delete();
1768 importedFile.renameTo(prevFile);
1770 } catch (IOException e) {
1775 public static String getDefaultStartButtonImage(Context context) {
1776 SharedPreferences pref = getSharedPreferences(context);
1777 return pref.getBoolean("app_drawer_icon", false)
1782 private static boolean shouldLaunchTouchAbsorber(Context context) {
1783 return isOverridingFreeformHack(context) && !isChromeOs(context) && getCurrentApiVersion() < 29.0f;
1786 public static boolean isDesktopIconsEnabled(Context context) {
1787 return !canBootToFreeform(context, false) && !shouldLaunchTouchAbsorber(context);
1790 public static boolean isSystemTrayEnabled(Context context) {
1791 SharedPreferences pref = getSharedPreferences(context);
1793 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1794 && pref.getBoolean("sys_tray", context.getResources().getBoolean(R.bool.tb_def_sys_tray))
1795 && pref.getBoolean("full_length", context.getResources().getBoolean(R.bool.tb_def_full_length))
1796 && !getTaskbarPosition(context).contains("vertical");
1799 @SuppressWarnings("deprecation")
1800 public static boolean isLibrary(Context context) {
1801 return !context.getPackageName().equals(BuildConfig.APPLICATION_ID);
1804 public static boolean applyDisplayCutoutModeTo(WindowManager.LayoutParams params) {
1805 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
1806 params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
1813 private static boolean hasAndroidTVSettings(Context context) {
1814 return getInstalledPackage(context, "com.android.tv.settings") != null
1815 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
1818 public static void restartApp(Activity activity, boolean shouldFade) {
1819 Intent restartIntent = new Intent(activity, MainActivity.class);
1820 restartIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
1821 activity.startActivity(restartIntent);
1823 activity.overridePendingTransition(
1824 shouldFade ? android.R.anim.fade_in : 0,
1825 shouldFade ? android.R.anim.fade_out : 0
1831 public static boolean isDesktopModeSupported(Context context) {
1832 if(isLauncherPermanentlyEnabled(context)
1833 || isLibrary(context)
1834 || !BuildConfig.DEBUG // TODO remove this line
1835 || isChromeOs(context))
1838 return Build.VERSION.SDK_INT > Build.VERSION_CODES.P
1839 && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
1842 public static boolean isDesktopModeActive(Context context) {
1843 if(!isDesktopModeSupported(context)) return false;
1845 boolean desktopModePrefEnabled;
1848 desktopModePrefEnabled = Settings.Global.getInt(context.getContentResolver(), "force_desktop_mode_on_external_displays") == 1;
1849 } catch (Settings.SettingNotFoundException e) {
1850 desktopModePrefEnabled = false;
1853 return desktopModePrefEnabled && getExternalDisplayID(context) != Display.DEFAULT_DISPLAY;
1856 public static boolean shouldStartDesktopMode(Context context) {
1857 SharedPreferences pref = getSharedPreferences(context);
1859 return isDesktopModeSupported(context)
1860 && pref.getBoolean("desktop_mode", false)
1861 && !pref.getBoolean("launcher", false);
1864 // TODO remove this in favor of the existing getDisplayID method?
1865 public static int getExternalDisplayID(Context context) {
1866 DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1867 Display[] displays = dm.getDisplays();
1869 return displays[displays.length - 1].getDisplayId();
1872 @SuppressLint("PrivateApi")
1873 private static Object getWindowManagerService() throws Exception {
1874 return Class.forName("android.view.WindowManagerGlobal")
1875 .getMethod("getWindowManagerService")
1879 @SuppressLint("PrivateApi")
1880 private static void setDensity(int displayID, String value) throws Exception {
1881 // From android.os.UserHandle
1882 final int USER_CURRENT_OR_SELF = -3;
1884 if(value.equals("reset")) {
1885 Class.forName("android.view.IWindowManager")
1886 .getMethod("clearForcedDisplayDensityForUser", int.class, int.class)
1887 .invoke(getWindowManagerService(), displayID, USER_CURRENT_OR_SELF);
1889 int density = Integer.parseInt(value);
1891 Class.forName("android.view.IWindowManager")
1892 .getMethod("setForcedDisplayDensityForUser", int.class, int.class, int.class)
1893 .invoke(getWindowManagerService(), displayID, density, USER_CURRENT_OR_SELF);
1897 @SuppressLint("PrivateApi")
1898 private static void setOverscan(int displayID, int value) throws Exception {
1899 Class.forName("android.view.IWindowManager")
1900 .getMethod("setOverscan", int.class, int.class, int.class, int.class, int.class)
1901 .invoke(getWindowManagerService(), displayID, 0, 0, 0, value);
1904 public static void registerReceiver(Context context, BroadcastReceiver receiver, String... actions) {
1905 unregisterReceiver(context, receiver);
1907 IntentFilter intentFilter = new IntentFilter();
1908 for(String action : actions) {
1909 intentFilter.addAction(action);
1912 LocalBroadcastManager.getInstance(context).registerReceiver(receiver, intentFilter);
1915 public static void unregisterReceiver(Context context, BroadcastReceiver receiver) {
1916 LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver);
1919 public static void sendBroadcast(Context context, String action) {
1920 sendBroadcast(context, new Intent(action));
1923 public static void sendBroadcast(Context context, Intent intent) {
1924 LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
1927 @SuppressWarnings("rawtypes")
1928 public static void setComponentEnabled(Context context, Class clazz, boolean enabled) {
1929 ComponentName component = new ComponentName(context, clazz);
1930 context.getPackageManager().setComponentEnabledSetting(component,
1931 enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
1932 : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
1933 PackageManager.DONT_KILL_APP);