OSDN Git Service

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