OSDN Git Service

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