OSDN Git Service

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