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.getApplicationContext(), 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))
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 boolean realOpenInNewWindow = openInNewWindow || pref.getBoolean("force_new_window", false);
378 if(realOpenInNewWindow) {
379 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
381 ActivityInfo activityInfo = intent.resolveActivityInfo(context.getPackageManager(), 0);
382 if(activityInfo != null) {
383 switch(activityInfo.launchMode) {
384 case ActivityInfo.LAUNCH_SINGLE_TASK:
385 case ActivityInfo.LAUNCH_SINGLE_INSTANCE:
386 intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
392 ApplicationType type = getApplicationType(context, entry.getPackageName());
394 if(windowSize == null)
395 windowSize = SavedWindowSizes.getInstance(context).getWindowSize(context, entry.getPackageName());
397 Bundle bundle = getActivityOptionsBundle(context, type, windowSize, view);
399 prepareToStartActivity(context, realOpenInNewWindow, () -> {
400 if(shortcut == null) {
401 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
402 if(entry.getUserId(context) == userManager.getSerialNumberForUser(Process.myUserHandle())) {
404 context.startActivity(intent, bundle);
405 } catch (ActivityNotFoundException e) {
406 launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context));
407 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
409 launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context));
411 launchShortcut(context, shortcut, bundle);
414 if(shouldCollapse(context, true))
415 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_TASKBAR"));
417 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_START_MENU"));
420 @TargetApi(Build.VERSION_CODES.N)
421 private static Bundle launchMode1(Context context, ApplicationType type, View view) {
422 DisplayInfo display = getDisplayInfo(context);
424 int width1 = display.width / 8;
425 int width2 = display.width - width1;
426 int height1 = display.height / 8;
427 int height2 = display.height - height1;
429 return getActivityOptions(context, type, view).setLaunchBounds(new Rect(
437 @TargetApi(Build.VERSION_CODES.N)
438 private static Bundle launchMode2(Context context, int launchType, ApplicationType type, View view) {
439 DisplayInfo display = getDisplayInfo(context);
441 int statusBarHeight = getStatusBarHeight(context);
442 String position = getTaskbarPosition(context);
444 boolean isPortrait = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
445 boolean isLandscape = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
448 int top = statusBarHeight;
449 int right = display.width;
450 int bottom = display.height;
452 int iconSize = isOverridingFreeformHack(context) && !LauncherHelper.getInstance().isOnHomeScreen()
454 : context.getResources().getDimensionPixelSize(R.dimen.icon_size);
456 if(position.contains("vertical_left"))
457 left = left + iconSize;
458 else if(position.contains("vertical_right"))
459 right = right - iconSize;
460 else if(position.contains("bottom"))
461 bottom = bottom - iconSize;
463 top = top + iconSize;
465 int halfLandscape = (right / 2) + ((iconSize / 2) * (position.contains("vertical_left") ? 1 : 0));
466 int halfPortrait = (bottom / 2) + ((iconSize / 2) * ((position.equals("top_left") || position.equals("top_right")) ? 1 : 0));
468 if(launchType == RIGHT && isLandscape)
469 left = halfLandscape;
470 else if(launchType == RIGHT && isPortrait)
472 else if(launchType == LEFT && isLandscape)
473 right = halfLandscape;
474 else if(launchType == LEFT && isPortrait)
475 bottom = halfPortrait;
477 return getActivityOptions(context, type, view)
478 .setLaunchBounds(new Rect(left, top, right, bottom)).toBundle();
481 @TargetApi(Build.VERSION_CODES.N)
482 private static Bundle launchMode3(Context context, ApplicationType type, View view) {
483 DisplayInfo display = getDisplayInfo(context);
485 int width1 = display.width / 2;
486 int width2 = context.getResources().getDimensionPixelSize(R.dimen.phone_size_width) / 2;
487 int height1 = display.height / 2;
488 int height2 = context.getResources().getDimensionPixelSize(R.dimen.phone_size_height) / 2;
490 return getActivityOptions(context, type, view).setLaunchBounds(new Rect(
498 private static void launchAndroidForWork(Context context, ComponentName componentName, Bundle bundle, long userId) {
499 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
500 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
503 launcherApps.startMainActivity(componentName, userManager.getUserForSerialNumber(userId), null, bundle);
504 } catch (ActivityNotFoundException | NullPointerException e) { /* Gracefully fail */ }
507 @TargetApi(Build.VERSION_CODES.N_MR1)
508 private static void launchShortcut(Context context, ShortcutInfo shortcut, Bundle bundle) {
509 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
511 if(launcherApps.hasShortcutHostPermission()) {
513 launcherApps.startShortcut(shortcut, null, bundle);
514 } catch (ActivityNotFoundException | NullPointerException e) { /* Gracefully fail */ }
518 private static void prepareToStartActivity(Context context, boolean openInNewWindow, Runnable runnable) {
519 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_CONTEXT_MENU"));
521 if(!FreeformHackHelper.getInstance().isTouchAbsorberActive()
522 && shouldLaunchTouchAbsorber(context)) {
523 startTouchAbsorberActivity(context);
524 new Handler().postDelayed(runnable, 100);
525 } else if(openInNewWindow) {
526 Intent intent = new Intent(context, DummyActivity.class);
527 intent.putExtra("finish_on_pause", true);
528 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
529 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
530 startActivityLowerRight(context, intent);
532 new Handler().postDelayed(runnable, 100);
537 public static void startActivityMaximized(Context context, Intent intent) {
538 Bundle bundle = launchMode2(context, MAXIMIZED, ApplicationType.CONTEXT_MENU, null);
539 prepareToStartActivity(context, false, () -> context.startActivity(intent, bundle));
542 @TargetApi(Build.VERSION_CODES.N)
543 public static void startActivityLowerRight(Context context, Intent intent) {
544 DisplayInfo display = getDisplayInfo(context);
546 context.startActivity(intent,
547 getActivityOptions(context, ApplicationType.FREEFORM_HACK, null)
548 .setLaunchBounds(new Rect(
554 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
557 @TargetApi(Build.VERSION_CODES.N)
558 public static void startTouchAbsorberActivity(Context context) {
559 String position = getTaskbarPosition(context);
560 DisplayInfo display = getDisplayInfo(context);
564 int right = display.width;
565 int bottom = display.height;
567 int iconSize = context.getResources().getDimensionPixelSize(R.dimen.icon_size);
569 if(position.contains("vertical_left"))
571 else if(position.contains("vertical_right"))
572 left = right - iconSize;
573 else if(position.contains("bottom"))
574 top = bottom - iconSize;
578 Intent intent = new Intent(context, TouchAbsorberActivity.class);
579 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
580 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
583 context.startActivity(intent,
584 getActivityOptions(context, ApplicationType.FREEFORM_HACK, null)
585 .setLaunchBounds(new Rect(left, top, right, bottom)).toBundle());
586 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
589 public static void startContextMenuActivity(Context context, Bundle args) {
590 SharedPreferences pref = getSharedPreferences(context);
591 Intent intent = null;
593 switch(pref.getString("theme", "light")) {
595 intent = new Intent(context, ContextMenuActivity.class);
598 intent = new Intent(context, ContextMenuActivityDark.class);
603 intent.putExtra("args", args);
604 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
607 if(hasFreeformSupport(context) && FreeformHackHelper.getInstance().isInFreeformWorkspace()) {
608 DisplayInfo display = getDisplayInfo(context);
610 if(intent != null && hasBrokenSetLaunchBoundsApi())
611 intent.putExtra("context_menu_fix", true);
613 context.startActivity(intent,
614 getActivityOptions(context, ApplicationType.CONTEXT_MENU, null)
616 new Rect(0, 0, display.width, display.height)
619 context.startActivity(intent);
622 public static void checkForUpdates(Context context) {
624 if(isPlayStoreRelease(context)) {
625 if(BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
626 && !isPlayStoreInstalled(context))
627 url = "https://github.com/farmerbb/Taskbar/releases";
629 url = "https://play.google.com/store/apps/details?id=" + BuildConfig.APPLICATION_ID;
631 url = "https://f-droid.org/repository/browse/?fdid=" + BuildConfig.APPLICATION_ID;
633 Intent intent = new Intent(Intent.ACTION_VIEW);
634 intent.setData(Uri.parse(url));
635 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
638 context.startActivity(intent);
639 } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
642 public static boolean launcherIsDefault(Context context) {
643 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
644 homeIntent.addCategory(Intent.CATEGORY_HOME);
645 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
647 return defaultLauncher.activityInfo.packageName.equals(BuildConfig.APPLICATION_ID);
650 public static void setCachedRotation(int cachedRotation) {
651 U.cachedRotation = cachedRotation;
654 public static String getTaskbarPosition(Context context) {
655 SharedPreferences pref = getSharedPreferences(context);
656 String position = pref.getString("position", "bottom_left");
658 if(pref.getBoolean("anchor", false)) {
659 WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
660 int rotation = cachedRotation != null ? cachedRotation : windowManager.getDefaultDisplay().getRotation();
665 case Surface.ROTATION_0:
666 return "bottom_left";
667 case Surface.ROTATION_90:
668 return "bottom_vertical_right";
669 case Surface.ROTATION_180:
671 case Surface.ROTATION_270:
672 return "top_vertical_left";
675 case "bottom_vertical_left":
677 case Surface.ROTATION_0:
678 return "bottom_vertical_left";
679 case Surface.ROTATION_90:
680 return "bottom_right";
681 case Surface.ROTATION_180:
682 return "top_vertical_right";
683 case Surface.ROTATION_270:
689 case Surface.ROTATION_0:
690 return "bottom_right";
691 case Surface.ROTATION_90:
692 return "top_vertical_right";
693 case Surface.ROTATION_180:
695 case Surface.ROTATION_270:
696 return "bottom_vertical_left";
699 case "bottom_vertical_right":
701 case Surface.ROTATION_0:
702 return "bottom_vertical_right";
703 case Surface.ROTATION_90:
705 case Surface.ROTATION_180:
706 return "top_vertical_left";
707 case Surface.ROTATION_270:
708 return "bottom_left";
713 case Surface.ROTATION_0:
715 case Surface.ROTATION_90:
716 return "bottom_vertical_left";
717 case Surface.ROTATION_180:
718 return "bottom_right";
719 case Surface.ROTATION_270:
720 return "top_vertical_right";
723 case "top_vertical_left":
725 case Surface.ROTATION_0:
726 return "top_vertical_left";
727 case Surface.ROTATION_90:
728 return "bottom_left";
729 case Surface.ROTATION_180:
730 return "bottom_vertical_right";
731 case Surface.ROTATION_270:
737 case Surface.ROTATION_0:
739 case Surface.ROTATION_90:
740 return "top_vertical_left";
741 case Surface.ROTATION_180:
742 return "bottom_left";
743 case Surface.ROTATION_270:
744 return "bottom_vertical_right";
747 case "top_vertical_right":
749 case Surface.ROTATION_0:
750 return "top_vertical_right";
751 case Surface.ROTATION_90:
753 case Surface.ROTATION_180:
754 return "bottom_vertical_left";
755 case Surface.ROTATION_270:
756 return "bottom_right";
765 private static int getMaxNumOfColumns(Context context) {
766 SharedPreferences pref = getSharedPreferences(context);
767 DisplayInfo display = getDisplayInfo(context);
768 float density = display.density / 160;
769 float baseTaskbarSize = getBaseTaskbarSizeFloat(context) / density;
770 int numOfColumns = 0;
772 float maxScreenSize = getTaskbarPosition(context).contains("vertical")
773 ? (display.height - getStatusBarHeight(context)) / density
774 : display.width / density;
776 float iconSize = context.getResources().getDimension(R.dimen.icon_size) / density;
778 int userMaxNumOfColumns = Integer.valueOf(pref.getString("max_num_of_recents", "10"));
780 while(baseTaskbarSize + iconSize < maxScreenSize
781 && numOfColumns < userMaxNumOfColumns) {
782 baseTaskbarSize = baseTaskbarSize + iconSize;
789 public static int getMaxNumOfEntries(Context context) {
790 SharedPreferences pref = getSharedPreferences(context);
791 return pref.getBoolean("disable_scrolling_list", false)
792 ? getMaxNumOfColumns(context)
793 : Integer.valueOf(pref.getString("max_num_of_recents", "10"));
796 public static int getStatusBarHeight(Context context) {
797 return getSystemDimen(context, "status_bar_height");
800 private static int getNavbarHeight(Context context) {
801 return getSystemDimen(context, "navigation_bar_height");
804 private static int getSystemDimen(Context context, String id) {
806 int resourceId = context.getResources().getIdentifier(id, "dimen", "android");
808 value = context.getResources().getDimensionPixelSize(resourceId);
813 public static void refreshPinnedIcons(Context context) {
814 IconCache.getInstance(context).clearCache();
816 PinnedBlockedApps pba = PinnedBlockedApps.getInstance(context);
817 List<AppEntry> pinnedAppsList = new ArrayList<>(pba.getPinnedApps());
818 List<AppEntry> blockedAppsList = new ArrayList<>(pba.getBlockedApps());
819 PackageManager pm = context.getPackageManager();
823 for(AppEntry entry : pinnedAppsList) {
824 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
825 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
827 final List<UserHandle> userHandles = userManager.getUserProfiles();
828 LauncherActivityInfo appInfo = null;
830 for(UserHandle handle : userHandles) {
831 List<LauncherActivityInfo> list = launcherApps.getActivityList(entry.getPackageName(), handle);
832 if(!list.isEmpty()) {
833 // Google App workaround
834 if(!entry.getPackageName().equals("com.google.android.googlequicksearchbox"))
835 appInfo = list.get(0);
837 boolean added = false;
838 for(LauncherActivityInfo info : list) {
839 if(info.getName().equals("com.google.android.googlequicksearchbox.SearchActivity")) {
845 if(!added) appInfo = list.get(0);
852 if(appInfo != null) {
853 AppEntry newEntry = new AppEntry(
854 entry.getPackageName(),
855 entry.getComponentName(),
857 IconCache.getInstance(context).getIcon(context, pm, appInfo),
860 newEntry.setUserId(entry.getUserId(context));
861 pba.addPinnedApp(context, newEntry);
865 for(AppEntry entry : blockedAppsList) {
866 pba.addBlockedApp(context, entry);
870 public static Intent getShortcutIntent(Context context) {
871 Intent shortcutIntent = new Intent(context, ShortcutActivity.class);
872 shortcutIntent.setAction(Intent.ACTION_MAIN);
873 shortcutIntent.putExtra("is_launching_shortcut", true);
875 Intent intent = new Intent();
876 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
877 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.mipmap.ic_freeform_mode));
878 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, context.getString(R.string.pref_header_freeform));
883 public static Intent getStartStopIntent(Context context) {
884 Intent shortcutIntent = new Intent(context, StartTaskbarActivity.class);
885 shortcutIntent.setAction(Intent.ACTION_MAIN);
886 shortcutIntent.putExtra("is_launching_shortcut", true);
888 Intent intent = new Intent();
889 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
890 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.mipmap.ic_launcher));
891 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, context.getString(R.string.start_taskbar));
896 public static boolean canEnableFreeform() {
897 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
900 @TargetApi(Build.VERSION_CODES.N)
901 public static boolean hasFreeformSupport(Context context) {
902 return canEnableFreeform()
903 && (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
904 || Settings.Global.getInt(context.getContentResolver(), "enable_freeform_support", 0) != 0
905 || (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
906 && Settings.Global.getInt(context.getContentResolver(), "force_resizable_activities", 0) != 0));
909 public static boolean canBootToFreeform(Context context) {
910 SharedPreferences pref = getSharedPreferences(context);
911 return hasFreeformSupport(context)
912 && pref.getBoolean("freeform_hack", false)
913 && !isOverridingFreeformHack(context);
916 public static boolean isSamsungDevice() {
917 return Build.MANUFACTURER.equalsIgnoreCase("Samsung");
920 private static boolean isNvidiaDevice() {
921 return Build.MANUFACTURER.equalsIgnoreCase("NVIDIA");
924 public static boolean isServiceRunning(Context context, Class<? extends Service> cls) {
925 if(LauncherHelper.getInstance().isOnHomeScreen(false, true)
926 && (cls.equals(TaskbarService.class)
927 || cls.equals(StartMenuService.class)
928 || cls.equals(DashboardService.class)))
931 return isServiceRunning(context, cls.getName());
934 private static boolean isServiceRunning(Context context, String className) {
935 ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
936 for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
937 if(className.equals(service.service.getClassName()))
944 public static int getBackgroundTint(Context context) {
945 SharedPreferences pref = getSharedPreferences(context);
947 // Import old background tint preference
948 if(pref.contains("show_background")) {
949 SharedPreferences.Editor editor = pref.edit();
951 if(!pref.getBoolean("show_background", true))
952 editor.putInt("background_tint", Color.TRANSPARENT).apply();
954 editor.remove("show_background");
958 return pref.getInt("background_tint", context.getResources().getInteger(R.integer.translucent_gray));
961 public static int getAccentColor(Context context) {
962 SharedPreferences pref = getSharedPreferences(context);
963 return pref.getInt("accent_color", context.getResources().getInteger(R.integer.translucent_white));
966 @TargetApi(Build.VERSION_CODES.M)
967 public static boolean canDrawOverlays(Context context) {
968 return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context);
971 public static boolean isGame(Context context, String packageName) {
972 SharedPreferences pref = getSharedPreferences(context);
973 if(pref.getBoolean("launch_games_fullscreen", true)) {
974 PackageManager pm = context.getPackageManager();
977 ApplicationInfo info = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
978 return (info.flags & ApplicationInfo.FLAG_IS_GAME) != 0 || (info.metaData != null && info.metaData.getBoolean("isGame", false));
979 } catch (PackageManager.NameNotFoundException e) {
986 private static ActivityOptions getActivityOptions(View view) {
987 return getActivityOptions(null, null, view);
990 @TargetApi(Build.VERSION_CODES.N)
991 private static ActivityOptions getActivityOptions(Context context, ApplicationType applicationType, View view) {
992 ActivityOptions options;
994 options = ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight());
996 options = ActivityOptions.makeBasic();
998 if(applicationType == null)
1003 switch(applicationType) {
1005 if(FreeformHackHelper.getInstance().isFreeformHackActive())
1006 stackId = getFreeformWindowModeId();
1008 stackId = getFullscreenWindowModeId();
1011 stackId = getFullscreenWindowModeId();
1014 stackId = getFreeformWindowModeId();
1017 if(hasBrokenSetLaunchBoundsApi()
1018 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f))
1019 stackId = getFullscreenWindowModeId();
1024 Method method = ActivityOptions.class.getMethod(getWindowingModeMethodName(), int.class);
1025 method.invoke(options, stackId);
1026 } catch (Exception e) { /* Gracefully fail */ }
1028 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1029 int launchDisplayId = LauncherHelper.getInstance().getSecondaryDisplayId();
1030 if(launchDisplayId != -1)
1031 options.setLaunchDisplayId(launchDisplayId);
1037 private static int getFullscreenWindowModeId() {
1038 if(getCurrentApiVersion() >= 28.0f)
1039 return WINDOWING_MODE_FULLSCREEN;
1041 return FULLSCREEN_WORKSPACE_STACK_ID;
1044 private static int getFreeformWindowModeId() {
1045 if(getCurrentApiVersion() >= 28.0f)
1046 return WINDOWING_MODE_FREEFORM;
1048 return FREEFORM_WORKSPACE_STACK_ID;
1051 private static String getWindowingModeMethodName() {
1052 if(getCurrentApiVersion() >= 28.0f)
1053 return "setLaunchWindowingMode";
1055 return "setLaunchStackId";
1058 public static Bundle getActivityOptionsBundle(Context context, ApplicationType type, View view) {
1059 SharedPreferences pref = getSharedPreferences(context);
1061 return getActivityOptionsBundle(context, type, pref.getString("window_size", context.getString(R.string.def_window_size)), view);
1064 private static Bundle getActivityOptionsBundle(Context context, ApplicationType type, String windowSize, View view) {
1065 SharedPreferences pref = getSharedPreferences(context);
1066 if(!canEnableFreeform() || !pref.getBoolean("freeform_hack", false))
1067 return getActivityOptions(view).toBundle();
1069 switch(windowSize) {
1071 return launchMode1(context, type, view);
1073 return launchMode2(context, MAXIMIZED, type, view);
1075 return launchMode2(context, LEFT, type, view);
1077 return launchMode2(context, RIGHT, type, view);
1079 return launchMode3(context, type, view);
1082 return getActivityOptions(context, type, view).toBundle();
1085 private static ApplicationType getApplicationType(Context context, String packageName) {
1086 return isGame(context, packageName) ? ApplicationType.GAME : ApplicationType.APPLICATION;
1089 public static boolean isSystemApp(Context context) {
1091 ApplicationInfo info = context.getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
1092 int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
1093 return (info.flags & mask) != 0;
1094 } catch (PackageManager.NameNotFoundException e) {
1099 public static boolean isChromeOs(Context context) {
1100 return context.getPackageManager().hasSystemFeature("org.chromium.arc");
1103 public static boolean isBlissOs(Context context) {
1104 boolean validBlissOsBuildProp = false;
1106 String blissVersion = getSystemProperty("ro.bliss.version");
1107 if(blissVersion != null && !blissVersion.isEmpty())
1108 validBlissOsBuildProp = true;
1110 String buildUser = getSystemProperty("ro.build.user");
1111 if(buildUser != null && buildUser.equals("electrikjesus"))
1112 validBlissOsBuildProp = true;
1114 return validBlissOsBuildProp
1115 && BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
1116 && isSystemApp(context);
1119 public static boolean isLauncherPermanentlyEnabled(Context context) {
1120 if(BuildConfig.APPLICATION_ID.equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
1123 PackageManager pm = context.getPackageManager();
1125 pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1126 return pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, BuildConfig.APPLICATION_ID) == PackageManager.SIGNATURE_MATCH
1127 && BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
1128 && isSystemApp(context);
1129 } catch (PackageManager.NameNotFoundException e) {
1134 public static int getBaseTaskbarSize(Context context) {
1135 return Math.round(getBaseTaskbarSizeFloat(context));
1138 private static float getBaseTaskbarSizeFloat(Context context) {
1139 SharedPreferences pref = getSharedPreferences(context);
1140 float baseTaskbarSize = context.getResources().getDimension(R.dimen.base_taskbar_size);
1141 boolean navbarButtonsEnabled = false;
1143 if(pref.getBoolean("dashboard", context.getResources().getBoolean(R.bool.def_dashboard)))
1144 baseTaskbarSize += context.getResources().getDimension(R.dimen.dashboard_button_size);
1146 if(pref.getBoolean("button_back", false)) {
1147 navbarButtonsEnabled = true;
1148 baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
1151 if(pref.getBoolean("button_home", false)) {
1152 navbarButtonsEnabled = true;
1153 baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
1156 if(pref.getBoolean("button_recents", false)) {
1157 navbarButtonsEnabled = true;
1158 baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
1161 if(navbarButtonsEnabled)
1162 baseTaskbarSize += context.getResources().getDimension(R.dimen.navbar_buttons_margin);
1164 if(isSystemTrayEnabled(context))
1165 baseTaskbarSize += context.getResources().getDimension(R.dimen.systray_size);
1167 return baseTaskbarSize;
1170 private static void startTaskbarService(Context context, boolean fullRestart) {
1171 context.startService(new Intent(context, TaskbarService.class));
1172 context.startService(new Intent(context, StartMenuService.class));
1173 context.startService(new Intent(context, DashboardService.class));
1174 if(fullRestart) context.startService(new Intent(context, NotificationService.class));
1177 private static void stopTaskbarService(Context context, boolean fullRestart) {
1178 context.stopService(new Intent(context, TaskbarService.class));
1179 context.stopService(new Intent(context, StartMenuService.class));
1180 context.stopService(new Intent(context, DashboardService.class));
1181 if(fullRestart) context.stopService(new Intent(context, NotificationService.class));
1184 public static void restartTaskbar(Context context) {
1185 SharedPreferences pref = getSharedPreferences(context);
1186 if(pref.getBoolean("taskbar_active", false) && !pref.getBoolean("is_hidden", false)) {
1188 .putBoolean("is_restarting", true)
1189 .putBoolean("skip_auto_hide_navbar", true)
1192 stopTaskbarService(context, true);
1193 startTaskbarService(context, true);
1194 } else if(isServiceRunning(context, StartMenuService.class)) {
1195 pref.edit().putBoolean("skip_auto_hide_navbar", true).apply();
1197 stopTaskbarService(context, false);
1198 startTaskbarService(context, false);
1201 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.RESTART"));
1204 public static void restartNotificationService(Context context) {
1205 if(isServiceRunning(context, NotificationService.class)) {
1206 SharedPreferences pref = getSharedPreferences(context);
1207 pref.edit().putBoolean("is_restarting", true).apply();
1209 Intent intent = new Intent(context, NotificationService.class);
1210 context.stopService(intent);
1211 context.startService(intent);
1215 public static void showHideNavigationBar(Context context, boolean show) {
1216 // Show or hide the system navigation bar on Bliss-x86
1218 if(getCurrentApiVersion() >= 28.0f)
1219 Settings.Secure.putInt(context.getContentResolver(), "navigation_bar_visible", show ? 1 : 0);
1221 Settings.System.putInt(context.getContentResolver(), "navigation_bar_show", show ? 1 : 0);
1222 } catch (Exception e) { /* Gracefully fail */ }
1225 public static void initPrefs(Context context) {
1226 // Enable freeform hack automatically on supported devices
1227 SharedPreferences pref = getSharedPreferences(context);
1228 if(canEnableFreeform()) {
1229 if(!pref.getBoolean("freeform_hack_override", false)) {
1231 .putBoolean("freeform_hack", hasFreeformSupport(context) && !isSamsungDevice())
1232 .putBoolean("save_window_sizes", false)
1233 .putBoolean("freeform_hack_override", true)
1235 } else if(!hasFreeformSupport(context)) {
1236 pref.edit().putBoolean("freeform_hack", false).apply();
1238 stopFreeformHack(context);
1241 boolean freeformWasEnabled = pref.getBoolean("freeform_hack", false)
1242 || pref.getBoolean("show_freeform_disabled_message", false);
1245 .putBoolean("freeform_hack", false)
1246 .putBoolean("show_freeform_disabled_message", freeformWasEnabled)
1249 SavedWindowSizes.getInstance(context).clear(context);
1250 stopFreeformHack(context);
1253 // Customizations for BlissOS
1254 if(isBlissOs(context) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1255 && !pref.getBoolean("bliss_os_prefs", false)) {
1256 SharedPreferences.Editor editor = pref.edit();
1258 if(hasFreeformSupport(context)) {
1259 editor.putBoolean("freeform_hack", true);
1262 editor.putString("recents_amount", "running_apps_only");
1263 editor.putString("refresh_frequency", "0");
1264 editor.putString("max_num_of_recents", "2147483647");
1265 editor.putString("sort_order", "true");
1266 editor.putString("start_button_image", "app_logo");
1267 editor.putBoolean("button_back", true);
1268 editor.putBoolean("button_home", true);
1269 editor.putBoolean("button_recents", true);
1270 editor.putBoolean("auto_hide_navbar", true);
1271 // editor.putBoolean("shortcut_icon", false);
1272 editor.putBoolean("bliss_os_prefs", true);
1276 // Customizations for Android-x86 devices (non-Bliss)
1277 if(BuildConfig.APPLICATION_ID.equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
1278 && isSystemApp(context)
1279 && !pref.getBoolean("android_x86_prefs", false)) {
1281 .putString("recents_amount", "running_apps_only")
1282 .putString("refresh_frequency", "0")
1283 .putString("max_num_of_recents", "2147483647")
1284 .putString("sort_order", "true")
1285 .putBoolean("shortcut_icon", false)
1286 .putBoolean("android_x86_prefs", true)
1291 public static DisplayInfo getDisplayInfo(Context context) {
1292 return getDisplayInfo(context, false);
1295 public static DisplayInfo getDisplayInfo(Context context, boolean fromTaskbar) {
1296 if(LauncherHelper.getInstance().getSecondaryDisplayId() == -1)
1297 context = context.getApplicationContext();
1299 WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
1300 Display disp = wm.getDefaultDisplay();
1302 DisplayMetrics metrics = new DisplayMetrics();
1303 disp.getMetrics(metrics);
1305 DisplayMetrics realMetrics = new DisplayMetrics();
1306 disp.getRealMetrics(realMetrics);
1308 DisplayInfo display = new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);
1310 if(isChromeOs(context)) {
1311 SharedPreferences pref = getSharedPreferences(context);
1312 if(!pref.getBoolean("chrome_os_context_menu_fix", true)) {
1313 display.width = realMetrics.widthPixels;
1314 display.height = realMetrics.heightPixels;
1320 // Workaround for incorrect display size on devices with notches in landscape mode
1321 if(fromTaskbar && context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)
1324 boolean sameWidth = metrics.widthPixels == realMetrics.widthPixels;
1325 boolean sameHeight = metrics.heightPixels == realMetrics.heightPixels;
1327 if(sameWidth && !sameHeight) {
1328 display.width = realMetrics.widthPixels;
1329 display.height = realMetrics.heightPixels - getNavbarHeight(context);
1332 if(!sameWidth && sameHeight) {
1333 display.width = realMetrics.widthPixels - getNavbarHeight(context);
1334 display.height = realMetrics.heightPixels;
1340 public static void pinAppShortcut(Context context) {
1341 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1342 ShortcutManager mShortcutManager = context.getSystemService(ShortcutManager.class);
1344 if(mShortcutManager.isRequestPinShortcutSupported()) {
1345 ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder(context, "freeform_mode").build();
1347 mShortcutManager.requestPinShortcut(pinShortcutInfo, null);
1349 showToastLong(context, R.string.pin_shortcut_not_supported);
1351 Intent intent = getShortcutIntent(context);
1352 intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
1353 intent.putExtra("duplicate", false);
1355 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1356 homeIntent.addCategory(Intent.CATEGORY_HOME);
1357 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
1359 intent.setPackage(defaultLauncher.activityInfo.packageName);
1360 context.sendBroadcast(intent);
1362 showToast(context, R.string.shortcut_created);
1366 public static boolean shouldCollapse(Context context, boolean pendingAppLaunch) {
1367 SharedPreferences pref = getSharedPreferences(context);
1368 if(pref.getBoolean("hide_taskbar", true)) {
1369 if(isOverridingFreeformHack(context))
1370 return !LauncherHelper.getInstance().isOnHomeScreen();
1372 FreeformHackHelper helper = FreeformHackHelper.getInstance();
1373 if(pendingAppLaunch)
1374 return !helper.isFreeformHackActive();
1376 return !helper.isInFreeformWorkspace();
1382 public static boolean isOverridingFreeformHack(Context context) {
1383 SharedPreferences pref = getSharedPreferences(context);
1384 return pref.getBoolean("freeform_hack", false)
1385 && ((isChromeOs(context) && pref.getBoolean("chrome_os_context_menu_fix", true))
1386 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f));
1389 public static boolean isPlayStoreInstalled(Context context) {
1391 context.getPackageManager().getPackageInfo("com.android.vending", 0);
1393 } catch (PackageManager.NameNotFoundException e) {
1398 public static float getCurrentApiVersion() {
1399 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1400 return Float.valueOf(Build.VERSION.SDK_INT + "." + Build.VERSION.PREVIEW_SDK_INT);
1402 return (float) Build.VERSION.SDK_INT;
1405 public static boolean hasBrokenSetLaunchBoundsApi() {
1406 return getCurrentApiVersion() >= 26.0f
1407 && getCurrentApiVersion() < 28.0f
1408 && !isSamsungDevice()
1409 && !isNvidiaDevice();
1412 public static String getSecondScreenPackageName(Context context) {
1413 return getInstalledPackage(context, Arrays.asList(
1414 "com.farmerbb.secondscreen.free",
1415 "com.farmerbb.secondscreen"));
1418 // Returns the name of an installed package from a list of package names, in order of preference
1419 private static String getInstalledPackage(Context context, List<String> packageNames) {
1420 if(packageNames == null || packageNames.isEmpty())
1423 List<String> packages = packageNames instanceof ArrayList ? packageNames : new ArrayList<>(packageNames);
1424 String packageName = packages.get(0);
1427 context.getPackageManager().getPackageInfo(packageName, 0);
1429 } catch (PackageManager.NameNotFoundException e) {
1431 return getInstalledPackage(context, packages);
1435 public static boolean visualFeedbackEnabled(Context context) {
1436 SharedPreferences pref = getSharedPreferences(context);
1437 return (getCurrentApiVersion() < 26.0f || getCurrentApiVersion() >= 28.0f)
1438 && pref.getBoolean("visual_feedback", true)
1439 && !isNvidiaDevice();
1442 public static void showRecentAppsDialog(Context context) {
1443 showRecentAppsDialog(context, null, null);
1446 public static AlertDialog showRecentAppsDialog(Context context, Runnable onError, Runnable onFinish) {
1447 Runnable finalOnFinish = onFinish == null
1451 Runnable finalOnError = onError == null
1452 ? () -> showErrorDialog(context, "GET_USAGE_STATS", finalOnFinish)
1455 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isSystemApp(context)) {
1456 ApplicationInfo applicationInfo = null;
1458 applicationInfo = context.getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
1459 } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
1461 if(applicationInfo != null) {
1462 AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
1463 int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
1465 if(mode != AppOpsManager.MODE_ALLOWED) {
1466 AlertDialog.Builder builder = new AlertDialog.Builder(context);
1467 builder.setTitle(R.string.pref_header_recent_apps)
1468 .setMessage(R.string.enable_recent_apps)
1469 .setPositiveButton(R.string.action_ok, (dialog, which) -> {
1471 context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
1472 showToastLong(context, R.string.usage_stats_message);
1474 finalOnFinish.run();
1475 } catch (ActivityNotFoundException e) {
1479 .setNegativeButton(R.string.action_cancel, (dialog, which) -> finalOnFinish.run());
1481 AlertDialog dialog = builder.create();
1483 dialog.setCancelable(false);
1490 finalOnFinish.run();
1494 public static Context wrapContext(Context context) {
1495 SharedPreferences pref = getSharedPreferences(context);
1498 switch(pref.getString("theme", "light")) {
1500 theme = R.style.AppTheme;
1503 theme = R.style.AppTheme_Dark;
1507 return theme > -1 ? new ContextThemeWrapper(context, theme) : context;
1510 public static boolean isPlayStoreRelease(Context context) {
1511 return isPlayStoreRelease(context, BuildConfig.APPLICATION_ID);
1514 @SuppressLint("PackageManagerGetSignatures")
1515 public static boolean isPlayStoreRelease(Context context, String packageName) {
1516 Signature playStoreSignature = new Signature(context.getString(R.string.signature));
1518 PackageManager pm = context.getPackageManager();
1519 PackageInfo info = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
1520 for(Signature signature : info.signatures) {
1521 if(signature.equals(playStoreSignature))
1524 } catch (Exception e) { /* Gracefully fail */ }
1529 public static boolean isExternalAccessDisabled(Context context) {
1530 SharedPreferences pref = getSharedPreferences(context);
1531 return !pref.getBoolean("tasker_enabled", true);
1534 public static boolean enableFreeformModeShortcut(Context context) {
1535 return canEnableFreeform()
1536 && !isOverridingFreeformHack(context)
1537 && !isChromeOs(context);
1540 public static void startForegroundService(Context context, Intent intent) {
1541 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1542 if(Settings.canDrawOverlays(context))
1543 context.startForegroundService(intent);
1545 context.startService(intent);
1548 public static int getOverlayType() {
1549 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
1550 ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
1551 : WindowManager.LayoutParams.TYPE_PHONE;
1554 public static boolean isDelegatingHomeActivity(Context context) {
1555 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1556 homeIntent.addCategory(Intent.CATEGORY_HOME);
1558 final List<ResolveInfo> listOfLaunchers = context.getPackageManager().queryIntentActivities(homeIntent, 0);
1559 for(ResolveInfo launcher : listOfLaunchers) {
1560 if(launcher.activityInfo.packageName.equals(BuildConfig.SUPPORT_APPLICATION_ID))
1567 @SuppressLint("PrivateApi")
1568 private static String getSystemProperty(String key) {
1570 Class<?> cls = Class.forName("android.os.SystemProperties");
1571 return cls.getMethod("get", String.class).invoke(null, key).toString();
1572 } catch (Exception e) {
1577 @SuppressWarnings("ResultOfMethodCallIgnored")
1578 public static boolean importCustomStartButtonImage(Context context, Uri uri) {
1580 File imagesDir = new File(context.getFilesDir(), "images");
1583 File importedFile = new File(imagesDir, "custom_image_new");
1584 if(importedFile.exists()) importedFile.delete();
1586 BufferedInputStream is = new BufferedInputStream(context.getContentResolver().openInputStream(uri));
1587 byte[] data = new byte[is.available()];
1589 if(data.length > 0) {
1590 BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(importedFile));
1597 File prevFile = new File(imagesDir, "custom_image");
1598 if(prevFile.exists()) prevFile.delete();
1600 importedFile.renameTo(prevFile);
1602 } catch (IOException e) {
1607 public static String getDefaultStartButtonImage(Context context) {
1608 SharedPreferences pref = getSharedPreferences(context);
1609 return pref.getBoolean("app_drawer_icon", false)
1614 private static boolean shouldLaunchTouchAbsorber(Context context) {
1615 return isOverridingFreeformHack(context) && !isChromeOs(context) && getCurrentApiVersion() < 29.0f;
1618 public static boolean isDesktopIconsEnabled(Context context) {
1619 return !canBootToFreeform(context) && !shouldLaunchTouchAbsorber(context);
1622 public static boolean isSystemTrayEnabled(Context context) {
1623 SharedPreferences pref = getSharedPreferences(context);
1625 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1626 && pref.getBoolean("sys_tray", context.getResources().getBoolean(R.bool.def_sys_tray))
1627 && pref.getBoolean("full_length", context.getResources().getBoolean(R.bool.def_full_length))
1628 && !getTaskbarPosition(context).contains("vertical");