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) {
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 boolean shouldLaunchTouchAbsorber =
521 !FreeformHackHelper.getInstance().isTouchAbsorberActive()
522 && isOverridingFreeformHack(context)
523 && !isChromeOs(context)
524 && getCurrentApiVersion() < 29.0f;
526 if(!shouldLaunchTouchAbsorber) {
531 startTouchAbsorberActivity(context);
532 new Handler().postDelayed(runnable, 100);
535 public static void startActivityMaximized(Context context, Intent intent) {
536 Bundle bundle = launchMode2(context, MAXIMIZED, ApplicationType.CONTEXT_MENU, null);
537 prepareToStartActivity(context, () -> context.startActivity(intent, bundle));
540 @TargetApi(Build.VERSION_CODES.N)
541 public static void startActivityLowerRight(Context context, Intent intent) {
542 DisplayInfo display = getDisplayInfo(context);
544 context.startActivity(intent,
545 getActivityOptions(context, ApplicationType.FREEFORM_HACK, null)
546 .setLaunchBounds(new Rect(
552 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
555 @TargetApi(Build.VERSION_CODES.N)
556 public static void startTouchAbsorberActivity(Context context) {
557 String position = getTaskbarPosition(context);
558 DisplayInfo display = getDisplayInfo(context);
562 int right = display.width;
563 int bottom = display.height;
565 int iconSize = context.getResources().getDimensionPixelSize(R.dimen.icon_size);
567 if(position.contains("vertical_left"))
569 else if(position.contains("vertical_right"))
570 left = right - iconSize;
571 else if(position.contains("bottom"))
572 top = bottom - iconSize;
576 Intent intent = new Intent(context, TouchAbsorberActivity.class);
577 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
578 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
581 context.startActivity(intent,
582 getActivityOptions(context, ApplicationType.FREEFORM_HACK, null)
583 .setLaunchBounds(new Rect(left, top, right, bottom)).toBundle());
584 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
587 public static void startContextMenuActivity(Context context, Bundle args) {
588 SharedPreferences pref = getSharedPreferences(context);
589 Intent intent = null;
591 switch(pref.getString("theme", "light")) {
593 intent = new Intent(context, ContextMenuActivity.class);
596 intent = new Intent(context, ContextMenuActivityDark.class);
601 intent.putExtra("args", args);
602 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
605 if(hasFreeformSupport(context) && FreeformHackHelper.getInstance().isInFreeformWorkspace()) {
606 DisplayInfo display = getDisplayInfo(context);
608 if(intent != null && hasBrokenSetLaunchBoundsApi())
609 intent.putExtra("context_menu_fix", true);
611 context.startActivity(intent,
612 getActivityOptions(context, ApplicationType.CONTEXT_MENU, null)
614 new Rect(0, 0, display.width, display.height)
617 context.startActivity(intent);
620 public static void checkForUpdates(Context context) {
622 if(isPlayStoreRelease(context)) {
623 if(BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
624 && !isPlayStoreInstalled(context))
625 url = "https://github.com/farmerbb/Taskbar/releases";
627 url = "https://play.google.com/store/apps/details?id=" + BuildConfig.APPLICATION_ID;
629 url = "https://f-droid.org/repository/browse/?fdid=" + BuildConfig.APPLICATION_ID;
631 Intent intent = new Intent(Intent.ACTION_VIEW);
632 intent.setData(Uri.parse(url));
633 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
636 context.startActivity(intent);
637 } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
640 public static boolean launcherIsDefault(Context context) {
641 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
642 homeIntent.addCategory(Intent.CATEGORY_HOME);
643 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
645 return defaultLauncher.activityInfo.packageName.equals(BuildConfig.APPLICATION_ID);
648 public static void setCachedRotation(int cachedRotation) {
649 U.cachedRotation = cachedRotation;
652 public static String getTaskbarPosition(Context context) {
653 SharedPreferences pref = getSharedPreferences(context);
654 String position = pref.getString("position", "bottom_left");
656 if(pref.getBoolean("anchor", false)) {
657 WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
658 int rotation = cachedRotation != null ? cachedRotation : windowManager.getDefaultDisplay().getRotation();
663 case Surface.ROTATION_0:
664 return "bottom_left";
665 case Surface.ROTATION_90:
666 return "bottom_vertical_right";
667 case Surface.ROTATION_180:
669 case Surface.ROTATION_270:
670 return "top_vertical_left";
673 case "bottom_vertical_left":
675 case Surface.ROTATION_0:
676 return "bottom_vertical_left";
677 case Surface.ROTATION_90:
678 return "bottom_right";
679 case Surface.ROTATION_180:
680 return "top_vertical_right";
681 case Surface.ROTATION_270:
687 case Surface.ROTATION_0:
688 return "bottom_right";
689 case Surface.ROTATION_90:
690 return "top_vertical_right";
691 case Surface.ROTATION_180:
693 case Surface.ROTATION_270:
694 return "bottom_vertical_left";
697 case "bottom_vertical_right":
699 case Surface.ROTATION_0:
700 return "bottom_vertical_right";
701 case Surface.ROTATION_90:
703 case Surface.ROTATION_180:
704 return "top_vertical_left";
705 case Surface.ROTATION_270:
706 return "bottom_left";
711 case Surface.ROTATION_0:
713 case Surface.ROTATION_90:
714 return "bottom_vertical_left";
715 case Surface.ROTATION_180:
716 return "bottom_right";
717 case Surface.ROTATION_270:
718 return "top_vertical_right";
721 case "top_vertical_left":
723 case Surface.ROTATION_0:
724 return "top_vertical_left";
725 case Surface.ROTATION_90:
726 return "bottom_left";
727 case Surface.ROTATION_180:
728 return "bottom_vertical_right";
729 case Surface.ROTATION_270:
735 case Surface.ROTATION_0:
737 case Surface.ROTATION_90:
738 return "top_vertical_left";
739 case Surface.ROTATION_180:
740 return "bottom_left";
741 case Surface.ROTATION_270:
742 return "bottom_vertical_right";
745 case "top_vertical_right":
747 case Surface.ROTATION_0:
748 return "top_vertical_right";
749 case Surface.ROTATION_90:
751 case Surface.ROTATION_180:
752 return "bottom_vertical_left";
753 case Surface.ROTATION_270:
754 return "bottom_right";
763 private static int getMaxNumOfColumns(Context context) {
764 SharedPreferences pref = getSharedPreferences(context);
765 DisplayInfo display = getDisplayInfo(context);
766 float density = display.density / 160;
767 float baseTaskbarSize = getBaseTaskbarSizeFloat(context) / density;
768 int numOfColumns = 0;
770 float maxScreenSize = getTaskbarPosition(context).contains("vertical")
771 ? (display.height - getStatusBarHeight(context)) / density
772 : display.width / density;
774 float iconSize = context.getResources().getDimension(R.dimen.icon_size) / density;
776 int userMaxNumOfColumns = Integer.valueOf(pref.getString("max_num_of_recents", "10"));
778 while(baseTaskbarSize + iconSize < maxScreenSize
779 && numOfColumns < userMaxNumOfColumns) {
780 baseTaskbarSize = baseTaskbarSize + iconSize;
787 public static int getMaxNumOfEntries(Context context) {
788 SharedPreferences pref = getSharedPreferences(context);
789 return pref.getBoolean("disable_scrolling_list", false)
790 ? getMaxNumOfColumns(context)
791 : Integer.valueOf(pref.getString("max_num_of_recents", "10"));
794 public static int getStatusBarHeight(Context context) {
795 return getSystemDimen(context, "status_bar_height");
798 private static int getNavbarHeight(Context context) {
799 return getSystemDimen(context, "navigation_bar_height");
802 private static int getSystemDimen(Context context, String id) {
804 int resourceId = context.getResources().getIdentifier(id, "dimen", "android");
806 value = context.getResources().getDimensionPixelSize(resourceId);
811 public static void refreshPinnedIcons(Context context) {
812 IconCache.getInstance(context).clearCache();
814 PinnedBlockedApps pba = PinnedBlockedApps.getInstance(context);
815 List<AppEntry> pinnedAppsList = new ArrayList<>(pba.getPinnedApps());
816 List<AppEntry> blockedAppsList = new ArrayList<>(pba.getBlockedApps());
817 PackageManager pm = context.getPackageManager();
821 for(AppEntry entry : pinnedAppsList) {
822 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
823 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
825 final List<UserHandle> userHandles = userManager.getUserProfiles();
826 LauncherActivityInfo appInfo = null;
828 for(UserHandle handle : userHandles) {
829 List<LauncherActivityInfo> list = launcherApps.getActivityList(entry.getPackageName(), handle);
830 if(!list.isEmpty()) {
831 // Google App workaround
832 if(!entry.getPackageName().equals("com.google.android.googlequicksearchbox"))
833 appInfo = list.get(0);
835 boolean added = false;
836 for(LauncherActivityInfo info : list) {
837 if(info.getName().equals("com.google.android.googlequicksearchbox.SearchActivity")) {
843 if(!added) appInfo = list.get(0);
850 if(appInfo != null) {
851 AppEntry newEntry = new AppEntry(
852 entry.getPackageName(),
853 entry.getComponentName(),
855 IconCache.getInstance(context).getIcon(context, pm, appInfo),
858 newEntry.setUserId(entry.getUserId(context));
859 pba.addPinnedApp(context, newEntry);
863 for(AppEntry entry : blockedAppsList) {
864 pba.addBlockedApp(context, entry);
868 public static Intent getShortcutIntent(Context context) {
869 Intent shortcutIntent = new Intent(context, ShortcutActivity.class);
870 shortcutIntent.setAction(Intent.ACTION_MAIN);
871 shortcutIntent.putExtra("is_launching_shortcut", true);
873 Intent intent = new Intent();
874 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
875 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.mipmap.ic_freeform_mode));
876 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, context.getString(R.string.pref_header_freeform));
881 public static Intent getStartStopIntent(Context context) {
882 Intent shortcutIntent = new Intent(context, StartTaskbarActivity.class);
883 shortcutIntent.setAction(Intent.ACTION_MAIN);
884 shortcutIntent.putExtra("is_launching_shortcut", true);
886 Intent intent = new Intent();
887 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
888 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.mipmap.ic_launcher));
889 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, context.getString(R.string.start_taskbar));
894 public static boolean canEnableFreeform() {
895 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
898 @TargetApi(Build.VERSION_CODES.N)
899 public static boolean hasFreeformSupport(Context context) {
900 return canEnableFreeform()
901 && (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
902 || Settings.Global.getInt(context.getContentResolver(), "enable_freeform_support", 0) != 0
903 || (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
904 && Settings.Global.getInt(context.getContentResolver(), "force_resizable_activities", 0) != 0));
907 public static boolean canBootToFreeform(Context context) {
908 SharedPreferences pref = getSharedPreferences(context);
909 return hasFreeformSupport(context)
910 && pref.getBoolean("freeform_hack", false)
911 && !isOverridingFreeformHack(context);
914 public static boolean isSamsungDevice() {
915 return Build.MANUFACTURER.equalsIgnoreCase("Samsung");
918 private static boolean isNvidiaDevice() {
919 return Build.MANUFACTURER.equalsIgnoreCase("NVIDIA");
922 public static boolean isServiceRunning(Context context, Class<? extends Service> cls) {
923 if(LauncherHelper.getInstance().isOnHomeScreen()
924 && FeatureFlags.homeActivityUIHost()
925 && (cls.equals(TaskbarService.class)
926 || cls.equals(StartMenuService.class)
927 || cls.equals(DashboardService.class)))
930 return isServiceRunning(context, cls.getName());
933 private static boolean isServiceRunning(Context context, String className) {
934 ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
935 for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
936 if(className.equals(service.service.getClassName()))
943 public static int getBackgroundTint(Context context) {
944 SharedPreferences pref = getSharedPreferences(context);
946 // Import old background tint preference
947 if(pref.contains("show_background")) {
948 SharedPreferences.Editor editor = pref.edit();
950 if(!pref.getBoolean("show_background", true))
951 editor.putInt("background_tint", Color.TRANSPARENT).apply();
953 editor.remove("show_background");
957 return pref.getInt("background_tint", context.getResources().getInteger(R.integer.translucent_gray));
960 public static int getAccentColor(Context context) {
961 SharedPreferences pref = getSharedPreferences(context);
962 return pref.getInt("accent_color", context.getResources().getInteger(R.integer.translucent_white));
965 @TargetApi(Build.VERSION_CODES.M)
966 public static boolean canDrawOverlays(Context context, boolean forHomeScreen) {
967 return (forHomeScreen && FeatureFlags.homeActivityUIHost() && !canBootToFreeform(context))
968 || Build.VERSION.SDK_INT < Build.VERSION_CODES.M
969 || Settings.canDrawOverlays(context);
972 public static boolean isGame(Context context, String packageName) {
973 SharedPreferences pref = getSharedPreferences(context);
974 if(pref.getBoolean("launch_games_fullscreen", true)) {
975 PackageManager pm = context.getPackageManager();
978 ApplicationInfo info = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
979 return (info.flags & ApplicationInfo.FLAG_IS_GAME) != 0 || (info.metaData != null && info.metaData.getBoolean("isGame", false));
980 } catch (PackageManager.NameNotFoundException e) {
987 private static ActivityOptions getActivityOptions(View view) {
988 return getActivityOptions(null, null, view);
991 @TargetApi(Build.VERSION_CODES.N)
992 private static ActivityOptions getActivityOptions(Context context, ApplicationType applicationType, View view) {
993 ActivityOptions options;
995 options = ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight());
997 options = ActivityOptions.makeBasic();
999 if(applicationType == null)
1004 switch(applicationType) {
1006 if(FreeformHackHelper.getInstance().isFreeformHackActive())
1007 stackId = getFreeformWindowModeId();
1009 stackId = getFullscreenWindowModeId();
1012 stackId = getFullscreenWindowModeId();
1015 stackId = getFreeformWindowModeId();
1018 if(hasBrokenSetLaunchBoundsApi()
1019 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f))
1020 stackId = getFullscreenWindowModeId();
1025 Method method = ActivityOptions.class.getMethod(getWindowingModeMethodName(), int.class);
1026 method.invoke(options, stackId);
1027 } catch (Exception e) { /* Gracefully fail */ }
1032 private static int getFullscreenWindowModeId() {
1033 if(getCurrentApiVersion() >= 28.0f)
1034 return WINDOWING_MODE_FULLSCREEN;
1036 return FULLSCREEN_WORKSPACE_STACK_ID;
1039 private static int getFreeformWindowModeId() {
1040 if(getCurrentApiVersion() >= 28.0f)
1041 return WINDOWING_MODE_FREEFORM;
1043 return FREEFORM_WORKSPACE_STACK_ID;
1046 private static String getWindowingModeMethodName() {
1047 if(getCurrentApiVersion() >= 28.0f)
1048 return "setLaunchWindowingMode";
1050 return "setLaunchStackId";
1053 public static Bundle getActivityOptionsBundle(Context context, ApplicationType type, View view) {
1054 SharedPreferences pref = getSharedPreferences(context);
1056 return getActivityOptionsBundle(context, type, pref.getString("window_size", "standard"), view);
1059 private static Bundle getActivityOptionsBundle(Context context, ApplicationType type, String windowSize, View view) {
1060 SharedPreferences pref = getSharedPreferences(context);
1061 if(!canEnableFreeform() || !pref.getBoolean("freeform_hack", false))
1062 return getActivityOptions(view).toBundle();
1064 switch(windowSize) {
1066 return launchMode1(context, type, view);
1068 return launchMode2(context, MAXIMIZED, type, view);
1070 return launchMode2(context, LEFT, type, view);
1072 return launchMode2(context, RIGHT, type, view);
1074 return launchMode3(context, type, view);
1077 return getActivityOptions(context, type, view).toBundle();
1080 private static ApplicationType getApplicationType(Context context, String packageName) {
1081 return isGame(context, packageName) ? ApplicationType.GAME : ApplicationType.APPLICATION;
1084 public static boolean isSystemApp(Context context) {
1086 ApplicationInfo info = context.getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
1087 int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
1088 return (info.flags & mask) != 0;
1089 } catch (PackageManager.NameNotFoundException e) {
1094 public static boolean isChromeOs(Context context) {
1095 return context.getPackageManager().hasSystemFeature("org.chromium.arc");
1098 public static boolean isBlissOs(Context context) {
1099 boolean validBlissOsBuildProp = false;
1101 String blissVersion = getSystemProperty("ro.bliss.version");
1102 if(blissVersion != null && !blissVersion.isEmpty())
1103 validBlissOsBuildProp = true;
1105 String buildUser = getSystemProperty("ro.build.user");
1106 if(buildUser != null && buildUser.equals("electrikjesus"))
1107 validBlissOsBuildProp = true;
1109 return validBlissOsBuildProp
1110 && BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
1111 && isSystemApp(context);
1114 public static boolean isLauncherPermanentlyEnabled(Context context) {
1115 if(BuildConfig.APPLICATION_ID.equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
1118 PackageManager pm = context.getPackageManager();
1120 pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1121 return pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, BuildConfig.APPLICATION_ID) == PackageManager.SIGNATURE_MATCH
1122 && BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
1123 && isSystemApp(context);
1124 } catch (PackageManager.NameNotFoundException e) {
1129 public static int getBaseTaskbarSize(Context context) {
1130 return Math.round(getBaseTaskbarSizeFloat(context));
1133 private static float getBaseTaskbarSizeFloat(Context context) {
1134 SharedPreferences pref = getSharedPreferences(context);
1135 float baseTaskbarSize = context.getResources().getDimension(R.dimen.base_taskbar_size);
1136 boolean navbarButtonsEnabled = false;
1138 if(pref.getBoolean("dashboard", false))
1139 baseTaskbarSize += context.getResources().getDimension(R.dimen.dashboard_button_size);
1141 if(pref.getBoolean("button_back", false)) {
1142 navbarButtonsEnabled = true;
1143 baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
1146 if(pref.getBoolean("button_home", false)) {
1147 navbarButtonsEnabled = true;
1148 baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
1151 if(pref.getBoolean("button_recents", false)) {
1152 navbarButtonsEnabled = true;
1153 baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
1156 if(navbarButtonsEnabled)
1157 baseTaskbarSize += context.getResources().getDimension(R.dimen.navbar_buttons_margin);
1159 return baseTaskbarSize;
1162 private static void startTaskbarService(Context context, boolean fullRestart) {
1163 context.startService(new Intent(context, TaskbarService.class));
1164 context.startService(new Intent(context, StartMenuService.class));
1165 context.startService(new Intent(context, DashboardService.class));
1166 if(fullRestart) context.startService(new Intent(context, NotificationService.class));
1169 private static void stopTaskbarService(Context context, boolean fullRestart) {
1170 context.stopService(new Intent(context, TaskbarService.class));
1171 context.stopService(new Intent(context, StartMenuService.class));
1172 context.stopService(new Intent(context, DashboardService.class));
1173 if(fullRestart) context.stopService(new Intent(context, NotificationService.class));
1176 public static void restartTaskbar(Context context) {
1177 SharedPreferences pref = getSharedPreferences(context);
1178 if(pref.getBoolean("taskbar_active", false) && !pref.getBoolean("is_hidden", false)) {
1180 .putBoolean("is_restarting", true)
1181 .putBoolean("skip_auto_hide_navbar", true)
1184 stopTaskbarService(context, true);
1185 startTaskbarService(context, true);
1186 } else if(isServiceRunning(context, StartMenuService.class)) {
1187 pref.edit().putBoolean("skip_auto_hide_navbar", true).apply();
1189 stopTaskbarService(context, false);
1190 startTaskbarService(context, false);
1193 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.RESTART"));
1196 public static void restartNotificationService(Context context) {
1197 if(isServiceRunning(context, NotificationService.class)) {
1198 SharedPreferences pref = getSharedPreferences(context);
1199 pref.edit().putBoolean("is_restarting", true).apply();
1201 Intent intent = new Intent(context, NotificationService.class);
1202 context.stopService(intent);
1203 context.startService(intent);
1207 public static void showHideNavigationBar(Context context, boolean show) {
1208 // Show or hide the system navigation bar on Bliss-x86
1210 if(getCurrentApiVersion() >= 28.0f)
1211 Settings.Secure.putInt(context.getContentResolver(), "navigation_bar_visible", show ? 1 : 0);
1213 Settings.System.putInt(context.getContentResolver(), "navigation_bar_show", show ? 1 : 0);
1214 } catch (Exception e) { /* Gracefully fail */ }
1217 public static void initPrefs(Context context) {
1218 // On smaller-screened devices, set "Grid" as the default start menu layout
1219 SharedPreferences pref = getSharedPreferences(context);
1220 if(context.getApplicationContext().getResources().getConfiguration().smallestScreenWidthDp < 720
1221 && pref.getString("start_menu_layout", "null").equals("null")) {
1222 pref.edit().putString("start_menu_layout", "grid").apply();
1225 // Enable freeform hack automatically on supported devices
1226 if(canEnableFreeform()) {
1227 if(!pref.getBoolean("freeform_hack_override", false)) {
1229 .putBoolean("freeform_hack", hasFreeformSupport(context) && !isSamsungDevice())
1230 .putBoolean("save_window_sizes", false)
1231 .putBoolean("freeform_hack_override", true)
1233 } else if(!hasFreeformSupport(context)) {
1234 pref.edit().putBoolean("freeform_hack", false).apply();
1236 stopFreeformHack(context);
1239 boolean freeformWasEnabled = pref.getBoolean("freeform_hack", false)
1240 || pref.getBoolean("show_freeform_disabled_message", false);
1243 .putBoolean("freeform_hack", false)
1244 .putBoolean("show_freeform_disabled_message", freeformWasEnabled)
1247 SavedWindowSizes.getInstance(context).clear(context);
1248 stopFreeformHack(context);
1251 // Customizations for BlissOS
1252 if(isBlissOs(context) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1253 && !pref.getBoolean("bliss_os_prefs", false)) {
1254 SharedPreferences.Editor editor = pref.edit();
1256 if(hasFreeformSupport(context)) {
1257 editor.putBoolean("freeform_hack", true);
1260 editor.putString("recents_amount", "running_apps_only");
1261 editor.putString("refresh_frequency", "0");
1262 editor.putString("max_num_of_recents", "2147483647");
1263 editor.putString("sort_order", "true");
1264 editor.putString("window_size", "phone_size");
1265 editor.putString("start_button_image", "app_logo");
1266 editor.putBoolean("full_length", true);
1267 editor.putBoolean("dashboard", true);
1268 editor.putBoolean("button_back", true);
1269 editor.putBoolean("button_home", true);
1270 editor.putBoolean("button_recents", true);
1271 editor.putBoolean("auto_hide_navbar", true);
1272 // editor.putBoolean("shortcut_icon", false);
1273 editor.putBoolean("bliss_os_prefs", true);
1277 // Customizations for Android-x86 devices (non-Bliss)
1278 if(BuildConfig.APPLICATION_ID.equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
1279 && isSystemApp(context)
1280 && !pref.getBoolean("android_x86_prefs", false)) {
1282 .putString("recents_amount", "running_apps_only")
1283 .putString("refresh_frequency", "0")
1284 .putString("max_num_of_recents", "2147483647")
1285 .putString("sort_order", "true")
1286 .putString("window_size", "phone_size")
1287 .putBoolean("full_length", true)
1288 .putBoolean("dashboard", true)
1289 // .putBoolean("shortcut_icon", false)
1290 .putBoolean("android_x86_prefs", true)
1295 public static DisplayInfo getDisplayInfo(Context context) {
1296 context = context.getApplicationContext();
1298 WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
1299 Display disp = wm.getDefaultDisplay();
1301 DisplayMetrics metrics = new DisplayMetrics();
1302 disp.getMetrics(metrics);
1304 DisplayMetrics realMetrics = new DisplayMetrics();
1305 disp.getRealMetrics(realMetrics);
1307 DisplayInfo display = new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);
1309 if(isChromeOs(context)) {
1310 SharedPreferences pref = getSharedPreferences(context);
1311 if(!pref.getBoolean("chrome_os_context_menu_fix", true)) {
1312 display.width = realMetrics.widthPixels;
1313 display.height = realMetrics.heightPixels;
1319 boolean sameWidth = metrics.widthPixels == realMetrics.widthPixels;
1320 boolean sameHeight = metrics.heightPixels == realMetrics.heightPixels;
1322 if(sameWidth && !sameHeight) {
1323 display.width = realMetrics.widthPixels;
1324 display.height = realMetrics.heightPixels - getNavbarHeight(context);
1327 if(!sameWidth && sameHeight) {
1328 display.width = realMetrics.widthPixels - getNavbarHeight(context);
1329 display.height = realMetrics.heightPixels;
1335 public static void pinAppShortcut(Context context) {
1336 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1337 ShortcutManager mShortcutManager = context.getSystemService(ShortcutManager.class);
1339 if(mShortcutManager.isRequestPinShortcutSupported()) {
1340 ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder(context, "freeform_mode").build();
1342 mShortcutManager.requestPinShortcut(pinShortcutInfo, null);
1344 showToastLong(context, R.string.pin_shortcut_not_supported);
1346 Intent intent = getShortcutIntent(context);
1347 intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
1348 intent.putExtra("duplicate", false);
1350 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1351 homeIntent.addCategory(Intent.CATEGORY_HOME);
1352 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
1354 intent.setPackage(defaultLauncher.activityInfo.packageName);
1355 context.sendBroadcast(intent);
1357 showToast(context, R.string.shortcut_created);
1361 public static boolean shouldCollapse(Context context, boolean pendingAppLaunch) {
1362 SharedPreferences pref = getSharedPreferences(context);
1363 if(pref.getBoolean("hide_taskbar", true)) {
1364 if(isOverridingFreeformHack(context))
1365 return !LauncherHelper.getInstance().isOnHomeScreen();
1367 FreeformHackHelper helper = FreeformHackHelper.getInstance();
1368 if(pendingAppLaunch)
1369 return !helper.isFreeformHackActive();
1371 return !helper.isInFreeformWorkspace();
1377 public static boolean isOverridingFreeformHack(Context context) {
1378 SharedPreferences pref = getSharedPreferences(context);
1379 return pref.getBoolean("freeform_hack", false)
1380 && ((isChromeOs(context) && pref.getBoolean("chrome_os_context_menu_fix", true))
1381 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f));
1384 public static boolean isPlayStoreInstalled(Context context) {
1386 context.getPackageManager().getPackageInfo("com.android.vending", 0);
1388 } catch (PackageManager.NameNotFoundException e) {
1393 private static float getCurrentApiVersion() {
1394 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1395 return Float.valueOf(Build.VERSION.SDK_INT + "." + Build.VERSION.PREVIEW_SDK_INT);
1397 return (float) Build.VERSION.SDK_INT;
1400 public static boolean hasBrokenSetLaunchBoundsApi() {
1401 return getCurrentApiVersion() >= 26.0f
1402 && getCurrentApiVersion() < 28.0f
1403 && !isSamsungDevice()
1404 && !isNvidiaDevice();
1407 public static String getSecondScreenPackageName(Context context) {
1408 return getInstalledPackage(context, Arrays.asList(
1409 "com.farmerbb.secondscreen.free",
1410 "com.farmerbb.secondscreen"));
1413 // Returns the name of an installed package from a list of package names, in order of preference
1414 private static String getInstalledPackage(Context context, List<String> packageNames) {
1415 if(packageNames == null || packageNames.isEmpty())
1418 List<String> packages = packageNames instanceof ArrayList ? packageNames : new ArrayList<>(packageNames);
1419 String packageName = packages.get(0);
1422 context.getPackageManager().getPackageInfo(packageName, 0);
1424 } catch (PackageManager.NameNotFoundException e) {
1426 return getInstalledPackage(context, packages);
1430 public static boolean visualFeedbackEnabled(Context context) {
1431 SharedPreferences pref = getSharedPreferences(context);
1432 return (getCurrentApiVersion() < 26.0f || getCurrentApiVersion() >= 28.0f)
1433 && pref.getBoolean("visual_feedback", true);
1436 public static void showRecentAppsDialog(Context context) {
1437 showRecentAppsDialog(context, null, null);
1440 public static AlertDialog showRecentAppsDialog(Context context, Runnable onError, Runnable onFinish) {
1441 Runnable finalOnFinish = onFinish == null
1445 Runnable finalOnError = onError == null
1446 ? () -> showErrorDialog(context, "GET_USAGE_STATS", finalOnFinish)
1449 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isSystemApp(context)) {
1450 ApplicationInfo applicationInfo = null;
1452 applicationInfo = context.getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
1453 } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
1455 if(applicationInfo != null) {
1456 AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
1457 int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
1459 if(mode != AppOpsManager.MODE_ALLOWED) {
1460 AlertDialog.Builder builder = new AlertDialog.Builder(context);
1461 builder.setTitle(R.string.pref_header_recent_apps)
1462 .setMessage(R.string.enable_recent_apps)
1463 .setPositiveButton(R.string.action_ok, (dialog, which) -> {
1465 context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
1466 showToastLong(context, R.string.usage_stats_message);
1468 finalOnFinish.run();
1469 } catch (ActivityNotFoundException e) {
1473 .setNegativeButton(R.string.action_cancel, (dialog, which) -> finalOnFinish.run());
1475 AlertDialog dialog = builder.create();
1477 dialog.setCancelable(false);
1484 finalOnFinish.run();
1488 public static Context wrapContext(Context context) {
1489 SharedPreferences pref = getSharedPreferences(context);
1492 switch(pref.getString("theme", "light")) {
1494 theme = R.style.AppTheme;
1497 theme = R.style.AppTheme_Dark;
1501 return theme > -1 ? new ContextThemeWrapper(context, theme) : context;
1504 public static boolean isPlayStoreRelease(Context context) {
1505 return isPlayStoreRelease(context, BuildConfig.APPLICATION_ID);
1508 @SuppressLint("PackageManagerGetSignatures")
1509 public static boolean isPlayStoreRelease(Context context, String packageName) {
1510 Signature playStoreSignature = new Signature(context.getString(R.string.signature));
1512 PackageManager pm = context.getPackageManager();
1513 PackageInfo info = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
1514 for(Signature signature : info.signatures) {
1515 if(signature.equals(playStoreSignature))
1518 } catch (Exception e) { /* Gracefully fail */ }
1523 public static boolean isExternalAccessDisabled(Context context) {
1524 SharedPreferences pref = getSharedPreferences(context);
1525 return !pref.getBoolean("tasker_enabled", true);
1528 public static boolean enableFreeformModeShortcut(Context context) {
1529 return canEnableFreeform()
1530 && !U.isOverridingFreeformHack(context)
1531 && !U.isChromeOs(context);
1534 public static void startForegroundService(Context context, Intent intent) {
1535 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1536 if(Settings.canDrawOverlays(context))
1537 context.startForegroundService(intent);
1539 context.startService(intent);
1542 public static int getOverlayType() {
1543 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
1544 ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
1545 : WindowManager.LayoutParams.TYPE_PHONE;
1548 public static boolean isDelegatingHomeActivity(Context context) {
1549 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1550 homeIntent.addCategory(Intent.CATEGORY_HOME);
1552 final List<ResolveInfo> listOfLaunchers = context.getPackageManager().queryIntentActivities(homeIntent, 0);
1553 for(ResolveInfo launcher : listOfLaunchers) {
1554 if(launcher.activityInfo.packageName.equals(BuildConfig.SUPPORT_APPLICATION_ID))
1561 @SuppressLint("PrivateApi")
1562 private static String getSystemProperty(String key) {
1564 Class<?> cls = Class.forName("android.os.SystemProperties");
1565 return cls.getMethod("get", String.class).invoke(null, key).toString();
1566 } catch (Exception e) {
1571 @SuppressWarnings("ResultOfMethodCallIgnored")
1572 public static boolean importCustomStartButtonImage(Context context, Uri uri) {
1574 File imagesDir = new File(context.getFilesDir(), "images");
1577 File importedFile = new File(imagesDir, "custom_image_new");
1578 if(importedFile.exists()) importedFile.delete();
1580 BufferedInputStream is = new BufferedInputStream(context.getContentResolver().openInputStream(uri));
1581 byte[] data = new byte[is.available()];
1583 if(data.length > 0) {
1584 BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(importedFile));
1591 File prevFile = new File(imagesDir, "custom_image");
1592 if(prevFile.exists()) prevFile.delete();
1594 importedFile.renameTo(prevFile);
1596 } catch (IOException e) {
1601 public static String getDefaultStartButtonImage(Context context) {
1602 SharedPreferences pref = getSharedPreferences(context);
1603 return pref.getBoolean("app_drawer_icon", false)