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.io.BufferedInputStream;
77 import java.io.BufferedOutputStream;
79 import java.io.FileOutputStream;
80 import java.io.IOException;
81 import java.lang.reflect.Method;
82 import java.util.ArrayList;
83 import java.util.Arrays;
84 import java.util.List;
90 private static Integer cachedRotation;
92 private static final int MAXIMIZED = 0;
93 private static final int LEFT = -1;
94 private static final int RIGHT = 1;
96 public static final int HIDDEN = 0;
97 public static final int TOP_APPS = 1;
99 // From android.app.ActivityManager.StackId
100 private static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
101 private static final int FREEFORM_WORKSPACE_STACK_ID = 2;
103 // From android.app.WindowConfiguration
104 private static final int WINDOWING_MODE_FULLSCREEN = 1;
105 private static final int WINDOWING_MODE_FREEFORM = 5;
107 public static SharedPreferences getSharedPreferences(Context context) {
108 return context.getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", Context.MODE_PRIVATE);
111 public static void showPermissionDialog(Context context) {
112 showPermissionDialog(context, null, null);
115 @TargetApi(Build.VERSION_CODES.M)
116 public static AlertDialog showPermissionDialog(Context context, Runnable onError, Runnable onFinish) {
117 Runnable finalOnFinish = onFinish == null
121 Runnable finalOnError = onError == null
122 ? () -> showErrorDialog(context, "SYSTEM_ALERT_WINDOW", finalOnFinish)
125 AlertDialog.Builder builder = new AlertDialog.Builder(context);
126 builder.setTitle(R.string.permission_dialog_title)
127 .setMessage(R.string.permission_dialog_message)
128 .setPositiveButton(R.string.action_grant_permission, (dialog, which) -> {
130 context.startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
131 Uri.parse("package:" + BuildConfig.APPLICATION_ID)));
134 } catch (ActivityNotFoundException e) {
139 AlertDialog dialog = builder.create();
141 dialog.setCancelable(false);
146 public static AlertDialog showErrorDialog(Context context, String appopCmd) {
147 return showErrorDialog(context, appopCmd, null);
150 private static AlertDialog showErrorDialog(Context context, String appopCmd, Runnable onFinish) {
151 Runnable finalOnFinish = onFinish == null
155 AlertDialog.Builder builder = new AlertDialog.Builder(context);
156 builder.setTitle(R.string.error_dialog_title)
157 .setMessage(context.getString(R.string.error_dialog_message, BuildConfig.APPLICATION_ID, appopCmd))
158 .setPositiveButton(R.string.action_ok, (dialog, which) -> finalOnFinish.run());
160 AlertDialog dialog = builder.create();
162 dialog.setCancelable(false);
167 public static void sendAccessibilityAction(Context context, int action) {
168 sendAccessibilityAction(context, action, null);
171 public static void sendAccessibilityAction(Context context, int action, Runnable onComplete) {
172 ComponentName component = new ComponentName(context, PowerMenuService.class);
173 context.getPackageManager().setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
174 PackageManager.DONT_KILL_APP);
176 boolean isAccessibilityServiceEnabled = isAccessibilityServiceEnabled(context);
178 if(!isAccessibilityServiceEnabled
179 && hasWriteSecureSettingsPermission(context)) {
180 String services = Settings.Secure.getString(context.getContentResolver(),
181 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
183 String finalServices = services == null ? "" : services;
185 String powerMenuService = new ComponentName(context, PowerMenuService.class).flattenToString();
187 if(!finalServices.contains(powerMenuService)) {
189 Settings.Secure.putString(context.getContentResolver(),
190 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
191 finalServices.isEmpty()
193 : finalServices + ":" + powerMenuService);
194 } catch (Exception e) { /* Gracefully fail */ }
197 new Handler().postDelayed(() -> {
198 Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
199 intent.putExtra("action", action);
200 LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
203 Settings.Secure.putString(context.getContentResolver(),
204 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
206 } catch (Exception e) { /* Gracefully fail */ }
208 if(onComplete != null) onComplete.run();
210 } else if(isAccessibilityServiceEnabled) {
211 Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
212 intent.putExtra("action", action);
213 LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
215 if(onComplete != null) onComplete.run();
217 launchApp(context, () -> {
218 Intent intent = new Intent(context, DummyActivity.class);
219 intent.putExtra("accessibility", true);
220 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
223 context.startActivity(intent, getActivityOptionsBundle(context, ApplicationType.APPLICATION, null));
224 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
229 public static boolean isAccessibilityServiceEnabled(Context context) {
230 String accessibilityServices = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
231 ComponentName component = new ComponentName(context, PowerMenuService.class);
233 return accessibilityServices != null
234 && (accessibilityServices.contains(component.flattenToString())
235 || accessibilityServices.contains(component.flattenToShortString()));
238 public static boolean hasWriteSecureSettingsPermission(Context context) {
239 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
240 && context.checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) == PackageManager.PERMISSION_GRANTED;
243 public static void showToast(Context context, int message) {
244 showToast(context, context.getString(message), Toast.LENGTH_SHORT);
247 public static void showToastLong(Context context, int message) {
248 showToast(context, context.getString(message), Toast.LENGTH_LONG);
251 public static void showToast(Context context, String message, int length) {
254 ToastInterface toast = DependencyUtils.createToast(context, message, length);
257 ToastHelper.getInstance().setLastToast(toast);
260 public static void cancelToast() {
261 ToastInterface toast = ToastHelper.getInstance().getLastToast();
262 if(toast != null) toast.cancel();
265 public static void startShortcut(Context context, AppEntry entry, ShortcutInfo shortcut, View view) {
275 public static void launchApp(final Context context,
276 final AppEntry entry,
277 final String windowSize,
278 final boolean launchedFromTaskbar,
279 final boolean openInNewWindow,
290 private static void launchApp(final Context context,
291 final AppEntry entry,
292 final String windowSize,
293 final boolean launchedFromTaskbar,
294 final boolean openInNewWindow,
295 final ShortcutInfo shortcut,
297 launchApp(context, launchedFromTaskbar, () -> continueLaunchingApp(context, entry,
298 windowSize, openInNewWindow, shortcut, view));
301 public static void launchApp(Context context, Runnable runnable) {
302 launchApp(context, true, runnable);
305 private static void launchApp(Context context, boolean launchedFromTaskbar, Runnable runnable) {
306 SharedPreferences pref = getSharedPreferences(context);
307 FreeformHackHelper helper = FreeformHackHelper.getInstance();
309 boolean specialLaunch = hasBrokenSetLaunchBoundsApi()
310 && helper.isInFreeformWorkspace()
311 && MenuHelper.getInstance().isContextMenuOpen();
313 boolean noAnimation = pref.getBoolean("disable_animations", false);
315 if(hasFreeformSupport(context)
316 && pref.getBoolean("freeform_hack", false)
317 && (!helper.isInFreeformWorkspace() || specialLaunch)) {
318 new Handler().postDelayed(() -> {
319 startFreeformHack(context, true);
321 new Handler().postDelayed(runnable, helper.isFreeformHackActive() ? 0 : 100);
322 }, launchedFromTaskbar ? 0 : 100);
324 new Handler().postDelayed(runnable, !launchedFromTaskbar && noAnimation ? 100 : 0);
327 public static void startFreeformHack(Context context) {
328 startFreeformHack(context, false);
331 @TargetApi(Build.VERSION_CODES.N)
332 public static void startFreeformHack(Context context, boolean checkMultiWindow) {
333 Intent freeformHackIntent = new Intent(context, InvisibleActivityFreeform.class);
334 freeformHackIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
335 | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
336 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
339 freeformHackIntent.putExtra("check_multiwindow", true);
341 if(canDrawOverlays(context, false))
342 startActivityLowerRight(context, freeformHackIntent);
345 public static void stopFreeformHack(Context context) {
346 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.FINISH_FREEFORM_ACTIVITY"));
348 if(isOverridingFreeformHack(context)) {
349 FreeformHackHelper helper = FreeformHackHelper.getInstance();
350 helper.setFreeformHackActive(false);
351 helper.setInFreeformWorkspace(false);
355 @TargetApi(Build.VERSION_CODES.N)
356 private static void continueLaunchingApp(Context context,
359 boolean openInNewWindow,
360 ShortcutInfo shortcut,
362 SharedPreferences pref = getSharedPreferences(context);
363 Intent intent = new Intent();
364 intent.setComponent(ComponentName.unflattenFromString(entry.getComponentName()));
365 intent.setAction(Intent.ACTION_MAIN);
366 intent.addCategory(Intent.CATEGORY_LAUNCHER);
367 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
368 intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
370 if(FreeformHackHelper.getInstance().isInFreeformWorkspace()
371 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1)
372 intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
374 if(pref.getBoolean("disable_animations", false))
375 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
377 if(openInNewWindow || pref.getBoolean("force_new_window", false)) {
378 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
380 ActivityInfo activityInfo = intent.resolveActivityInfo(context.getPackageManager(), 0);
381 if(activityInfo != null) {
382 switch(activityInfo.launchMode) {
383 case ActivityInfo.LAUNCH_SINGLE_TASK:
384 case ActivityInfo.LAUNCH_SINGLE_INSTANCE:
385 intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
391 ApplicationType type = getApplicationType(context, entry.getPackageName());
393 if(windowSize == null)
394 windowSize = SavedWindowSizes.getInstance(context).getWindowSize(context, entry.getPackageName());
396 Bundle bundle = getActivityOptionsBundle(context, type, windowSize, view);
398 prepareToStartActivity(context, () -> {
399 if(shortcut == null) {
400 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
401 if(entry.getUserId(context) == userManager.getSerialNumberForUser(Process.myUserHandle())) {
403 context.startActivity(intent, bundle);
404 } catch (ActivityNotFoundException e) {
405 launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context));
406 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
408 launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context));
410 launchShortcut(context, shortcut, bundle);
413 if(shouldCollapse(context, true))
414 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_TASKBAR"));
416 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_START_MENU"));
419 @TargetApi(Build.VERSION_CODES.N)
420 private static Bundle launchMode1(Context context, ApplicationType type, View view) {
421 DisplayInfo display = getDisplayInfo(context);
423 int width1 = display.width / 8;
424 int width2 = display.width - width1;
425 int height1 = display.height / 8;
426 int height2 = display.height - height1;
428 return getActivityOptions(context, type, view).setLaunchBounds(new Rect(
436 @TargetApi(Build.VERSION_CODES.N)
437 private static Bundle launchMode2(Context context, int launchType, ApplicationType type, View view) {
438 DisplayInfo display = getDisplayInfo(context);
440 int statusBarHeight = getStatusBarHeight(context);
441 String position = getTaskbarPosition(context);
443 boolean isPortrait = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
444 boolean isLandscape = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
447 int top = statusBarHeight;
448 int right = display.width;
449 int bottom = display.height;
451 int iconSize = isOverridingFreeformHack(context) && !LauncherHelper.getInstance().isOnHomeScreen()
453 : context.getResources().getDimensionPixelSize(R.dimen.icon_size);
455 if(position.contains("vertical_left"))
456 left = left + iconSize;
457 else if(position.contains("vertical_right"))
458 right = right - iconSize;
459 else if(position.contains("bottom"))
460 bottom = bottom - iconSize;
462 top = top + iconSize;
464 int halfLandscape = (right / 2) + ((iconSize / 2) * (position.contains("vertical_left") ? 1 : 0));
465 int halfPortrait = (bottom / 2) + ((iconSize / 2) * ((position.equals("top_left") || position.equals("top_right")) ? 1 : 0));
467 if(launchType == RIGHT && isLandscape)
468 left = halfLandscape;
469 else if(launchType == RIGHT && isPortrait)
471 else if(launchType == LEFT && isLandscape)
472 right = halfLandscape;
473 else if(launchType == LEFT && isPortrait)
474 bottom = halfPortrait;
476 return getActivityOptions(context, type, view)
477 .setLaunchBounds(new Rect(left, top, right, bottom)).toBundle();
480 @TargetApi(Build.VERSION_CODES.N)
481 private static Bundle launchMode3(Context context, ApplicationType type, View view) {
482 DisplayInfo display = getDisplayInfo(context);
484 int width1 = display.width / 2;
485 int width2 = context.getResources().getDimensionPixelSize(R.dimen.phone_size_width) / 2;
486 int height1 = display.height / 2;
487 int height2 = context.getResources().getDimensionPixelSize(R.dimen.phone_size_height) / 2;
489 return getActivityOptions(context, type, view).setLaunchBounds(new Rect(
497 private static void launchAndroidForWork(Context context, ComponentName componentName, Bundle bundle, long userId) {
498 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
499 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
502 launcherApps.startMainActivity(componentName, userManager.getUserForSerialNumber(userId), null, bundle);
503 } catch (ActivityNotFoundException | NullPointerException e) { /* Gracefully fail */ }
506 @TargetApi(Build.VERSION_CODES.N_MR1)
507 private static void launchShortcut(Context context, ShortcutInfo shortcut, Bundle bundle) {
508 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
510 if(launcherApps.hasShortcutHostPermission()) {
512 launcherApps.startShortcut(shortcut, null, bundle);
513 } catch (ActivityNotFoundException | NullPointerException e) { /* Gracefully fail */ }
517 private static void prepareToStartActivity(Context context, Runnable runnable) {
518 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_CONTEXT_MENU"));
520 if(!shouldLaunchTouchAbsorber(context)) {
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 public static boolean isPlayStoreRelease(Context context) {
1499 return isPlayStoreRelease(context, BuildConfig.APPLICATION_ID);
1502 @SuppressLint("PackageManagerGetSignatures")
1503 public static boolean isPlayStoreRelease(Context context, String packageName) {
1504 Signature playStoreSignature = new Signature(context.getString(R.string.signature));
1506 PackageManager pm = context.getPackageManager();
1507 PackageInfo info = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
1508 for(Signature signature : info.signatures) {
1509 if(signature.equals(playStoreSignature))
1512 } catch (Exception e) { /* Gracefully fail */ }
1517 public static boolean isExternalAccessDisabled(Context context) {
1518 SharedPreferences pref = getSharedPreferences(context);
1519 return !pref.getBoolean("tasker_enabled", true);
1522 public static boolean enableFreeformModeShortcut(Context context) {
1523 return canEnableFreeform()
1524 && !U.isOverridingFreeformHack(context)
1525 && !U.isChromeOs(context);
1528 public static void startForegroundService(Context context, Intent intent) {
1529 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1530 if(Settings.canDrawOverlays(context))
1531 context.startForegroundService(intent);
1533 context.startService(intent);
1536 public static int getOverlayType() {
1537 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
1538 ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
1539 : WindowManager.LayoutParams.TYPE_PHONE;
1542 public static boolean isDelegatingHomeActivity(Context context) {
1543 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1544 homeIntent.addCategory(Intent.CATEGORY_HOME);
1546 final List<ResolveInfo> listOfLaunchers = context.getPackageManager().queryIntentActivities(homeIntent, 0);
1547 for(ResolveInfo launcher : listOfLaunchers) {
1548 if(launcher.activityInfo.packageName.equals(BuildConfig.SUPPORT_APPLICATION_ID))
1555 @SuppressLint("PrivateApi")
1556 private static String getSystemProperty(String key) {
1558 Class<?> cls = Class.forName("android.os.SystemProperties");
1559 return cls.getMethod("get", String.class).invoke(null, key).toString();
1560 } catch (Exception e) {
1565 @SuppressWarnings("ResultOfMethodCallIgnored")
1566 public static boolean importCustomStartButtonImage(Context context, Uri uri) {
1568 File imagesDir = new File(context.getFilesDir(), "images");
1571 File importedFile = new File(imagesDir, "custom_image_new");
1572 if(importedFile.exists()) importedFile.delete();
1574 BufferedInputStream is = new BufferedInputStream(context.getContentResolver().openInputStream(uri));
1575 byte[] data = new byte[is.available()];
1577 if(data.length > 0) {
1578 BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(importedFile));
1585 File prevFile = new File(imagesDir, "custom_image");
1586 if(prevFile.exists()) prevFile.delete();
1588 importedFile.renameTo(prevFile);
1590 } catch (IOException e) {
1595 public static String getDefaultStartButtonImage(Context context) {
1596 SharedPreferences pref = getSharedPreferences(context);
1597 return pref.getBoolean("app_drawer_icon", false)
1602 private static boolean shouldLaunchTouchAbsorber(Context context) {
1603 return !FreeformHackHelper.getInstance().isTouchAbsorberActive()
1604 && isOverridingFreeformHack(context)
1605 && !isChromeOs(context)
1606 && getCurrentApiVersion() < 29.0f;
1609 public static boolean isDesktopIconsEnabled(Context context) {
1610 return !U.canBootToFreeform(context) && !U.shouldLaunchTouchAbsorber(context);