OSDN Git Service

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