OSDN Git Service

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