OSDN Git Service

Fixes for disabling activity animations
[android-x86/packages-apps-Taskbar.git] / app / src / main / java / com / farmerbb / taskbar / util / U.java
1 /* Copyright 2016 Braden Farmer
2  *
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
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
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.
14  */
15
16 package com.farmerbb.taskbar.util;
17
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.ShortcutManager;
41 import android.content.pm.Signature;
42 import android.content.res.Configuration;
43 import android.graphics.Color;
44 import android.graphics.Rect;
45 import android.net.Uri;
46 import android.os.Build;
47 import android.os.Bundle;
48 import android.os.Handler;
49 import android.os.Process;
50 import android.os.UserHandle;
51 import android.os.UserManager;
52 import android.provider.Settings;
53 import android.support.v4.content.LocalBroadcastManager;
54 import android.support.v7.view.ContextThemeWrapper;
55 import android.util.DisplayMetrics;
56 import android.view.Display;
57 import android.view.Surface;
58 import android.view.View;
59 import android.view.WindowManager;
60 import android.widget.Toast;
61
62 import com.farmerbb.taskbar.BuildConfig;
63 import com.farmerbb.taskbar.R;
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.receiver.LockDeviceReceiver;
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;
75
76 import java.lang.reflect.Method;
77 import java.util.ArrayList;
78 import java.util.Arrays;
79 import java.util.List;
80
81 public class U {
82
83     private U() {}
84
85     private static Integer cachedRotation;
86
87     private static final int MAXIMIZED = 0;
88     private static final int LEFT = -1;
89     private static final int RIGHT = 1;
90
91     public static final int HIDDEN = 0;
92     public static final int TOP_APPS = 1;
93
94     // From android.app.ActivityManager.StackId
95     private static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
96     private static final int FREEFORM_WORKSPACE_STACK_ID = 2;
97
98     // From android.app.WindowConfiguration
99     private static final int WINDOWING_MODE_FULLSCREEN = 1;
100     private static final int WINDOWING_MODE_FREEFORM = 5;
101
102     public static SharedPreferences getSharedPreferences(Context context) {
103         return getSharedPreferences(context, Context.MODE_PRIVATE);
104     }
105
106     private static SharedPreferences getSharedPreferences(Context context, int mode) {
107         return context.getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", mode);
108     }
109
110     public static void showPermissionDialog(Context context) {
111         showPermissionDialog(context, null, null);
112     }
113
114     @TargetApi(Build.VERSION_CODES.M)
115     public static AlertDialog showPermissionDialog(Context context, Runnable onError, Runnable onFinish) {
116         Runnable finalOnFinish = onFinish == null
117                 ? () -> {}
118                 : onFinish;
119
120         Runnable finalOnError = onError == null
121                 ? () -> showErrorDialog(context, "SYSTEM_ALERT_WINDOW", finalOnFinish)
122                 : onError;
123
124         AlertDialog.Builder builder = new AlertDialog.Builder(context);
125         builder.setTitle(R.string.permission_dialog_title)
126                 .setMessage(R.string.permission_dialog_message)
127                 .setPositiveButton(R.string.action_grant_permission, (dialog, which) -> {
128                     try {
129                         context.startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
130                                 Uri.parse("package:" + BuildConfig.APPLICATION_ID)));
131
132                         finalOnFinish.run();
133                     } catch (ActivityNotFoundException e) {
134                         finalOnError.run();
135                     }
136                 });
137
138         AlertDialog dialog = builder.create();
139         dialog.show();
140         dialog.setCancelable(false);
141
142         return dialog;
143     }
144
145     public static AlertDialog showErrorDialog(Context context, String appopCmd) {
146         return showErrorDialog(context, appopCmd, null);
147     }
148
149     private static AlertDialog showErrorDialog(Context context, String appopCmd, Runnable onFinish) {
150         Runnable finalOnFinish = onFinish == null
151                 ? () -> {}
152                 : onFinish;
153
154         AlertDialog.Builder builder = new AlertDialog.Builder(context);
155         builder.setTitle(R.string.error_dialog_title)
156                 .setMessage(context.getString(R.string.error_dialog_message, BuildConfig.APPLICATION_ID, appopCmd))
157                 .setPositiveButton(R.string.action_ok, (dialog, which) -> finalOnFinish.run());
158
159         AlertDialog dialog = builder.create();
160         dialog.show();
161         dialog.setCancelable(false);
162
163         return dialog;
164     }
165
166     public static void lockDevice(Context context) {
167         ComponentName component = new ComponentName(context, LockDeviceReceiver.class);
168         context.getPackageManager().setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
169                 PackageManager.DONT_KILL_APP);
170
171         DevicePolicyManager mDevicePolicyManager = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
172         if(mDevicePolicyManager.isAdminActive(component))
173             mDevicePolicyManager.lockNow();
174         else {
175             launchApp(context, () -> {
176                 Intent intent = new Intent(context, DummyActivity.class);
177                 intent.putExtra("device_admin", true);
178                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
179                 context.startActivity(intent, getActivityOptionsBundle(context, ApplicationType.APPLICATION));
180             });
181         }
182     }
183
184     public static void sendAccessibilityAction(Context context, int action) {
185         ComponentName component = new ComponentName(context, PowerMenuService.class);
186         context.getPackageManager().setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
187                 PackageManager.DONT_KILL_APP);
188
189         if(isAccessibilityServiceEnabled(context)) {
190             Intent intent = new Intent("com.farmerbb.taskbar.ACCESSIBILITY_ACTION");
191             intent.putExtra("action", action);
192             LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
193         } else {
194             launchApp(context, () -> {
195                 Intent intent = new Intent(context, DummyActivity.class);
196                 intent.putExtra("accessibility", true);
197                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
198                 context.startActivity(intent, getActivityOptionsBundle(context, ApplicationType.APPLICATION));
199             });
200         }
201     }
202
203     private static boolean isAccessibilityServiceEnabled(Context context) {
204         String accessibilityServices = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
205         ComponentName component = new ComponentName(context, PowerMenuService.class);
206
207         return accessibilityServices != null
208                 && (accessibilityServices.contains(component.flattenToString())
209                 || accessibilityServices.contains(component.flattenToShortString()));
210     }
211
212     public static void showToast(Context context, int message) {
213         showToast(context, context.getString(message), Toast.LENGTH_SHORT);
214     }
215
216     public static void showToastLong(Context context, int message) {
217         showToast(context, context.getString(message), Toast.LENGTH_LONG);
218     }
219
220     public static void showToast(Context context, String message, int length) {
221         cancelToast();
222
223         ToastInterface toast = DependencyUtils.createToast(context, message, length);
224         toast.show();
225
226         ToastHelper.getInstance().setLastToast(toast);
227     }
228
229     public static void cancelToast() {
230         ToastInterface toast = ToastHelper.getInstance().getLastToast();
231         if(toast != null) toast.cancel();
232     }
233
234     public static void startShortcut(Context context, String packageName, String componentName, ShortcutInfo shortcut) {
235         launchApp(context,
236                 packageName,
237                 componentName,
238                 0,
239                 null,
240                 false,
241                 false,
242                 shortcut);
243     }
244
245     public static void launchApp(final Context context,
246                                  final String packageName,
247                                  final String componentName,
248                                  final long userId, final String windowSize,
249                                  final boolean launchedFromTaskbar,
250                                  final boolean openInNewWindow) {
251         launchApp(context,
252                 packageName,
253                 componentName,
254                 userId,
255                 windowSize,
256                 launchedFromTaskbar,
257                 openInNewWindow,
258                 null);
259     }
260
261     private static void launchApp(final Context context,
262                                   final String packageName,
263                                   final String componentName,
264                                   final long userId, final String windowSize,
265                                   final boolean launchedFromTaskbar,
266                                   final boolean openInNewWindow,
267                                   final ShortcutInfo shortcut) {
268         launchApp(context, launchedFromTaskbar, () -> continueLaunchingApp(context, packageName, componentName, userId,
269                 windowSize, openInNewWindow, shortcut));
270     }
271
272     public static void launchApp(Context context, Runnable runnable) {
273         launchApp(context, true, runnable);
274     }
275
276     private static void launchApp(Context context, boolean launchedFromTaskbar, Runnable runnable) {
277         SharedPreferences pref = getSharedPreferences(context);
278         FreeformHackHelper helper = FreeformHackHelper.getInstance();
279
280         boolean specialLaunch = hasBrokenSetLaunchBoundsApi()
281                 && FreeformHackHelper.getInstance().isInFreeformWorkspace()
282                 && MenuHelper.getInstance().isContextMenuOpen();
283
284         boolean noAnimation = pref.getBoolean("disable_animations", false);
285
286         if(hasFreeformSupport(context)
287                 && pref.getBoolean("freeform_hack", false)
288                 && (!helper.isInFreeformWorkspace() || specialLaunch)) {
289             new Handler().postDelayed(() -> {
290                 startFreeformHack(context, true);
291
292                 new Handler().postDelayed(runnable, helper.isFreeformHackActive() ? 0 : 100);
293             }, launchedFromTaskbar ? 0 : 100);
294         } else
295             new Handler().postDelayed(runnable, !launchedFromTaskbar && noAnimation ? 100 : 0);
296     }
297
298     public static void startFreeformHack(Context context) {
299         startFreeformHack(context, false);
300     }
301
302     @TargetApi(Build.VERSION_CODES.N)
303     public static void startFreeformHack(Context context, boolean checkMultiWindow) {
304         Intent freeformHackIntent = new Intent(context, InvisibleActivityFreeform.class);
305         freeformHackIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
306                 | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
307                 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
308
309         if(checkMultiWindow)
310             freeformHackIntent.putExtra("check_multiwindow", true);
311
312         if(canDrawOverlays(context))
313             startActivityLowerRight(context, freeformHackIntent);
314     }
315
316     public static void stopFreeformHack(Context context) {
317         LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.FINISH_FREEFORM_ACTIVITY"));
318
319         if(isOverridingFreeformHack(context)) {
320             FreeformHackHelper helper = FreeformHackHelper.getInstance();
321             helper.setFreeformHackActive(false);
322             helper.setInFreeformWorkspace(false);
323         }
324     }
325
326     @TargetApi(Build.VERSION_CODES.N)
327     private static void continueLaunchingApp(Context context,
328                                              String packageName,
329                                              String componentName,
330                                              long userId,
331                                              String windowSize,
332                                              boolean openInNewWindow,
333                                              ShortcutInfo shortcut) {
334         SharedPreferences pref = getSharedPreferences(context);
335         Intent intent = new Intent();
336         intent.setComponent(ComponentName.unflattenFromString(componentName));
337         intent.setAction(Intent.ACTION_MAIN);
338         intent.addCategory(Intent.CATEGORY_LAUNCHER);
339         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
340         intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
341
342         if(FreeformHackHelper.getInstance().isInFreeformWorkspace()
343                 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1)
344             intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
345
346         if(pref.getBoolean("disable_animations", false))
347             intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
348
349         if(openInNewWindow || pref.getBoolean("force_new_window", false)) {
350             intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
351
352             ActivityInfo activityInfo = intent.resolveActivityInfo(context.getPackageManager(), 0);
353             if(activityInfo != null) {
354                 switch(activityInfo.launchMode) {
355                     case ActivityInfo.LAUNCH_SINGLE_TASK:
356                     case ActivityInfo.LAUNCH_SINGLE_INSTANCE:
357                         intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
358                         break;
359                 }
360             }
361         }
362
363         ApplicationType type = getApplicationType(context, packageName);
364
365         if(windowSize == null)
366             windowSize = SavedWindowSizes.getInstance(context).getWindowSize(context, packageName);
367
368         if(!canEnableFreeform()
369                 || !pref.getBoolean("freeform_hack", false)
370                 || windowSize.equals("standard")) {
371             launchStandard(context, intent, userId, shortcut, type);
372         } else switch(windowSize) {
373             case "large":
374                 launchMode1(context, intent, userId, shortcut, type);
375                 break;
376             case "fullscreen":
377                 launchMode2(context, intent, MAXIMIZED, userId, shortcut, type);
378                 break;
379             case "half_left":
380                 launchMode2(context, intent, LEFT, userId, shortcut, type);
381                 break;
382             case "half_right":
383                 launchMode2(context, intent, RIGHT, userId, shortcut, type);
384                 break;
385             case "phone_size":
386                 launchMode3(context, intent, userId, shortcut, type);
387                 break;
388         }
389
390         if(shouldCollapse(context, true))
391             LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_TASKBAR"));
392         else
393             LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_START_MENU"));
394     }
395
396     private static void launchStandard(Context context, Intent intent, long userId, ShortcutInfo shortcut, ApplicationType type) {
397         Bundle bundle = canEnableFreeform() ? getActivityOptions(context, type).toBundle() : null;
398         if(shortcut == null) {
399             UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
400             if(userId == userManager.getSerialNumberForUser(Process.myUserHandle())) {
401                 try {
402                     startActivity(context, intent, bundle);
403                 } catch (ActivityNotFoundException e) {
404                     launchAndroidForWork(context, intent.getComponent(), bundle, userId);
405                 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
406             } else
407                 launchAndroidForWork(context, intent.getComponent(), bundle, userId);
408         } else
409             launchShortcut(context, shortcut, bundle);
410     }
411
412     @SuppressWarnings("deprecation")
413     @TargetApi(Build.VERSION_CODES.N)
414     private static void launchMode1(Context context, Intent intent, long userId, ShortcutInfo shortcut, ApplicationType type) {
415         DisplayMetrics metrics = getRealDisplayMetrics(context);
416
417         int width1 = metrics.widthPixels / 8;
418         int width2 = metrics.widthPixels - width1;
419         int height1 = metrics.heightPixels / 8;
420         int height2 = metrics.heightPixels - height1;
421
422         Bundle bundle = getActivityOptions(context, type).setLaunchBounds(new Rect(
423                 width1,
424                 height1,
425                 width2,
426                 height2
427         )).toBundle();
428
429         if(shortcut == null) {
430             UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
431             if(userId == userManager.getSerialNumberForUser(Process.myUserHandle())) {
432                 try {
433                     startActivity(context, intent, bundle);
434                 } catch (ActivityNotFoundException e) {
435                     launchAndroidForWork(context, intent.getComponent(), bundle, userId);
436                 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
437             } else
438                 launchAndroidForWork(context, intent.getComponent(), bundle, userId);
439         } else
440             launchShortcut(context, shortcut, bundle);
441     }
442
443     @SuppressWarnings("deprecation")
444     @TargetApi(Build.VERSION_CODES.N)
445     private static void launchMode2(Context context, Intent intent, int launchType, long userId, ShortcutInfo shortcut, ApplicationType type) {
446         DisplayMetrics metrics = getRealDisplayMetrics(context);
447
448         int statusBarHeight = getStatusBarHeight(context);
449         String position = getTaskbarPosition(context);
450
451         boolean isPortrait = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
452         boolean isLandscape = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
453
454         int left = 0;
455         int top = statusBarHeight;
456         int right = metrics.widthPixels;
457         int bottom = metrics.heightPixels;
458
459         int iconSize = isOverridingFreeformHack(context) && !LauncherHelper.getInstance().isOnHomeScreen()
460                 ? 0
461                 : context.getResources().getDimensionPixelSize(R.dimen.icon_size);
462
463         if(position.contains("vertical_left"))
464             left = left + iconSize;
465         else if(position.contains("vertical_right"))
466             right = right - iconSize;
467         else if(position.contains("bottom"))
468             bottom = bottom - iconSize;
469         else
470             top = top + iconSize;
471
472         if(launchType == RIGHT && isLandscape)
473             left = (right / 2) + ((iconSize / 2) * (position.contains("vertical_left") ? 1 : 0));
474         else if(launchType == RIGHT && isPortrait)
475             top = (bottom / 2) + ((iconSize / 2)
476                     * ((position.equals("top_left") || position.equals("top_right")) ? 1 : 0));
477         else if(launchType == LEFT && isLandscape)
478             right = (right / 2) + ((iconSize / 2) * (position.contains("vertical_left") ? 1 : 0));
479         else if(launchType == LEFT && isPortrait)
480             bottom = (bottom / 2) + ((iconSize / 2)
481                     * ((position.equals("top_left") || position.equals("top_right")) ? 1 : 0));
482
483         Bundle bundle = getActivityOptions(context, type)
484                 .setLaunchBounds(new Rect(left, top, right, bottom)).toBundle();
485
486         if(shortcut == null) {
487             UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
488             if(userId == userManager.getSerialNumberForUser(Process.myUserHandle())) {
489                 try {
490                     startActivity(context, intent, bundle);
491                 } catch (ActivityNotFoundException e) {
492                     launchAndroidForWork(context, intent.getComponent(), bundle, userId);
493                 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
494             } else
495                 launchAndroidForWork(context, intent.getComponent(), bundle, userId);
496         } else
497             launchShortcut(context, shortcut, bundle);
498     }
499
500     @SuppressWarnings("deprecation")
501     @TargetApi(Build.VERSION_CODES.N)
502     private static void launchMode3(Context context, Intent intent, long userId, ShortcutInfo shortcut, ApplicationType type) {
503         DisplayMetrics metrics = getRealDisplayMetrics(context);
504
505         int width1 = metrics.widthPixels / 2;
506         int width2 = context.getResources().getDimensionPixelSize(R.dimen.phone_size_width) / 2;
507         int height1 = metrics.heightPixels / 2;
508         int height2 = context.getResources().getDimensionPixelSize(R.dimen.phone_size_height) / 2;
509
510         Bundle bundle = getActivityOptions(context, type).setLaunchBounds(new Rect(
511                 width1 - width2,
512                 height1 - height2,
513                 width1 + width2,
514                 height1 + height2
515         )).toBundle();
516
517         if(shortcut == null) {
518             UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
519             if(userId == userManager.getSerialNumberForUser(Process.myUserHandle())) {
520                 try {
521                     startActivity(context, intent, bundle);
522                 } catch (ActivityNotFoundException e) {
523                     launchAndroidForWork(context, intent.getComponent(), bundle, userId);
524                 } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
525             } else
526                 launchAndroidForWork(context, intent.getComponent(), bundle, userId);
527         } else
528             launchShortcut(context, shortcut, bundle);
529     }
530
531     private static void launchAndroidForWork(Context context, ComponentName componentName, Bundle bundle, long userId) {
532         UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
533         LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
534
535         try {
536             launcherApps.startMainActivity(componentName, userManager.getUserForSerialNumber(userId), null, bundle);
537         } catch (ActivityNotFoundException | NullPointerException e) { /* Gracefully fail */ }
538     }
539
540     @TargetApi(Build.VERSION_CODES.N_MR1)
541     private static void launchShortcut(Context context, ShortcutInfo shortcut, Bundle bundle) {
542         LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
543
544         if(launcherApps.hasShortcutHostPermission()) {
545             try {
546                 launcherApps.startShortcut(shortcut, null, bundle);
547             } catch (ActivityNotFoundException | NullPointerException e) { /* Gracefully fail */ }
548         }
549     }
550
551     private static void startActivity(Context context, Intent intent, Bundle bundle) {
552         boolean shouldLaunchTouchAbsorber =
553                 !FreeformHackHelper.getInstance().isTouchAbsorberActive()
554                         && isOverridingFreeformHack(context)
555                         && !isChromeOs(context);
556
557         if(!shouldLaunchTouchAbsorber) {
558             context.startActivity(intent, bundle);
559             return;
560         }
561
562         startTouchAbsorberActivity(context);
563         new Handler().postDelayed(() -> context.startActivity(intent, bundle), 100);
564     }
565
566     public static void startActivityMaximized(Context context, Intent intent) {
567         UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
568         long userId = userManager.getSerialNumberForUser(Process.myUserHandle());
569
570         launchMode2(context, intent, MAXIMIZED, userId, null, ApplicationType.CONTEXT_MENU);
571     }
572
573     @TargetApi(Build.VERSION_CODES.N)
574     public static void startActivityLowerRight(Context context, Intent intent) {
575         DisplayMetrics metrics = getRealDisplayMetrics(context);
576         try {
577             context.startActivity(intent,
578                     getActivityOptions(context, ApplicationType.FREEFORM_HACK)
579                             .setLaunchBounds(new Rect(
580                                     metrics.widthPixels,
581                                     metrics.heightPixels,
582                                     metrics.widthPixels + 1,
583                                     metrics.heightPixels + 1
584                             )).toBundle());
585         } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
586     }
587
588     @TargetApi(Build.VERSION_CODES.N)
589     public static void startTouchAbsorberActivity(Context context) {
590         String position = getTaskbarPosition(context);
591         DisplayMetrics metrics = getRealDisplayMetrics(context);
592
593         int left = 0;
594         int top = 0;
595         int right = metrics.widthPixels;
596         int bottom = metrics.heightPixels;
597
598         int iconSize = context.getResources().getDimensionPixelSize(R.dimen.icon_size);
599
600         if(position.contains("vertical_left"))
601             right = iconSize;
602         else if(position.contains("vertical_right"))
603             left = right - iconSize;
604         else if(position.contains("bottom"))
605             top = bottom - iconSize;
606         else
607             bottom = iconSize;
608
609         Intent intent = new Intent(context, TouchAbsorberActivity.class);
610         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
611         intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
612
613         try {
614             context.startActivity(intent,
615                     getActivityOptions(context, ApplicationType.FREEFORM_HACK)
616                             .setLaunchBounds(new Rect(left, top, right, bottom)).toBundle());
617         } catch (IllegalArgumentException | SecurityException e) { /* Gracefully fail */ }
618     }
619
620     public static void checkForUpdates(Context context) {
621         String url;
622         if(isPlayStoreRelease(context))
623             url = "https://play.google.com/store/apps/details?id=" + BuildConfig.APPLICATION_ID;
624         else
625             url = "https://f-droid.org/repository/browse/?fdid=" + BuildConfig.APPLICATION_ID;
626
627         Intent intent = new Intent(Intent.ACTION_VIEW);
628         intent.setData(Uri.parse(url));
629         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
630
631         try {
632             context.startActivity(intent);
633         } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
634     }
635
636     public static boolean launcherIsDefault(Context context) {
637         Intent homeIntent = new Intent(Intent.ACTION_MAIN);
638         homeIntent.addCategory(Intent.CATEGORY_HOME);
639         ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
640
641         return defaultLauncher.activityInfo.packageName.equals(BuildConfig.APPLICATION_ID);
642     }
643
644     public static void setCachedRotation(int cachedRotation) {
645         U.cachedRotation = cachedRotation;
646     }
647
648     public static String getTaskbarPosition(Context context) {
649         SharedPreferences pref = getSharedPreferences(context);
650         String position = pref.getString("position", "bottom_left");
651
652         if(pref.getBoolean("anchor", false)) {
653             WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
654             int rotation = cachedRotation != null ? cachedRotation : windowManager.getDefaultDisplay().getRotation();
655
656             switch(position) {
657                 case "bottom_left":
658                     switch(rotation) {
659                         case Surface.ROTATION_0:
660                             return "bottom_left";
661                         case Surface.ROTATION_90:
662                             return "bottom_vertical_right";
663                         case Surface.ROTATION_180:
664                             return "top_right";
665                         case Surface.ROTATION_270:
666                             return "top_vertical_left";
667                     }
668                     break;
669                 case "bottom_vertical_left":
670                     switch(rotation) {
671                         case Surface.ROTATION_0:
672                             return "bottom_vertical_left";
673                         case Surface.ROTATION_90:
674                             return "bottom_right";
675                         case Surface.ROTATION_180:
676                             return "top_vertical_right";
677                         case Surface.ROTATION_270:
678                             return "top_left";
679                     }
680                     break;
681                 case "bottom_right":
682                     switch(rotation) {
683                         case Surface.ROTATION_0:
684                             return "bottom_right";
685                         case Surface.ROTATION_90:
686                             return "top_vertical_right";
687                         case Surface.ROTATION_180:
688                             return "top_left";
689                         case Surface.ROTATION_270:
690                             return "bottom_vertical_left";
691                     }
692                     break;
693                 case "bottom_vertical_right":
694                     switch(rotation) {
695                         case Surface.ROTATION_0:
696                             return "bottom_vertical_right";
697                         case Surface.ROTATION_90:
698                             return "top_right";
699                         case Surface.ROTATION_180:
700                             return "top_vertical_left";
701                         case Surface.ROTATION_270:
702                             return "bottom_left";
703                     }
704                     break;
705                 case "top_left":
706                     switch(rotation) {
707                         case Surface.ROTATION_0:
708                             return "top_left";
709                         case Surface.ROTATION_90:
710                             return "bottom_vertical_left";
711                         case Surface.ROTATION_180:
712                             return "bottom_right";
713                         case Surface.ROTATION_270:
714                             return "top_vertical_right";
715                     }
716                     break;
717                 case "top_vertical_left":
718                     switch(rotation) {
719                         case Surface.ROTATION_0:
720                             return "top_vertical_left";
721                         case Surface.ROTATION_90:
722                             return "bottom_left";
723                         case Surface.ROTATION_180:
724                             return "bottom_vertical_right";
725                         case Surface.ROTATION_270:
726                             return "top_right";
727                     }
728                     break;
729                 case "top_right":
730                     switch(rotation) {
731                         case Surface.ROTATION_0:
732                             return "top_right";
733                         case Surface.ROTATION_90:
734                             return "top_vertical_left";
735                         case Surface.ROTATION_180:
736                             return "bottom_left";
737                         case Surface.ROTATION_270:
738                             return "bottom_vertical_right";
739                     }
740                     break;
741                 case "top_vertical_right":
742                     switch(rotation) {
743                         case Surface.ROTATION_0:
744                             return "top_vertical_right";
745                         case Surface.ROTATION_90:
746                             return "top_left";
747                         case Surface.ROTATION_180:
748                             return "bottom_vertical_left";
749                         case Surface.ROTATION_270:
750                             return "bottom_right";
751                     }
752                     break;
753             }
754         }
755
756         return position;
757     }
758
759     private static int getMaxNumOfColumns(Context context) {
760         SharedPreferences pref = getSharedPreferences(context);
761         DisplayMetrics metrics = getRealDisplayMetrics(context);
762         float baseTaskbarSize = getBaseTaskbarSizeFloat(context) / metrics.density;
763         int numOfColumns = 0;
764
765         float maxScreenSize = getTaskbarPosition(context).contains("vertical")
766                 ? (metrics.heightPixels - getStatusBarHeight(context)) / metrics.density
767                 : metrics.widthPixels / metrics.density;
768
769         float iconSize = context.getResources().getDimension(R.dimen.icon_size) / metrics.density;
770
771         int userMaxNumOfColumns = Integer.valueOf(pref.getString("max_num_of_recents", "10"));
772
773         while(baseTaskbarSize + iconSize < maxScreenSize
774                 && numOfColumns < userMaxNumOfColumns) {
775             baseTaskbarSize = baseTaskbarSize + iconSize;
776             numOfColumns++;
777         }
778
779         return numOfColumns;
780     }
781
782     public static int getMaxNumOfEntries(Context context) {
783         SharedPreferences pref = getSharedPreferences(context);
784         return pref.getBoolean("disable_scrolling_list", false)
785                 ? getMaxNumOfColumns(context)
786                 : Integer.valueOf(pref.getString("max_num_of_recents", "10"));
787     }
788
789     public static int getStatusBarHeight(Context context) {
790         int statusBarHeight = 0;
791         int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
792         if(resourceId > 0)
793             statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
794
795         return statusBarHeight;
796     }
797
798     public static void refreshPinnedIcons(Context context) {
799         IconCache.getInstance(context).clearCache();
800
801         PinnedBlockedApps pba = PinnedBlockedApps.getInstance(context);
802         List<AppEntry> pinnedAppsList = new ArrayList<>(pba.getPinnedApps());
803         List<AppEntry> blockedAppsList = new ArrayList<>(pba.getBlockedApps());
804         PackageManager pm = context.getPackageManager();
805
806         pba.clear(context);
807
808         for(AppEntry entry : pinnedAppsList) {
809             UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
810             LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
811
812             final List<UserHandle> userHandles = userManager.getUserProfiles();
813             LauncherActivityInfo appInfo = null;
814
815             for(UserHandle handle : userHandles) {
816                 List<LauncherActivityInfo> list = launcherApps.getActivityList(entry.getPackageName(), handle);
817                 if(!list.isEmpty()) {
818                     // Google App workaround
819                     if(!entry.getPackageName().equals("com.google.android.googlequicksearchbox"))
820                         appInfo = list.get(0);
821                     else {
822                         boolean added = false;
823                         for(LauncherActivityInfo info : list) {
824                             if(info.getName().equals("com.google.android.googlequicksearchbox.SearchActivity")) {
825                                 appInfo = info;
826                                 added = true;
827                             }
828                         }
829
830                         if(!added) appInfo = list.get(0);
831                     }
832
833                     break;
834                 }
835             }
836
837             if(appInfo != null) {
838                 AppEntry newEntry = new AppEntry(
839                         entry.getPackageName(),
840                         entry.getComponentName(),
841                         entry.getLabel(),
842                         IconCache.getInstance(context).getIcon(context, pm, appInfo),
843                         true);
844
845                 newEntry.setUserId(entry.getUserId(context));
846                 pba.addPinnedApp(context, newEntry);
847             }
848         }
849
850         for(AppEntry entry : blockedAppsList) {
851             pba.addBlockedApp(context, entry);
852         }
853     }
854
855     public static Intent getShortcutIntent(Context context) {
856         Intent shortcutIntent = new Intent(context, ShortcutActivity.class);
857         shortcutIntent.setAction(Intent.ACTION_MAIN);
858         shortcutIntent.putExtra("is_launching_shortcut", true);
859
860         Intent intent = new Intent();
861         intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
862         intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.mipmap.ic_freeform_mode));
863         intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, context.getString(R.string.pref_header_freeform));
864
865         return intent;
866     }
867
868     public static Intent getStartStopIntent(Context context) {
869         Intent shortcutIntent = new Intent(context, StartTaskbarActivity.class);
870         shortcutIntent.setAction(Intent.ACTION_MAIN);
871         shortcutIntent.putExtra("is_launching_shortcut", true);
872
873         Intent intent = new Intent();
874         intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
875         intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.mipmap.ic_launcher));
876         intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, context.getString(R.string.start_taskbar));
877
878         return intent;
879     }
880
881     public static boolean canEnableFreeform() {
882         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
883     }
884
885     @TargetApi(Build.VERSION_CODES.N)
886     public static boolean hasFreeformSupport(Context context) {
887         return canEnableFreeform()
888                 && (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
889                 || Settings.Global.getInt(context.getContentResolver(), "enable_freeform_support", 0) != 0
890                 || (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
891                 && Settings.Global.getInt(context.getContentResolver(), "force_resizable_activities", 0) != 0));
892     }
893
894     public static boolean hasPartialFreeformSupport() {
895         return Build.MANUFACTURER.equalsIgnoreCase("Samsung");
896     }
897
898     public static boolean isServiceRunning(Context context, Class<? extends Service> cls) {
899         return isServiceRunning(context, cls.getName());
900     }
901
902     private static boolean isServiceRunning(Context context, String className) {
903         ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
904         for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
905             if(className.equals(service.service.getClassName()))
906                 return true;
907         }
908
909         return false;
910     }
911
912     public static int getBackgroundTint(Context context) {
913         SharedPreferences pref = getSharedPreferences(context);
914
915         // Import old background tint preference
916         if(pref.contains("show_background")) {
917             SharedPreferences.Editor editor = pref.edit();
918
919             if(!pref.getBoolean("show_background", true))
920                 editor.putInt("background_tint", Color.TRANSPARENT).apply();
921
922             editor.remove("show_background");
923             editor.apply();
924         }
925
926         return pref.getInt("background_tint", context.getResources().getInteger(R.integer.translucent_gray));
927     }
928
929     public static int getAccentColor(Context context) {
930         SharedPreferences pref = getSharedPreferences(context);
931         return pref.getInt("accent_color", context.getResources().getInteger(R.integer.translucent_white));
932     }
933
934     @TargetApi(Build.VERSION_CODES.M)
935     public static boolean canDrawOverlays(Context context) {
936         return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context);
937     }
938
939     public static boolean isGame(Context context, String packageName) {
940         SharedPreferences pref = getSharedPreferences(context);
941         if(pref.getBoolean("launch_games_fullscreen", true)) {
942             PackageManager pm = context.getPackageManager();
943
944             try {
945                 ApplicationInfo info = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
946                 return (info.flags & ApplicationInfo.FLAG_IS_GAME) != 0 || (info.metaData != null && info.metaData.getBoolean("isGame", false));
947             } catch (PackageManager.NameNotFoundException e) {
948                 return false;
949             }
950         } else
951             return false;
952     }
953
954     @TargetApi(Build.VERSION_CODES.N)
955     public static ActivityOptions getActivityOptions(Context context, ApplicationType applicationType) {
956         ActivityOptions options = ActivityOptions.makeBasic();
957         int stackId = -1;
958
959         switch(applicationType) {
960             case APPLICATION:
961                 if(FreeformHackHelper.getInstance().isFreeformHackActive())
962                     stackId = getFreeformWindowModeId();
963                 else
964                     stackId = getFullscreenWindowModeId();
965                 break;
966             case GAME:
967                 stackId = getFullscreenWindowModeId();
968                 break;
969             case FREEFORM_HACK:
970                 stackId = getFreeformWindowModeId();
971                 break;
972             case CONTEXT_MENU:
973                 if(hasBrokenSetLaunchBoundsApi()
974                         || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f))
975                     stackId = getFullscreenWindowModeId();
976                 break;
977         }
978
979         try {
980             Method method = ActivityOptions.class.getMethod(getWindowingModeMethodName(), int.class);
981             method.invoke(options, stackId);
982         } catch (Exception e) { /* Gracefully fail */ }
983
984         return options;
985     }
986
987     private static int getFullscreenWindowModeId() {
988         if(getCurrentApiVersion() >= 28.0f)
989             return WINDOWING_MODE_FULLSCREEN;
990         else
991             return FULLSCREEN_WORKSPACE_STACK_ID;
992     }
993
994     private static int getFreeformWindowModeId() {
995         if(getCurrentApiVersion() >= 28.0f)
996             return WINDOWING_MODE_FREEFORM;
997         else
998             return FREEFORM_WORKSPACE_STACK_ID;
999     }
1000
1001     private static String getWindowingModeMethodName() {
1002         if(getCurrentApiVersion() >= 28.0f)
1003             return "setLaunchWindowingMode";
1004         else
1005             return "setLaunchStackId";
1006     }
1007
1008     public static Bundle getActivityOptionsBundle(Context context, ApplicationType applicationType) {
1009         if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
1010             return null;
1011         else
1012             return getActivityOptions(context, applicationType).toBundle();
1013     }
1014
1015     private static ApplicationType getApplicationType(Context context, String packageName) {
1016         return isGame(context, packageName) ? ApplicationType.GAME : ApplicationType.APPLICATION;
1017     }
1018
1019     public static boolean isSystemApp(Context context) {
1020         try {
1021             ApplicationInfo info = context.getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
1022             int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
1023             return (info.flags & mask) != 0;
1024         } catch (PackageManager.NameNotFoundException e) {
1025             return false;
1026         }
1027     }
1028
1029     public static boolean isChromeOs(Context context) {
1030         return context.getPackageManager().hasSystemFeature("org.chromium.arc");
1031     }
1032
1033     public static boolean isBlissOs(Context context) {
1034         boolean validBlissOsBuildProp = false;
1035
1036         String blissVersion = DependencyUtils.getSystemProperty("ro.bliss.version");
1037         if(blissVersion != null && !blissVersion.isEmpty())
1038             validBlissOsBuildProp = true;
1039
1040         String buildUser = DependencyUtils.getSystemProperty("ro.build.user");
1041         if(buildUser != null && buildUser.equals("electrikjesus"))
1042             validBlissOsBuildProp = true;
1043
1044         return validBlissOsBuildProp
1045                 && BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
1046                 && isSystemApp(context);
1047     }
1048
1049     public static boolean isLauncherPermanentlyEnabled(Context context) {
1050         if(BuildConfig.APPLICATION_ID.equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
1051             return true;
1052
1053         PackageManager pm = context.getPackageManager();
1054         try {
1055             pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1056             return pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, BuildConfig.APPLICATION_ID) == PackageManager.SIGNATURE_MATCH
1057                     && BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
1058                     && isSystemApp(context);
1059         } catch (PackageManager.NameNotFoundException e) {
1060             return false;
1061         }
1062     }
1063
1064     public static int getBaseTaskbarSize(Context context) {
1065         return Math.round(getBaseTaskbarSizeFloat(context));
1066     }
1067
1068     private static float getBaseTaskbarSizeFloat(Context context) {
1069         SharedPreferences pref = getSharedPreferences(context);
1070         float baseTaskbarSize = context.getResources().getDimension(R.dimen.base_taskbar_size);
1071         boolean navbarButtonsEnabled = false;
1072
1073         if(pref.getBoolean("dashboard", false))
1074             baseTaskbarSize += context.getResources().getDimension(R.dimen.dashboard_button_size);
1075
1076         if(pref.getBoolean("button_back", false)) {
1077             navbarButtonsEnabled = true;
1078             baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
1079         }
1080
1081         if(pref.getBoolean("button_home", false)) {
1082             navbarButtonsEnabled = true;
1083             baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
1084         }
1085
1086         if(pref.getBoolean("button_recents", false)) {
1087             navbarButtonsEnabled = true;
1088             baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
1089         }
1090
1091         if(navbarButtonsEnabled)
1092             baseTaskbarSize += context.getResources().getDimension(R.dimen.navbar_buttons_margin);
1093
1094         return baseTaskbarSize;
1095     }
1096
1097     private static void startTaskbarService(Context context, boolean fullRestart) {
1098         context.startService(new Intent(context, TaskbarService.class));
1099         context.startService(new Intent(context, StartMenuService.class));
1100         context.startService(new Intent(context, DashboardService.class));
1101         if(fullRestart) context.startService(new Intent(context, NotificationService.class));
1102     }
1103
1104     private static void stopTaskbarService(Context context, boolean fullRestart) {
1105         context.stopService(new Intent(context, TaskbarService.class));
1106         context.stopService(new Intent(context, StartMenuService.class));
1107         context.stopService(new Intent(context, DashboardService.class));
1108         if(fullRestart) context.stopService(new Intent(context, NotificationService.class));
1109     }
1110
1111     public static void restartTaskbar(Context context) {
1112         SharedPreferences pref = getSharedPreferences(context);
1113         if(pref.getBoolean("taskbar_active", false) && !pref.getBoolean("is_hidden", false)) {
1114             pref.edit()
1115                     .putBoolean("is_restarting", true)
1116                     .putBoolean("skip_auto_hide_navbar", true)
1117                     .apply();
1118
1119             stopTaskbarService(context, true);
1120             startTaskbarService(context, true);
1121         } else if(isServiceRunning(context, StartMenuService.class)) {
1122             pref.edit().putBoolean("skip_auto_hide_navbar", true).apply();
1123
1124             stopTaskbarService(context, false);
1125             startTaskbarService(context, false);
1126         }
1127     }
1128
1129     public static void restartNotificationService(Context context) {
1130         if(isServiceRunning(context, NotificationService.class)) {
1131             SharedPreferences pref = getSharedPreferences(context);
1132             pref.edit().putBoolean("is_restarting", true).apply();
1133
1134             Intent intent = new Intent(context, NotificationService.class);
1135             context.stopService(intent);
1136             context.startService(intent);
1137         }
1138     }
1139
1140     public static void showHideNavigationBar(Context context, boolean show) {
1141         // Show or hide the system navigation bar on Bliss-x86
1142         try {
1143             Settings.System.putInt(context.getContentResolver(), "navigation_bar_show", show ? 1 : 0);
1144         } catch (Exception e) { /* Gracefully fail */ }
1145     }
1146
1147     public static void initPrefs(Context context) {
1148         // On smaller-screened devices, set "Grid" as the default start menu layout
1149         SharedPreferences pref = getSharedPreferences(context);
1150         if(context.getApplicationContext().getResources().getConfiguration().smallestScreenWidthDp < 720
1151                 && pref.getString("start_menu_layout", "null").equals("null")) {
1152             pref.edit().putString("start_menu_layout", "grid").apply();
1153         }
1154
1155         // Enable freeform hack automatically on supported devices
1156         if(canEnableFreeform()) {
1157             if(!pref.getBoolean("freeform_hack_override", false)) {
1158                 pref.edit()
1159                         .putBoolean("freeform_hack", hasFreeformSupport(context) && !hasPartialFreeformSupport())
1160                         .putBoolean("save_window_sizes", false)
1161                         .putBoolean("freeform_hack_override", true)
1162                         .apply();
1163             } else if(!hasFreeformSupport(context)) {
1164                 pref.edit().putBoolean("freeform_hack", false).apply();
1165
1166                 stopFreeformHack(context);
1167             }
1168         } else {
1169             boolean freeformWasEnabled = pref.getBoolean("freeform_hack", false)
1170                     || pref.getBoolean("show_freeform_disabled_message", false);
1171
1172             pref.edit()
1173                     .putBoolean("freeform_hack", false)
1174                     .putBoolean("show_freeform_disabled_message", freeformWasEnabled)
1175                     .apply();
1176
1177             SavedWindowSizes.getInstance(context).clear(context);
1178             stopFreeformHack(context);
1179         }
1180
1181         // Customizations for BlissOS
1182         if(isBlissOs(context) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1183                 && !pref.getBoolean("bliss_os_prefs", false)) {
1184             SharedPreferences.Editor editor = pref.edit();
1185
1186             if(hasFreeformSupport(context)) {
1187                 editor.putBoolean("freeform_hack", true);
1188             }
1189
1190             editor.putString("recents_amount", "running_apps_only");
1191             editor.putString("refresh_frequency", "0");
1192             editor.putString("max_num_of_recents", "2147483647");
1193             editor.putString("sort_order", "true");
1194             editor.putBoolean("full_length", true);
1195             editor.putBoolean("dashboard", true);
1196             editor.putBoolean("app_drawer_icon", true);
1197             editor.putBoolean("button_back", true);
1198             editor.putBoolean("button_home", true);
1199             editor.putBoolean("button_recents", true);
1200             editor.putBoolean("auto_hide_navbar", true);
1201             editor.putBoolean("bliss_os_prefs", true);
1202
1203             try {
1204                 Settings.Secure.putString(context.getContentResolver(),
1205                         Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
1206                         new ComponentName(context, PowerMenuService.class).flattenToString());
1207             } catch (Exception e) { /* Gracefully fail */ }
1208
1209             editor.apply();
1210         }
1211
1212         // Customizations for Android-x86 devices (non-Bliss)
1213         if(BuildConfig.APPLICATION_ID.equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
1214                 && isSystemApp(context)
1215                 && !pref.getBoolean("android_x86_prefs", false)) {
1216             pref.edit()
1217                     .putString("recents_amount", "running_apps_only")
1218                     .putString("refresh_frequency", "0")
1219                     .putString("max_num_of_recents", "2147483647")
1220                     .putString("sort_order", "true")
1221                     .putBoolean("full_length", true)
1222                     .putBoolean("dashboard", true)
1223                     .putBoolean("android_x86_prefs", true)
1224                     .apply();
1225         }
1226     }
1227
1228     public static DisplayMetrics getRealDisplayMetrics(Context context) {
1229         DisplayMetrics metrics = new DisplayMetrics();
1230         WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
1231         Display disp = wm.getDefaultDisplay();
1232
1233         SharedPreferences pref = getSharedPreferences(context);
1234         if(isChromeOs(context) && !pref.getBoolean("chrome_os_context_menu_fix", true))
1235             disp.getRealMetrics(metrics);
1236         else
1237             disp.getMetrics(metrics);
1238
1239         return metrics;
1240     }
1241
1242     public static void pinAppShortcut(Context context) {
1243         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1244             ShortcutManager mShortcutManager = context.getSystemService(ShortcutManager.class);
1245
1246             if(mShortcutManager.isRequestPinShortcutSupported()) {
1247                 ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder(context, "freeform_mode").build();
1248
1249                 mShortcutManager.requestPinShortcut(pinShortcutInfo, null);
1250             } else
1251                 showToastLong(context, R.string.pin_shortcut_not_supported);
1252         } else {
1253             Intent intent = getShortcutIntent(context);
1254             intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
1255             intent.putExtra("duplicate", false);
1256
1257             Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1258             homeIntent.addCategory(Intent.CATEGORY_HOME);
1259             ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
1260
1261             intent.setPackage(defaultLauncher.activityInfo.packageName);
1262             context.sendBroadcast(intent);
1263
1264             showToast(context, R.string.shortcut_created);
1265         }
1266     }
1267
1268     public static boolean shouldCollapse(Context context, boolean pendingAppLaunch) {
1269         SharedPreferences pref = getSharedPreferences(context);
1270         if(pref.getBoolean("hide_taskbar", true)) {
1271             if(isOverridingFreeformHack(context))
1272                 return !LauncherHelper.getInstance().isOnHomeScreen();
1273             else {
1274                 FreeformHackHelper helper = FreeformHackHelper.getInstance();
1275                 if(pendingAppLaunch)
1276                     return !helper.isFreeformHackActive();
1277                 else
1278                     return !helper.isInFreeformWorkspace();
1279             }
1280         } else
1281             return false;
1282     }
1283
1284     public static boolean isOverridingFreeformHack(Context context) {
1285         SharedPreferences pref = getSharedPreferences(context);
1286         return pref.getBoolean("freeform_hack", false)
1287                 && ((isChromeOs(context) && pref.getBoolean("chrome_os_context_menu_fix", true))
1288                 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f));
1289     }
1290
1291     @SuppressWarnings("unchecked")
1292     public static <T extends View> T findViewById(Activity target, int id) {
1293         return (T) target.findViewById(id);
1294     }
1295
1296     @SuppressWarnings("unchecked")
1297     public static <T extends View> T findViewById(View target, int id) {
1298         return (T) target.findViewById(id);
1299     }
1300
1301     public static boolean isPlayStoreInstalled(Context context) {
1302         try {
1303             context.getPackageManager().getPackageInfo("com.android.vending", 0);
1304             return true;
1305         } catch (PackageManager.NameNotFoundException e) {
1306             return false;
1307         }
1308     }
1309
1310     private static float getCurrentApiVersion() {
1311         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1312             return Float.valueOf(Build.VERSION.SDK_INT + "." + Build.VERSION.PREVIEW_SDK_INT);
1313         else
1314             return (float) Build.VERSION.SDK_INT;
1315     }
1316
1317     public static boolean hasBrokenSetLaunchBoundsApi() {
1318         return getCurrentApiVersion() >= 26.0f && getCurrentApiVersion() < 28.0f;
1319     }
1320
1321     public static String getSecondScreenPackageName(Context context) {
1322         return getInstalledPackage(context, Arrays.asList(
1323                 "com.farmerbb.secondscreen.free",
1324                 "com.farmerbb.secondscreen"));
1325     }
1326
1327     // Returns the name of an installed package from a list of package names, in order of preference
1328     private static String getInstalledPackage(Context context, List<String> packageNames) {
1329         if(packageNames == null || packageNames.isEmpty())
1330             return null;
1331
1332         List<String> packages = packageNames instanceof ArrayList ? packageNames : new ArrayList<>(packageNames);
1333         String packageName = packages.get(0);
1334
1335         try {
1336             context.getPackageManager().getPackageInfo(packageName, 0);
1337             return packageName;
1338         } catch (PackageManager.NameNotFoundException e) {
1339             packages.remove(0);
1340             return getInstalledPackage(context, packages);
1341         }
1342     }
1343
1344     public static boolean visualFeedbackEnabled(Context context) {
1345         SharedPreferences pref = getSharedPreferences(context);
1346         return (getCurrentApiVersion() < 26.0f || getCurrentApiVersion() >= 28.0f)
1347                 && pref.getBoolean("visual_feedback", true);
1348     }
1349
1350     public static void showRecentAppsDialog(Context context) {
1351         showRecentAppsDialog(context, null, null);
1352     }
1353
1354     public static AlertDialog showRecentAppsDialog(Context context, Runnable onError, Runnable onFinish) {
1355         Runnable finalOnFinish = onFinish == null
1356                 ? () -> {}
1357                 : onFinish;
1358
1359         Runnable finalOnError = onError == null
1360                 ? () -> showErrorDialog(context, "GET_USAGE_STATS", finalOnFinish)
1361                 : onError;
1362
1363         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isSystemApp(context)) {
1364             ApplicationInfo applicationInfo = null;
1365             try {
1366                 applicationInfo = context.getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
1367             } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
1368
1369             if(applicationInfo != null) {
1370                 AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
1371                 int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
1372
1373                 if(mode != AppOpsManager.MODE_ALLOWED) {
1374                     AlertDialog.Builder builder = new AlertDialog.Builder(context);
1375                     builder.setTitle(R.string.pref_header_recent_apps)
1376                             .setMessage(R.string.enable_recent_apps)
1377                             .setPositiveButton(R.string.action_ok, (dialog, which) -> {
1378                                 try {
1379                                     context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
1380                                     showToastLong(context, R.string.usage_stats_message);
1381
1382                                     finalOnFinish.run();
1383                                 } catch (ActivityNotFoundException e) {
1384                                     finalOnError.run();
1385                                 }
1386                             })
1387                             .setNegativeButton(R.string.action_cancel, (dialog, which) -> finalOnFinish.run());
1388
1389                     AlertDialog dialog = builder.create();
1390                     dialog.show();
1391                     dialog.setCancelable(false);
1392
1393                     return dialog;
1394                 }
1395             }
1396         }
1397
1398         finalOnFinish.run();
1399         return null;
1400     }
1401
1402     public static Context wrapContext(Context context) {
1403         SharedPreferences pref = getSharedPreferences(context);
1404
1405         int theme = -1;
1406         switch(pref.getString("theme", "light")) {
1407             case "light":
1408                 theme = R.style.AppTheme;
1409                 break;
1410             case "dark":
1411                 theme = R.style.AppTheme_Dark;
1412                 break;
1413         }
1414
1415         return theme > -1 ? new ContextThemeWrapper(context, theme) : context;
1416     }
1417
1418     @SuppressLint("PackageManagerGetSignatures")
1419     public static boolean isPlayStoreRelease(Context context) {
1420         Signature playStoreSignature = new Signature(context.getString(R.string.signature));
1421         try {
1422             PackageManager pm = context.getPackageManager();
1423             PackageInfo info = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
1424             for(Signature signature : info.signatures) {
1425                 if(signature.equals(playStoreSignature))
1426                     return true;
1427             }
1428         } catch (Exception e) { /* Gracefully fail */ }
1429
1430         return false;
1431     }
1432
1433     public static boolean isTaskerDisabled(Context context) {
1434         SharedPreferences pref = getSharedPreferences(context, Context.MODE_MULTI_PROCESS);
1435         return !pref.getBoolean("tasker_enabled", true);
1436     }
1437
1438     public static boolean enableFreeformModeShortcut(Context context) {
1439         return canEnableFreeform()
1440                 && !U.isOverridingFreeformHack(context)
1441                 && !U.isChromeOs(context);
1442     }
1443
1444     public static void startForegroundService(Context context, Intent intent) {
1445         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1446             if(Settings.canDrawOverlays(context))
1447                 context.startForegroundService(intent);
1448         } else
1449             context.startService(intent);
1450     }
1451
1452     public static int getOverlayType() {
1453         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
1454                 ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
1455                 : WindowManager.LayoutParams.TYPE_PHONE;
1456     }
1457 }