OSDN Git Service

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