OSDN Git Service

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