OSDN Git Service

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