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.Bitmap;
47 import android.graphics.BitmapFactory;
48 import android.graphics.Canvas;
49 import android.graphics.Color;
50 import android.graphics.Rect;
51 import android.graphics.drawable.BitmapDrawable;
52 import android.graphics.drawable.Drawable;
53 import android.hardware.display.DisplayManager;
54 import android.net.Uri;
55 import android.os.Build;
56 import android.os.Bundle;
57 import android.os.Handler;
58 import android.os.Looper;
59 import android.os.Process;
60 import android.os.UserHandle;
61 import android.os.UserManager;
62 import android.provider.Settings;
64 import androidx.annotation.DimenRes;
65 import androidx.annotation.VisibleForTesting;
66 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
67 import androidx.appcompat.view.ContextThemeWrapper;
68 import android.util.DisplayMetrics;
69 import android.view.Display;
70 import android.view.View;
71 import android.view.WindowManager;
72 import android.widget.ImageView;
73 import android.widget.Toast;
75 import com.farmerbb.taskbar.BuildConfig;
76 import com.farmerbb.taskbar.R;
77 import com.farmerbb.taskbar.activity.ContextMenuActivity;
78 import com.farmerbb.taskbar.activity.DummyActivity;
79 import com.farmerbb.taskbar.activity.InvisibleActivityFreeform;
80 import com.farmerbb.taskbar.activity.MainActivity;
81 import com.farmerbb.taskbar.activity.TouchAbsorberActivity;
82 import com.farmerbb.taskbar.helper.GlobalHelper;
83 import com.farmerbb.taskbar.helper.FreeformHackHelper;
84 import com.farmerbb.taskbar.helper.LauncherHelper;
85 import com.farmerbb.taskbar.helper.MenuHelper;
86 import com.farmerbb.taskbar.helper.ToastHelper;
87 import com.farmerbb.taskbar.service.DashboardService;
88 import com.farmerbb.taskbar.service.NotificationService;
89 import com.farmerbb.taskbar.service.PowerMenuService;
90 import com.farmerbb.taskbar.service.StartMenuService;
91 import com.farmerbb.taskbar.service.TaskbarService;
93 import java.io.BufferedInputStream;
94 import java.io.BufferedOutputStream;
96 import java.io.FileOutputStream;
97 import java.io.IOException;
98 import java.lang.reflect.Constructor;
99 import java.lang.reflect.Field;
100 import java.lang.reflect.Method;
101 import java.util.ArrayList;
102 import java.util.Arrays;
103 import java.util.List;
104 import java.util.Map;
106 import static com.farmerbb.taskbar.util.Constants.*;
112 private static final int MAXIMIZED = 0;
113 private static final int LEFT = -1;
114 private static final int RIGHT = 1;
116 public static final int HIDDEN = 0;
117 public static final int TOP_APPS = 1;
119 // From android.app.ActivityManager.StackId
120 private static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
121 private static final int FREEFORM_WORKSPACE_STACK_ID = 2;
123 // From android.app.WindowConfiguration
124 private static final int WINDOWING_MODE_FULLSCREEN = 1;
125 private static final int WINDOWING_MODE_FREEFORM = 5;
127 public static final int EXPORT = 123;
128 public static final int IMPORT = 456;
130 public static final int IMAGE_REQUEST_CODE = 1001;
132 public static SharedPreferences getSharedPreferences(Context context) {
133 return context.getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", Context.MODE_PRIVATE);
136 public static void showPermissionDialog(Context context) {
137 Callbacks callbacks = new Callbacks();
138 callbacks.onError = () -> showErrorDialog(context, "SYSTEM_ALERT_WINDOW", callbacks);
140 showPermissionDialog(context, callbacks);
143 public static AlertDialog showPermissionDialog(Context context, Callbacks callbacks) {
144 AlertDialog.Builder builder;
145 if(hasAndroidTVSettings(context))
146 builder = buildPermissionDialogAndroidTV(context, callbacks);
148 builder = buildPermissionDialogStandard(context, callbacks);
150 AlertDialog dialog = builder.create();
152 dialog.setCancelable(false);
157 @TargetApi(Build.VERSION_CODES.M)
158 private static AlertDialog.Builder buildPermissionDialogStandard(Context context, Callbacks callbacks) {
159 String message = context.getString(R.string.tb_permission_dialog_message, getAppName(context))
160 + context.getString(R.string.tb_permission_dialog_instructions_phone);
162 return new AlertDialog.Builder(context)
163 .setTitle(R.string.tb_permission_dialog_title)
165 .setPositiveButton(R.string.tb_action_grant_permission, (dialog, which) -> {
167 Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context.getPackageName()));
168 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
169 context.startActivity(intent);
171 callbacks.onFinish.run();
172 } catch (ActivityNotFoundException e) {
173 callbacks.onError.run();
178 private static AlertDialog.Builder buildPermissionDialogAndroidTV(Context context, Callbacks callbacks) {
179 String message = context.getString(R.string.tb_permission_dialog_message, getAppName(context))
180 + context.getString(R.string.tb_permission_dialog_instructions_tv);
182 return new AlertDialog.Builder(context)
183 .setTitle(R.string.tb_permission_dialog_title)
185 .setPositiveButton(R.string.tb_action_open_settings, (dialog, which) -> {
187 Intent intent = new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS);
188 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
189 context.startActivity(intent);
191 callbacks.onFinish.run();
192 } catch (ActivityNotFoundException e) {
193 callbacks.onError.run();
198 public static AlertDialog showErrorDialog(Context context, String appopCmd) {
199 return showErrorDialog(context, appopCmd, new Callbacks());
202 private static AlertDialog showErrorDialog(Context context, String appopCmd, Callbacks callbacks) {
203 AlertDialog.Builder builder = new AlertDialog.Builder(context);
204 builder.setTitle(R.string.tb_error_dialog_title)
205 .setMessage(context.getString(R.string.tb_error_dialog_message, context.getPackageName(), appopCmd))
206 .setPositiveButton(R.string.tb_action_ok, (dialog, which) -> callbacks.onFinish.run());
208 AlertDialog dialog = builder.create();
210 dialog.setCancelable(false);
215 @TargetApi(Build.VERSION_CODES.P)
216 public static void lockDevice(Context context) {
217 sendAccessibilityAction(context, AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN);
220 public static void sendAccessibilityAction(Context context, int action) {
221 sendAccessibilityAction(context, action, null);
224 public static void sendAccessibilityAction(Context context, int action, Runnable onComplete) {
225 setComponentEnabled(context, PowerMenuService.class, true);
227 boolean isAccessibilityServiceEnabled = isAccessibilityServiceEnabled(context);
229 if(!isAccessibilityServiceEnabled
230 && hasWriteSecureSettingsPermission(context)) {
231 String services = Settings.Secure.getString(context.getContentResolver(),
232 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
234 String finalServices = services == null ? "" : services;
236 String powerMenuService = new ComponentName(context, PowerMenuService.class).flattenToString();
238 if(!finalServices.contains(powerMenuService)) {
240 Settings.Secure.putString(context.getContentResolver(),
241 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
242 finalServices.isEmpty()
244 : finalServices + ":" + powerMenuService);
245 } catch (Exception ignored) {}
248 newHandler().postDelayed(() -> {
249 Intent intent = new Intent(ACTION_ACCESSIBILITY_ACTION);
250 intent.putExtra(EXTRA_ACTION, action);
251 sendBroadcast(context, intent);
254 Settings.Secure.putString(context.getContentResolver(),
255 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
257 } catch (Exception ignored) {}
259 if(onComplete != null) onComplete.run();
261 } else if(isAccessibilityServiceEnabled) {
262 Intent intent = new Intent(ACTION_ACCESSIBILITY_ACTION);
263 intent.putExtra(EXTRA_ACTION, action);
264 sendBroadcast(context, intent);
266 if(onComplete != null) onComplete.run();
268 launchApp(context, () -> {
269 Intent intent = new Intent(context, DummyActivity.class);
270 intent.putExtra("accessibility", true);
271 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
274 context.startActivity(intent, getActivityOptionsBundle(context, ApplicationType.APP_PORTRAIT, null));
275 } catch (IllegalArgumentException | SecurityException ignored) {}
280 public static boolean isAccessibilityServiceEnabled(Context context) {
281 String accessibilityServices = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
282 ComponentName component = new ComponentName(context, PowerMenuService.class);
284 return accessibilityServices != null
285 && (accessibilityServices.contains(component.flattenToString())
286 || accessibilityServices.contains(component.flattenToShortString()));
289 public static boolean hasWriteSecureSettingsPermission(Context context) {
290 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
291 && context.checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) == PackageManager.PERMISSION_GRANTED;
294 public static void showToast(Context context, int message) {
295 showToast(context, context.getString(message), Toast.LENGTH_SHORT);
298 public static void showToastLong(Context context, int message) {
299 showToast(context, context.getString(message), Toast.LENGTH_LONG);
302 public static void showToast(Context context, String message, int length) {
305 ToastInterface toast = DependencyUtils.createToast(getDisplayContext(context), message, length);
308 ToastHelper.getInstance().setLastToast(toast);
311 public static void cancelToast() {
312 ToastInterface toast = ToastHelper.getInstance().getLastToast();
313 if(toast != null) toast.cancel();
316 public static void startShortcut(Context context, AppEntry entry, ShortcutInfo shortcut, View view) {
328 public static void launchApp(final Context context,
329 final AppEntry entry,
330 final String windowSize,
331 final boolean launchedFromTaskbar,
332 final boolean openInNewWindow,
345 // Used for launching Persistent Shortcuts via the home screen or quick settings
346 public static void launchApp(final Context context,
347 final AppEntry entry,
348 final String windowSize,
349 final Runnable onError) {
361 private static void launchApp(final Context context,
362 final AppEntry entry,
363 final String windowSize,
364 final boolean launchedFromTaskbar,
365 final boolean isPersistentShortcut,
366 final boolean openInNewWindow,
367 final ShortcutInfo shortcut,
369 final Runnable onError) {
370 launchApp(context, launchedFromTaskbar, isPersistentShortcut, () ->
371 continueLaunchingApp(context, entry, windowSize, openInNewWindow, shortcut, view, onError)
375 public static void launchApp(Context context, Runnable runnable) {
376 launchApp(context, true, false, runnable);
379 private static void launchApp(Context context, boolean launchedFromTaskbar, boolean isPersistentShortcut, Runnable runnable) {
380 SharedPreferences pref = getSharedPreferences(context);
381 FreeformHackHelper helper = FreeformHackHelper.getInstance();
383 boolean specialLaunch = hasBrokenSetLaunchBoundsApi()
384 && helper.isInFreeformWorkspace()
385 && MenuHelper.getInstance().isContextMenuOpen();
387 boolean noAnimation = pref.getBoolean(PREF_DISABLE_ANIMATIONS, false);
388 boolean isAndroidR = getCurrentApiVersion() > 29.0;
390 if(hasFreeformSupport(context)
391 && (isFreeformModeEnabled(context) || isPersistentShortcut)
392 && (!helper.isInFreeformWorkspace() || specialLaunch)) {
393 newHandler().postDelayed(() -> {
394 startFreeformHack(context, true);
396 newHandler().postDelayed(runnable, helper.isFreeformHackActive() ? 0 : isAndroidR ? 300 : 100);
397 }, launchedFromTaskbar ? 0 : 100);
399 newHandler().postDelayed(runnable, !launchedFromTaskbar && noAnimation ? 100 : isAndroidR ? 100 : 0);
402 public static void startFreeformHack(Context context) {
403 startFreeformHack(context, false);
406 @TargetApi(Build.VERSION_CODES.N)
407 public static void startFreeformHack(Context context, boolean checkMultiWindow) {
408 Intent freeformHackIntent = new Intent(context, InvisibleActivityFreeform.class);
409 freeformHackIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
410 | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
411 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
414 freeformHackIntent.putExtra("check_multiwindow", true);
416 if(canDrawOverlays(context))
417 startActivityLowerRight(context, freeformHackIntent);
420 public static void stopFreeformHack(Context context) {
421 sendBroadcast(context, ACTION_FINISH_FREEFORM_ACTIVITY);
423 if(isOverridingFreeformHack(context, false)) {
424 FreeformHackHelper helper = FreeformHackHelper.getInstance();
425 helper.setFreeformHackActive(false);
426 helper.setInFreeformWorkspace(false);
430 @TargetApi(Build.VERSION_CODES.N)
431 private static void continueLaunchingApp(Context context,
434 boolean openInNewWindow,
435 ShortcutInfo shortcut,
438 SharedPreferences pref = getSharedPreferences(context);
439 Intent intent = new Intent();
440 intent.setComponent(ComponentName.unflattenFromString(entry.getComponentName()));
441 intent.setAction(Intent.ACTION_MAIN);
442 intent.addCategory(Intent.CATEGORY_LAUNCHER);
443 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
444 intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
446 if(FreeformHackHelper.getInstance().isInFreeformWorkspace()
447 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1)
448 intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
450 if(pref.getBoolean(PREF_DISABLE_ANIMATIONS, false))
451 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
453 boolean realOpenInNewWindow = openInNewWindow || pref.getBoolean(PREF_FORCE_NEW_WINDOW, false);
454 if(realOpenInNewWindow) applyOpenInNewWindow(context, intent);
456 ApplicationType type = getApplicationType(context, entry);
458 if(windowSize == null)
459 windowSize = SavedWindowSizes.getInstance(context).getWindowSize(context, entry.getPackageName());
461 Bundle bundle = getActivityOptionsBundle(context, type, windowSize, view);
463 prepareToStartActivity(context, realOpenInNewWindow, () -> {
464 if(shortcut == null) {
465 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
466 if(entry.getUserId(context) == userManager.getSerialNumberForUser(Process.myUserHandle())) {
468 context.startActivity(intent, bundle);
469 } catch (ActivityNotFoundException e) {
470 launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context), onError);
471 } catch (IllegalArgumentException | SecurityException ignored) {}
473 launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context), onError);
475 launchShortcut(context, shortcut, bundle, onError);
478 if(shouldCollapse(context, true)) {
479 sendBroadcast(context, ACTION_HIDE_TASKBAR);
481 sendBroadcast(context, ACTION_HIDE_START_MENU);
485 private static Bundle launchMode1(Context context, ApplicationType type, View view, int factor) {
486 DisplayInfo display = getDisplayInfo(context);
488 int width1 = display.width / factor;
489 int width2 = display.width - width1;
490 int height1 = display.height / factor;
491 int height2 = display.height - height1;
493 return getActivityOptionsBundle(context, type, view,
501 private static Bundle launchMode2(Context context,
503 ApplicationType type,
505 DisplayInfo display = getDisplayInfo(context);
507 int statusBarHeight = getStatusBarHeight(context);
508 String position = TaskbarPosition.getTaskbarPosition(context);
510 int orientation = getDisplayOrientation(context);
511 boolean isPortrait = orientation == Configuration.ORIENTATION_PORTRAIT;
512 boolean isLandscape = orientation == Configuration.ORIENTATION_LANDSCAPE;
515 int top = statusBarHeight;
516 int right = display.width;
517 int bottom = display.height;
519 int iconSize = isOverridingFreeformHack(context) && !LauncherHelper.getInstance().isOnHomeScreen(context)
520 ? 0 : context.getResources().getDimensionPixelSize(R.dimen.tb_icon_size);
522 if(TaskbarPosition.isVerticalLeft(position))
523 left = left + iconSize;
524 else if(TaskbarPosition.isVerticalRight(position))
525 right = right - iconSize;
526 else if(TaskbarPosition.isBottom(position))
527 bottom = bottom - iconSize;
529 top = top + iconSize;
533 + ((iconSize / 2) * (TaskbarPosition.isVerticalLeft(position) ? 1 : 0));
534 boolean isTopLeft = POSITION_TOP_LEFT.equals(position);
535 boolean isTopRight = POSITION_TOP_RIGHT.equals(position);
537 (bottom / 2) + ((iconSize / 2) * ((isTopLeft || isTopRight) ? 1 : 0));
539 if(launchType == RIGHT && isLandscape)
540 left = halfLandscape;
541 else if(launchType == RIGHT && isPortrait)
543 else if(launchType == LEFT && isLandscape)
544 right = halfLandscape;
545 else if(launchType == LEFT && isPortrait)
546 bottom = halfPortrait;
548 return getActivityOptionsBundle(context, type, view, left, top, right, bottom);
551 private static Bundle launchMode3(Context context, ApplicationType type, View view) {
552 DisplayInfo display = getDisplayInfo(context);
554 boolean isLandscape = type == ApplicationType.APP_LANDSCAPE;
555 int widthDimen = isLandscape ? R.dimen.tb_phone_size_height : R.dimen.tb_phone_size_width;
556 int heightDimen = isLandscape ? R.dimen.tb_phone_size_width : R.dimen.tb_phone_size_height;
558 int width1 = display.width / 2;
559 int width2 = context.getResources().getDimensionPixelSize(widthDimen) / 2;
560 int height1 = display.height / 2;
561 int height2 = context.getResources().getDimensionPixelSize(heightDimen) / 2;
563 return getActivityOptionsBundle(context, type, view,
571 private static void launchAndroidForWork(Context context, ComponentName componentName, Bundle bundle, long userId, Runnable onError) {
572 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
573 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
576 launcherApps.startMainActivity(componentName, userManager.getUserForSerialNumber(userId), null, bundle);
577 } catch (ActivityNotFoundException | NullPointerException
578 | IllegalStateException | SecurityException e) {
579 if(onError != null) launchApp(context, onError);
583 @TargetApi(Build.VERSION_CODES.N_MR1)
584 private static void launchShortcut(Context context, ShortcutInfo shortcut, Bundle bundle, Runnable onError) {
585 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
587 if(launcherApps.hasShortcutHostPermission()) {
589 launcherApps.startShortcut(shortcut, null, bundle);
590 } catch (ActivityNotFoundException | NullPointerException
591 | IllegalStateException | SecurityException e) {
592 if(onError != null) launchApp(context, onError);
597 private static void prepareToStartActivity(Context context, boolean openInNewWindow, Runnable runnable) {
598 sendBroadcast(context, ACTION_HIDE_CONTEXT_MENU);
600 if(!FreeformHackHelper.getInstance().isTouchAbsorberActive()
601 && shouldLaunchTouchAbsorber(context)) {
602 startTouchAbsorberActivity(context);
603 newHandler().postDelayed(runnable, 100);
604 } else if(openInNewWindow) {
605 Intent intent = new Intent(context, DummyActivity.class);
606 intent.putExtra("finish_on_pause", true);
607 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
608 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
609 startActivityLowerRight(context, intent);
611 newHandler().postDelayed(runnable, 100);
616 public static void startActivityMaximized(Context context, Intent intent) {
617 Bundle bundle = launchMode2(context, MAXIMIZED, ApplicationType.CONTEXT_MENU, null);
618 prepareToStartActivity(context, false, () -> context.startActivity(intent, bundle));
621 public static void startActivityLowerRight(Context context, Intent intent) {
622 DisplayInfo display = getDisplayInfo(context);
624 context.startActivity(intent,
625 getActivityOptionsBundle(context, ApplicationType.FREEFORM_HACK, null,
631 } catch (IllegalArgumentException | SecurityException ignored) {}
634 public static void startTouchAbsorberActivity(Context context) {
635 String position = TaskbarPosition.getTaskbarPosition(context);
636 DisplayInfo display = getDisplayInfo(context);
640 int right = display.width;
641 int bottom = display.height;
643 int iconSize = context.getResources().getDimensionPixelSize(R.dimen.tb_icon_size);
645 if(TaskbarPosition.isVerticalLeft(position))
647 else if(TaskbarPosition.isVerticalRight(position))
648 left = right - iconSize;
649 else if(TaskbarPosition.isBottom(position))
650 top = bottom - iconSize;
654 Intent intent = new Intent(context, TouchAbsorberActivity.class);
655 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
656 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
659 context.startActivity(intent,
660 getActivityOptionsBundle(context, ApplicationType.FREEFORM_HACK, null,
661 left, top, right, bottom));
662 } catch (IllegalArgumentException | SecurityException ignored) {}
665 public static void startContextMenuActivity(Context context, Bundle args) {
666 Intent intent = getThemedIntent(context, ContextMenuActivity.class);
667 intent.putExtra("args", args);
668 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
670 if(hasFreeformSupport(context) && FreeformHackHelper.getInstance().isInFreeformWorkspace()) {
671 DisplayInfo display = getDisplayInfo(context);
673 if(hasBrokenSetLaunchBoundsApi()) {
674 intent.putExtra(EXTRA_CONTEXT_MENU_FIX, true);
677 context.startActivity(intent,
678 getActivityOptionsBundle(context, ApplicationType.CONTEXT_MENU, null,
679 0, 0, display.width, display.height));
681 context.startActivity(intent);
684 public static void checkForUpdates(Context context) {
686 if(isPlayStoreRelease(context)) {
687 if(context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
688 && !isPlayStoreInstalled(context))
689 url = "https://github.com/farmerbb/Taskbar/releases";
691 url = "https://play.google.com/store/apps/details?id=" + context.getPackageName();
693 url = "https://f-droid.org/packages/" + context.getPackageName();
695 Intent intent = new Intent(Intent.ACTION_VIEW);
696 intent.setData(Uri.parse(url));
697 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
700 context.startActivity(intent);
701 } catch (ActivityNotFoundException ignored) {}
704 public static boolean launcherIsDefault(Context context) {
705 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
706 homeIntent.addCategory(Intent.CATEGORY_HOME);
707 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
710 return defaultLauncher.activityInfo.packageName.equals(context.getPackageName());
711 } catch (NullPointerException e) {
716 private static int getMaxNumOfColumns(Context context) {
717 SharedPreferences pref = getSharedPreferences(context);
718 DisplayInfo display = getDisplayInfo(context);
719 float density = display.currentDensity / 160.0f;
720 float baseTaskbarSize = getBaseTaskbarSize(context) / density;
721 int numOfColumns = 0;
723 float maxScreenSize = TaskbarPosition.isVertical(context)
724 ? (display.height - getStatusBarHeight(context)) / density
725 : display.width / density;
727 float iconSize = context.getResources().getDimension(R.dimen.tb_icon_size) / density;
729 int userMaxNumOfColumns = Integer.parseInt(pref.getString(PREF_MAX_NUM_OF_RECENTS, "10"));
731 while(baseTaskbarSize + iconSize < maxScreenSize
732 && numOfColumns < userMaxNumOfColumns) {
733 baseTaskbarSize = baseTaskbarSize + iconSize;
740 public static int getMaxNumOfEntries(Context context) {
741 SharedPreferences pref = getSharedPreferences(context);
742 return pref.getBoolean(PREF_DISABLE_SCROLLING_LIST, false)
743 ? getMaxNumOfColumns(context)
744 : Integer.parseInt(pref.getString(PREF_MAX_NUM_OF_RECENTS, "10"));
747 public static int getStatusBarHeight(Context context) {
748 return LauncherHelper.getInstance().isOnSecondaryHomeScreen(context)
749 ? 0 : getSystemDimen(context, "status_bar_height");
752 public static int getNavbarHeight(Context context) {
753 SharedPreferences pref = getSharedPreferences(context);
754 boolean isNavbarHidden = isShowHideNavbarSupported()
755 && LauncherHelper.getInstance().isOnSecondaryHomeScreen(context)
756 && pref.getBoolean(PREF_AUTO_HIDE_NAVBAR_DESKTOP_MODE, false);
758 return isNavbarHidden ? 0 : getSystemDimen(context, "navigation_bar_height");
761 private static int getSystemDimen(Context context, String id) {
762 context = getDisplayContext(context);
765 int resourceId = context.getResources().getIdentifier(id, "dimen", "android");
767 value = context.getResources().getDimensionPixelSize(resourceId);
772 public static void refreshPinnedIcons(Context context) {
773 IconCache.getInstance(context).clearCache();
775 PinnedBlockedApps pba = PinnedBlockedApps.getInstance(context);
776 List<AppEntry> pinnedAppsList = new ArrayList<>(pba.getPinnedApps());
777 List<AppEntry> blockedAppsList = new ArrayList<>(pba.getBlockedApps());
778 PackageManager pm = context.getPackageManager();
782 for(AppEntry entry : pinnedAppsList) {
783 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
784 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
786 final List<UserHandle> userHandles = userManager.getUserProfiles();
787 LauncherActivityInfo appInfo = null;
789 for(UserHandle handle : userHandles) {
790 List<LauncherActivityInfo> list = launcherApps.getActivityList(entry.getPackageName(), handle);
791 if(!list.isEmpty()) {
792 // Google App workaround
793 if(!entry.getPackageName().equals("com.google.android.googlequicksearchbox"))
794 appInfo = list.get(0);
796 boolean added = false;
797 for(LauncherActivityInfo info : list) {
798 if(info.getName().equals("com.google.android.googlequicksearchbox.SearchActivity")) {
804 if(!added) appInfo = list.get(0);
811 if(appInfo != null) {
812 AppEntry newEntry = new AppEntry(
813 entry.getPackageName(),
814 entry.getComponentName(),
816 IconCache.getInstance(context).getIcon(context, pm, appInfo),
819 newEntry.setUserId(entry.getUserId(context));
820 pba.addPinnedApp(context, newEntry);
824 for(AppEntry entry : blockedAppsList) {
825 pba.addBlockedApp(context, entry);
829 public static boolean canEnableFreeform() {
830 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
833 @TargetApi(Build.VERSION_CODES.N)
834 public static boolean hasFreeformSupport(Context context) {
835 return canEnableFreeform()
836 && (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
837 || Settings.Global.getInt(context.getContentResolver(), "enable_freeform_support", 0) != 0
838 || (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
839 && Settings.Global.getInt(context.getContentResolver(), "force_resizable_activities", 0) != 0));
842 public static boolean canBootToFreeform(Context context) {
843 return canBootToFreeform(context, true);
846 private static boolean canBootToFreeform(Context context, boolean checkPref) {
847 return hasFreeformSupport(context) && !isOverridingFreeformHack(context, checkPref);
850 public static boolean isSamsungDevice() {
851 return Build.MANUFACTURER.equalsIgnoreCase("Samsung");
854 private static boolean isNvidiaDevice() {
855 return Build.MANUFACTURER.equalsIgnoreCase("NVIDIA");
858 public static boolean isServiceRunning(Context context, Class<? extends Service> cls) {
859 if(LauncherHelper.getInstance().isOnSecondaryHomeScreen(context)
860 && (cls.equals(TaskbarService.class)
861 || cls.equals(StartMenuService.class)
862 || cls.equals(DashboardService.class)))
865 return isServiceRunning(context, cls.getName());
868 private static boolean isServiceRunning(Context context, String className) {
869 ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
870 for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
871 if(className.equals(service.service.getClassName()))
878 public static int getBackgroundTint(Context context) {
879 SharedPreferences pref = getSharedPreferences(context);
881 // Import old background tint preference
882 if(pref.contains(PREF_SHOW_BACKGROUND)) {
883 SharedPreferences.Editor editor = pref.edit();
885 if(!pref.getBoolean(PREF_SHOW_BACKGROUND, true))
886 editor.putInt(PREF_BACKGROUND_TINT, Color.TRANSPARENT).apply();
888 editor.remove(PREF_SHOW_BACKGROUND);
892 return pref.getInt(PREF_BACKGROUND_TINT, context.getResources().getInteger(R.integer.tb_translucent_gray));
895 public static int getAccentColor(Context context) {
896 SharedPreferences pref = getSharedPreferences(context);
897 return pref.getInt(PREF_ACCENT_COLOR, context.getResources().getInteger(R.integer.tb_translucent_white));
900 public static boolean canDrawOverlays(Context context) {
901 return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context);
904 public static boolean isGame(Context context, String packageName) {
905 SharedPreferences pref = getSharedPreferences(context);
906 if(pref.getBoolean(PREF_LAUNCH_GAMES_FULLSCREEN, true)) {
907 PackageManager pm = context.getPackageManager();
910 ApplicationInfo info = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
911 return (info.flags & ApplicationInfo.FLAG_IS_GAME) != 0 || (info.metaData != null && info.metaData.getBoolean("isGame", false));
912 } catch (PackageManager.NameNotFoundException e) {
919 private static ActivityOptions getActivityOptions(View view) {
920 return getActivityOptions(null, null, view);
923 public static ActivityOptions getActivityOptions(Context context, ApplicationType applicationType, View view) {
924 ActivityOptions options;
926 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
927 options = ActivityOptions.makeClipRevealAnimation(view, 0, 0, view.getWidth(), view.getHeight());
929 options = ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight());
930 } else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
931 options = ActivityOptions.makeBasic();
934 Constructor<ActivityOptions> constructor = ActivityOptions.class.getDeclaredConstructor();
935 constructor.setAccessible(true);
936 options = constructor.newInstance();
937 } catch (Exception e) {
942 if(applicationType == null)
947 switch(applicationType) {
950 if(FreeformHackHelper.getInstance().isFreeformHackActive())
951 stackId = getFreeformWindowModeId();
953 stackId = getFullscreenWindowModeId();
956 stackId = getFullscreenWindowModeId();
959 stackId = getFreeformWindowModeId();
962 if(hasBrokenSetLaunchBoundsApi()
963 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f)
964 || (isChromeOs(context) && getCurrentApiVersion() >= 30.0f))
965 stackId = getFullscreenWindowModeId();
972 Method method = ActivityOptions.class.getMethod(getWindowingModeMethodName(), int.class);
973 method.invoke(options, stackId);
974 } catch (Exception ignored) {}
977 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
978 int launchDisplayId = LauncherHelper.getInstance().getSecondaryDisplayId();
979 if(launchDisplayId != -1)
980 options.setLaunchDisplayId(launchDisplayId);
986 private static int getFullscreenWindowModeId() {
987 if(getCurrentApiVersion() >= 28.0f)
988 return WINDOWING_MODE_FULLSCREEN;
990 return FULLSCREEN_WORKSPACE_STACK_ID;
993 private static int getFreeformWindowModeId() {
994 if(getCurrentApiVersion() >= 28.0f)
995 return WINDOWING_MODE_FREEFORM;
997 return FREEFORM_WORKSPACE_STACK_ID;
1000 private static String getWindowingModeMethodName() {
1001 if(getCurrentApiVersion() >= 28.0f)
1002 return "setLaunchWindowingMode";
1004 return "setLaunchStackId";
1007 public static Bundle getActivityOptionsBundle(Context context, ApplicationType type, View view) {
1008 SharedPreferences pref = getSharedPreferences(context);
1009 return getActivityOptionsBundle(context, type, pref.getString(PREF_WINDOW_SIZE, "standard"), view);
1012 private static Bundle getActivityOptionsBundle(Context context, ApplicationType type, String windowSize, View view) {
1013 if(!canEnableFreeform() || !isFreeformModeEnabled(context))
1014 return getActivityOptions(view).toBundle();
1016 switch(windowSize) {
1018 if(getCurrentApiVersion() > 29.0f)
1019 return launchMode1(context, type, view, 4);
1022 return launchMode1(context, type, view, 8);
1024 return launchMode2(context, MAXIMIZED, type, view);
1026 return launchMode2(context, LEFT, type, view);
1028 return launchMode2(context, RIGHT, type, view);
1030 return launchMode3(context, type, view);
1033 return getActivityOptions(context, type, view).toBundle();
1036 private static Bundle getActivityOptionsBundle(Context context,
1037 ApplicationType applicationType,
1043 ActivityOptions options = getActivityOptions(context, applicationType, view);
1044 if(options == null) return null;
1046 if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
1047 return options.toBundle();
1049 return options.setLaunchBounds(new Rect(left, top, right, bottom)).toBundle();
1052 @SuppressLint("SwitchIntDef")
1053 private static ApplicationType getApplicationType(Context context, AppEntry entry) {
1054 if(isGame(context, entry.getPackageName()))
1055 return ApplicationType.APP_FULLSCREEN;
1058 ActivityInfo info = context.getPackageManager().getActivityInfo(
1059 ComponentName.unflattenFromString(entry.getComponentName()),
1063 switch(info.screenOrientation) {
1064 case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
1065 case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
1066 case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
1067 case ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE:
1068 return ApplicationType.APP_LANDSCAPE;
1070 case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
1071 case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:
1072 case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:
1073 case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT:
1074 return ApplicationType.APP_PORTRAIT;
1076 } catch (PackageManager.NameNotFoundException ignored) {}
1078 return context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
1079 ? ApplicationType.APP_LANDSCAPE
1080 : ApplicationType.APP_PORTRAIT;
1083 public static boolean isSystemApp(Context context) {
1085 ApplicationInfo info = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
1086 int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
1087 return (info.flags & mask) != 0;
1088 } catch (PackageManager.NameNotFoundException e) {
1093 public static boolean isChromeOs(Context context) {
1094 return context.getPackageManager().hasSystemFeature("org.chromium.arc");
1097 @TargetApi(Build.VERSION_CODES.O)
1098 public static boolean isAndroidTV(Context context) {
1099 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY);
1102 public static boolean isBlissOs(Context context) {
1103 boolean validBlissOsBuildProp = false;
1105 String blissVersion = getSystemProperty("ro.bliss.version");
1106 if(blissVersion != null && !blissVersion.isEmpty())
1107 validBlissOsBuildProp = true;
1109 return validBlissOsBuildProp
1110 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1111 && isSystemApp(context);
1114 public static boolean isAndroidGeneric(Context context) {
1115 if(isBlissOs(context)) return true;
1117 boolean validAndroidGenericBuildProp = false;
1119 String manufacturer = getSystemProperty("ro.ag.product.manufacturer");
1120 if(manufacturer != null && manufacturer.equals("Android-Generic"))
1121 validAndroidGenericBuildProp = true;
1123 return validAndroidGenericBuildProp
1124 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1125 && isSystemApp(context);
1128 public static boolean isLauncherPermanentlyEnabled(Context context) {
1129 if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
1132 return hasSupportLibrary(context, 0);
1135 public static boolean hasSupportLibrary(Context context, int minVersion) {
1136 PackageManager pm = context.getPackageManager();
1138 PackageInfo pInfo = pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1139 return pInfo.versionCode >= minVersion
1140 && pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, context.getPackageName()) == PackageManager.SIGNATURE_MATCH
1141 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1142 && isSystemApp(context);
1143 } catch (PackageManager.NameNotFoundException e) {
1149 public static float getBaseTaskbarSize(Context context) {
1150 return getBaseTaskbarSizeStart(context) + getBaseTaskbarSizeEnd(context, null);
1153 public static float getBaseTaskbarSizeStart(Context context) {
1154 SharedPreferences pref = getSharedPreferences(context);
1155 float baseTaskbarSize = context.getResources().getDimension(R.dimen.tb_base_size_start_plus_divider);
1157 baseTaskbarSize += pref.getBoolean(PREF_ALT_BUTTON_CONFIG, false)
1158 ? context.getResources().getDimension(R.dimen.tb_base_size_collapse_button) : 0;
1160 boolean navbarButtonsEnabled = false;
1162 if(getBooleanPrefWithDefault(context, PREF_DASHBOARD))
1163 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_dashboard_button_size);
1165 if(pref.getBoolean(PREF_BUTTON_BACK, false)) {
1166 navbarButtonsEnabled = true;
1167 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1170 if(pref.getBoolean(PREF_BUTTON_HOME, false)) {
1171 navbarButtonsEnabled = true;
1172 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1175 if(pref.getBoolean(PREF_BUTTON_RECENTS, false)) {
1176 navbarButtonsEnabled = true;
1177 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1180 if(navbarButtonsEnabled)
1181 baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_navbar_buttons_margin);
1183 return baseTaskbarSize;
1186 public static float getBaseTaskbarSizeEnd(Context context, Map<Integer, Boolean> sysTrayIconStates) {
1187 SharedPreferences pref = getSharedPreferences(context);
1188 float baseTaskbarSize = pref.getBoolean(PREF_ALT_BUTTON_CONFIG, false)
1189 ? 0 : context.getResources().getDimension(R.dimen.tb_base_size_collapse_button);
1191 if(isSystemTrayEnabled(context)) {
1192 float sysTraySize = context.getResources().getDimension(R.dimen.tb_systray_size);
1194 if(sysTrayIconStates != null) {
1195 for(Integer key : sysTrayIconStates.keySet()) {
1196 if(!sysTrayIconStates.get(key)) {
1197 sysTraySize -= context.getResources().getDimension(key == R.id.notification_count
1198 ? R.dimen.tb_systray_icon_size_notifications : R.dimen.tb_systray_icon_size);
1203 baseTaskbarSize += sysTraySize;
1206 return baseTaskbarSize;
1209 private static void startTaskbarService(Context context, boolean fullRestart) {
1210 context.startService(new Intent(context, TaskbarService.class));
1211 context.startService(new Intent(context, StartMenuService.class));
1212 context.startService(new Intent(context, DashboardService.class));
1213 if(fullRestart) context.startService(new Intent(context, NotificationService.class));
1216 private static void stopTaskbarService(Context context, boolean fullRestart) {
1217 context.stopService(new Intent(context, TaskbarService.class));
1218 context.stopService(new Intent(context, StartMenuService.class));
1219 context.stopService(new Intent(context, DashboardService.class));
1220 if(fullRestart) context.stopService(new Intent(context, NotificationService.class));
1223 public static void restartTaskbar(Context context) {
1224 SharedPreferences pref = getSharedPreferences(context);
1225 if(pref.getBoolean(PREF_TASKBAR_ACTIVE, false)
1226 && !pref.getBoolean(PREF_IS_HIDDEN, false)) {
1228 .putBoolean(PREF_IS_RESTARTING, true)
1229 .putBoolean(PREF_SKIP_AUTO_HIDE_NAVBAR, true)
1232 stopTaskbarService(context, true);
1233 startTaskbarService(context, true);
1234 } else if(isServiceRunning(context, StartMenuService.class)) {
1235 pref.edit().putBoolean(PREF_SKIP_AUTO_HIDE_NAVBAR, true).apply();
1237 stopTaskbarService(context, false);
1238 startTaskbarService(context, false);
1241 sendBroadcast(context, ACTION_RESTART);
1244 public static void restartNotificationService(Context context) {
1245 if(isServiceRunning(context, NotificationService.class)) {
1246 SharedPreferences pref = getSharedPreferences(context);
1247 pref.edit().putBoolean(PREF_IS_RESTARTING, true).apply();
1249 Intent intent = new Intent(context, NotificationService.class);
1250 context.stopService(intent);
1251 context.startService(intent);
1255 public static void showHideNavigationBar(Context context, boolean show) {
1256 showHideNavigationBar(context, getTaskbarDisplayID(context), show, 0);
1259 public static void showHideNavigationBar(Context context, int displayID, boolean show, int delay) {
1260 if(!isShowHideNavbarSupported()
1261 || (!isDesktopModeActive(context)
1262 && !isBlissOs(context)
1263 && !hasSupportLibrary(context, 7))) {
1267 Lazy<Integer> value = () -> show ? 0 : getSystemDimen(context, "navigation_bar_height") * -1;
1269 if(hasWriteSecureSettingsPermission(context)) {
1270 Runnable runnable = () -> {
1272 setOverscan(displayID, value.get());
1273 } catch (Exception ignored) {}
1279 newHandler().postDelayed(runnable, delay);
1284 if(hasSupportLibrary(context, 7)) {
1285 Intent intent = new Intent(BuildConfig.SUPPORT_APPLICATION_ID + ".CHANGE_OVERSCAN");
1286 intent.setPackage(BuildConfig.SUPPORT_APPLICATION_ID);
1288 intent.putExtra("display_id", displayID);
1289 intent.putExtra("value", value.get());
1291 context.sendBroadcast(intent);
1295 // Show or hide the system navigation bar on Bliss-x86
1296 if(!isBlissOs(context)) return;
1299 if(getCurrentApiVersion() >= 28.0f)
1300 Settings.Secure.putInt(context.getContentResolver(), "navigation_bar_visible", show ? 1 : 0);
1302 Settings.System.putInt(context.getContentResolver(), "navigation_bar_show", show ? 1 : 0);
1303 } catch (Exception ignored) {}
1306 public static boolean isShowHideNavbarSupported() {
1307 return getCurrentApiVersion() <= 29.0f;
1310 public static void initPrefs(Context context) {
1311 // Enable freeform hack automatically on supported devices
1312 SharedPreferences pref = getSharedPreferences(context);
1313 if(canEnableFreeform()) {
1314 if(!pref.getBoolean(PREF_FREEFORM_HACK_OVERRIDE, false)) {
1316 .putBoolean(PREF_FREEFORM_HACK, hasFreeformSupport(context) && !isSamsungDevice())
1317 .putBoolean(PREF_SAVE_WINDOW_SIZES, false)
1318 .putBoolean(PREF_FREEFORM_HACK_OVERRIDE, true)
1320 } else if(!hasFreeformSupport(context)) {
1321 pref.edit().putBoolean(PREF_FREEFORM_HACK, false).apply();
1323 stopFreeformHack(context);
1326 boolean freeformWasEnabled = isFreeformModeEnabled(context)
1327 || pref.getBoolean(PREF_SHOW_FREEFORM_DISABLED_MESSAGE, false);
1330 .putBoolean(PREF_FREEFORM_HACK, false)
1331 .putBoolean(PREF_SHOW_FREEFORM_DISABLED_MESSAGE, freeformWasEnabled)
1334 SavedWindowSizes.getInstance(context).clear(context);
1335 stopFreeformHack(context);
1338 // Customizations for BlissOS
1339 if(isAndroidGeneric(context) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1340 && !pref.getBoolean(PREF_BLISS_OS_PREFS, false)) {
1341 SharedPreferences.Editor editor = pref.edit();
1343 if(hasFreeformSupport(context)) {
1344 editor.putBoolean(PREF_FREEFORM_HACK, true);
1347 editor.putString(PREF_START_BUTTON_IMAGE, PREF_START_BUTTON_IMAGE_APP_LOGO);
1348 editor.putString(PREF_RECENTS_AMOUNT, PREF_RECENTS_AMOUNT_RUNNING_APPS_ONLY);
1349 editor.putString(PREF_REFRESH_FREQUENCY, "0");
1350 editor.putString(PREF_MAX_NUM_OF_RECENTS, "2147483647");
1351 editor.putString(PREF_SORT_ORDER, "true");
1352 editor.putBoolean(PREF_BUTTON_BACK, true);
1353 editor.putBoolean(PREF_BUTTON_HOME, true);
1354 editor.putBoolean(PREF_BUTTON_RECENTS, true);
1355 editor.putBoolean(PREF_AUTO_HIDE_NAVBAR, true);
1356 editor.putBoolean(PREF_SHORTCUT_ICON, false);
1357 editor.putBoolean(PREF_BLISS_OS_PREFS, true);
1361 // Customizations for Android-x86 devices (non-Bliss)
1362 if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
1363 && isSystemApp(context)
1364 && !pref.getBoolean(PREF_ANDROID_X86_PREFS, false)) {
1366 .putString(PREF_RECENTS_AMOUNT, PREF_RECENTS_AMOUNT_RUNNING_APPS_ONLY)
1367 .putString(PREF_REFRESH_FREQUENCY, "0")
1368 .putString(PREF_MAX_NUM_OF_RECENTS, "2147483647")
1369 .putString(PREF_SORT_ORDER, "true")
1370 .putBoolean(PREF_SHORTCUT_ICON, false)
1371 .putBoolean(PREF_ANDROID_X86_PREFS, true)
1376 public static DisplayInfo getDisplayInfo(Context context) {
1377 context = getDisplayContext(context);
1378 int displayID = getTaskbarDisplayID(context);
1380 DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1381 Display currentDisplay = null;
1383 for(Display display : dm.getDisplays()) {
1384 if(display.getDisplayId() == displayID) {
1385 currentDisplay = display;
1390 if(currentDisplay == null)
1391 return new DisplayInfo(0, 0, 0, 0, false);
1393 DisplayMetrics metrics = new DisplayMetrics();
1394 currentDisplay.getRealMetrics(metrics);
1396 boolean displayDefaultsToFreeform = displayDefaultsToFreeform(context, currentDisplay);
1397 return new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi, 0, displayDefaultsToFreeform);
1400 private static int getTaskbarDisplayID(Context context) {
1401 LauncherHelper helper = LauncherHelper.getInstance();
1403 if(helper.isOnSecondaryHomeScreen(context))
1404 return helper.getSecondaryDisplayId();
1406 return Display.DEFAULT_DISPLAY;
1409 public static void pinAppShortcut(Context context) {
1410 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1411 ShortcutManager mShortcutManager = context.getSystemService(ShortcutManager.class);
1413 if(mShortcutManager.isRequestPinShortcutSupported()) {
1414 ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder(context, "freeform_mode").build();
1416 mShortcutManager.requestPinShortcut(pinShortcutInfo, null);
1418 showToastLong(context, R.string.tb_pin_shortcut_not_supported);
1420 Intent intent = ShortcutUtils.getShortcutIntent(context);
1421 intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
1422 intent.putExtra("duplicate", false);
1424 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1425 homeIntent.addCategory(Intent.CATEGORY_HOME);
1426 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
1428 intent.setPackage(defaultLauncher.activityInfo.packageName);
1429 context.sendBroadcast(intent);
1431 showToast(context, R.string.tb_shortcut_created);
1435 public static boolean shouldCollapse(Context context, boolean pendingAppLaunch) {
1436 SharedPreferences pref = getSharedPreferences(context);
1437 if(pref.getBoolean(PREF_HIDE_TASKBAR, true)) {
1438 if(!isFreeformModeEnabled(context)
1439 || isOverridingFreeformHack(context, false))
1440 return !LauncherHelper.getInstance().isOnHomeScreen(context);
1442 FreeformHackHelper helper = FreeformHackHelper.getInstance();
1443 if(pendingAppLaunch)
1444 return !helper.isFreeformHackActive();
1446 return !helper.isInFreeformWorkspace();
1452 public static boolean isOverridingFreeformHack(Context context) {
1453 return isOverridingFreeformHack(context, true);
1456 public static boolean isOverridingFreeformHack(Context context, boolean checkPref) {
1457 SharedPreferences pref = getSharedPreferences(context);
1458 return (!checkPref || isFreeformModeEnabled(context))
1459 && ((isChromeOs(context) && (getChromeOsContextMenuFix(context)
1460 || (pref.getBoolean(PREF_LAUNCHER, false) && launcherIsDefault(context))))
1461 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f)
1462 || (isChromeOs(context) && getCurrentApiVersion() >= 30.0f));
1465 public static boolean isPlayStoreInstalled(Context context) {
1467 context.getPackageManager().getPackageInfo("com.android.vending", 0);
1469 } catch (PackageManager.NameNotFoundException e) {
1474 public static float getCurrentApiVersion() {
1475 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1476 return Float.parseFloat(Build.VERSION.SDK_INT + "." + Build.VERSION.PREVIEW_SDK_INT);
1478 return (float) Build.VERSION.SDK_INT;
1481 public static boolean hasBrokenSetLaunchBoundsApi() {
1482 return getCurrentApiVersion() >= 26.0f
1483 && getCurrentApiVersion() < 28.0f
1484 && !isSamsungDevice()
1485 && !isNvidiaDevice();
1488 public static String getSecondScreenPackageName(Context context) {
1489 return getInstalledPackage(context,
1490 "com.farmerbb.secondscreen.free",
1491 "com.farmerbb.secondscreen");
1494 // Returns the name of an installed package from a list of package names, in order of preference
1495 private static String getInstalledPackage(Context context, String... packageNames) {
1496 return getInstalledPackage(context, Arrays.asList(packageNames));
1499 private static String getInstalledPackage(Context context, List<String> packageNames) {
1500 if(packageNames == null || packageNames.isEmpty())
1503 List<String> packages = packageNames instanceof ArrayList ? packageNames : new ArrayList<>(packageNames);
1504 String packageName = packages.get(0);
1507 context.getPackageManager().getPackageInfo(packageName, 0);
1509 } catch (PackageManager.NameNotFoundException e) {
1511 return getInstalledPackage(context, packages);
1515 public static void showRecentAppsDialog(Context context) {
1516 Callbacks callbacks = new Callbacks();
1517 callbacks.onError = () -> showErrorDialog(context, "GET_USAGE_STATS", callbacks);
1519 showRecentAppsDialog(context, callbacks);
1522 public static AlertDialog showRecentAppsDialog(Context context, Callbacks callbacks) {
1523 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isSystemApp(context)) {
1524 ApplicationInfo applicationInfo = null;
1526 applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
1527 } catch (PackageManager.NameNotFoundException ignored) {}
1529 if(applicationInfo != null) {
1530 AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
1531 int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
1533 if(mode != AppOpsManager.MODE_ALLOWED) {
1534 AlertDialog.Builder builder;
1535 if(hasAndroidTVSettings(context))
1536 builder = buildRecentAppsDialogAndroidTV(context, callbacks);
1538 builder = buildRecentAppsDialogStandard(context, callbacks);
1540 AlertDialog dialog = builder.create();
1542 dialog.setCancelable(false);
1549 callbacks.onFinish.run();
1553 private static AlertDialog.Builder buildRecentAppsDialogStandard(Context context, Callbacks callbacks) {
1554 String message = context.getString(R.string.tb_enable_recent_apps, getAppName(context))
1555 + context.getString(R.string.tb_enable_recent_apps_instructions_phone);
1557 return new AlertDialog.Builder(context)
1558 .setTitle(R.string.tb_pref_header_recent_apps)
1559 .setMessage(message)
1560 .setPositiveButton(R.string.tb_action_ok, (dialog, which) -> {
1562 Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
1564 LauncherHelper helper = LauncherHelper.getInstance();
1565 if(helper.isOnHomeScreen(context))
1566 applyOpenInNewWindow(context, intent);
1568 context.startActivity(intent);
1569 showToast(context, context.getString(R.string.tb_usage_stats_message, getAppName(context)), Toast.LENGTH_LONG);
1571 callbacks.onFinish.run();
1572 } catch (ActivityNotFoundException e) {
1573 callbacks.onError.run();
1576 .setNegativeButton(R.string.tb_action_cancel, (dialog, which) -> callbacks.onFinish.run());
1579 private static AlertDialog.Builder buildRecentAppsDialogAndroidTV(Context context, Callbacks callbacks) {
1580 String message = context.getString(R.string.tb_enable_recent_apps, getAppName(context))
1581 + context.getString(R.string.tb_enable_recent_apps_instructions_tv);
1583 return new AlertDialog.Builder(context)
1584 .setTitle(R.string.tb_pref_header_recent_apps)
1585 .setMessage(message)
1586 .setPositiveButton(R.string.tb_action_open_settings, (dialog, which) -> {
1588 context.startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));
1589 callbacks.onFinish.run();
1590 } catch (ActivityNotFoundException e) {
1591 callbacks.onError.run();
1594 .setNegativeButton(R.string.tb_action_cancel, (dialog, which) -> callbacks.onFinish.run());
1597 public static Context wrapContext(Context context) {
1599 if(isDarkTheme(context))
1600 theme = R.style.Taskbar_Dark;
1602 theme = R.style.Taskbar;
1604 return new ContextThemeWrapper(context, theme);
1607 public static boolean isPlayStoreRelease(Context context) {
1608 return isPlayStoreRelease(context, context.getPackageName());
1611 @SuppressLint("PackageManagerGetSignatures")
1612 public static boolean isPlayStoreRelease(Context context, String packageName) {
1613 Signature playStoreSignature = new Signature(context.getString(R.string.tb_signature));
1615 PackageManager pm = context.getPackageManager();
1616 PackageInfo info = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
1617 for(Signature signature : info.signatures) {
1618 if(signature.equals(playStoreSignature))
1621 } catch (Exception ignored) {}
1626 public static boolean isExternalAccessDisabled(Context context) {
1627 if(isLibrary(context)) return true;
1629 SharedPreferences pref = getSharedPreferences(context);
1630 return !pref.getBoolean(PREF_TASKER_ENABLED, true);
1633 public static boolean enableFreeformModeShortcut(Context context) {
1634 return canEnableFreeform()
1635 && !isOverridingFreeformHack(context, false)
1636 && !isChromeOs(context);
1639 public static void startForegroundService(Context context, Intent intent) {
1640 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1641 if(Settings.canDrawOverlays(context))
1642 context.startForegroundService(intent);
1644 context.startService(intent);
1647 public static int getOverlayType() {
1648 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
1649 ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
1650 : WindowManager.LayoutParams.TYPE_PHONE;
1653 public static boolean isDelegatingHomeActivity(Context context) {
1654 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1655 homeIntent.addCategory(Intent.CATEGORY_HOME);
1657 final List<ResolveInfo> listOfLaunchers = context.getPackageManager().queryIntentActivities(homeIntent, 0);
1658 for(ResolveInfo launcher : listOfLaunchers) {
1659 if(launcher.activityInfo.packageName.equals(BuildConfig.SUPPORT_APPLICATION_ID))
1666 @SuppressLint("PrivateApi")
1667 public static String getSystemProperty(String key) {
1670 Class<?> cls = Class.forName("android.os.SystemProperties");
1671 return cls.getMethod("get", String.class).invoke(null, key).toString();
1672 } catch (Exception e) {
1677 public static void showImageChooser(Activity activity) {
1678 Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
1679 intent.setType("image/*");
1680 intent.addCategory(Intent.CATEGORY_OPENABLE);
1682 String message = activity.getResources().getString(R.string.tb_filepicker_select_an_image_file);
1685 activity.startActivityForResult(Intent.createChooser(intent, message), IMAGE_REQUEST_CODE);
1686 } catch (ActivityNotFoundException ex) {
1687 showToast(activity, activity.getResources().getString(R.string.tb_filepicker_install_file_manager), 50);
1691 @SuppressWarnings("ResultOfMethodCallIgnored")
1692 public static boolean importImage(Context context, Uri uri, String filename) {
1694 File imagesDir = new File(context.getFilesDir(), "tb_images");
1697 File importedFile = new File(imagesDir, filename + "_new");
1698 if(importedFile.exists()) importedFile.delete();
1700 BufferedInputStream is = new BufferedInputStream(context.getContentResolver().openInputStream(uri));
1701 byte[] data = new byte[is.available()];
1703 if(data.length > 0) {
1704 BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(importedFile));
1711 File prevFile = new File(imagesDir, filename);
1712 if(prevFile.exists()) prevFile.delete();
1714 importedFile.renameTo(prevFile);
1716 } catch (IOException e) {
1721 public static String[] getImageFilenames() {
1722 return new String[] {"custom_image", "desktop_wallpaper"};
1725 public static void applyCustomImage(Context context, String filename, ImageView view, Drawable errorDrawable) {
1726 File file = new File(context.getFilesDir() + "/tb_images", filename);
1728 Handler handler = newHandler();
1730 Bitmap bitmap = BitmapFactory.decodeFile(file.getPath());
1731 handler.post(() -> {
1732 if(bitmap != null) {
1733 BitmapDrawable bitmapDrawable = new BitmapDrawable(context.getResources(), bitmap);
1734 bitmapDrawable.setFilterBitmap(bitmap.getWidth() * bitmap.getHeight() > 2000);
1735 view.setImageDrawable(bitmapDrawable);
1737 showToastLong(context, R.string.tb_error_reading_custom_start_image);
1738 view.setImageDrawable(errorDrawable);
1743 view.setImageDrawable(errorDrawable);
1746 public static String getDefaultStartButtonImage(Context context) {
1747 SharedPreferences pref = getSharedPreferences(context);
1748 return pref.getBoolean(PREF_APP_DRAWER_ICON, false)
1749 ? PREF_START_BUTTON_IMAGE_APP_LOGO
1750 : PREF_START_BUTTON_IMAGE_DEFAULT;
1753 private static boolean shouldLaunchTouchAbsorber(Context context) {
1754 return isOverridingFreeformHack(context) && !isChromeOs(context) && getCurrentApiVersion() < 29.0f;
1757 public static boolean isDesktopIconsEnabled(Context context) {
1758 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1
1759 && !canBootToFreeform(context, false)
1760 && !shouldLaunchTouchAbsorber(context);
1763 public static boolean isSystemTrayEnabled(Context context) {
1764 SharedPreferences pref = getSharedPreferences(context);
1766 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1767 && getBooleanPrefWithDefault(context, PREF_SYS_TRAY)
1768 && pref.getBoolean(PREF_FULL_LENGTH, true)
1769 && !TaskbarPosition.isVertical(context);
1772 public static boolean isLibrary(Context context) {
1773 return !context.getPackageName().equals(BuildConfig.APPLICATION_ID);
1776 public static boolean applyDisplayCutoutModeTo(WindowManager.LayoutParams params) {
1777 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
1778 params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
1785 public static boolean hasAndroidTVSettings(Context context) {
1786 return getInstalledPackage(context, "com.android.tv.settings") != null
1787 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
1790 public static void restartApp(Activity activity, boolean shouldFade) {
1791 Intent restartIntent = new Intent(activity, MainActivity.class);
1792 restartIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
1793 activity.startActivity(restartIntent);
1795 activity.overridePendingTransition(
1796 shouldFade ? android.R.anim.fade_in : 0,
1797 shouldFade ? android.R.anim.fade_out : 0
1803 public static boolean isDesktopModeSupported(Context context) {
1804 if(isLauncherPermanentlyEnabled(context) || isChromeOs(context) || isAndroidTV(context))
1807 return Build.VERSION.SDK_INT > Build.VERSION_CODES.P
1808 && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
1811 public static boolean isDesktopModePrefEnabled(Context context) {
1812 if(!isDesktopModeSupported(context) || !hasFreeformSupport(context))
1815 boolean desktopModePrefEnabled;
1818 desktopModePrefEnabled = Settings.Global.getInt(context.getContentResolver(), "force_desktop_mode_on_external_displays") == 1;
1819 } catch (Settings.SettingNotFoundException e) {
1820 desktopModePrefEnabled = false;
1823 return desktopModePrefEnabled;
1826 public static boolean isDesktopModeActive(Context context) {
1827 return isDesktopModePrefEnabled(context) && getExternalDisplayID(context) != Display.DEFAULT_DISPLAY;
1830 private static Display getExternalDisplay(Context context) {
1831 DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1832 Display[] displays = dm.getDisplays();
1834 return displays[displays.length - 1];
1837 public static int getExternalDisplayID(Context context) {
1838 return getExternalDisplay(context).getDisplayId();
1841 public static DisplayInfo getExternalDisplayInfo(Context context) {
1842 Display display = getExternalDisplay(context);
1844 return new DisplayInfo(0, 0, 0, 0, false);
1846 DisplayMetrics metrics = new DisplayMetrics();
1847 display.getRealMetrics(metrics);
1851 defaultDensity = getDefaultDensity(display.getDisplayId());
1852 } catch (Exception e) {
1856 boolean displayDefaultsToFreeform = displayDefaultsToFreeform(context, display);
1857 return new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi, defaultDensity, displayDefaultsToFreeform);
1860 @SuppressLint("PrivateApi")
1861 private static Object getWindowManagerService() throws Exception {
1863 return Class.forName("android.view.WindowManagerGlobal")
1864 .getMethod("getWindowManagerService")
1868 @SuppressLint("PrivateApi")
1869 public static void setDensity(int displayID, String value) throws Exception {
1870 // From android.os.UserHandle
1871 final int USER_CURRENT_OR_SELF = -3;
1874 if(value.equals("reset")) {
1875 Class.forName("android.view.IWindowManager")
1876 .getMethod("clearForcedDisplayDensityForUser", int.class, int.class)
1877 .invoke(getWindowManagerService(), displayID, USER_CURRENT_OR_SELF);
1879 int density = Integer.parseInt(value);
1881 Class.forName("android.view.IWindowManager")
1882 .getMethod("setForcedDisplayDensityForUser", int.class, int.class, int.class)
1883 .invoke(getWindowManagerService(), displayID, density, USER_CURRENT_OR_SELF);
1887 @SuppressLint("PrivateApi")
1888 private static void setOverscan(int displayID, int value) throws Exception {
1890 Class.forName("android.view.IWindowManager")
1891 .getMethod("setOverscan", int.class, int.class, int.class, int.class, int.class)
1892 .invoke(getWindowManagerService(), displayID, 0, 0, 0, value);
1895 @SuppressLint("PrivateApi")
1896 private static Integer getDefaultDensity(int displayID) throws Exception {
1898 return (Integer) Class.forName("android.view.IWindowManager")
1899 .getMethod("getInitialDisplayDensity", int.class)
1900 .invoke(getWindowManagerService(), displayID);
1903 public static void registerReceiver(Context context, BroadcastReceiver receiver, String... actions) {
1904 unregisterReceiver(context, receiver);
1906 IntentFilter intentFilter = new IntentFilter();
1907 for(String action : actions) {
1908 intentFilter.addAction(action);
1911 LocalBroadcastManager.getInstance(context).registerReceiver(receiver, intentFilter);
1914 public static void unregisterReceiver(Context context, BroadcastReceiver receiver) {
1915 LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver);
1918 public static void sendBroadcast(Context context, String action) {
1919 sendBroadcast(context, new Intent(action));
1922 public static void sendBroadcast(Context context, Intent intent) {
1923 LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
1926 public static void setComponentEnabled(Context context, Class<?> clazz, boolean enabled) {
1927 ComponentName component = new ComponentName(context, clazz);
1928 context.getPackageManager().setComponentEnabledSetting(component,
1929 enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
1930 : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
1931 PackageManager.DONT_KILL_APP);
1934 public static BitmapDrawable convertToBitmapDrawable(Context context, Drawable drawable) {
1935 if(drawable instanceof BitmapDrawable)
1936 return (BitmapDrawable) drawable;
1938 int width = Math.max(drawable.getIntrinsicWidth(), 1);
1939 int height = Math.max(drawable.getIntrinsicHeight(), 1);
1941 Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
1942 Canvas canvas = new Canvas(bitmap);
1944 drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
1945 drawable.draw(canvas);
1947 return new BitmapDrawable(context.getResources(), bitmap);
1950 public static BitmapDrawable convertToMonochrome(Context context, Drawable drawable, float threshold) {
1951 Bitmap bitmap = convertToBitmapDrawable(context, drawable).getBitmap();
1952 Bitmap monoBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
1954 // From https://stackoverflow.com/a/38635239
1955 float[] hsv = new float[3];
1956 for(int col = 0; col < bitmap.getWidth(); col++) {
1957 for(int row = 0; row < bitmap.getHeight(); row++) {
1958 Color.colorToHSV(bitmap.getPixel(col, row), hsv);
1959 if(hsv[2] > threshold) {
1960 monoBitmap.setPixel(col, row, 0xffffffff);
1962 monoBitmap.setPixel(col, row, 0x00000000);
1967 return new BitmapDrawable(context.getResources(), monoBitmap);
1970 public static BitmapDrawable resizeDrawable(Context context, Drawable drawable, @DimenRes int iconSizeRes) {
1971 int width = Math.max(1, drawable.getIntrinsicWidth());
1972 int height = Math.max(1, drawable.getIntrinsicHeight());
1974 Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
1975 Canvas canvas = new Canvas(bitmap);
1977 drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
1978 drawable.draw(canvas);
1980 int iconSize = context.getApplicationContext().getResources().getDimensionPixelSize(iconSizeRes);
1981 Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, true);
1983 return new BitmapDrawable(context.getResources(), resizedBitmap);
1986 private static String getCurrentTheme(Context context) {
1987 String defaultTheme = context.getString(R.string.tb_pref_theme_default);
1989 SharedPreferences pref = getSharedPreferences(context);
1990 String themePref = pref.getString(PREF_THEME, defaultTheme);
1992 if(themePref.equals("system")) {
1993 Configuration configuration = context.getResources().getConfiguration();
1994 int currentNightMode = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
1995 switch(currentNightMode) {
1996 case Configuration.UI_MODE_NIGHT_NO:
1997 // Night mode is not active, we're using the light theme
1999 case Configuration.UI_MODE_NIGHT_YES:
2000 // Night mode is active, we're using dark theme
2006 return defaultTheme;
2009 public static boolean isFavoriteAppTilesEnabled(Context context) {
2010 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && !isChromeOs(context) && !isLibrary(context);
2013 public static Intent getThemedIntent(Context context, Class<?> clazz) {
2016 if(!isDarkTheme(context))
2019 newClass = Class.forName(clazz.getPackage().getName() + ".dark." + clazz.getSimpleName() + "Dark");
2020 } catch (ClassNotFoundException | NullPointerException e) {
2024 return new Intent(context, newClass);
2027 public static boolean isDarkTheme(Context context) {
2028 return getCurrentTheme(context).equals("dark");
2031 public static boolean getBooleanPrefWithDefault(Context context, String key) {
2032 context = getDisplayContext(context);
2033 int resId = getDefaultPrefResID(key, R.bool.class);
2035 SharedPreferences pref = getSharedPreferences(context);
2036 boolean def = pref.getBoolean(key + "_default", context.getResources().getBoolean(resId));
2037 return pref.getBoolean(key, def);
2040 public static int getIntPrefWithDefault(Context context, String key) {
2041 context = getDisplayContext(context);
2042 int resId = getDefaultPrefResID(key, R.integer.class);
2044 SharedPreferences pref = getSharedPreferences(context);
2045 int def = pref.getInt(key + "_default", context.getResources().getInteger(resId));
2046 return pref.getInt(key, def);
2049 private static int getDefaultPrefResID(String key, Class<?> rClass) {
2053 Field field = rClass.getField("tb_def_" + key);
2054 resId = field.getInt(null);
2055 } catch (NoSuchFieldException | IllegalAccessException e) {
2056 // Pref does not have a default
2063 public static boolean isFreeformModeEnabled(Context context) {
2064 if(isLibrary(context)) return true;
2066 SharedPreferences pref = getSharedPreferences(context);
2067 return pref.getBoolean(PREF_DESKTOP_MODE, false) || pref.getBoolean(PREF_FREEFORM_HACK, false);
2070 public static Context getDisplayContext(Context context) {
2071 if(isDesktopModeActive(context))
2072 return context.createDisplayContext(getExternalDisplay(context));
2074 return context.getApplicationContext();
2077 public static int getDisplayOrientation(Context context) {
2078 return getDisplayContext(context).getResources().getConfiguration().orientation;
2081 @TargetApi(Build.VERSION_CODES.N)
2082 public static void applyOpenInNewWindow(Context context, Intent intent) {
2083 if(!isFreeformModeEnabled(context)) return;
2085 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
2087 ActivityInfo activityInfo = intent.resolveActivityInfo(context.getPackageManager(), 0);
2088 if(activityInfo != null) {
2089 switch(activityInfo.launchMode) {
2090 case ActivityInfo.LAUNCH_SINGLE_TASK:
2091 case ActivityInfo.LAUNCH_SINGLE_INSTANCE:
2092 intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
2098 public static boolean isConsumerBuild(Context context) {
2099 return !BuildConfig.DEBUG
2100 && !context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
2101 && !isLibrary(context);
2104 public static void allowReflection() {
2105 GlobalHelper helper = GlobalHelper.getInstance();
2106 if(helper.isReflectionAllowed()) return;
2109 Method forName = Class.class.getDeclaredMethod("forName", String.class);
2110 Method getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class);
2112 Class<?> vmRuntimeClass = (Class<?>) forName.invoke(null, "dalvik.system.VMRuntime");
2113 Method getRuntime = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null);
2114 Method setHiddenApiExemptions = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", new Class[]{String[].class});
2116 Object vmRuntime = getRuntime.invoke(null);
2117 setHiddenApiExemptions.invoke(vmRuntime, new Object[]{new String[]{"L"}});
2118 } catch (Throwable ignored) {}
2120 helper.setReflectionAllowed(true);
2123 public static Handler newHandler() {
2124 return new Handler(Looper.getMainLooper());
2127 public static CharSequence getAppName(Context context) {
2128 return context.getApplicationInfo().loadLabel(context.getPackageManager());
2131 public static boolean getChromeOsContextMenuFix(Context context) {
2132 if(getCurrentApiVersion() >= 30.0f) return false;
2134 SharedPreferences pref = getSharedPreferences(context);
2135 return pref.getBoolean(PREF_CHROME_OS_CONTEXT_MENU_FIX, true);
2138 private static boolean displayDefaultsToFreeform(Context context, Display display) {
2139 Context dispContext = context.createDisplayContext(display);
2140 String configString = dispContext.getResources().getConfiguration().toString();
2141 return configString.contains("mDisplayWindowingMode=freeform");