OSDN Git Service

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