OSDN Git Service

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