1 /* Copyright 2016 Braden Farmer
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
16 package com.farmerbb.taskbar.util;
18 import android.Manifest;
19 import android.annotation.SuppressLint;
20 import android.annotation.TargetApi;
21 import android.app.ActivityManager;
22 import android.app.ActivityOptions;
23 import android.app.AlertDialog;
24 import android.app.AppOpsManager;
25 import android.app.Service;
26 import android.content.ActivityNotFoundException;
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.SharedPreferences;
31 import android.content.pm.ActivityInfo;
32 import android.content.pm.ApplicationInfo;
33 import android.content.pm.LauncherActivityInfo;
34 import android.content.pm.LauncherApps;
35 import android.content.pm.PackageInfo;
36 import android.content.pm.PackageManager;
37 import android.content.pm.ResolveInfo;
38 import android.content.pm.ShortcutInfo;
39 import android.content.pm.ShortcutManager;
40 import android.content.pm.Signature;
41 import android.content.res.Configuration;
42 import android.graphics.Color;
43 import android.graphics.Rect;
44 import android.net.Uri;
45 import android.os.Build;
46 import android.os.Bundle;
47 import android.os.Handler;
48 import android.os.Process;
49 import android.os.UserHandle;
50 import android.os.UserManager;
51 import android.provider.Settings;
52 import android.support.v4.content.LocalBroadcastManager;
53 import android.support.v7.view.ContextThemeWrapper;
54 import android.util.DisplayMetrics;
55 import android.view.Display;
56 import android.view.Surface;
57 import android.view.View;
58 import android.view.WindowManager;
59 import android.widget.Toast;
61 import com.farmerbb.taskbar.BuildConfig;
62 import com.farmerbb.taskbar.R;
63 import com.farmerbb.taskbar.activity.ContextMenuActivity;
64 import com.farmerbb.taskbar.activity.DummyActivity;
65 import com.farmerbb.taskbar.activity.InvisibleActivityFreeform;
66 import com.farmerbb.taskbar.activity.ShortcutActivity;
67 import com.farmerbb.taskbar.activity.StartTaskbarActivity;
68 import com.farmerbb.taskbar.activity.TouchAbsorberActivity;
69 import com.farmerbb.taskbar.activity.dark.ContextMenuActivityDark;
70 import com.farmerbb.taskbar.service.DashboardService;
71 import com.farmerbb.taskbar.service.NotificationService;
72 import com.farmerbb.taskbar.service.PowerMenuService;
73 import com.farmerbb.taskbar.service.StartMenuService;
74 import com.farmerbb.taskbar.service.TaskbarService;
76 import java.io.BufferedInputStream;
77 import java.io.BufferedOutputStream;
79 import java.io.FileOutputStream;
80 import java.io.IOException;
81 import java.lang.reflect.Method;
82 import java.util.ArrayList;
83 import java.util.Arrays;
84 import java.util.List;
90 private static Integer cachedRotation;
92 private static final int MAXIMIZED = 0;
93 private static final int LEFT = -1;
94 private static final int RIGHT = 1;
96 public static final int HIDDEN = 0;
97 public static final int TOP_APPS = 1;
99 // From android.app.ActivityManager.StackId
100 private static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
101 private static final int FREEFORM_WORKSPACE_STACK_ID = 2;
103 // From android.app.WindowConfiguration
104 private static final int WINDOWING_MODE_FULLSCREEN = 1;
105 private static final int WINDOWING_MODE_FREEFORM = 5;
107 public static SharedPreferences getSharedPreferences(Context context) {
108 return context.getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", Context.MODE_PRIVATE);
111 public static void showPermissionDialog(Context context) {
112 showPermissionDialog(context, null, null);
115 @TargetApi(Build.VERSION_CODES.M)
116 public static AlertDialog showPermissionDialog(Context context, Runnable onError, Runnable onFinish) {
117 Runnable finalOnFinish = onFinish == null
121 Runnable finalOnError = onError == null
122 ? () -> showErrorDialog(context, "SYSTEM_ALERT_WINDOW", finalOnFinish)
125 AlertDialog.Builder builder = new AlertDialog.Builder(context);
126 builder.setTitle(R.string.permission_dialog_title)
127 .setMessage(R.string.permission_dialog_message)
128 .setPositiveButton(R.string.action_grant_permission, (dialog, which) -> {
130 context.startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
131 Uri.parse("package:" + BuildConfig.APPLICATION_ID)));
134 } catch (ActivityNotFoundException e) {
139 AlertDialog dialog = builder.create();
141 dialog.setCancelable(false);
146 public static AlertDialog showErrorDialog(Context context, String appopCmd) {
147 return showErrorDialog(context, appopCmd, null);
150 private static AlertDialog showErrorDialog(Context context, String appopCmd, Runnable onFinish) {
151 Runnable finalOnFinish = onFinish == null
155 AlertDialog.Builder builder = new AlertDialog.Builder(context);
156 builder.setTitle(R.string.error_dialog_title)
157 .setMessage(context.getString(R.string.error_dialog_message, BuildConfig.APPLICATION_ID, appopCmd))
158 .setPositiveButton(R.string.action_ok, (dialog, which) -> finalOnFinish.run());
160 AlertDialog dialog = builder.create();
162 dialog.setCancelable(false);
167 public static void sendAccessibilityAction(Context context, int action) {
168 sendAccessibilityAction(context, action, null);
171 public static void sendAccessibilityAction(Context context, int action, Runnable onComplete) {
172 ComponentName component = new ComponentName(context, PowerMenuService.class);
173 context.getPackageManager().setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
174 PackageManager.DONT_KILL_APP);
176 boolean isAccessibilityServiceEnabled = isAccessibilityServiceEnabled(context);
178 if(!isAccessibilityServiceEnabled
179 && hasWriteSecureSettingsPermission(context)) {
180 String services = Settings.Secure.getString(context.getContentResolver(),
181 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
183 String finalServices = services == null ? "" : services;
185 String powerMenuService = new ComponentName(context, PowerMenuService.class).flattenToString();
187 if(!finalServices.contains(powerMenuService)) {
189 Settings.Secure.putString(context.getContentResolver(),
190 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
191 finalServices.isEmpty()
193 : finalServices + ":" + powerMenuService);
194 } catch (Exception e) { /* Gracefully fail */ }
197 new Handler().postDelayed(() -> {
198 Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
199 intent.putExtra("action", action);
200 LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
203 Settings.Secure.putString(context.getContentResolver(),
204 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
206 } catch (Exception e) { /* Gracefully fail */ }
208 if(onComplete != null) onComplete.run();
210 } else if(isAccessibilityServiceEnabled) {
211 Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
212 intent.putExtra("action", action);
213 LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
215 if(onComplete != null) onComplete.run();
217 launchApp(context, () -> {
218 Intent intent = new Intent(context, DummyActivity.class);
219 intent.putExtra("accessibility", true);
220 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
223 context.startActivity(intent, getActivityOptionsBundle(context, ApplicationType.APPLICATION, null));
224 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
229 public static boolean isAccessibilityServiceEnabled(Context context) {
230 String accessibilityServices = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
231 ComponentName component = new ComponentName(context, PowerMenuService.class);
233 return accessibilityServices != null
234 && (accessibilityServices.contains(component.flattenToString())
235 || accessibilityServices.contains(component.flattenToShortString()));
238 public static boolean hasWriteSecureSettingsPermission(Context context) {
239 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
240 && context.checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) == PackageManager.PERMISSION_GRANTED;
243 public static void showToast(Context context, int message) {
244 showToast(context, context.getString(message), Toast.LENGTH_SHORT);
247 public static void showToastLong(Context context, int message) {
248 showToast(context, context.getString(message), Toast.LENGTH_LONG);
251 public static void showToast(Context context, String message, int length) {
254 ToastInterface toast = DependencyUtils.createToast(context, message, length);
257 ToastHelper.getInstance().setLastToast(toast);
260 public static void cancelToast() {
261 ToastInterface toast = ToastHelper.getInstance().getLastToast();
262 if(toast != null) toast.cancel();
265 public static void startShortcut(Context context, AppEntry entry, ShortcutInfo shortcut, View view) {
275 public static void launchApp(final Context context,
276 final AppEntry entry,
277 final String windowSize,
278 final boolean launchedFromTaskbar,
279 final boolean openInNewWindow,
290 private static void launchApp(final Context context,
291 final AppEntry entry,
292 final String windowSize,
293 final boolean launchedFromTaskbar,
294 final boolean openInNewWindow,
295 final ShortcutInfo shortcut,
297 launchApp(context, launchedFromTaskbar, () -> continueLaunchingApp(context, entry,
298 windowSize, openInNewWindow, shortcut, view));
301 public static void launchApp(Context context, Runnable runnable) {
302 launchApp(context, true, runnable);
305 private static void launchApp(Context context, boolean launchedFromTaskbar, Runnable runnable) {
306 SharedPreferences pref = getSharedPreferences(context);
307 FreeformHackHelper helper = FreeformHackHelper.getInstance();
309 boolean specialLaunch = hasBrokenSetLaunchBoundsApi()
310 && helper.isInFreeformWorkspace()
311 && MenuHelper.getInstance().isContextMenuOpen();
313 boolean noAnimation = pref.getBoolean("disable_animations", false);
315 if(hasFreeformSupport(context)
316 && pref.getBoolean("freeform_hack", false)
317 && (!helper.isInFreeformWorkspace() || specialLaunch)) {
318 new Handler().postDelayed(() -> {
319 startFreeformHack(context, true);
321 new Handler().postDelayed(runnable, helper.isFreeformHackActive() ? 0 : 100);
322 }, launchedFromTaskbar ? 0 : 100);
324 new Handler().postDelayed(runnable, !launchedFromTaskbar && noAnimation ? 100 : 0);
327 public static void startFreeformHack(Context context) {
328 startFreeformHack(context, false);
331 @TargetApi(Build.VERSION_CODES.N)
332 public static void startFreeformHack(Context context, boolean checkMultiWindow) {
333 Intent freeformHackIntent = new Intent(context, InvisibleActivityFreeform.class);
334 freeformHackIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
335 | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
336 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
339 freeformHackIntent.putExtra("check_multiwindow", true);
341 if(canDrawOverlays(context, false))
342 startActivityLowerRight(context, freeformHackIntent);
345 public static void stopFreeformHack(Context context) {
346 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.FINISH_FREEFORM_ACTIVITY"));
348 if(isOverridingFreeformHack(context)) {
349 FreeformHackHelper helper = FreeformHackHelper.getInstance();
350 helper.setFreeformHackActive(false);
351 helper.setInFreeformWorkspace(false);
355 @TargetApi(Build.VERSION_CODES.N)
356 private static void continueLaunchingApp(Context context,
359 boolean openInNewWindow,
360 ShortcutInfo shortcut,
362 SharedPreferences pref = getSharedPreferences(context);
363 Intent intent = new Intent();
364 intent.setComponent(ComponentName.unflattenFromString(entry.getComponentName()));
365 intent.setAction(Intent.ACTION_MAIN);
366 intent.addCategory(Intent.CATEGORY_LAUNCHER);
367 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
368 intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
370 if(FreeformHackHelper.getInstance().isInFreeformWorkspace()
371 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1)
372 intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
374 if(pref.getBoolean("disable_animations", false))
375 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
377 if(openInNewWindow || pref.getBoolean("force_new_window", false)) {
378 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
380 ActivityInfo activityInfo = intent.resolveActivityInfo(context.getPackageManager(), 0);
381 if(activityInfo != null) {
382 switch(activityInfo.launchMode) {
383 case ActivityInfo.LAUNCH_SINGLE_TASK:
384 case ActivityInfo.LAUNCH_SINGLE_INSTANCE:
385 intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
391 ApplicationType type = getApplicationType(context, entry.getPackageName());
393 if(windowSize == null)
394 windowSize = SavedWindowSizes.getInstance(context).getWindowSize(context, entry.getPackageName());
396 Bundle bundle = getActivityOptionsBundle(context, type, windowSize, view);
398 prepareToStartActivity(context, () -> {
399 if(shortcut == null) {
400 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
401 if(entry.getUserId(context) == userManager.getSerialNumberForUser(Process.myUserHandle())) {
403 context.startActivity(intent, bundle);
404 } catch (ActivityNotFoundException e) {
405 launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context));
406 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
408 launchAndroidForWork(context, intent.getComponent(), bundle, entry.getUserId(context));
410 launchShortcut(context, shortcut, bundle);
413 if(shouldCollapse(context, true))
414 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_TASKBAR"));
416 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_START_MENU"));
419 @TargetApi(Build.VERSION_CODES.N)
420 private static Bundle launchMode1(Context context, ApplicationType type, View view) {
421 DisplayInfo display = getDisplayInfo(context);
423 int width1 = display.width / 8;
424 int width2 = display.width - width1;
425 int height1 = display.height / 8;
426 int height2 = display.height - height1;
428 return getActivityOptions(context, type, view).setLaunchBounds(new Rect(
436 @TargetApi(Build.VERSION_CODES.N)
437 private static Bundle launchMode2(Context context, int launchType, ApplicationType type, View view) {
438 DisplayInfo display = getDisplayInfo(context);
440 int statusBarHeight = getStatusBarHeight(context);
441 String position = getTaskbarPosition(context);
443 boolean isPortrait = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
444 boolean isLandscape = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
447 int top = statusBarHeight;
448 int right = display.width;
449 int bottom = display.height;
451 int iconSize = isOverridingFreeformHack(context) && !LauncherHelper.getInstance().isOnHomeScreen()
453 : context.getResources().getDimensionPixelSize(R.dimen.icon_size);
455 if(position.contains("vertical_left"))
456 left = left + iconSize;
457 else if(position.contains("vertical_right"))
458 right = right - iconSize;
459 else if(position.contains("bottom"))
460 bottom = bottom - iconSize;
462 top = top + iconSize;
464 int halfLandscape = (right / 2) + ((iconSize / 2) * (position.contains("vertical_left") ? 1 : 0));
465 int halfPortrait = (bottom / 2) + ((iconSize / 2) * ((position.equals("top_left") || position.equals("top_right")) ? 1 : 0));
467 if(launchType == RIGHT && isLandscape)
468 left = halfLandscape;
469 else if(launchType == RIGHT && isPortrait)
471 else if(launchType == LEFT && isLandscape)
472 right = halfLandscape;
473 else if(launchType == LEFT && isPortrait)
474 bottom = halfPortrait;
476 return getActivityOptions(context, type, view)
477 .setLaunchBounds(new Rect(left, top, right, bottom)).toBundle();
480 @TargetApi(Build.VERSION_CODES.N)
481 private static Bundle launchMode3(Context context, ApplicationType type, View view) {
482 DisplayInfo display = getDisplayInfo(context);
484 int width1 = display.width / 2;
485 int width2 = context.getResources().getDimensionPixelSize(R.dimen.phone_size_width) / 2;
486 int height1 = display.height / 2;
487 int height2 = context.getResources().getDimensionPixelSize(R.dimen.phone_size_height) / 2;
489 return getActivityOptions(context, type, view).setLaunchBounds(new Rect(
497 private static void launchAndroidForWork(Context context, ComponentName componentName, Bundle bundle, long userId) {
498 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
499 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
502 launcherApps.startMainActivity(componentName, userManager.getUserForSerialNumber(userId), null, bundle);
503 } catch (ActivityNotFoundException | NullPointerException e) { /* Gracefully fail */ }
506 @TargetApi(Build.VERSION_CODES.N_MR1)
507 private static void launchShortcut(Context context, ShortcutInfo shortcut, Bundle bundle) {
508 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
510 if(launcherApps.hasShortcutHostPermission()) {
512 launcherApps.startShortcut(shortcut, null, bundle);
513 } catch (ActivityNotFoundException | NullPointerException e) { /* Gracefully fail */ }
517 private static void prepareToStartActivity(Context context, Runnable runnable) {
518 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_CONTEXT_MENU"));
520 if(!FreeformHackHelper.getInstance().isTouchAbsorberActive()
521 && shouldLaunchTouchAbsorber(context)) {
522 startTouchAbsorberActivity(context);
523 new Handler().postDelayed(runnable, 100);
528 public static void startActivityMaximized(Context context, Intent intent) {
529 Bundle bundle = launchMode2(context, MAXIMIZED, ApplicationType.CONTEXT_MENU, null);
530 prepareToStartActivity(context, () -> context.startActivity(intent, bundle));
533 @TargetApi(Build.VERSION_CODES.N)
534 public static void startActivityLowerRight(Context context, Intent intent) {
535 DisplayInfo display = getDisplayInfo(context);
537 context.startActivity(intent,
538 getActivityOptions(context, ApplicationType.FREEFORM_HACK, null)
539 .setLaunchBounds(new Rect(
545 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
548 @TargetApi(Build.VERSION_CODES.N)
549 public static void startTouchAbsorberActivity(Context context) {
550 String position = getTaskbarPosition(context);
551 DisplayInfo display = getDisplayInfo(context);
555 int right = display.width;
556 int bottom = display.height;
558 int iconSize = context.getResources().getDimensionPixelSize(R.dimen.icon_size);
560 if(position.contains("vertical_left"))
562 else if(position.contains("vertical_right"))
563 left = right - iconSize;
564 else if(position.contains("bottom"))
565 top = bottom - iconSize;
569 Intent intent = new Intent(context, TouchAbsorberActivity.class);
570 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
571 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
574 context.startActivity(intent,
575 getActivityOptions(context, ApplicationType.FREEFORM_HACK, null)
576 .setLaunchBounds(new Rect(left, top, right, bottom)).toBundle());
577 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
580 public static void startContextMenuActivity(Context context, Bundle args) {
581 SharedPreferences pref = getSharedPreferences(context);
582 Intent intent = null;
584 switch(pref.getString("theme", "light")) {
586 intent = new Intent(context, ContextMenuActivity.class);
589 intent = new Intent(context, ContextMenuActivityDark.class);
594 intent.putExtra("args", args);
595 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
598 if(hasFreeformSupport(context) && FreeformHackHelper.getInstance().isInFreeformWorkspace()) {
599 DisplayInfo display = getDisplayInfo(context);
601 if(intent != null && hasBrokenSetLaunchBoundsApi())
602 intent.putExtra("context_menu_fix", true);
604 context.startActivity(intent,
605 getActivityOptions(context, ApplicationType.CONTEXT_MENU, null)
607 new Rect(0, 0, display.width, display.height)
610 context.startActivity(intent);
613 public static void checkForUpdates(Context context) {
615 if(isPlayStoreRelease(context)) {
616 if(BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
617 && !isPlayStoreInstalled(context))
618 url = "https://github.com/farmerbb/Taskbar/releases";
620 url = "https://play.google.com/store/apps/details?id=" + BuildConfig.APPLICATION_ID;
622 url = "https://f-droid.org/repository/browse/?fdid=" + BuildConfig.APPLICATION_ID;
624 Intent intent = new Intent(Intent.ACTION_VIEW);
625 intent.setData(Uri.parse(url));
626 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
629 context.startActivity(intent);
630 } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
633 public static boolean launcherIsDefault(Context context) {
634 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
635 homeIntent.addCategory(Intent.CATEGORY_HOME);
636 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
638 return defaultLauncher.activityInfo.packageName.equals(BuildConfig.APPLICATION_ID);
641 public static void setCachedRotation(int cachedRotation) {
642 U.cachedRotation = cachedRotation;
645 public static String getTaskbarPosition(Context context) {
646 SharedPreferences pref = getSharedPreferences(context);
647 String position = pref.getString("position", "bottom_left");
649 if(pref.getBoolean("anchor", false)) {
650 WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
651 int rotation = cachedRotation != null ? cachedRotation : windowManager.getDefaultDisplay().getRotation();
656 case Surface.ROTATION_0:
657 return "bottom_left";
658 case Surface.ROTATION_90:
659 return "bottom_vertical_right";
660 case Surface.ROTATION_180:
662 case Surface.ROTATION_270:
663 return "top_vertical_left";
666 case "bottom_vertical_left":
668 case Surface.ROTATION_0:
669 return "bottom_vertical_left";
670 case Surface.ROTATION_90:
671 return "bottom_right";
672 case Surface.ROTATION_180:
673 return "top_vertical_right";
674 case Surface.ROTATION_270:
680 case Surface.ROTATION_0:
681 return "bottom_right";
682 case Surface.ROTATION_90:
683 return "top_vertical_right";
684 case Surface.ROTATION_180:
686 case Surface.ROTATION_270:
687 return "bottom_vertical_left";
690 case "bottom_vertical_right":
692 case Surface.ROTATION_0:
693 return "bottom_vertical_right";
694 case Surface.ROTATION_90:
696 case Surface.ROTATION_180:
697 return "top_vertical_left";
698 case Surface.ROTATION_270:
699 return "bottom_left";
704 case Surface.ROTATION_0:
706 case Surface.ROTATION_90:
707 return "bottom_vertical_left";
708 case Surface.ROTATION_180:
709 return "bottom_right";
710 case Surface.ROTATION_270:
711 return "top_vertical_right";
714 case "top_vertical_left":
716 case Surface.ROTATION_0:
717 return "top_vertical_left";
718 case Surface.ROTATION_90:
719 return "bottom_left";
720 case Surface.ROTATION_180:
721 return "bottom_vertical_right";
722 case Surface.ROTATION_270:
728 case Surface.ROTATION_0:
730 case Surface.ROTATION_90:
731 return "top_vertical_left";
732 case Surface.ROTATION_180:
733 return "bottom_left";
734 case Surface.ROTATION_270:
735 return "bottom_vertical_right";
738 case "top_vertical_right":
740 case Surface.ROTATION_0:
741 return "top_vertical_right";
742 case Surface.ROTATION_90:
744 case Surface.ROTATION_180:
745 return "bottom_vertical_left";
746 case Surface.ROTATION_270:
747 return "bottom_right";
756 private static int getMaxNumOfColumns(Context context) {
757 SharedPreferences pref = getSharedPreferences(context);
758 DisplayInfo display = getDisplayInfo(context);
759 float density = display.density / 160;
760 float baseTaskbarSize = getBaseTaskbarSizeFloat(context) / density;
761 int numOfColumns = 0;
763 float maxScreenSize = getTaskbarPosition(context).contains("vertical")
764 ? (display.height - getStatusBarHeight(context)) / density
765 : display.width / density;
767 float iconSize = context.getResources().getDimension(R.dimen.icon_size) / density;
769 int userMaxNumOfColumns = Integer.valueOf(pref.getString("max_num_of_recents", "10"));
771 while(baseTaskbarSize + iconSize < maxScreenSize
772 && numOfColumns < userMaxNumOfColumns) {
773 baseTaskbarSize = baseTaskbarSize + iconSize;
780 public static int getMaxNumOfEntries(Context context) {
781 SharedPreferences pref = getSharedPreferences(context);
782 return pref.getBoolean("disable_scrolling_list", false)
783 ? getMaxNumOfColumns(context)
784 : Integer.valueOf(pref.getString("max_num_of_recents", "10"));
787 public static int getStatusBarHeight(Context context) {
788 return getSystemDimen(context, "status_bar_height");
791 private static int getNavbarHeight(Context context) {
792 return getSystemDimen(context, "navigation_bar_height");
795 private static int getSystemDimen(Context context, String id) {
797 int resourceId = context.getResources().getIdentifier(id, "dimen", "android");
799 value = context.getResources().getDimensionPixelSize(resourceId);
804 public static void refreshPinnedIcons(Context context) {
805 IconCache.getInstance(context).clearCache();
807 PinnedBlockedApps pba = PinnedBlockedApps.getInstance(context);
808 List<AppEntry> pinnedAppsList = new ArrayList<>(pba.getPinnedApps());
809 List<AppEntry> blockedAppsList = new ArrayList<>(pba.getBlockedApps());
810 PackageManager pm = context.getPackageManager();
814 for(AppEntry entry : pinnedAppsList) {
815 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
816 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
818 final List<UserHandle> userHandles = userManager.getUserProfiles();
819 LauncherActivityInfo appInfo = null;
821 for(UserHandle handle : userHandles) {
822 List<LauncherActivityInfo> list = launcherApps.getActivityList(entry.getPackageName(), handle);
823 if(!list.isEmpty()) {
824 // Google App workaround
825 if(!entry.getPackageName().equals("com.google.android.googlequicksearchbox"))
826 appInfo = list.get(0);
828 boolean added = false;
829 for(LauncherActivityInfo info : list) {
830 if(info.getName().equals("com.google.android.googlequicksearchbox.SearchActivity")) {
836 if(!added) appInfo = list.get(0);
843 if(appInfo != null) {
844 AppEntry newEntry = new AppEntry(
845 entry.getPackageName(),
846 entry.getComponentName(),
848 IconCache.getInstance(context).getIcon(context, pm, appInfo),
851 newEntry.setUserId(entry.getUserId(context));
852 pba.addPinnedApp(context, newEntry);
856 for(AppEntry entry : blockedAppsList) {
857 pba.addBlockedApp(context, entry);
861 public static Intent getShortcutIntent(Context context) {
862 Intent shortcutIntent = new Intent(context, ShortcutActivity.class);
863 shortcutIntent.setAction(Intent.ACTION_MAIN);
864 shortcutIntent.putExtra("is_launching_shortcut", true);
866 Intent intent = new Intent();
867 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
868 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.mipmap.ic_freeform_mode));
869 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, context.getString(R.string.pref_header_freeform));
874 public static Intent getStartStopIntent(Context context) {
875 Intent shortcutIntent = new Intent(context, StartTaskbarActivity.class);
876 shortcutIntent.setAction(Intent.ACTION_MAIN);
877 shortcutIntent.putExtra("is_launching_shortcut", true);
879 Intent intent = new Intent();
880 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
881 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.mipmap.ic_launcher));
882 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, context.getString(R.string.start_taskbar));
887 public static boolean canEnableFreeform() {
888 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
891 @TargetApi(Build.VERSION_CODES.N)
892 public static boolean hasFreeformSupport(Context context) {
893 return canEnableFreeform()
894 && (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
895 || Settings.Global.getInt(context.getContentResolver(), "enable_freeform_support", 0) != 0
896 || (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
897 && Settings.Global.getInt(context.getContentResolver(), "force_resizable_activities", 0) != 0));
900 public static boolean canBootToFreeform(Context context) {
901 SharedPreferences pref = getSharedPreferences(context);
902 return hasFreeformSupport(context)
903 && pref.getBoolean("freeform_hack", false)
904 && !isOverridingFreeformHack(context);
907 public static boolean isSamsungDevice() {
908 return Build.MANUFACTURER.equalsIgnoreCase("Samsung");
911 private static boolean isNvidiaDevice() {
912 return Build.MANUFACTURER.equalsIgnoreCase("NVIDIA");
915 public static boolean isServiceRunning(Context context, Class<? extends Service> cls) {
916 if(LauncherHelper.getInstance().isOnHomeScreen()
917 && FeatureFlags.homeActivityUIHost
918 && (cls.equals(TaskbarService.class)
919 || cls.equals(StartMenuService.class)
920 || cls.equals(DashboardService.class)))
923 return isServiceRunning(context, cls.getName());
926 private static boolean isServiceRunning(Context context, String className) {
927 ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
928 for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
929 if(className.equals(service.service.getClassName()))
936 public static int getBackgroundTint(Context context) {
937 SharedPreferences pref = getSharedPreferences(context);
939 // Import old background tint preference
940 if(pref.contains("show_background")) {
941 SharedPreferences.Editor editor = pref.edit();
943 if(!pref.getBoolean("show_background", true))
944 editor.putInt("background_tint", Color.TRANSPARENT).apply();
946 editor.remove("show_background");
950 return pref.getInt("background_tint", context.getResources().getInteger(R.integer.translucent_gray));
953 public static int getAccentColor(Context context) {
954 SharedPreferences pref = getSharedPreferences(context);
955 return pref.getInt("accent_color", context.getResources().getInteger(R.integer.translucent_white));
958 @TargetApi(Build.VERSION_CODES.M)
959 public static boolean canDrawOverlays(Context context, boolean forHomeScreen) {
960 return (forHomeScreen && FeatureFlags.homeActivityUIHost && !canBootToFreeform(context))
961 || Build.VERSION.SDK_INT < Build.VERSION_CODES.M
962 || Settings.canDrawOverlays(context);
965 public static boolean isGame(Context context, String packageName) {
966 SharedPreferences pref = getSharedPreferences(context);
967 if(pref.getBoolean("launch_games_fullscreen", true)) {
968 PackageManager pm = context.getPackageManager();
971 ApplicationInfo info = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
972 return (info.flags & ApplicationInfo.FLAG_IS_GAME) != 0 || (info.metaData != null && info.metaData.getBoolean("isGame", false));
973 } catch (PackageManager.NameNotFoundException e) {
980 private static ActivityOptions getActivityOptions(View view) {
981 return getActivityOptions(null, null, view);
984 @TargetApi(Build.VERSION_CODES.N)
985 private static ActivityOptions getActivityOptions(Context context, ApplicationType applicationType, View view) {
986 ActivityOptions options;
988 options = ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight());
990 options = ActivityOptions.makeBasic();
992 if(applicationType == null)
997 switch(applicationType) {
999 if(FreeformHackHelper.getInstance().isFreeformHackActive())
1000 stackId = getFreeformWindowModeId();
1002 stackId = getFullscreenWindowModeId();
1005 stackId = getFullscreenWindowModeId();
1008 stackId = getFreeformWindowModeId();
1011 if(hasBrokenSetLaunchBoundsApi()
1012 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f))
1013 stackId = getFullscreenWindowModeId();
1018 Method method = ActivityOptions.class.getMethod(getWindowingModeMethodName(), int.class);
1019 method.invoke(options, stackId);
1020 } catch (Exception e) { /* Gracefully fail */ }
1025 private static int getFullscreenWindowModeId() {
1026 if(getCurrentApiVersion() >= 28.0f)
1027 return WINDOWING_MODE_FULLSCREEN;
1029 return FULLSCREEN_WORKSPACE_STACK_ID;
1032 private static int getFreeformWindowModeId() {
1033 if(getCurrentApiVersion() >= 28.0f)
1034 return WINDOWING_MODE_FREEFORM;
1036 return FREEFORM_WORKSPACE_STACK_ID;
1039 private static String getWindowingModeMethodName() {
1040 if(getCurrentApiVersion() >= 28.0f)
1041 return "setLaunchWindowingMode";
1043 return "setLaunchStackId";
1046 public static Bundle getActivityOptionsBundle(Context context, ApplicationType type, View view) {
1047 SharedPreferences pref = getSharedPreferences(context);
1049 return getActivityOptionsBundle(context, type, pref.getString("window_size", "standard"), view);
1052 private static Bundle getActivityOptionsBundle(Context context, ApplicationType type, String windowSize, View view) {
1053 SharedPreferences pref = getSharedPreferences(context);
1054 if(!canEnableFreeform() || !pref.getBoolean("freeform_hack", false))
1055 return getActivityOptions(view).toBundle();
1057 switch(windowSize) {
1059 return launchMode1(context, type, view);
1061 return launchMode2(context, MAXIMIZED, type, view);
1063 return launchMode2(context, LEFT, type, view);
1065 return launchMode2(context, RIGHT, type, view);
1067 return launchMode3(context, type, view);
1070 return getActivityOptions(context, type, view).toBundle();
1073 private static ApplicationType getApplicationType(Context context, String packageName) {
1074 return isGame(context, packageName) ? ApplicationType.GAME : ApplicationType.APPLICATION;
1077 public static boolean isSystemApp(Context context) {
1079 ApplicationInfo info = context.getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
1080 int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
1081 return (info.flags & mask) != 0;
1082 } catch (PackageManager.NameNotFoundException e) {
1087 public static boolean isChromeOs(Context context) {
1088 return context.getPackageManager().hasSystemFeature("org.chromium.arc");
1091 public static boolean isBlissOs(Context context) {
1092 boolean validBlissOsBuildProp = false;
1094 String blissVersion = getSystemProperty("ro.bliss.version");
1095 if(blissVersion != null && !blissVersion.isEmpty())
1096 validBlissOsBuildProp = true;
1098 String buildUser = getSystemProperty("ro.build.user");
1099 if(buildUser != null && buildUser.equals("electrikjesus"))
1100 validBlissOsBuildProp = true;
1102 return validBlissOsBuildProp
1103 && BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
1104 && isSystemApp(context);
1107 public static boolean isLauncherPermanentlyEnabled(Context context) {
1108 if(BuildConfig.APPLICATION_ID.equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
1111 PackageManager pm = context.getPackageManager();
1113 pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1114 return pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, BuildConfig.APPLICATION_ID) == PackageManager.SIGNATURE_MATCH
1115 && BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
1116 && isSystemApp(context);
1117 } catch (PackageManager.NameNotFoundException e) {
1122 public static int getBaseTaskbarSize(Context context) {
1123 return Math.round(getBaseTaskbarSizeFloat(context));
1126 private static float getBaseTaskbarSizeFloat(Context context) {
1127 SharedPreferences pref = getSharedPreferences(context);
1128 float baseTaskbarSize = context.getResources().getDimension(R.dimen.base_taskbar_size);
1129 boolean navbarButtonsEnabled = false;
1131 if(pref.getBoolean("dashboard", false))
1132 baseTaskbarSize += context.getResources().getDimension(R.dimen.dashboard_button_size);
1134 if(pref.getBoolean("button_back", false)) {
1135 navbarButtonsEnabled = true;
1136 baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
1139 if(pref.getBoolean("button_home", false)) {
1140 navbarButtonsEnabled = true;
1141 baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
1144 if(pref.getBoolean("button_recents", false)) {
1145 navbarButtonsEnabled = true;
1146 baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
1149 if(navbarButtonsEnabled)
1150 baseTaskbarSize += context.getResources().getDimension(R.dimen.navbar_buttons_margin);
1152 return baseTaskbarSize;
1155 private static void startTaskbarService(Context context, boolean fullRestart) {
1156 context.startService(new Intent(context, TaskbarService.class));
1157 context.startService(new Intent(context, StartMenuService.class));
1158 context.startService(new Intent(context, DashboardService.class));
1159 if(fullRestart) context.startService(new Intent(context, NotificationService.class));
1162 private static void stopTaskbarService(Context context, boolean fullRestart) {
1163 context.stopService(new Intent(context, TaskbarService.class));
1164 context.stopService(new Intent(context, StartMenuService.class));
1165 context.stopService(new Intent(context, DashboardService.class));
1166 if(fullRestart) context.stopService(new Intent(context, NotificationService.class));
1169 public static void restartTaskbar(Context context) {
1170 SharedPreferences pref = getSharedPreferences(context);
1171 if(pref.getBoolean("taskbar_active", false) && !pref.getBoolean("is_hidden", false)) {
1173 .putBoolean("is_restarting", true)
1174 .putBoolean("skip_auto_hide_navbar", true)
1177 stopTaskbarService(context, true);
1178 startTaskbarService(context, true);
1179 } else if(isServiceRunning(context, StartMenuService.class)) {
1180 pref.edit().putBoolean("skip_auto_hide_navbar", true).apply();
1182 stopTaskbarService(context, false);
1183 startTaskbarService(context, false);
1186 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.RESTART"));
1189 public static void restartNotificationService(Context context) {
1190 if(isServiceRunning(context, NotificationService.class)) {
1191 SharedPreferences pref = getSharedPreferences(context);
1192 pref.edit().putBoolean("is_restarting", true).apply();
1194 Intent intent = new Intent(context, NotificationService.class);
1195 context.stopService(intent);
1196 context.startService(intent);
1200 public static void showHideNavigationBar(Context context, boolean show) {
1201 // Show or hide the system navigation bar on Bliss-x86
1203 if(getCurrentApiVersion() >= 28.0f)
1204 Settings.Secure.putInt(context.getContentResolver(), "navigation_bar_visible", show ? 1 : 0);
1206 Settings.System.putInt(context.getContentResolver(), "navigation_bar_show", show ? 1 : 0);
1207 } catch (Exception e) { /* Gracefully fail */ }
1210 public static void initPrefs(Context context) {
1211 // On smaller-screened devices, set "Grid" as the default start menu layout
1212 SharedPreferences pref = getSharedPreferences(context);
1213 if(context.getApplicationContext().getResources().getConfiguration().smallestScreenWidthDp < 720
1214 && pref.getString("start_menu_layout", "null").equals("null")) {
1215 pref.edit().putString("start_menu_layout", "grid").apply();
1218 // Enable freeform hack automatically on supported devices
1219 if(canEnableFreeform()) {
1220 if(!pref.getBoolean("freeform_hack_override", false)) {
1222 .putBoolean("freeform_hack", hasFreeformSupport(context) && !isSamsungDevice())
1223 .putBoolean("save_window_sizes", false)
1224 .putBoolean("freeform_hack_override", true)
1226 } else if(!hasFreeformSupport(context)) {
1227 pref.edit().putBoolean("freeform_hack", false).apply();
1229 stopFreeformHack(context);
1232 boolean freeformWasEnabled = pref.getBoolean("freeform_hack", false)
1233 || pref.getBoolean("show_freeform_disabled_message", false);
1236 .putBoolean("freeform_hack", false)
1237 .putBoolean("show_freeform_disabled_message", freeformWasEnabled)
1240 SavedWindowSizes.getInstance(context).clear(context);
1241 stopFreeformHack(context);
1244 // Customizations for BlissOS
1245 if(isBlissOs(context) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1246 && !pref.getBoolean("bliss_os_prefs", false)) {
1247 SharedPreferences.Editor editor = pref.edit();
1249 if(hasFreeformSupport(context)) {
1250 editor.putBoolean("freeform_hack", true);
1253 editor.putString("recents_amount", "running_apps_only");
1254 editor.putString("refresh_frequency", "0");
1255 editor.putString("max_num_of_recents", "2147483647");
1256 editor.putString("sort_order", "true");
1257 editor.putString("window_size", "phone_size");
1258 editor.putString("start_button_image", "app_logo");
1259 editor.putBoolean("full_length", true);
1260 editor.putBoolean("dashboard", true);
1261 editor.putBoolean("button_back", true);
1262 editor.putBoolean("button_home", true);
1263 editor.putBoolean("button_recents", true);
1264 editor.putBoolean("auto_hide_navbar", true);
1265 // editor.putBoolean("shortcut_icon", false);
1266 editor.putBoolean("bliss_os_prefs", true);
1270 // Customizations for Android-x86 devices (non-Bliss)
1271 if(BuildConfig.APPLICATION_ID.equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
1272 && isSystemApp(context)
1273 && !pref.getBoolean("android_x86_prefs", false)) {
1275 .putString("recents_amount", "running_apps_only")
1276 .putString("refresh_frequency", "0")
1277 .putString("max_num_of_recents", "2147483647")
1278 .putString("sort_order", "true")
1279 .putString("window_size", "phone_size")
1280 .putBoolean("full_length", true)
1281 .putBoolean("dashboard", true)
1282 // .putBoolean("shortcut_icon", false)
1283 .putBoolean("android_x86_prefs", true)
1288 public static DisplayInfo getDisplayInfo(Context context) {
1289 context = context.getApplicationContext();
1291 WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
1292 Display disp = wm.getDefaultDisplay();
1294 DisplayMetrics metrics = new DisplayMetrics();
1295 disp.getMetrics(metrics);
1297 DisplayMetrics realMetrics = new DisplayMetrics();
1298 disp.getRealMetrics(realMetrics);
1300 DisplayInfo display = new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);
1302 if(isChromeOs(context)) {
1303 SharedPreferences pref = getSharedPreferences(context);
1304 if(!pref.getBoolean("chrome_os_context_menu_fix", true)) {
1305 display.width = realMetrics.widthPixels;
1306 display.height = realMetrics.heightPixels;
1312 boolean sameWidth = metrics.widthPixels == realMetrics.widthPixels;
1313 boolean sameHeight = metrics.heightPixels == realMetrics.heightPixels;
1315 if(sameWidth && !sameHeight) {
1316 display.width = realMetrics.widthPixels;
1317 display.height = realMetrics.heightPixels - getNavbarHeight(context);
1320 if(!sameWidth && sameHeight) {
1321 display.width = realMetrics.widthPixels - getNavbarHeight(context);
1322 display.height = realMetrics.heightPixels;
1328 public static void pinAppShortcut(Context context) {
1329 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1330 ShortcutManager mShortcutManager = context.getSystemService(ShortcutManager.class);
1332 if(mShortcutManager.isRequestPinShortcutSupported()) {
1333 ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder(context, "freeform_mode").build();
1335 mShortcutManager.requestPinShortcut(pinShortcutInfo, null);
1337 showToastLong(context, R.string.pin_shortcut_not_supported);
1339 Intent intent = getShortcutIntent(context);
1340 intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
1341 intent.putExtra("duplicate", false);
1343 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1344 homeIntent.addCategory(Intent.CATEGORY_HOME);
1345 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
1347 intent.setPackage(defaultLauncher.activityInfo.packageName);
1348 context.sendBroadcast(intent);
1350 showToast(context, R.string.shortcut_created);
1354 public static boolean shouldCollapse(Context context, boolean pendingAppLaunch) {
1355 SharedPreferences pref = getSharedPreferences(context);
1356 if(pref.getBoolean("hide_taskbar", true)) {
1357 if(isOverridingFreeformHack(context))
1358 return !LauncherHelper.getInstance().isOnHomeScreen();
1360 FreeformHackHelper helper = FreeformHackHelper.getInstance();
1361 if(pendingAppLaunch)
1362 return !helper.isFreeformHackActive();
1364 return !helper.isInFreeformWorkspace();
1370 public static boolean isOverridingFreeformHack(Context context) {
1371 SharedPreferences pref = getSharedPreferences(context);
1372 return pref.getBoolean("freeform_hack", false)
1373 && ((isChromeOs(context) && pref.getBoolean("chrome_os_context_menu_fix", true))
1374 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f));
1377 public static boolean isPlayStoreInstalled(Context context) {
1379 context.getPackageManager().getPackageInfo("com.android.vending", 0);
1381 } catch (PackageManager.NameNotFoundException e) {
1386 private static float getCurrentApiVersion() {
1387 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1388 return Float.valueOf(Build.VERSION.SDK_INT + "." + Build.VERSION.PREVIEW_SDK_INT);
1390 return (float) Build.VERSION.SDK_INT;
1393 public static boolean hasBrokenSetLaunchBoundsApi() {
1394 return getCurrentApiVersion() >= 26.0f
1395 && getCurrentApiVersion() < 28.0f
1396 && !isSamsungDevice()
1397 && !isNvidiaDevice();
1400 public static String getSecondScreenPackageName(Context context) {
1401 return getInstalledPackage(context, Arrays.asList(
1402 "com.farmerbb.secondscreen.free",
1403 "com.farmerbb.secondscreen"));
1406 // Returns the name of an installed package from a list of package names, in order of preference
1407 private static String getInstalledPackage(Context context, List<String> packageNames) {
1408 if(packageNames == null || packageNames.isEmpty())
1411 List<String> packages = packageNames instanceof ArrayList ? packageNames : new ArrayList<>(packageNames);
1412 String packageName = packages.get(0);
1415 context.getPackageManager().getPackageInfo(packageName, 0);
1417 } catch (PackageManager.NameNotFoundException e) {
1419 return getInstalledPackage(context, packages);
1423 public static boolean visualFeedbackEnabled(Context context) {
1424 SharedPreferences pref = getSharedPreferences(context);
1425 return (getCurrentApiVersion() < 26.0f || getCurrentApiVersion() >= 28.0f)
1426 && pref.getBoolean("visual_feedback", true);
1429 public static void showRecentAppsDialog(Context context) {
1430 showRecentAppsDialog(context, null, null);
1433 public static AlertDialog showRecentAppsDialog(Context context, Runnable onError, Runnable onFinish) {
1434 Runnable finalOnFinish = onFinish == null
1438 Runnable finalOnError = onError == null
1439 ? () -> showErrorDialog(context, "GET_USAGE_STATS", finalOnFinish)
1442 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isSystemApp(context)) {
1443 ApplicationInfo applicationInfo = null;
1445 applicationInfo = context.getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
1446 } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
1448 if(applicationInfo != null) {
1449 AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
1450 int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
1452 if(mode != AppOpsManager.MODE_ALLOWED) {
1453 AlertDialog.Builder builder = new AlertDialog.Builder(context);
1454 builder.setTitle(R.string.pref_header_recent_apps)
1455 .setMessage(R.string.enable_recent_apps)
1456 .setPositiveButton(R.string.action_ok, (dialog, which) -> {
1458 context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
1459 showToastLong(context, R.string.usage_stats_message);
1461 finalOnFinish.run();
1462 } catch (ActivityNotFoundException e) {
1466 .setNegativeButton(R.string.action_cancel, (dialog, which) -> finalOnFinish.run());
1468 AlertDialog dialog = builder.create();
1470 dialog.setCancelable(false);
1477 finalOnFinish.run();
1481 public static Context wrapContext(Context context) {
1482 SharedPreferences pref = getSharedPreferences(context);
1485 switch(pref.getString("theme", "light")) {
1487 theme = R.style.AppTheme;
1490 theme = R.style.AppTheme_Dark;
1494 return theme > -1 ? new ContextThemeWrapper(context, theme) : context;
1497 public static boolean isPlayStoreRelease(Context context) {
1498 return isPlayStoreRelease(context, BuildConfig.APPLICATION_ID);
1501 @SuppressLint("PackageManagerGetSignatures")
1502 public static boolean isPlayStoreRelease(Context context, String packageName) {
1503 Signature playStoreSignature = new Signature(context.getString(R.string.signature));
1505 PackageManager pm = context.getPackageManager();
1506 PackageInfo info = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
1507 for(Signature signature : info.signatures) {
1508 if(signature.equals(playStoreSignature))
1511 } catch (Exception e) { /* Gracefully fail */ }
1516 public static boolean isExternalAccessDisabled(Context context) {
1517 SharedPreferences pref = getSharedPreferences(context);
1518 return !pref.getBoolean("tasker_enabled", true);
1521 public static boolean enableFreeformModeShortcut(Context context) {
1522 return canEnableFreeform()
1523 && !U.isOverridingFreeformHack(context)
1524 && !U.isChromeOs(context);
1527 public static void startForegroundService(Context context, Intent intent) {
1528 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1529 if(Settings.canDrawOverlays(context))
1530 context.startForegroundService(intent);
1532 context.startService(intent);
1535 public static int getOverlayType() {
1536 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
1537 ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
1538 : WindowManager.LayoutParams.TYPE_PHONE;
1541 public static boolean isDelegatingHomeActivity(Context context) {
1542 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1543 homeIntent.addCategory(Intent.CATEGORY_HOME);
1545 final List<ResolveInfo> listOfLaunchers = context.getPackageManager().queryIntentActivities(homeIntent, 0);
1546 for(ResolveInfo launcher : listOfLaunchers) {
1547 if(launcher.activityInfo.packageName.equals(BuildConfig.SUPPORT_APPLICATION_ID))
1554 @SuppressLint("PrivateApi")
1555 private static String getSystemProperty(String key) {
1557 Class<?> cls = Class.forName("android.os.SystemProperties");
1558 return cls.getMethod("get", String.class).invoke(null, key).toString();
1559 } catch (Exception e) {
1564 @SuppressWarnings("ResultOfMethodCallIgnored")
1565 public static boolean importCustomStartButtonImage(Context context, Uri uri) {
1567 File imagesDir = new File(context.getFilesDir(), "images");
1570 File importedFile = new File(imagesDir, "custom_image_new");
1571 if(importedFile.exists()) importedFile.delete();
1573 BufferedInputStream is = new BufferedInputStream(context.getContentResolver().openInputStream(uri));
1574 byte[] data = new byte[is.available()];
1576 if(data.length > 0) {
1577 BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(importedFile));
1584 File prevFile = new File(imagesDir, "custom_image");
1585 if(prevFile.exists()) prevFile.delete();
1587 importedFile.renameTo(prevFile);
1589 } catch (IOException e) {
1594 public static String getDefaultStartButtonImage(Context context) {
1595 SharedPreferences pref = getSharedPreferences(context);
1596 return pref.getBoolean("app_drawer_icon", false)
1601 private static boolean shouldLaunchTouchAbsorber(Context context) {
1602 return isOverridingFreeformHack(context) && !isChromeOs(context) && getCurrentApiVersion() < 29.0f;
1605 public static boolean isDesktopIconsEnabled(Context context) {
1606 return !U.canBootToFreeform(context) && !U.shouldLaunchTouchAbsorber(context);