OSDN Git Service

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