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.annotation.SuppressLint;
19 import android.annotation.TargetApi;
20 import android.app.Activity;
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.app.admin.DevicePolicyManager;
27 import android.content.ActivityNotFoundException;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.SharedPreferences;
32 import android.content.pm.ActivityInfo;
33 import android.content.pm.ApplicationInfo;
34 import android.content.pm.LauncherActivityInfo;
35 import android.content.pm.LauncherApps;
36 import android.content.pm.PackageInfo;
37 import android.content.pm.PackageManager;
38 import android.content.pm.ResolveInfo;
39 import android.content.pm.ShortcutInfo;
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.DummyActivity;
64 import com.farmerbb.taskbar.activity.InvisibleActivityFreeform;
65 import com.farmerbb.taskbar.activity.ShortcutActivity;
66 import com.farmerbb.taskbar.activity.StartTaskbarActivity;
67 import com.farmerbb.taskbar.receiver.LockDeviceReceiver;
68 import com.farmerbb.taskbar.service.DashboardService;
69 import com.farmerbb.taskbar.service.NotificationService;
70 import com.farmerbb.taskbar.service.PowerMenuService;
71 import com.farmerbb.taskbar.service.StartMenuService;
72 import com.farmerbb.taskbar.service.TaskbarService;
74 import java.lang.reflect.Method;
75 import java.util.ArrayList;
76 import java.util.Arrays;
77 import java.util.List;
83 private static SharedPreferences pref;
84 private static Integer cachedRotation;
86 private static final int MAXIMIZED = 0;
87 private static final int LEFT = -1;
88 private static final int RIGHT = 1;
90 public static final int HIDDEN = 0;
91 public static final int TOP_APPS = 1;
93 // From android.app.ActivityManager.StackId
94 private static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
95 private static final int FREEFORM_WORKSPACE_STACK_ID = 2;
97 public static SharedPreferences getSharedPreferences(Context context) {
98 if(pref == null) pref = context.getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", Context.MODE_PRIVATE);
102 public static void showPermissionDialog(Context context) {
103 showPermissionDialog(context, null, null);
106 @TargetApi(Build.VERSION_CODES.M)
107 public static AlertDialog showPermissionDialog(Context context, Runnable onError, Runnable onFinish) {
108 Runnable finalOnFinish = onFinish == null
112 Runnable finalOnError = onError == null
113 ? () -> showErrorDialog(context, "SYSTEM_ALERT_WINDOW", finalOnFinish)
116 AlertDialog.Builder builder = new AlertDialog.Builder(context);
117 builder.setTitle(R.string.permission_dialog_title)
118 .setMessage(R.string.permission_dialog_message)
119 .setPositiveButton(R.string.action_grant_permission, (dialog, which) -> {
121 context.startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
122 Uri.parse("package:" + BuildConfig.APPLICATION_ID)));
125 } catch (ActivityNotFoundException e) {
130 AlertDialog dialog = builder.create();
132 dialog.setCancelable(false);
137 public static AlertDialog showErrorDialog(Context context, String appopCmd) {
138 return showErrorDialog(context, appopCmd, null);
141 private static AlertDialog showErrorDialog(Context context, String appopCmd, Runnable onFinish) {
142 Runnable finalOnFinish = onFinish == null
146 AlertDialog.Builder builder = new AlertDialog.Builder(context);
147 builder.setTitle(R.string.error_dialog_title)
148 .setMessage(context.getString(R.string.error_dialog_message, BuildConfig.APPLICATION_ID, appopCmd))
149 .setPositiveButton(R.string.action_ok, (dialog, which) -> finalOnFinish.run());
151 AlertDialog dialog = builder.create();
153 dialog.setCancelable(false);
158 public static void lockDevice(Context context) {
159 ComponentName component = new ComponentName(context, LockDeviceReceiver.class);
160 context.getPackageManager().setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
161 PackageManager.DONT_KILL_APP);
163 DevicePolicyManager mDevicePolicyManager = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
164 if(mDevicePolicyManager.isAdminActive(component))
165 mDevicePolicyManager.lockNow();
167 launchApp(context, () -> {
168 Intent intent = new Intent(context, DummyActivity.class);
169 intent.putExtra("device_admin", true);
170 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
171 context.startActivity(intent, getActivityOptionsBundle(ApplicationType.APPLICATION));
173 if(context instanceof Activity)
174 ((Activity) context).overridePendingTransition(0, 0);
179 public static void sendAccessibilityAction(Context context, int action) {
180 ComponentName component = new ComponentName(context, PowerMenuService.class);
181 context.getPackageManager().setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
182 PackageManager.DONT_KILL_APP);
184 if(isAccessibilityServiceEnabled(context)) {
185 Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
186 intent.putExtra("action", action);
187 LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
189 launchApp(context, () -> {
190 Intent intent = new Intent(context, DummyActivity.class);
191 intent.putExtra("accessibility", true);
192 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
193 context.startActivity(intent, getActivityOptionsBundle(ApplicationType.APPLICATION));
195 if(context instanceof Activity)
196 ((Activity) context).overridePendingTransition(0, 0);
201 private static boolean isAccessibilityServiceEnabled(Context context) {
202 String accessibilityServices = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
203 ComponentName component = new ComponentName(context, PowerMenuService.class);
205 return accessibilityServices != null
206 && (accessibilityServices.contains(component.flattenToString())
207 || accessibilityServices.contains(component.flattenToShortString()));
210 public static void showToast(Context context, int message) {
211 showToast(context, context.getString(message), Toast.LENGTH_SHORT);
214 public static void showToastLong(Context context, int message) {
215 showToast(context, context.getString(message), Toast.LENGTH_LONG);
218 public static void showToast(Context context, String message, int length) {
221 ToastInterface toast = DependencyUtils.createToast(context, message, length);
224 ToastHelper.getInstance().setLastToast(toast);
227 public static void cancelToast() {
228 ToastInterface toast = ToastHelper.getInstance().getLastToast();
229 if(toast != null) toast.cancel();
232 public static void startShortcut(Context context, String packageName, String componentName, ShortcutInfo shortcut) {
243 public static void launchApp(final Context context,
244 final String packageName,
245 final String componentName,
246 final long userId, final String windowSize,
247 final boolean launchedFromTaskbar,
248 final boolean openInNewWindow) {
259 private static void launchApp(final Context context,
260 final String packageName,
261 final String componentName,
262 final long userId, final String windowSize,
263 final boolean launchedFromTaskbar,
264 final boolean openInNewWindow,
265 final ShortcutInfo shortcut) {
266 launchApp(context, launchedFromTaskbar, () -> continueLaunchingApp(context, packageName, componentName, userId,
267 windowSize, launchedFromTaskbar, openInNewWindow, shortcut));
270 public static void launchApp(Context context, Runnable runnable) {
271 launchApp(context, true, runnable);
274 private static void launchApp(Context context, boolean launchedFromTaskbar, Runnable runnable) {
275 SharedPreferences pref = getSharedPreferences(context);
276 FreeformHackHelper helper = FreeformHackHelper.getInstance();
278 boolean specialLaunch = hasBrokenSetLaunchBoundsApi()
279 && FreeformHackHelper.getInstance().isInFreeformWorkspace()
280 && MenuHelper.getInstance().isContextMenuOpen();
282 if(hasFreeformSupport(context)
283 && pref.getBoolean("freeform_hack", false)
284 && (!helper.isInFreeformWorkspace() || specialLaunch)) {
285 new Handler().postDelayed(() -> {
286 startFreeformHack(context, true, launchedFromTaskbar);
288 new Handler().postDelayed(runnable, helper.isFreeformHackActive() ? 0 : 100);
289 }, launchedFromTaskbar ? 0 : 100);
294 @SuppressWarnings("deprecation")
295 @TargetApi(Build.VERSION_CODES.N)
296 public static void startFreeformHack(Context context, boolean checkMultiWindow, boolean launchedFromTaskbar) {
297 Intent freeformHackIntent = new Intent(context, InvisibleActivityFreeform.class);
298 freeformHackIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
301 freeformHackIntent.putExtra("check_multiwindow", true);
303 if(launchedFromTaskbar) {
304 SharedPreferences pref = getSharedPreferences(context);
305 if(pref.getBoolean("disable_animations", false))
306 freeformHackIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
309 if(canDrawOverlays(context))
310 launchAppLowerRight(context, freeformHackIntent);
313 @TargetApi(Build.VERSION_CODES.N)
314 private static void continueLaunchingApp(Context context,
316 String componentName,
319 boolean launchedFromTaskbar,
320 boolean openInNewWindow,
321 ShortcutInfo shortcut) {
322 SharedPreferences pref = getSharedPreferences(context);
323 Intent intent = new Intent();
324 intent.setComponent(ComponentName.unflattenFromString(componentName));
325 intent.setAction(Intent.ACTION_MAIN);
326 intent.addCategory(Intent.CATEGORY_LAUNCHER);
327 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
328 intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
330 if(FreeformHackHelper.getInstance().isInFreeformWorkspace()
331 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1)
332 intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
334 if(launchedFromTaskbar) {
335 if(pref.getBoolean("disable_animations", false))
336 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
339 if(openInNewWindow || pref.getBoolean("force_new_window", false)) {
340 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
342 ActivityInfo activityInfo = intent.resolveActivityInfo(context.getPackageManager(), 0);
343 if(activityInfo != null) {
344 switch(activityInfo.launchMode) {
345 case ActivityInfo.LAUNCH_SINGLE_TASK:
346 case ActivityInfo.LAUNCH_SINGLE_INSTANCE:
347 intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
353 ApplicationType type = getApplicationType(context, packageName);
355 if(windowSize == null)
356 windowSize = SavedWindowSizes.getInstance(context).getWindowSize(context, packageName);
358 if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N
359 || !pref.getBoolean("freeform_hack", false)
360 || windowSize.equals("standard")) {
361 launchStandard(context, intent, userId, shortcut, type);
362 } else switch(windowSize) {
364 launchMode1(context, intent, userId, shortcut, type);
367 launchMode2(context, intent, MAXIMIZED, userId, shortcut, type);
370 launchMode2(context, intent, LEFT, userId, shortcut, type);
373 launchMode2(context, intent, RIGHT, userId, shortcut, type);
376 launchMode3(context, intent, userId, shortcut, type);
380 if(shouldCollapse(context, true))
381 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_TASKBAR"));
383 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_START_MENU"));
386 private static void launchStandard(Context context, Intent intent, long userId, ShortcutInfo shortcut, ApplicationType type) {
387 Bundle bundle = Build.VERSION.SDK_INT < Build.VERSION_CODES.N ? null : getActivityOptions(type).toBundle();
388 if(shortcut == null) {
389 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
390 if(userId == userManager.getSerialNumberForUser(Process.myUserHandle())) {
392 context.startActivity(intent, bundle);
393 } catch (ActivityNotFoundException e) {
394 launchAndroidForWork(context, intent.getComponent(), bundle, userId);
395 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
397 launchAndroidForWork(context, intent.getComponent(), bundle, userId);
399 launchShortcut(context, shortcut, bundle);
402 @SuppressWarnings("deprecation")
403 @TargetApi(Build.VERSION_CODES.N)
404 private static void launchMode1(Context context, Intent intent, long userId, ShortcutInfo shortcut, ApplicationType type) {
405 DisplayMetrics metrics = getRealDisplayMetrics(context);
407 int width1 = metrics.widthPixels / 8;
408 int width2 = metrics.widthPixels - width1;
409 int height1 = metrics.heightPixels / 8;
410 int height2 = metrics.heightPixels - height1;
412 Bundle bundle = getActivityOptions(type).setLaunchBounds(new Rect(
419 if(shortcut == null) {
420 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
421 if(userId == userManager.getSerialNumberForUser(Process.myUserHandle())) {
423 context.startActivity(intent, bundle);
424 } catch (ActivityNotFoundException e) {
425 launchAndroidForWork(context, intent.getComponent(), bundle, userId);
426 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
428 launchAndroidForWork(context, intent.getComponent(), bundle, userId);
430 launchShortcut(context, shortcut, bundle);
433 @SuppressWarnings("deprecation")
434 @TargetApi(Build.VERSION_CODES.N)
435 private static void launchMode2(Context context, Intent intent, int launchType, long userId, ShortcutInfo shortcut, ApplicationType type) {
436 DisplayMetrics metrics = getRealDisplayMetrics(context);
438 int statusBarHeight = getStatusBarHeight(context);
439 String position = getTaskbarPosition(context);
441 boolean isPortrait = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
442 boolean isLandscape = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
444 int left = launchType == RIGHT && isLandscape
445 ? metrics.widthPixels / 2
448 int top = launchType == RIGHT && isPortrait
449 ? metrics.heightPixels / 2
452 int right = launchType == LEFT && isLandscape
453 ? metrics.widthPixels / 2
454 : metrics.widthPixels;
456 int bottom = launchType == LEFT && isPortrait
457 ? metrics.heightPixels / 2
458 : metrics.heightPixels;
460 int iconSize = isOverridingFreeformHack(context)
462 : context.getResources().getDimensionPixelSize(R.dimen.icon_size);
464 if(position.contains("vertical_left")) {
465 if(launchType != RIGHT || isPortrait) left = left + iconSize;
466 } else if(position.contains("vertical_right")) {
467 if(launchType != LEFT || isPortrait) right = right - iconSize;
468 } else if(position.contains("bottom")) {
469 if(isLandscape || (launchType != LEFT && isPortrait))
470 bottom = bottom - iconSize;
471 } else if(isLandscape || (launchType != RIGHT && isPortrait))
472 top = top + iconSize;
474 Bundle bundle = getActivityOptions(type).setLaunchBounds(new Rect(
481 if(shortcut == null) {
482 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
483 if(userId == userManager.getSerialNumberForUser(Process.myUserHandle())) {
485 context.startActivity(intent, bundle);
486 } catch (ActivityNotFoundException e) {
487 launchAndroidForWork(context, intent.getComponent(), bundle, userId);
488 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
490 launchAndroidForWork(context, intent.getComponent(), bundle, userId);
492 launchShortcut(context, shortcut, bundle);
495 @SuppressWarnings("deprecation")
496 @TargetApi(Build.VERSION_CODES.N)
497 private static void launchMode3(Context context, Intent intent, long userId, ShortcutInfo shortcut, ApplicationType type) {
498 DisplayMetrics metrics = getRealDisplayMetrics(context);
500 int width1 = metrics.widthPixels / 2;
501 int width2 = context.getResources().getDimensionPixelSize(R.dimen.phone_size_width) / 2;
502 int height1 = metrics.heightPixels / 2;
503 int height2 = context.getResources().getDimensionPixelSize(R.dimen.phone_size_height) / 2;
505 Bundle bundle = getActivityOptions(type).setLaunchBounds(new Rect(
512 if(shortcut == null) {
513 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
514 if(userId == userManager.getSerialNumberForUser(Process.myUserHandle())) {
516 context.startActivity(intent, bundle);
517 } catch (ActivityNotFoundException e) {
518 launchAndroidForWork(context, intent.getComponent(), bundle, userId);
519 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
521 launchAndroidForWork(context, intent.getComponent(), bundle, userId);
523 launchShortcut(context, shortcut, bundle);
526 private static void launchAndroidForWork(Context context, ComponentName componentName, Bundle bundle, long userId) {
527 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
528 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
531 launcherApps.startMainActivity(componentName, userManager.getUserForSerialNumber(userId), null, bundle);
532 } catch (ActivityNotFoundException | NullPointerException e) { /* Gracefully fail */ }
535 @TargetApi(Build.VERSION_CODES.N_MR1)
536 private static void launchShortcut(Context context, ShortcutInfo shortcut, Bundle bundle) {
537 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
539 if(launcherApps.hasShortcutHostPermission()) {
541 launcherApps.startShortcut(shortcut, null, bundle);
542 } catch (ActivityNotFoundException | NullPointerException e) { /* Gracefully fail */ }
546 public static void launchAppMaximized(Context context, Intent intent) {
547 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
548 long userId = userManager.getSerialNumberForUser(Process.myUserHandle());
550 launchMode2(context, intent, MAXIMIZED, userId, null, ApplicationType.CONTEXT_MENU);
553 @SuppressWarnings("deprecation")
554 @TargetApi(Build.VERSION_CODES.N)
555 public static void launchAppLowerRight(Context context, Intent intent) {
556 DisplayMetrics metrics = getRealDisplayMetrics(context);
558 context.startActivity(intent, getActivityOptions(ApplicationType.FREEFORM_HACK).setLaunchBounds(new Rect(
560 metrics.heightPixels,
561 metrics.widthPixels + 1,
562 metrics.heightPixels + 1
564 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
567 public static void checkForUpdates(Context context) {
569 if(isPlayStoreRelease(context))
570 url = "https://play.google.com/store/apps/details?id=" + BuildConfig.APPLICATION_ID;
572 url = "https://f-droid.org/repository/browse/?fdid=" + BuildConfig.APPLICATION_ID;
574 Intent intent = new Intent(Intent.ACTION_VIEW);
575 intent.setData(Uri.parse(url));
576 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
579 context.startActivity(intent);
580 } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
583 public static boolean launcherIsDefault(Context context) {
584 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
585 homeIntent.addCategory(Intent.CATEGORY_HOME);
586 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
588 return defaultLauncher.activityInfo.packageName.equals(BuildConfig.APPLICATION_ID);
591 public static void setCachedRotation(int cachedRotation) {
592 U.cachedRotation = cachedRotation;
595 public static String getTaskbarPosition(Context context) {
596 SharedPreferences pref = getSharedPreferences(context);
597 String position = pref.getString("position", "bottom_left");
599 if(pref.getBoolean("anchor", false)) {
600 WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
601 int rotation = cachedRotation != null ? cachedRotation : windowManager.getDefaultDisplay().getRotation();
606 case Surface.ROTATION_0:
607 return "bottom_left";
608 case Surface.ROTATION_90:
609 return "bottom_vertical_right";
610 case Surface.ROTATION_180:
612 case Surface.ROTATION_270:
613 return "top_vertical_left";
616 case "bottom_vertical_left":
618 case Surface.ROTATION_0:
619 return "bottom_vertical_left";
620 case Surface.ROTATION_90:
621 return "bottom_right";
622 case Surface.ROTATION_180:
623 return "top_vertical_right";
624 case Surface.ROTATION_270:
630 case Surface.ROTATION_0:
631 return "bottom_right";
632 case Surface.ROTATION_90:
633 return "top_vertical_right";
634 case Surface.ROTATION_180:
636 case Surface.ROTATION_270:
637 return "bottom_vertical_left";
640 case "bottom_vertical_right":
642 case Surface.ROTATION_0:
643 return "bottom_vertical_right";
644 case Surface.ROTATION_90:
646 case Surface.ROTATION_180:
647 return "top_vertical_left";
648 case Surface.ROTATION_270:
649 return "bottom_left";
654 case Surface.ROTATION_0:
656 case Surface.ROTATION_90:
657 return "bottom_vertical_left";
658 case Surface.ROTATION_180:
659 return "bottom_right";
660 case Surface.ROTATION_270:
661 return "top_vertical_right";
664 case "top_vertical_left":
666 case Surface.ROTATION_0:
667 return "top_vertical_left";
668 case Surface.ROTATION_90:
669 return "bottom_left";
670 case Surface.ROTATION_180:
671 return "bottom_vertical_right";
672 case Surface.ROTATION_270:
678 case Surface.ROTATION_0:
680 case Surface.ROTATION_90:
681 return "top_vertical_left";
682 case Surface.ROTATION_180:
683 return "bottom_left";
684 case Surface.ROTATION_270:
685 return "bottom_vertical_right";
688 case "top_vertical_right":
690 case Surface.ROTATION_0:
691 return "top_vertical_right";
692 case Surface.ROTATION_90:
694 case Surface.ROTATION_180:
695 return "bottom_vertical_left";
696 case Surface.ROTATION_270:
697 return "bottom_right";
706 private static int getMaxNumOfColumns(Context context) {
707 SharedPreferences pref = getSharedPreferences(context);
708 DisplayMetrics metrics = getRealDisplayMetrics(context);
709 float baseTaskbarSize = getBaseTaskbarSizeFloat(context) / metrics.density;
710 int numOfColumns = 0;
712 float maxScreenSize = getTaskbarPosition(context).contains("vertical")
713 ? (metrics.heightPixels - getStatusBarHeight(context)) / metrics.density
714 : metrics.widthPixels / metrics.density;
716 float iconSize = context.getResources().getDimension(R.dimen.icon_size) / metrics.density;
718 int userMaxNumOfColumns = Integer.valueOf(pref.getString("max_num_of_recents", "10"));
720 while(baseTaskbarSize + iconSize < maxScreenSize
721 && numOfColumns < userMaxNumOfColumns) {
722 baseTaskbarSize = baseTaskbarSize + iconSize;
729 public static int getMaxNumOfEntries(Context context) {
730 SharedPreferences pref = getSharedPreferences(context);
731 return pref.getBoolean("disable_scrolling_list", false)
732 ? getMaxNumOfColumns(context)
733 : Integer.valueOf(pref.getString("max_num_of_recents", "10"));
736 public static int getStatusBarHeight(Context context) {
737 int statusBarHeight = 0;
738 int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
740 statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
742 return statusBarHeight;
745 public static void refreshPinnedIcons(Context context) {
746 IconCache.getInstance(context).clearCache();
748 PinnedBlockedApps pba = PinnedBlockedApps.getInstance(context);
749 List<AppEntry> pinnedAppsList = new ArrayList<>(pba.getPinnedApps());
750 List<AppEntry> blockedAppsList = new ArrayList<>(pba.getBlockedApps());
751 PackageManager pm = context.getPackageManager();
755 for(AppEntry entry : pinnedAppsList) {
756 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
757 LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
759 final List<UserHandle> userHandles = userManager.getUserProfiles();
760 LauncherActivityInfo appInfo = null;
762 for(UserHandle handle : userHandles) {
763 List<LauncherActivityInfo> list = launcherApps.getActivityList(entry.getPackageName(), handle);
764 if(!list.isEmpty()) {
765 // Google App workaround
766 if(!entry.getPackageName().equals("com.google.android.googlequicksearchbox"))
767 appInfo = list.get(0);
769 boolean added = false;
770 for(LauncherActivityInfo info : list) {
771 if(info.getName().equals("com.google.android.googlequicksearchbox.SearchActivity")) {
777 if(!added) appInfo = list.get(0);
784 if(appInfo != null) {
785 AppEntry newEntry = new AppEntry(
786 entry.getPackageName(),
787 entry.getComponentName(),
789 IconCache.getInstance(context).getIcon(context, pm, appInfo),
792 newEntry.setUserId(entry.getUserId(context));
793 pba.addPinnedApp(context, newEntry);
797 for(AppEntry entry : blockedAppsList) {
798 pba.addBlockedApp(context, entry);
802 public static Intent getShortcutIntent(Context context) {
803 Intent shortcutIntent = new Intent(context, ShortcutActivity.class);
804 shortcutIntent.setAction(Intent.ACTION_MAIN);
805 shortcutIntent.putExtra("is_launching_shortcut", true);
807 Intent intent = new Intent();
808 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
809 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.mipmap.ic_freeform_mode));
810 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, context.getString(R.string.pref_header_freeform));
815 public static Intent getStartStopIntent(Context context) {
816 Intent shortcutIntent = new Intent(context, StartTaskbarActivity.class);
817 shortcutIntent.setAction(Intent.ACTION_MAIN);
818 shortcutIntent.putExtra("is_launching_shortcut", true);
820 Intent intent = new Intent();
821 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
822 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.mipmap.ic_launcher));
823 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, context.getString(R.string.start_taskbar));
828 public static boolean canEnableFreeform(Context context) {
829 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
830 && (getCurrentApiVersion() <= 27
831 || context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT));
834 public static boolean hasFreeformSupport(Context context) {
835 return canEnableFreeform(context)
836 && (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
837 || Settings.Global.getInt(context.getContentResolver(), "enable_freeform_support", 0) != 0
838 || (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
839 && Settings.Global.getInt(context.getContentResolver(), "force_resizable_activities", 0) != 0));
842 public static boolean hasPartialFreeformSupport() {
843 return Build.MANUFACTURER.equalsIgnoreCase("Samsung");
846 public static boolean isServiceRunning(Context context, Class<? extends Service> cls) {
847 return isServiceRunning(context, cls.getName());
850 private static boolean isServiceRunning(Context context, String className) {
851 ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
852 for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
853 if(className.equals(service.service.getClassName()))
860 public static int getBackgroundTint(Context context) {
861 SharedPreferences pref = getSharedPreferences(context);
863 // Import old background tint preference
864 if(pref.contains("show_background")) {
865 SharedPreferences.Editor editor = pref.edit();
867 if(!pref.getBoolean("show_background", true))
868 editor.putInt("background_tint", Color.TRANSPARENT).apply();
870 editor.remove("show_background");
874 return pref.getInt("background_tint", context.getResources().getInteger(R.integer.translucent_gray));
877 public static int getAccentColor(Context context) {
878 SharedPreferences pref = getSharedPreferences(context);
879 return pref.getInt("accent_color", context.getResources().getInteger(R.integer.translucent_white));
882 @TargetApi(Build.VERSION_CODES.M)
883 public static boolean canDrawOverlays(Context context) {
884 return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context);
887 public static boolean isGame(Context context, String packageName) {
888 SharedPreferences pref = getSharedPreferences(context);
889 if(pref.getBoolean("launch_games_fullscreen", true)) {
890 PackageManager pm = context.getPackageManager();
893 ApplicationInfo info = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
894 return (info.flags & ApplicationInfo.FLAG_IS_GAME) != 0 || (info.metaData != null && info.metaData.getBoolean("isGame", false));
895 } catch (PackageManager.NameNotFoundException e) {
902 @TargetApi(Build.VERSION_CODES.N)
903 public static ActivityOptions getActivityOptions(ApplicationType applicationType) {
904 ActivityOptions options = ActivityOptions.makeBasic();
907 switch(applicationType) {
909 if(FreeformHackHelper.getInstance().isFreeformHackActive())
910 stackId = FREEFORM_WORKSPACE_STACK_ID;
912 stackId = FULLSCREEN_WORKSPACE_STACK_ID;
915 stackId = FULLSCREEN_WORKSPACE_STACK_ID;
918 stackId = FREEFORM_WORKSPACE_STACK_ID;
921 if(hasBrokenSetLaunchBoundsApi())
922 stackId = FULLSCREEN_WORKSPACE_STACK_ID;
927 Method method = ActivityOptions.class.getMethod("setLaunchStackId", int.class);
928 method.invoke(options, stackId);
929 } catch (Exception e) { /* Gracefully fail */ }
934 public static Bundle getActivityOptionsBundle(ApplicationType applicationType) {
935 if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
938 return getActivityOptions(applicationType).toBundle();
941 private static ApplicationType getApplicationType(Context context, String packageName) {
942 return isGame(context, packageName) ? ApplicationType.GAME : ApplicationType.APPLICATION;
945 public static boolean isSystemApp(Context context) {
947 ApplicationInfo info = context.getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
948 int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
949 return (info.flags & mask) != 0;
950 } catch (PackageManager.NameNotFoundException e) {
955 public static boolean isChromeOs(Context context) {
956 return context.getPackageManager().hasSystemFeature("org.chromium.arc");
959 public static boolean isBlissOs(Context context) {
960 String blissVersion = DependencyUtils.getBlissOsVersion();
961 return blissVersion != null && !blissVersion.isEmpty()
962 && BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
963 && isSystemApp(context);
966 public static boolean isLauncherPermanentlyEnabled(Context context) {
967 if(BuildConfig.APPLICATION_ID.equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
970 PackageManager pm = context.getPackageManager();
972 pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
973 return pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, BuildConfig.APPLICATION_ID) == PackageManager.SIGNATURE_MATCH
974 && BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
975 && isSystemApp(context);
976 } catch (PackageManager.NameNotFoundException e) {
981 public static int getBaseTaskbarSize(Context context) {
982 return Math.round(getBaseTaskbarSizeFloat(context));
985 private static float getBaseTaskbarSizeFloat(Context context) {
986 SharedPreferences pref = getSharedPreferences(context);
987 float baseTaskbarSize = context.getResources().getDimension(R.dimen.base_taskbar_size);
988 boolean navbarButtonsEnabled = false;
990 if(pref.getBoolean("dashboard", false))
991 baseTaskbarSize += context.getResources().getDimension(R.dimen.dashboard_button_size);
993 if(pref.getBoolean("button_back", false)) {
994 navbarButtonsEnabled = true;
995 baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
998 if(pref.getBoolean("button_home", false)) {
999 navbarButtonsEnabled = true;
1000 baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
1003 if(pref.getBoolean("button_recents", false)) {
1004 navbarButtonsEnabled = true;
1005 baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
1008 if(navbarButtonsEnabled)
1009 baseTaskbarSize += context.getResources().getDimension(R.dimen.navbar_buttons_margin);
1011 return baseTaskbarSize;
1014 private static void startTaskbarService(Context context, boolean fullRestart) {
1015 context.startService(new Intent(context, TaskbarService.class));
1016 context.startService(new Intent(context, StartMenuService.class));
1017 context.startService(new Intent(context, DashboardService.class));
1018 if(fullRestart) context.startService(new Intent(context, NotificationService.class));
1021 private static void stopTaskbarService(Context context, boolean fullRestart) {
1022 context.stopService(new Intent(context, TaskbarService.class));
1023 context.stopService(new Intent(context, StartMenuService.class));
1024 context.stopService(new Intent(context, DashboardService.class));
1025 if(fullRestart) context.stopService(new Intent(context, NotificationService.class));
1028 public static void restartTaskbar(Context context) {
1029 SharedPreferences pref = getSharedPreferences(context);
1030 if(pref.getBoolean("taskbar_active", false) && !pref.getBoolean("is_hidden", false)) {
1032 .putBoolean("is_restarting", true)
1033 .putBoolean("skip_auto_hide_navbar", true)
1036 stopTaskbarService(context, true);
1037 startTaskbarService(context, true);
1038 } else if(isServiceRunning(context, StartMenuService.class)) {
1039 pref.edit().putBoolean("skip_auto_hide_navbar", true).apply();
1041 stopTaskbarService(context, false);
1042 startTaskbarService(context, false);
1046 public static void restartNotificationService(Context context) {
1047 if(isServiceRunning(context, NotificationService.class)) {
1048 SharedPreferences pref = getSharedPreferences(context);
1049 pref.edit().putBoolean("is_restarting", true).apply();
1051 Intent intent = new Intent(context, NotificationService.class);
1052 context.stopService(intent);
1053 context.startService(intent);
1057 public static void showHideNavigationBar(Context context, boolean show) {
1058 // Show or hide the system navigation bar on Bliss-x86
1060 Settings.System.putInt(context.getContentResolver(), "navigation_bar_show", show ? 1 : 0);
1061 } catch (Exception e) { /* Gracefully fail */ }
1064 public static void initPrefs(Context context) {
1065 // On smaller-screened devices, set "Grid" as the default start menu layout
1066 SharedPreferences pref = getSharedPreferences(context);
1067 if(context.getApplicationContext().getResources().getConfiguration().smallestScreenWidthDp < 720
1068 && pref.getString("start_menu_layout", "null").equals("null")) {
1069 pref.edit().putString("start_menu_layout", "grid").apply();
1072 // Enable freeform hack automatically on supported devices
1073 if(canEnableFreeform(context)) {
1074 if(!pref.getBoolean("freeform_hack_override", false)) {
1076 .putBoolean("freeform_hack", hasFreeformSupport(context) && !hasPartialFreeformSupport())
1077 .putBoolean("save_window_sizes", false)
1078 .putBoolean("freeform_hack_override", true)
1080 } else if(!hasFreeformSupport(context)) {
1081 pref.edit().putBoolean("freeform_hack", false).apply();
1083 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.FINISH_FREEFORM_ACTIVITY"));
1086 boolean freeformWasEnabled = pref.getBoolean("freeform_hack", false)
1087 || pref.getBoolean("show_freeform_disabled_message", false);
1090 .putBoolean("freeform_hack", false)
1091 .putBoolean("show_freeform_disabled_message", freeformWasEnabled)
1094 SavedWindowSizes.getInstance(context).clear(context);
1095 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.FINISH_FREEFORM_ACTIVITY"));
1098 // Customizations for BlissOS
1099 if(isBlissOs(context) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1100 && !pref.getBoolean("bliss_os_prefs", false)) {
1101 SharedPreferences.Editor editor = pref.edit();
1103 if(hasFreeformSupport(context)) {
1104 editor.putBoolean("freeform_hack", true);
1107 editor.putString("recents_amount", "running_apps_only");
1108 editor.putString("refresh_frequency", "0");
1109 editor.putString("max_num_of_recents", "2147483647");
1110 editor.putString("sort_order", "true");
1111 editor.putBoolean("full_length", true);
1112 editor.putBoolean("dashboard", true);
1113 editor.putBoolean("app_drawer_icon", true);
1114 editor.putBoolean("button_back", true);
1115 editor.putBoolean("button_home", true);
1116 editor.putBoolean("button_recents", true);
1117 editor.putBoolean("auto_hide_navbar", true);
1118 editor.putBoolean("bliss_os_prefs", true);
1121 Settings.Secure.putString(context.getContentResolver(),
1122 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
1123 new ComponentName(context, PowerMenuService.class).flattenToString());
1124 } catch (Exception e) { /* Gracefully fail */ }
1129 // Customizations for Android-x86 devices (non-Bliss)
1130 if(BuildConfig.APPLICATION_ID.equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
1131 && isSystemApp(context)
1132 && !pref.getBoolean("android_x86_prefs", false)) {
1134 .putString("recents_amount", "running_apps_only")
1135 .putString("refresh_frequency", "0")
1136 .putString("max_num_of_recents", "2147483647")
1137 .putString("sort_order", "true")
1138 .putBoolean("full_length", true)
1139 .putBoolean("dashboard", true)
1140 .putBoolean("android_x86_prefs", true)
1145 public static DisplayMetrics getRealDisplayMetrics(Context context) {
1146 DisplayMetrics metrics = new DisplayMetrics();
1147 WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
1148 Display disp = wm.getDefaultDisplay();
1150 SharedPreferences pref = getSharedPreferences(context);
1151 if(isChromeOs(context) && !pref.getBoolean("chrome_os_context_menu_fix", true))
1152 disp.getRealMetrics(metrics);
1154 disp.getMetrics(metrics);
1159 static void pinAppShortcut(Context context) {
1160 Intent intent = getShortcutIntent(context);
1161 intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
1162 intent.putExtra("duplicate", false);
1164 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1165 homeIntent.addCategory(Intent.CATEGORY_HOME);
1166 ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
1168 intent.setPackage(defaultLauncher.activityInfo.packageName);
1169 context.sendBroadcast(intent);
1171 showToast(context, R.string.shortcut_created);
1174 public static boolean shouldCollapse(Context context, boolean pendingAppLaunch) {
1175 SharedPreferences pref = getSharedPreferences(context);
1176 if(pref.getBoolean("hide_taskbar", true)) {
1177 if(isOverridingFreeformHack(context))
1180 FreeformHackHelper helper = FreeformHackHelper.getInstance();
1181 if(pendingAppLaunch)
1182 return !helper.isFreeformHackActive();
1184 return !helper.isInFreeformWorkspace();
1190 public static boolean isOverridingFreeformHack(Context context) {
1191 SharedPreferences pref = getSharedPreferences(context);
1192 return isChromeOs(context) && pref.getBoolean("chrome_os_context_menu_fix", true);
1195 @SuppressWarnings("unchecked")
1196 public static <T extends View> T findViewById(Activity target, int id) {
1197 return (T) target.findViewById(id);
1200 @SuppressWarnings("unchecked")
1201 public static <T extends View> T findViewById(View target, int id) {
1202 return (T) target.findViewById(id);
1205 public static boolean isPlayStoreInstalled(Context context) {
1207 context.getPackageManager().getPackageInfo("com.android.vending", 0);
1209 } catch (PackageManager.NameNotFoundException e) {
1214 public static boolean isUntestedAndroidVersion(Context context) {
1215 SharedPreferences pref = getSharedPreferences(context);
1216 float testedApiVersion = 27.0f;
1218 return getCurrentApiVersion() > Math.max(testedApiVersion, pref.getFloat("current_api_version_new", testedApiVersion));
1221 public static float getCurrentApiVersion() {
1222 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1223 return Float.valueOf(Build.VERSION.SDK_INT + "." + Build.VERSION.PREVIEW_SDK_INT);
1225 return (float) Build.VERSION.SDK_INT;
1228 public static boolean hasBrokenSetLaunchBoundsApi() {
1229 return Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1;
1232 public static String getSecondScreenPackageName(Context context) {
1233 return getInstalledPackage(context, Arrays.asList(
1234 "com.farmerbb.secondscreen.free",
1235 "com.farmerbb.secondscreen"));
1238 // Returns the name of an installed package from a list of package names, in order of preference
1239 private static String getInstalledPackage(Context context, List<String> packageNames) {
1240 if(packageNames == null || packageNames.isEmpty())
1243 List<String> packages = packageNames instanceof ArrayList ? packageNames : new ArrayList<>(packageNames);
1244 String packageName = packages.get(0);
1247 context.getPackageManager().getPackageInfo(packageName, 0);
1249 } catch (PackageManager.NameNotFoundException e) {
1251 return getInstalledPackage(context, packages);
1255 public static boolean visualFeedbackEnabled(Context context) {
1256 SharedPreferences pref = getSharedPreferences(context);
1257 return Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1 && pref.getBoolean("visual_feedback", true);
1260 public static void showRecentAppsDialog(Context context) {
1261 showRecentAppsDialog(context, null, null);
1264 public static AlertDialog showRecentAppsDialog(Context context, Runnable onError, Runnable onFinish) {
1265 Runnable finalOnFinish = onFinish == null
1269 Runnable finalOnError = onError == null
1270 ? () -> showErrorDialog(context, "GET_USAGE_STATS", finalOnFinish)
1273 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isSystemApp(context)) {
1274 ApplicationInfo applicationInfo = null;
1276 applicationInfo = context.getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
1277 } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
1279 if(applicationInfo != null) {
1280 AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
1281 int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
1283 if(mode != AppOpsManager.MODE_ALLOWED) {
1284 AlertDialog.Builder builder = new AlertDialog.Builder(context);
1285 builder.setTitle(R.string.pref_header_recent_apps)
1286 .setMessage(R.string.enable_recent_apps)
1287 .setPositiveButton(R.string.action_ok, (dialog, which) -> {
1289 context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
1290 showToastLong(context, R.string.usage_stats_message);
1292 finalOnFinish.run();
1293 } catch (ActivityNotFoundException e) {
1297 .setNegativeButton(R.string.action_cancel, (dialog, which) -> finalOnFinish.run());
1299 AlertDialog dialog = builder.create();
1301 dialog.setCancelable(false);
1308 finalOnFinish.run();
1312 public static Context wrapContext(Context context) {
1313 SharedPreferences pref = getSharedPreferences(context);
1316 switch(pref.getString("theme", "light")) {
1318 theme = R.style.AppTheme;
1321 theme = R.style.AppTheme_Dark;
1325 return theme > -1 ? new ContextThemeWrapper(context, theme) : context;
1328 @SuppressLint("PackageManagerGetSignatures")
1329 public static boolean isPlayStoreRelease(Context context) {
1330 Signature playStoreSignature = new Signature(context.getString(R.string.signature));
1332 PackageManager pm = context.getPackageManager();
1333 PackageInfo info = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
1334 for(Signature signature : info.signatures) {
1335 if(signature.equals(playStoreSignature))
1338 } catch (Exception e) { /* Gracefully fail */ }