OSDN Git Service

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