1 /* Copyright 2016 Braden Farmer
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
16 package com.farmerbb.taskbar.util;
18 import android.Manifest;
19 import android.annotation.SuppressLint;
20 import android.annotation.TargetApi;
21 import android.app.ActivityManager;
22 import android.app.ActivityOptions;
23 import android.app.AlertDialog;
24 import android.app.AppOpsManager;
25 import android.app.Service;
26 import android.content.ActivityNotFoundException;
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.SharedPreferences;
31 import android.content.pm.ActivityInfo;
32 import android.content.pm.ApplicationInfo;
33 import android.content.pm.LauncherActivityInfo;
34 import android.content.pm.LauncherApps;
35 import android.content.pm.PackageInfo;
36 import android.content.pm.PackageManager;
37 import android.content.pm.ResolveInfo;
38 import android.content.pm.ShortcutInfo;
39 import android.content.pm.ShortcutManager;
40 import android.content.pm.Signature;
41 import android.content.res.Configuration;
42 import android.graphics.Color;
43 import android.graphics.Rect;
44 import android.net.Uri;
45 import android.os.Build;
46 import android.os.Bundle;
47 import android.os.Handler;
48 import android.os.Process;
49 import android.os.UserHandle;
50 import android.os.UserManager;
51 import android.provider.Settings;
52 import android.support.v4.content.LocalBroadcastManager;
53 import android.support.v7.view.ContextThemeWrapper;
54 import android.util.DisplayMetrics;
55 import android.view.Display;
56 import android.view.Surface;
57 import android.view.View;
58 import android.view.WindowManager;
59 import android.widget.Toast;
61 import com.farmerbb.taskbar.BuildConfig;
62 import com.farmerbb.taskbar.R;
63 import com.farmerbb.taskbar.activity.ContextMenuActivity;
64 import com.farmerbb.taskbar.activity.DummyActivity;
65 import com.farmerbb.taskbar.activity.InvisibleActivityFreeform;
66 import com.farmerbb.taskbar.activity.ShortcutActivity;
67 import com.farmerbb.taskbar.activity.StartTaskbarActivity;
68 import com.farmerbb.taskbar.activity.TouchAbsorberActivity;
69 import com.farmerbb.taskbar.activity.dark.ContextMenuActivityDark;
70 import com.farmerbb.taskbar.service.DashboardService;
71 import com.farmerbb.taskbar.service.NotificationService;
72 import com.farmerbb.taskbar.service.PowerMenuService;
73 import com.farmerbb.taskbar.service.StartMenuService;
74 import com.farmerbb.taskbar.service.TaskbarService;
76 import java.lang.reflect.Method;
77 import java.util.ArrayList;
78 import java.util.Arrays;
79 import java.util.List;
85 private static Integer cachedRotation;
87 private static final int MAXIMIZED = 0;
88 private static final int LEFT = -1;
89 private static final int RIGHT = 1;
91 public static final int HIDDEN = 0;
92 public static final int TOP_APPS = 1;
94 // From android.app.ActivityManager.StackId
95 private static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
96 private static final int FREEFORM_WORKSPACE_STACK_ID = 2;
98 // From android.app.WindowConfiguration
99 private static final int WINDOWING_MODE_FULLSCREEN = 1;
100 private static final int WINDOWING_MODE_FREEFORM = 5;
102 public static SharedPreferences getSharedPreferences(Context context) {
103 return context.getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", Context.MODE_PRIVATE);
106 public static void showPermissionDialog(Context context) {
107 showPermissionDialog(context, null, null);
110 @TargetApi(Build.VERSION_CODES.M)
111 public static AlertDialog showPermissionDialog(Context context, Runnable onError, Runnable onFinish) {
112 Runnable finalOnFinish = onFinish == null
116 Runnable finalOnError = onError == null
117 ? () -> showErrorDialog(context, "SYSTEM_ALERT_WINDOW", finalOnFinish)
120 AlertDialog.Builder builder = new AlertDialog.Builder(context);
121 builder.setTitle(R.string.permission_dialog_title)
122 .setMessage(R.string.permission_dialog_message)
123 .setPositiveButton(R.string.action_grant_permission, (dialog, which) -> {
125 context.startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
126 Uri.parse("package:" + BuildConfig.APPLICATION_ID)));
129 } catch (ActivityNotFoundException e) {
134 AlertDialog dialog = builder.create();
136 dialog.setCancelable(false);
141 public static AlertDialog showErrorDialog(Context context, String appopCmd) {
142 return showErrorDialog(context, appopCmd, null);
145 private static AlertDialog showErrorDialog(Context context, String appopCmd, Runnable onFinish) {
146 Runnable finalOnFinish = onFinish == null
150 AlertDialog.Builder builder = new AlertDialog.Builder(context);
151 builder.setTitle(R.string.error_dialog_title)
152 .setMessage(context.getString(R.string.error_dialog_message, BuildConfig.APPLICATION_ID, appopCmd))
153 .setPositiveButton(R.string.action_ok, (dialog, which) -> finalOnFinish.run());
155 AlertDialog dialog = builder.create();
157 dialog.setCancelable(false);
162 public static void sendAccessibilityAction(Context context, int action) {
163 sendAccessibilityAction(context, action, null);
166 public static void sendAccessibilityAction(Context context, int action, Runnable onComplete) {
167 ComponentName component = new ComponentName(context, PowerMenuService.class);
168 context.getPackageManager().setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
169 PackageManager.DONT_KILL_APP);
171 boolean isAccessibilityServiceEnabled = isAccessibilityServiceEnabled(context);
173 if(!isAccessibilityServiceEnabled
174 && hasWriteSecureSettingsPermission(context)) {
175 String services = Settings.Secure.getString(context.getContentResolver(),
176 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
178 String finalServices = services == null ? "" : services;
180 String powerMenuService = new ComponentName(context, PowerMenuService.class).flattenToString();
182 if(!finalServices.contains(powerMenuService)) {
184 Settings.Secure.putString(context.getContentResolver(),
185 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
186 finalServices.isEmpty()
188 : finalServices + ":" + powerMenuService);
189 } catch (Exception e) { /* Gracefully fail */ }
192 new Handler().postDelayed(() -> {
193 Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
194 intent.putExtra("action", action);
195 LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
198 Settings.Secure.putString(context.getContentResolver(),
199 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
201 } catch (Exception e) { /* Gracefully fail */ }
203 if(onComplete != null) onComplete.run();
205 } else if(isAccessibilityServiceEnabled) {
206 Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
207 intent.putExtra("action", action);
208 LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
210 if(onComplete != null) onComplete.run();
212 launchApp(context, () -> {
213 Intent intent = new Intent(context, DummyActivity.class);
214 intent.putExtra("accessibility", true);
215 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
218 context.startActivity(intent, getActivityOptionsBundle(context, ApplicationType.APPLICATION, null));
219 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
224 public static boolean isAccessibilityServiceEnabled(Context context) {
225 String accessibilityServices = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
226 ComponentName component = new ComponentName(context, PowerMenuService.class);
228 return accessibilityServices != null
229 && (accessibilityServices.contains(component.flattenToString())
230 || accessibilityServices.contains(component.flattenToShortString()));
233 public static boolean hasWriteSecureSettingsPermission(Context context) {
234 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
235 && context.checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) == PackageManager.PERMISSION_GRANTED;
238 public static void showToast(Context context, int message) {
239 showToast(context, context.getString(message), Toast.LENGTH_SHORT);
242 public static void showToastLong(Context context, int message) {
243 showToast(context, context.getString(message), Toast.LENGTH_LONG);
246 public static void showToast(Context context, String message, int length) {
249 ToastInterface toast = DependencyUtils.createToast(context, message, length);
252 ToastHelper.getInstance().setLastToast(toast);
255 public static void cancelToast() {
256 ToastInterface toast = ToastHelper.getInstance().getLastToast();
257 if(toast != null) toast.cancel();
260 public static void startShortcut(Context context, AppEntry entry, ShortcutInfo shortcut) {
270 public static void launchApp(final Context context,
271 final AppEntry entry,
272 final String windowSize,
273 final boolean launchedFromTaskbar,
274 final boolean openInNewWindow,
285 private static void launchApp(final Context context,
286 final AppEntry entry,
287 final String windowSize,
288 final boolean launchedFromTaskbar,
289 final boolean openInNewWindow,
290 final ShortcutInfo shortcut,
292 launchApp(context, launchedFromTaskbar, () -> continueLaunchingApp(context, entry,
293 windowSize, openInNewWindow, shortcut, view));
296 public static void launchApp(Context context, Runnable runnable) {
297 launchApp(context, true, runnable);
300 private static void launchApp(Context context, boolean launchedFromTaskbar, Runnable runnable) {
301 SharedPreferences pref = getSharedPreferences(context);
302 FreeformHackHelper helper = FreeformHackHelper.getInstance();
304 boolean specialLaunch = hasBrokenSetLaunchBoundsApi()
305 && helper.isInFreeformWorkspace()
306 && MenuHelper.getInstance().isContextMenuOpen();
308 boolean noAnimation = pref.getBoolean("disable_animations", false);
310 if(hasFreeformSupport(context)
311 && pref.getBoolean("freeform_hack", false)
312 && (!helper.isInFreeformWorkspace() || specialLaunch)) {
313 new Handler().postDelayed(() -> {
314 startFreeformHack(context, true);
316 new Handler().postDelayed(runnable, helper.isFreeformHackActive() ? 0 : 100);
317 }, launchedFromTaskbar ? 0 : 100);
319 new Handler().postDelayed(runnable, !launchedFromTaskbar && noAnimation ? 100 : 0);
322 public static void startFreeformHack(Context context) {
323 startFreeformHack(context, false);
326 @TargetApi(Build.VERSION_CODES.N)
327 public static void startFreeformHack(Context context, boolean checkMultiWindow) {
328 Intent freeformHackIntent = new Intent(context, InvisibleActivityFreeform.class);
329 freeformHackIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
330 | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
331 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
334 freeformHackIntent.putExtra("check_multiwindow", true);
336 if(canDrawOverlays(context, false))
337 startActivityLowerRight(context, freeformHackIntent);
340 public static void stopFreeformHack(Context context) {
341 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.FINISH_FREEFORM_ACTIVITY"));
343 if(isOverridingFreeformHack(context)) {
344 FreeformHackHelper helper = FreeformHackHelper.getInstance();
345 helper.setFreeformHackActive(false);
346 helper.setInFreeformWorkspace(false);
350 @TargetApi(Build.VERSION_CODES.N)
351 private static void continueLaunchingApp(Context context,
354 boolean openInNewWindow,
355 ShortcutInfo shortcut,
357 SharedPreferences pref = getSharedPreferences(context);
358 Intent intent = new Intent();
359 intent.setComponent(ComponentName.unflattenFromString(entry.getComponentName()));
360 intent.setAction(Intent.ACTION_MAIN);
361 intent.addCategory(Intent.CATEGORY_LAUNCHER);
362 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
363 intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
365 if(FreeformHackHelper.getInstance().isInFreeformWorkspace()
366 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1)
367 intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
369 if(pref.getBoolean("disable_animations", false))
370 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
372 if(openInNewWindow || pref.getBoolean("force_new_window", false)) {
373 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
375 ActivityInfo activityInfo = intent.resolveActivityInfo(context.getPackageManager(), 0);
376 if(activityInfo != null) {
377 switch(activityInfo.launchMode) {
378 case ActivityInfo.LAUNCH_SINGLE_TASK:
379 case ActivityInfo.LAUNCH_SINGLE_INSTANCE:
380 intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
386 ApplicationType type = getApplicationType(context, entry.getPackageName());
388 if(windowSize == null)
389 windowSize = SavedWindowSizes.getInstance(context).getWindowSize(context, entry.getPackageName());
391 Bundle bundle = getActivityOptionsBundle(context, type, windowSize, view);
393 prepareToStartActivity(context, () -> {
394 if(shortcut == null) {
395 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
396 if(entry.getUserId(context) == userManager.getSerialNumberForUser(Process.myUserHandle())) {
398 context.startActivity(intent, bundle);
399 } catch (ActivityNotFoundException e) {
400 launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context));
401 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
403 launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context));
405 launchShortcut(context, shortcut, bundle);
408 if(shouldCollapse(context, true))
409 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_TASKBAR"));
411 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_START_MENU"));
414 @TargetApi(Build.VERSION_CODES.N)
415 private static Bundle launchMode1(Context context, ApplicationType type, View view) {
416 DisplayInfo display = getDisplayInfo(context);
418 int width1 = display.width / 8;
419 int width2 = display.width - width1;
420 int height1 = display.height / 8;
421 int height2 = display.height - height1;
423 return getActivityOptions(context, type, view).setLaunchBounds(new Rect(
431 @TargetApi(Build.VERSION_CODES.N)
432 private static Bundle launchMode2(Context context, int launchType, ApplicationType type, View view) {
433 DisplayInfo display = getDisplayInfo(context);
435 int statusBarHeight = getStatusBarHeight(context);
436 String position = getTaskbarPosition(context);
438 boolean isPortrait = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
439 boolean isLandscape = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
442 int top = statusBarHeight;
443 int right = display.width;
444 int bottom = display.height;
446 int iconSize = isOverridingFreeformHack(context) && !LauncherHelper.getInstance().isOnHomeScreen()
448 : context.getResources().getDimensionPixelSize(R.dimen.icon_size);
450 if(position.contains("vertical_left"))
451 left = left + iconSize;
452 else if(position.contains("vertical_right"))
453 right = right - iconSize;
454 else if(position.contains("bottom"))
455 bottom = bottom - iconSize;
457 top = top + iconSize;
459 int halfLandscape = (right / 2) + ((iconSize / 2) * (position.contains("vertical_left") ? 1 : 0));
460 int halfPortrait = (bottom / 2) + ((iconSize / 2) * ((position.equals("top_left") || position.equals("top_right")) ? 1 : 0));
462 if(launchType == RIGHT && isLandscape)
463 left = halfLandscape;
464 else if(launchType == RIGHT && isPortrait)
466 else if(launchType == LEFT && isLandscape)
467 right = halfLandscape;
468 else if(launchType == LEFT && isPortrait)
469 bottom = halfPortrait;
471 return getActivityOptions(context, type, view)
472 .setLaunchBounds(new Rect(left, top, right, bottom)).toBundle();
475 @TargetApi(Build.VERSION_CODES.N)
476 private static Bundle launchMode3(Context context, ApplicationType type, View view) {
477 DisplayInfo display = getDisplayInfo(context);
479 int width1 = display.width / 2;
480 int width2 = context.getResources().getDimensionPixelSize(R.dimen.phone_size_width) / 2;
481 int height1 = display.height / 2;
482 int height2 = context.getResources().getDimensionPixelSize(R.dimen.phone_size_height) / 2;
484 return getActivityOptions(context, type, view).setLaunchBounds(new Rect(
492 private static void launchAndroidForWork(Context context, ComponentName componentName, Bundle bundle, long userId) {
493 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
494 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
497 launcherApps.startMainActivity(componentName, userManager.getUserForSerialNumber(userId), null, bundle);
498 } catch (ActivityNotFoundException | NullPointerException e) { /* Gracefully fail */ }
501 @TargetApi(Build.VERSION_CODES.N_MR1)
502 private static void launchShortcut(Context context, ShortcutInfo shortcut, Bundle bundle) {
503 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
505 if(launcherApps.hasShortcutHostPermission()) {
507 launcherApps.startShortcut(shortcut, null, bundle);
508 } catch (ActivityNotFoundException | NullPointerException e) { /* Gracefully fail */ }
512 private static void prepareToStartActivity(Context context, Runnable runnable) {
513 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_CONTEXT_MENU"));
515 boolean shouldLaunchTouchAbsorber =
516 !FreeformHackHelper.getInstance().isTouchAbsorberActive()
517 && isOverridingFreeformHack(context)
518 && !isChromeOs(context);
520 if(!shouldLaunchTouchAbsorber) {
525 startTouchAbsorberActivity(context);
526 new Handler().postDelayed(runnable, 100);
529 public static void startActivityMaximized(Context context, Intent intent) {
530 Bundle bundle = launchMode2(context, MAXIMIZED, ApplicationType.CONTEXT_MENU, null);
531 prepareToStartActivity(context, () -> context.startActivity(intent, bundle));
534 @TargetApi(Build.VERSION_CODES.N)
535 public static void startActivityLowerRight(Context context, Intent intent) {
536 DisplayInfo display = getDisplayInfo(context);
538 context.startActivity(intent,
539 getActivityOptions(context, ApplicationType.FREEFORM_HACK, null)
540 .setLaunchBounds(new Rect(
546 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
549 @TargetApi(Build.VERSION_CODES.N)
550 public static void startTouchAbsorberActivity(Context context) {
551 String position = getTaskbarPosition(context);
552 DisplayInfo display = getDisplayInfo(context);
556 int right = display.width;
557 int bottom = display.height;
559 int iconSize = context.getResources().getDimensionPixelSize(R.dimen.icon_size);
561 if(position.contains("vertical_left"))
563 else if(position.contains("vertical_right"))
564 left = right - iconSize;
565 else if(position.contains("bottom"))
566 top = bottom - iconSize;
570 Intent intent = new Intent(context, TouchAbsorberActivity.class);
571 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
572 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
575 context.startActivity(intent,
576 getActivityOptions(context, ApplicationType.FREEFORM_HACK, null)
577 .setLaunchBounds(new Rect(left, top, right, bottom)).toBundle());
578 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
581 public static void startContextMenuActivity(Context context, Bundle args) {
582 SharedPreferences pref = getSharedPreferences(context);
583 Intent intent = null;
585 switch(pref.getString("theme", "light")) {
587 intent = new Intent(context, ContextMenuActivity.class);
590 intent = new Intent(context, ContextMenuActivityDark.class);
595 intent.putExtra("args", args);
596 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
599 if(hasFreeformSupport(context) && FreeformHackHelper.getInstance().isInFreeformWorkspace()) {
600 DisplayInfo display = getDisplayInfo(context);
602 if(intent != null && hasBrokenSetLaunchBoundsApi())
603 intent.putExtra("context_menu_fix", true);
605 context.startActivity(intent,
606 getActivityOptions(context, ApplicationType.CONTEXT_MENU, null)
608 new Rect(0, 0, display.width, display.height)
611 context.startActivity(intent);
614 public static void checkForUpdates(Context context) {
616 if(isPlayStoreRelease(context)) {
617 if(BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
618 && !isPlayStoreInstalled(context))
619 url = "https://github.com/farmerbb/Taskbar/releases";
621 url = "https://play.google.com/store/apps/details?id=" + BuildConfig.APPLICATION_ID;
623 url = "https://f-droid.org/repository/browse/?fdid=" + BuildConfig.APPLICATION_ID;
625 Intent intent = new Intent(Intent.ACTION_VIEW);
626 intent.setData(Uri.parse(url));
627 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
630 context.startActivity(intent);
631 } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
634 public static boolean launcherIsDefault(Context context) {
635 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
636 homeIntent.addCategory(Intent.CATEGORY_HOME);
637 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
639 return defaultLauncher.activityInfo.packageName.equals(BuildConfig.APPLICATION_ID);
642 public static void setCachedRotation(int cachedRotation) {
643 U.cachedRotation = cachedRotation;
646 public static String getTaskbarPosition(Context context) {
647 SharedPreferences pref = getSharedPreferences(context);
648 String position = pref.getString("position", "bottom_left");
650 if(pref.getBoolean("anchor", false)) {
651 WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
652 int rotation = cachedRotation != null ? cachedRotation : windowManager.getDefaultDisplay().getRotation();
657 case Surface.ROTATION_0:
658 return "bottom_left";
659 case Surface.ROTATION_90:
660 return "bottom_vertical_right";
661 case Surface.ROTATION_180:
663 case Surface.ROTATION_270:
664 return "top_vertical_left";
667 case "bottom_vertical_left":
669 case Surface.ROTATION_0:
670 return "bottom_vertical_left";
671 case Surface.ROTATION_90:
672 return "bottom_right";
673 case Surface.ROTATION_180:
674 return "top_vertical_right";
675 case Surface.ROTATION_270:
681 case Surface.ROTATION_0:
682 return "bottom_right";
683 case Surface.ROTATION_90:
684 return "top_vertical_right";
685 case Surface.ROTATION_180:
687 case Surface.ROTATION_270:
688 return "bottom_vertical_left";
691 case "bottom_vertical_right":
693 case Surface.ROTATION_0:
694 return "bottom_vertical_right";
695 case Surface.ROTATION_90:
697 case Surface.ROTATION_180:
698 return "top_vertical_left";
699 case Surface.ROTATION_270:
700 return "bottom_left";
705 case Surface.ROTATION_0:
707 case Surface.ROTATION_90:
708 return "bottom_vertical_left";
709 case Surface.ROTATION_180:
710 return "bottom_right";
711 case Surface.ROTATION_270:
712 return "top_vertical_right";
715 case "top_vertical_left":
717 case Surface.ROTATION_0:
718 return "top_vertical_left";
719 case Surface.ROTATION_90:
720 return "bottom_left";
721 case Surface.ROTATION_180:
722 return "bottom_vertical_right";
723 case Surface.ROTATION_270:
729 case Surface.ROTATION_0:
731 case Surface.ROTATION_90:
732 return "top_vertical_left";
733 case Surface.ROTATION_180:
734 return "bottom_left";
735 case Surface.ROTATION_270:
736 return "bottom_vertical_right";
739 case "top_vertical_right":
741 case Surface.ROTATION_0:
742 return "top_vertical_right";
743 case Surface.ROTATION_90:
745 case Surface.ROTATION_180:
746 return "bottom_vertical_left";
747 case Surface.ROTATION_270:
748 return "bottom_right";
757 private static int getMaxNumOfColumns(Context context) {
758 SharedPreferences pref = getSharedPreferences(context);
759 DisplayInfo display = getDisplayInfo(context);
760 float density = display.density / 160;
761 float baseTaskbarSize = getBaseTaskbarSizeFloat(context) / density;
762 int numOfColumns = 0;
764 float maxScreenSize = getTaskbarPosition(context).contains("vertical")
765 ? (display.height - getStatusBarHeight(context)) / density
766 : display.width / density;
768 float iconSize = context.getResources().getDimension(R.dimen.icon_size) / density;
770 int userMaxNumOfColumns = Integer.valueOf(pref.getString("max_num_of_recents", "10"));
772 while(baseTaskbarSize + iconSize < maxScreenSize
773 && numOfColumns < userMaxNumOfColumns) {
774 baseTaskbarSize = baseTaskbarSize + iconSize;
781 public static int getMaxNumOfEntries(Context context) {
782 SharedPreferences pref = getSharedPreferences(context);
783 return pref.getBoolean("disable_scrolling_list", false)
784 ? getMaxNumOfColumns(context)
785 : Integer.valueOf(pref.getString("max_num_of_recents", "10"));
788 public static int getStatusBarHeight(Context context) {
789 return getSystemDimen(context, "status_bar_height");
792 private static int getNavbarHeight(Context context) {
793 return getSystemDimen(context, "navigation_bar_height");
796 private static int getSystemDimen(Context context, String id) {
798 int resourceId = context.getResources().getIdentifier(id, "dimen", "android");
800 value = context.getResources().getDimensionPixelSize(resourceId);
805 public static void refreshPinnedIcons(Context context) {
806 IconCache.getInstance(context).clearCache();
808 PinnedBlockedApps pba = PinnedBlockedApps.getInstance(context);
809 List<AppEntry> pinnedAppsList = new ArrayList<>(pba.getPinnedApps());
810 List<AppEntry> blockedAppsList = new ArrayList<>(pba.getBlockedApps());
811 PackageManager pm = context.getPackageManager();
815 for(AppEntry entry : pinnedAppsList) {
816 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
817 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
819 final List<UserHandle> userHandles = userManager.getUserProfiles();
820 LauncherActivityInfo appInfo = null;
822 for(UserHandle handle : userHandles) {
823 List<LauncherActivityInfo> list = launcherApps.getActivityList(entry.getPackageName(), handle);
824 if(!list.isEmpty()) {
825 // Google App workaround
826 if(!entry.getPackageName().equals("com.google.android.googlequicksearchbox"))
827 appInfo = list.get(0);
829 boolean added = false;
830 for(LauncherActivityInfo info : list) {
831 if(info.getName().equals("com.google.android.googlequicksearchbox.SearchActivity")) {
837 if(!added) appInfo = list.get(0);
844 if(appInfo != null) {
845 AppEntry newEntry = new AppEntry(
846 entry.getPackageName(),
847 entry.getComponentName(),
849 IconCache.getInstance(context).getIcon(context, pm, appInfo),
852 newEntry.setUserId(entry.getUserId(context));
853 pba.addPinnedApp(context, newEntry);
857 for(AppEntry entry : blockedAppsList) {
858 pba.addBlockedApp(context, entry);
862 public static Intent getShortcutIntent(Context context) {
863 Intent shortcutIntent = new Intent(context, ShortcutActivity.class);
864 shortcutIntent.setAction(Intent.ACTION_MAIN);
865 shortcutIntent.putExtra("is_launching_shortcut", true);
867 Intent intent = new Intent();
868 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
869 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.mipmap.ic_freeform_mode));
870 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, context.getString(R.string.pref_header_freeform));
875 public static Intent getStartStopIntent(Context context) {
876 Intent shortcutIntent = new Intent(context, StartTaskbarActivity.class);
877 shortcutIntent.setAction(Intent.ACTION_MAIN);
878 shortcutIntent.putExtra("is_launching_shortcut", true);
880 Intent intent = new Intent();
881 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
882 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.mipmap.ic_launcher));
883 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, context.getString(R.string.start_taskbar));
888 public static boolean canEnableFreeform() {
889 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
892 @TargetApi(Build.VERSION_CODES.N)
893 public static boolean hasFreeformSupport(Context context) {
894 return canEnableFreeform()
895 && (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
896 || Settings.Global.getInt(context.getContentResolver(), "enable_freeform_support", 0) != 0
897 || (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
898 && Settings.Global.getInt(context.getContentResolver(), "force_resizable_activities", 0) != 0));
901 public static boolean canBootToFreeform(Context context) {
902 SharedPreferences pref = getSharedPreferences(context);
903 return hasFreeformSupport(context)
904 && pref.getBoolean("freeform_hack", false)
905 && !isOverridingFreeformHack(context);
908 public static boolean isSamsungDevice() {
909 return Build.MANUFACTURER.equalsIgnoreCase("Samsung");
912 private static boolean isNvidiaDevice() {
913 return Build.MANUFACTURER.equalsIgnoreCase("NVIDIA");
916 public static boolean isServiceRunning(Context context, Class<? extends Service> cls) {
917 if(LauncherHelper.getInstance().isOnHomeScreen()
918 && FeatureFlags.homeActivityUIHost()
919 && (cls.equals(TaskbarService.class)
920 || cls.equals(StartMenuService.class)
921 || cls.equals(DashboardService.class)))
924 return isServiceRunning(context, cls.getName());
927 private static boolean isServiceRunning(Context context, String className) {
928 ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
929 for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
930 if(className.equals(service.service.getClassName()))
937 public static int getBackgroundTint(Context context) {
938 SharedPreferences pref = getSharedPreferences(context);
940 // Import old background tint preference
941 if(pref.contains("show_background")) {
942 SharedPreferences.Editor editor = pref.edit();
944 if(!pref.getBoolean("show_background", true))
945 editor.putInt("background_tint", Color.TRANSPARENT).apply();
947 editor.remove("show_background");
951 return pref.getInt("background_tint", context.getResources().getInteger(R.integer.translucent_gray));
954 public static int getAccentColor(Context context) {
955 SharedPreferences pref = getSharedPreferences(context);
956 return pref.getInt("accent_color", context.getResources().getInteger(R.integer.translucent_white));
959 @TargetApi(Build.VERSION_CODES.M)
960 public static boolean canDrawOverlays(Context context, boolean forHomeScreen) {
961 return (forHomeScreen && FeatureFlags.homeActivityUIHost() && !canBootToFreeform(context))
962 || Build.VERSION.SDK_INT < Build.VERSION_CODES.M
963 || Settings.canDrawOverlays(context);
966 public static boolean isGame(Context context, String packageName) {
967 SharedPreferences pref = getSharedPreferences(context);
968 if(pref.getBoolean("launch_games_fullscreen", true)) {
969 PackageManager pm = context.getPackageManager();
972 ApplicationInfo info = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
973 return (info.flags & ApplicationInfo.FLAG_IS_GAME) != 0 || (info.metaData != null && info.metaData.getBoolean("isGame", false));
974 } catch (PackageManager.NameNotFoundException e) {
981 private static ActivityOptions getActivityOptions(View view) {
982 return getActivityOptions(null, null, view);
985 @TargetApi(Build.VERSION_CODES.N)
986 private static ActivityOptions getActivityOptions(Context context, ApplicationType applicationType, View view) {
987 ActivityOptions options;
989 options = ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight());
991 options = ActivityOptions.makeBasic();
993 if(applicationType == null)
998 switch(applicationType) {
1000 if(FreeformHackHelper.getInstance().isFreeformHackActive())
1001 stackId = getFreeformWindowModeId();
1003 stackId = getFullscreenWindowModeId();
1006 stackId = getFullscreenWindowModeId();
1009 stackId = getFreeformWindowModeId();
1012 if(hasBrokenSetLaunchBoundsApi()
1013 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f))
1014 stackId = getFullscreenWindowModeId();
1019 Method method = ActivityOptions.class.getMethod(getWindowingModeMethodName(), int.class);
1020 method.invoke(options, stackId);
1021 } catch (Exception e) { /* Gracefully fail */ }
1026 private static int getFullscreenWindowModeId() {
1027 if(getCurrentApiVersion() >= 28.0f)
1028 return WINDOWING_MODE_FULLSCREEN;
1030 return FULLSCREEN_WORKSPACE_STACK_ID;
1033 private static int getFreeformWindowModeId() {
1034 if(getCurrentApiVersion() >= 28.0f)
1035 return WINDOWING_MODE_FREEFORM;
1037 return FREEFORM_WORKSPACE_STACK_ID;
1040 private static String getWindowingModeMethodName() {
1041 if(getCurrentApiVersion() >= 28.0f)
1042 return "setLaunchWindowingMode";
1044 return "setLaunchStackId";
1047 public static Bundle getActivityOptionsBundle(Context context, ApplicationType type, View view) {
1048 SharedPreferences pref = getSharedPreferences(context);
1050 return getActivityOptionsBundle(context, type, pref.getString("window_size", "standard"), view);
1053 private static Bundle getActivityOptionsBundle(Context context, ApplicationType type, String windowSize, View view) {
1054 SharedPreferences pref = getSharedPreferences(context);
1055 if(!canEnableFreeform() || !pref.getBoolean("freeform_hack", false))
1056 return getActivityOptions(view).toBundle();
1058 switch(windowSize) {
1060 return launchMode1(context, type, view);
1062 return launchMode2(context, MAXIMIZED, type, view);
1064 return launchMode2(context, LEFT, type, view);
1066 return launchMode2(context, RIGHT, type, view);
1068 return launchMode3(context, type, view);
1071 return getActivityOptions(context, type, view).toBundle();
1074 private static ApplicationType getApplicationType(Context context, String packageName) {
1075 return isGame(context, packageName) ? ApplicationType.GAME : ApplicationType.APPLICATION;
1078 public static boolean isSystemApp(Context context) {
1080 ApplicationInfo info = context.getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
1081 int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
1082 return (info.flags & mask) != 0;
1083 } catch (PackageManager.NameNotFoundException e) {
1088 public static boolean isChromeOs(Context context) {
1089 return context.getPackageManager().hasSystemFeature("org.chromium.arc");
1092 public static boolean isBlissOs(Context context) {
1093 boolean validBlissOsBuildProp = false;
1095 String blissVersion = getSystemProperty("ro.bliss.version");
1096 if(blissVersion != null && !blissVersion.isEmpty())
1097 validBlissOsBuildProp = true;
1099 String buildUser = getSystemProperty("ro.build.user");
1100 if(buildUser != null && buildUser.equals("electrikjesus"))
1101 validBlissOsBuildProp = true;
1103 return validBlissOsBuildProp
1104 && BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
1105 && isSystemApp(context);
1108 public static boolean isLauncherPermanentlyEnabled(Context context) {
1109 if(BuildConfig.APPLICATION_ID.equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
1112 PackageManager pm = context.getPackageManager();
1114 pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1115 return pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, BuildConfig.APPLICATION_ID) == PackageManager.SIGNATURE_MATCH
1116 && BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
1117 && isSystemApp(context);
1118 } catch (PackageManager.NameNotFoundException e) {
1123 public static int getBaseTaskbarSize(Context context) {
1124 return Math.round(getBaseTaskbarSizeFloat(context));
1127 private static float getBaseTaskbarSizeFloat(Context context) {
1128 SharedPreferences pref = getSharedPreferences(context);
1129 float baseTaskbarSize = context.getResources().getDimension(R.dimen.base_taskbar_size);
1130 boolean navbarButtonsEnabled = false;
1132 if(pref.getBoolean("dashboard", false))
1133 baseTaskbarSize += context.getResources().getDimension(R.dimen.dashboard_button_size);
1135 if(pref.getBoolean("button_back", false)) {
1136 navbarButtonsEnabled = true;
1137 baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
1140 if(pref.getBoolean("button_home", false)) {
1141 navbarButtonsEnabled = true;
1142 baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
1145 if(pref.getBoolean("button_recents", false)) {
1146 navbarButtonsEnabled = true;
1147 baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
1150 if(navbarButtonsEnabled)
1151 baseTaskbarSize += context.getResources().getDimension(R.dimen.navbar_buttons_margin);
1153 return baseTaskbarSize;
1156 private static void startTaskbarService(Context context, boolean fullRestart) {
1157 context.startService(new Intent(context, TaskbarService.class));
1158 context.startService(new Intent(context, StartMenuService.class));
1159 context.startService(new Intent(context, DashboardService.class));
1160 if(fullRestart) context.startService(new Intent(context, NotificationService.class));
1163 private static void stopTaskbarService(Context context, boolean fullRestart) {
1164 context.stopService(new Intent(context, TaskbarService.class));
1165 context.stopService(new Intent(context, StartMenuService.class));
1166 context.stopService(new Intent(context, DashboardService.class));
1167 if(fullRestart) context.stopService(new Intent(context, NotificationService.class));
1170 public static void restartTaskbar(Context context) {
1171 SharedPreferences pref = getSharedPreferences(context);
1172 if(pref.getBoolean("taskbar_active", false) && !pref.getBoolean("is_hidden", false)) {
1174 .putBoolean("is_restarting", true)
1175 .putBoolean("skip_auto_hide_navbar", true)
1178 stopTaskbarService(context, true);
1179 startTaskbarService(context, true);
1180 } else if(isServiceRunning(context, StartMenuService.class)) {
1181 pref.edit().putBoolean("skip_auto_hide_navbar", true).apply();
1183 stopTaskbarService(context, false);
1184 startTaskbarService(context, false);
1187 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.RESTART"));
1190 public static void restartNotificationService(Context context) {
1191 if(isServiceRunning(context, NotificationService.class)) {
1192 SharedPreferences pref = getSharedPreferences(context);
1193 pref.edit().putBoolean("is_restarting", true).apply();
1195 Intent intent = new Intent(context, NotificationService.class);
1196 context.stopService(intent);
1197 context.startService(intent);
1201 public static void showHideNavigationBar(Context context, boolean show) {
1202 // Show or hide the system navigation bar on Bliss-x86
1204 if(getCurrentApiVersion() >= 28.0f)
1205 Settings.Secure.putInt(context.getContentResolver(), "navigation_bar_visible", show ? 1 : 0);
1207 Settings.System.putInt(context.getContentResolver(), "navigation_bar_show", show ? 1 : 0);
1208 } catch (Exception e) { /* Gracefully fail */ }
1211 public static void initPrefs(Context context) {
1212 // On smaller-screened devices, set "Grid" as the default start menu layout
1213 SharedPreferences pref = getSharedPreferences(context);
1214 if(context.getApplicationContext().getResources().getConfiguration().smallestScreenWidthDp < 720
1215 && pref.getString("start_menu_layout", "null").equals("null")) {
1216 pref.edit().putString("start_menu_layout", "grid").apply();
1219 // Enable freeform hack automatically on supported devices
1220 if(canEnableFreeform()) {
1221 if(!pref.getBoolean("freeform_hack_override", false)) {
1223 .putBoolean("freeform_hack", hasFreeformSupport(context) && !isSamsungDevice())
1224 .putBoolean("save_window_sizes", false)
1225 .putBoolean("freeform_hack_override", true)
1227 } else if(!hasFreeformSupport(context)) {
1228 pref.edit().putBoolean("freeform_hack", false).apply();
1230 stopFreeformHack(context);
1233 boolean freeformWasEnabled = pref.getBoolean("freeform_hack", false)
1234 || pref.getBoolean("show_freeform_disabled_message", false);
1237 .putBoolean("freeform_hack", false)
1238 .putBoolean("show_freeform_disabled_message", freeformWasEnabled)
1241 SavedWindowSizes.getInstance(context).clear(context);
1242 stopFreeformHack(context);
1245 // Customizations for BlissOS
1246 if(isBlissOs(context) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1247 && !pref.getBoolean("bliss_os_prefs", false)) {
1248 SharedPreferences.Editor editor = pref.edit();
1250 if(hasFreeformSupport(context)) {
1251 editor.putBoolean("freeform_hack", true);
1254 editor.putString("recents_amount", "running_apps_only");
1255 editor.putString("refresh_frequency", "0");
1256 editor.putString("max_num_of_recents", "2147483647");
1257 editor.putString("sort_order", "true");
1258 editor.putString("window_size", "phone_size");
1259 editor.putString("start_button_image", "app_logo");
1260 editor.putBoolean("full_length", true);
1261 editor.putBoolean("dashboard", true);
1262 editor.putBoolean("button_back", true);
1263 editor.putBoolean("button_home", true);
1264 editor.putBoolean("button_recents", true);
1265 editor.putBoolean("auto_hide_navbar", true);
1266 // editor.putBoolean("shortcut_icon", false);
1267 editor.putBoolean("bliss_os_prefs", true);
1271 // Customizations for Android-x86 devices (non-Bliss)
1272 if(BuildConfig.APPLICATION_ID.equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
1273 && isSystemApp(context)
1274 && !pref.getBoolean("android_x86_prefs", false)) {
1276 .putString("recents_amount", "running_apps_only")
1277 .putString("refresh_frequency", "0")
1278 .putString("max_num_of_recents", "2147483647")
1279 .putString("sort_order", "true")
1280 .putString("window_size", "phone_size")
1281 .putBoolean("full_length", true)
1282 .putBoolean("dashboard", true)
1283 // .putBoolean("shortcut_icon", false)
1284 .putBoolean("android_x86_prefs", true)
1289 public static DisplayInfo getDisplayInfo(Context context) {
1290 context = context.getApplicationContext();
1292 WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
1293 Display disp = wm.getDefaultDisplay();
1295 DisplayMetrics metrics = new DisplayMetrics();
1296 disp.getMetrics(metrics);
1298 DisplayMetrics realMetrics = new DisplayMetrics();
1299 disp.getRealMetrics(realMetrics);
1301 DisplayInfo display = new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);
1303 if(isChromeOs(context)) {
1304 SharedPreferences pref = getSharedPreferences(context);
1305 if(!pref.getBoolean("chrome_os_context_menu_fix", true)) {
1306 display.width = realMetrics.widthPixels;
1307 display.height = realMetrics.heightPixels;
1313 boolean sameWidth = metrics.widthPixels == realMetrics.widthPixels;
1314 boolean sameHeight = metrics.heightPixels == realMetrics.heightPixels;
1316 if(sameWidth && !sameHeight) {
1317 display.width = realMetrics.widthPixels;
1318 display.height = realMetrics.heightPixels - getNavbarHeight(context);
1321 if(!sameWidth && sameHeight) {
1322 display.width = realMetrics.widthPixels - getNavbarHeight(context);
1323 display.height = realMetrics.heightPixels;
1329 public static void pinAppShortcut(Context context) {
1330 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1331 ShortcutManager mShortcutManager = context.getSystemService(ShortcutManager.class);
1333 if(mShortcutManager.isRequestPinShortcutSupported()) {
1334 ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder(context, "freeform_mode").build();
1336 mShortcutManager.requestPinShortcut(pinShortcutInfo, null);
1338 showToastLong(context, R.string.pin_shortcut_not_supported);
1340 Intent intent = getShortcutIntent(context);
1341 intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
1342 intent.putExtra("duplicate", false);
1344 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1345 homeIntent.addCategory(Intent.CATEGORY_HOME);
1346 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
1348 intent.setPackage(defaultLauncher.activityInfo.packageName);
1349 context.sendBroadcast(intent);
1351 showToast(context, R.string.shortcut_created);
1355 public static boolean shouldCollapse(Context context, boolean pendingAppLaunch) {
1356 SharedPreferences pref = getSharedPreferences(context);
1357 if(pref.getBoolean("hide_taskbar", true)) {
1358 if(isOverridingFreeformHack(context))
1359 return !LauncherHelper.getInstance().isOnHomeScreen();
1361 FreeformHackHelper helper = FreeformHackHelper.getInstance();
1362 if(pendingAppLaunch)
1363 return !helper.isFreeformHackActive();
1365 return !helper.isInFreeformWorkspace();
1371 public static boolean isOverridingFreeformHack(Context context) {
1372 SharedPreferences pref = getSharedPreferences(context);
1373 return pref.getBoolean("freeform_hack", false)
1374 && ((isChromeOs(context) && pref.getBoolean("chrome_os_context_menu_fix", true))
1375 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f));
1378 public static boolean isPlayStoreInstalled(Context context) {
1380 context.getPackageManager().getPackageInfo("com.android.vending", 0);
1382 } catch (PackageManager.NameNotFoundException e) {
1387 private static float getCurrentApiVersion() {
1388 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1389 return Float.valueOf(Build.VERSION.SDK_INT + "." + Build.VERSION.PREVIEW_SDK_INT);
1391 return (float) Build.VERSION.SDK_INT;
1394 public static boolean hasBrokenSetLaunchBoundsApi() {
1395 return getCurrentApiVersion() >= 26.0f
1396 && getCurrentApiVersion() < 28.0f
1397 && !isSamsungDevice()
1398 && !isNvidiaDevice();
1401 public static String getSecondScreenPackageName(Context context) {
1402 return getInstalledPackage(context, Arrays.asList(
1403 "com.farmerbb.secondscreen.free",
1404 "com.farmerbb.secondscreen"));
1407 // Returns the name of an installed package from a list of package names, in order of preference
1408 private static String getInstalledPackage(Context context, List<String> packageNames) {
1409 if(packageNames == null || packageNames.isEmpty())
1412 List<String> packages = packageNames instanceof ArrayList ? packageNames : new ArrayList<>(packageNames);
1413 String packageName = packages.get(0);
1416 context.getPackageManager().getPackageInfo(packageName, 0);
1418 } catch (PackageManager.NameNotFoundException e) {
1420 return getInstalledPackage(context, packages);
1424 public static boolean visualFeedbackEnabled(Context context) {
1425 SharedPreferences pref = getSharedPreferences(context);
1426 return (getCurrentApiVersion() < 26.0f || getCurrentApiVersion() >= 28.0f)
1427 && pref.getBoolean("visual_feedback", true);
1430 public static void showRecentAppsDialog(Context context) {
1431 showRecentAppsDialog(context, null, null);
1434 public static AlertDialog showRecentAppsDialog(Context context, Runnable onError, Runnable onFinish) {
1435 Runnable finalOnFinish = onFinish == null
1439 Runnable finalOnError = onError == null
1440 ? () -> showErrorDialog(context, "GET_USAGE_STATS", finalOnFinish)
1443 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isSystemApp(context)) {
1444 ApplicationInfo applicationInfo = null;
1446 applicationInfo = context.getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
1447 } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
1449 if(applicationInfo != null) {
1450 AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
1451 int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
1453 if(mode != AppOpsManager.MODE_ALLOWED) {
1454 AlertDialog.Builder builder = new AlertDialog.Builder(context);
1455 builder.setTitle(R.string.pref_header_recent_apps)
1456 .setMessage(R.string.enable_recent_apps)
1457 .setPositiveButton(R.string.action_ok, (dialog, which) -> {
1459 context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
1460 showToastLong(context, R.string.usage_stats_message);
1462 finalOnFinish.run();
1463 } catch (ActivityNotFoundException e) {
1467 .setNegativeButton(R.string.action_cancel, (dialog, which) -> finalOnFinish.run());
1469 AlertDialog dialog = builder.create();
1471 dialog.setCancelable(false);
1478 finalOnFinish.run();
1482 public static Context wrapContext(Context context) {
1483 SharedPreferences pref = getSharedPreferences(context);
1486 switch(pref.getString("theme", "light")) {
1488 theme = R.style.AppTheme;
1491 theme = R.style.AppTheme_Dark;
1495 return theme > -1 ? new ContextThemeWrapper(context, theme) : context;
1498 @SuppressLint("PackageManagerGetSignatures")
1499 public static boolean isPlayStoreRelease(Context context) {
1500 Signature playStoreSignature = new Signature(context.getString(R.string.signature));
1502 PackageManager pm = context.getPackageManager();
1503 PackageInfo info = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
1504 for(Signature signature : info.signatures) {
1505 if(signature.equals(playStoreSignature))
1508 } catch (Exception e) { /* Gracefully fail */ }
1513 public static boolean isExternalAccessDisabled(Context context) {
1514 SharedPreferences pref = getSharedPreferences(context);
1515 return !pref.getBoolean("tasker_enabled", true);
1518 public static boolean enableFreeformModeShortcut(Context context) {
1519 return canEnableFreeform()
1520 && !U.isOverridingFreeformHack(context)
1521 && !U.isChromeOs(context);
1524 public static void startForegroundService(Context context, Intent intent) {
1525 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1526 if(Settings.canDrawOverlays(context))
1527 context.startForegroundService(intent);
1529 context.startService(intent);
1532 public static int getOverlayType() {
1533 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
1534 ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
1535 : WindowManager.LayoutParams.TYPE_PHONE;
1538 public static boolean isDelegatingHomeActivity(Context context) {
1539 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1540 homeIntent.addCategory(Intent.CATEGORY_HOME);
1542 final List<ResolveInfo> listOfLaunchers = context.getPackageManager().queryIntentActivities(homeIntent, 0);
1543 for(ResolveInfo launcher : listOfLaunchers) {
1544 if(launcher.activityInfo.packageName.equals(BuildConfig.SUPPORT_APPLICATION_ID))
1551 @SuppressLint("PrivateApi")
1552 private static String getSystemProperty(String key) {
1554 Class<?> cls = Class.forName("android.os.SystemProperties");
1555 return cls.getMethod("get", String.class).invoke(null, key).toString();
1556 } catch (Exception e) {
1561 public static String getDefaultStartButtonImage(Context context) {
1562 SharedPreferences pref = getSharedPreferences(context);
1563 return pref.getBoolean("app_drawer_icon", false)