OSDN Git Service

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