OSDN Git Service

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