OSDN Git Service

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