OSDN Git Service

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