OSDN Git Service

594538ed081e4ac257633f9af03cf6042a20cf62
[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))
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     @TargetApi(Build.VERSION_CODES.M)
967     public static boolean canDrawOverlays(Context context) {
968         return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context);
969     }
970
971     public static boolean isGame(Context context, String packageName) {
972         SharedPreferences pref = getSharedPreferences(context);
973         if(pref.getBoolean("launch_games_fullscreen", true)) {
974             PackageManager pm = context.getPackageManager();
975
976             try {
977                 ApplicationInfo info = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
978                 return (info.flags & ApplicationInfo.FLAG_IS_GAME) != 0 || (info.metaData != null && info.metaData.getBoolean("isGame", false));
979             } catch (PackageManager.NameNotFoundException e) {
980                 return false;
981             }
982         } else
983             return false;
984     }
985
986     private static ActivityOptions getActivityOptions(View view) {
987         return getActivityOptions(null, null, view);
988     }
989
990     @TargetApi(Build.VERSION_CODES.N)
991     private static ActivityOptions getActivityOptions(Context context, ApplicationType applicationType, View view) {
992         ActivityOptions options;
993         if(view != null)
994             options = ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight());
995         else
996             options = ActivityOptions.makeBasic();
997
998         if(applicationType == null)
999             return options;
1000
1001         int stackId = -1;
1002
1003         switch(applicationType) {
1004             case APPLICATION:
1005                 if(FreeformHackHelper.getInstance().isFreeformHackActive())
1006                     stackId = getFreeformWindowModeId();
1007                 else
1008                     stackId = getFullscreenWindowModeId();
1009                 break;
1010             case GAME:
1011                 stackId = getFullscreenWindowModeId();
1012                 break;
1013             case FREEFORM_HACK:
1014                 stackId = getFreeformWindowModeId();
1015                 break;
1016             case CONTEXT_MENU:
1017                 if(hasBrokenSetLaunchBoundsApi()
1018                         || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f))
1019                     stackId = getFullscreenWindowModeId();
1020                 break;
1021         }
1022
1023         try {
1024             Method method = ActivityOptions.class.getMethod(getWindowingModeMethodName(), int.class);
1025             method.invoke(options, stackId);
1026         } catch (Exception e) { /* Gracefully fail */ }
1027
1028         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1029             int launchDisplayId = LauncherHelper.getInstance().getSecondaryDisplayId();
1030             if(launchDisplayId != -1)
1031                 options.setLaunchDisplayId(launchDisplayId);
1032         }
1033
1034         return options;
1035     }
1036
1037     private static int getFullscreenWindowModeId() {
1038         if(getCurrentApiVersion() >= 28.0f)
1039             return WINDOWING_MODE_FULLSCREEN;
1040         else
1041             return FULLSCREEN_WORKSPACE_STACK_ID;
1042     }
1043
1044     private static int getFreeformWindowModeId() {
1045         if(getCurrentApiVersion() >= 28.0f)
1046             return WINDOWING_MODE_FREEFORM;
1047         else
1048             return FREEFORM_WORKSPACE_STACK_ID;
1049     }
1050
1051     private static String getWindowingModeMethodName() {
1052         if(getCurrentApiVersion() >= 28.0f)
1053             return "setLaunchWindowingMode";
1054         else
1055             return "setLaunchStackId";
1056     }
1057
1058     public static Bundle getActivityOptionsBundle(Context context, ApplicationType type, View view) {
1059         SharedPreferences pref = getSharedPreferences(context);
1060
1061         return getActivityOptionsBundle(context, type, pref.getString("window_size", context.getString(R.string.def_window_size)), view);
1062     }
1063
1064     private static Bundle getActivityOptionsBundle(Context context, ApplicationType type, String windowSize, View view) {
1065         SharedPreferences pref = getSharedPreferences(context);
1066         if(!canEnableFreeform() || !pref.getBoolean("freeform_hack", false))
1067             return getActivityOptions(view).toBundle();
1068
1069         switch(windowSize) {
1070             case "large":
1071                 return launchMode1(context, type, view);
1072             case "fullscreen":
1073                 return launchMode2(context, MAXIMIZED, type, view);
1074             case "half_left":
1075                 return launchMode2(context, LEFT, type, view);
1076             case "half_right":
1077                 return launchMode2(context, RIGHT, type, view);
1078             case "phone_size":
1079                 return launchMode3(context, type, view);
1080         }
1081
1082         return getActivityOptions(context, type, view).toBundle();
1083     }
1084
1085     private static ApplicationType getApplicationType(Context context, String packageName) {
1086         return isGame(context, packageName) ? ApplicationType.GAME : ApplicationType.APPLICATION;
1087     }
1088
1089     public static boolean isSystemApp(Context context) {
1090         try {
1091             ApplicationInfo info = context.getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
1092             int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
1093             return (info.flags & mask) != 0;
1094         } catch (PackageManager.NameNotFoundException e) {
1095             return false;
1096         }
1097     }
1098
1099     public static boolean isChromeOs(Context context) {
1100         return context.getPackageManager().hasSystemFeature("org.chromium.arc");
1101     }
1102
1103     public static boolean isBlissOs(Context context) {
1104         boolean validBlissOsBuildProp = false;
1105
1106         String blissVersion = getSystemProperty("ro.bliss.version");
1107         if(blissVersion != null && !blissVersion.isEmpty())
1108             validBlissOsBuildProp = true;
1109
1110         String buildUser = getSystemProperty("ro.build.user");
1111         if(buildUser != null && buildUser.equals("electrikjesus"))
1112             validBlissOsBuildProp = true;
1113
1114         return validBlissOsBuildProp
1115                 && BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
1116                 && isSystemApp(context);
1117     }
1118
1119     public static boolean isLauncherPermanentlyEnabled(Context context) {
1120         if(BuildConfig.APPLICATION_ID.equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
1121             return true;
1122
1123         PackageManager pm = context.getPackageManager();
1124         try {
1125             pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1126             return pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, BuildConfig.APPLICATION_ID) == PackageManager.SIGNATURE_MATCH
1127                     && BuildConfig.APPLICATION_ID.equals(BuildConfig.BASE_APPLICATION_ID)
1128                     && isSystemApp(context);
1129         } catch (PackageManager.NameNotFoundException e) {
1130             return false;
1131         }
1132     }
1133
1134     public static int getBaseTaskbarSize(Context context) {
1135         return Math.round(getBaseTaskbarSizeFloat(context));
1136     }
1137
1138     private static float getBaseTaskbarSizeFloat(Context context) {
1139         SharedPreferences pref = getSharedPreferences(context);
1140         float baseTaskbarSize = context.getResources().getDimension(R.dimen.base_taskbar_size);
1141         boolean navbarButtonsEnabled = false;
1142
1143         if(pref.getBoolean("dashboard", context.getResources().getBoolean(R.bool.def_dashboard)))
1144             baseTaskbarSize += context.getResources().getDimension(R.dimen.dashboard_button_size);
1145
1146         if(pref.getBoolean("button_back", false)) {
1147             navbarButtonsEnabled = true;
1148             baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
1149         }
1150
1151         if(pref.getBoolean("button_home", false)) {
1152             navbarButtonsEnabled = true;
1153             baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
1154         }
1155
1156         if(pref.getBoolean("button_recents", false)) {
1157             navbarButtonsEnabled = true;
1158             baseTaskbarSize += context.getResources().getDimension(R.dimen.icon_size);
1159         }
1160
1161         if(navbarButtonsEnabled)
1162             baseTaskbarSize += context.getResources().getDimension(R.dimen.navbar_buttons_margin);
1163
1164         if(isSystemTrayEnabled(context))
1165             baseTaskbarSize += context.getResources().getDimension(R.dimen.systray_size);
1166
1167         return baseTaskbarSize;
1168     }
1169
1170     private static void startTaskbarService(Context context, boolean fullRestart) {
1171         context.startService(new Intent(context, TaskbarService.class));
1172         context.startService(new Intent(context, StartMenuService.class));
1173         context.startService(new Intent(context, DashboardService.class));
1174         if(fullRestart) context.startService(new Intent(context, NotificationService.class));
1175     }
1176
1177     private static void stopTaskbarService(Context context, boolean fullRestart) {
1178         context.stopService(new Intent(context, TaskbarService.class));
1179         context.stopService(new Intent(context, StartMenuService.class));
1180         context.stopService(new Intent(context, DashboardService.class));
1181         if(fullRestart) context.stopService(new Intent(context, NotificationService.class));
1182     }
1183
1184     public static void restartTaskbar(Context context) {
1185         SharedPreferences pref = getSharedPreferences(context);
1186         if(pref.getBoolean("taskbar_active", false) && !pref.getBoolean("is_hidden", false)) {
1187             pref.edit()
1188                     .putBoolean("is_restarting", true)
1189                     .putBoolean("skip_auto_hide_navbar", true)
1190                     .apply();
1191
1192             stopTaskbarService(context, true);
1193             startTaskbarService(context, true);
1194         } else if(isServiceRunning(context, StartMenuService.class)) {
1195             pref.edit().putBoolean("skip_auto_hide_navbar", true).apply();
1196
1197             stopTaskbarService(context, false);
1198             startTaskbarService(context, false);
1199         }
1200
1201         LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.RESTART"));
1202     }
1203
1204     public static void restartNotificationService(Context context) {
1205         if(isServiceRunning(context, NotificationService.class)) {
1206             SharedPreferences pref = getSharedPreferences(context);
1207             pref.edit().putBoolean("is_restarting", true).apply();
1208
1209             Intent intent = new Intent(context, NotificationService.class);
1210             context.stopService(intent);
1211             context.startService(intent);
1212         }
1213     }
1214
1215     public static void showHideNavigationBar(Context context, boolean show) {
1216         // Show or hide the system navigation bar on Bliss-x86
1217         try {
1218             if(getCurrentApiVersion() >= 28.0f)
1219                 Settings.Secure.putInt(context.getContentResolver(), "navigation_bar_visible", show ? 1 : 0);
1220             else
1221                 Settings.System.putInt(context.getContentResolver(), "navigation_bar_show", show ? 1 : 0);
1222         } catch (Exception e) { /* Gracefully fail */ }
1223     }
1224
1225     public static void initPrefs(Context context) {
1226         // Enable freeform hack automatically on supported devices
1227         SharedPreferences pref = getSharedPreferences(context);
1228         if(canEnableFreeform()) {
1229             if(!pref.getBoolean("freeform_hack_override", false)) {
1230                 pref.edit()
1231                         .putBoolean("freeform_hack", hasFreeformSupport(context) && !isSamsungDevice())
1232                         .putBoolean("save_window_sizes", false)
1233                         .putBoolean("freeform_hack_override", true)
1234                         .apply();
1235             } else if(!hasFreeformSupport(context)) {
1236                 pref.edit().putBoolean("freeform_hack", false).apply();
1237
1238                 stopFreeformHack(context);
1239             }
1240         } else {
1241             boolean freeformWasEnabled = pref.getBoolean("freeform_hack", false)
1242                     || pref.getBoolean("show_freeform_disabled_message", false);
1243
1244             pref.edit()
1245                     .putBoolean("freeform_hack", false)
1246                     .putBoolean("show_freeform_disabled_message", freeformWasEnabled)
1247                     .apply();
1248
1249             SavedWindowSizes.getInstance(context).clear(context);
1250             stopFreeformHack(context);
1251         }
1252
1253         // Customizations for BlissOS
1254         if(isBlissOs(context) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1255                 && !pref.getBoolean("bliss_os_prefs", false)) {
1256             SharedPreferences.Editor editor = pref.edit();
1257
1258             if(hasFreeformSupport(context)) {
1259                 editor.putBoolean("freeform_hack", true);
1260             }
1261
1262             editor.putString("recents_amount", "running_apps_only");
1263             editor.putString("refresh_frequency", "0");
1264             editor.putString("max_num_of_recents", "2147483647");
1265             editor.putString("sort_order", "true");
1266             editor.putString("start_button_image", "app_logo");
1267             editor.putBoolean("button_back", true);
1268             editor.putBoolean("button_home", true);
1269             editor.putBoolean("button_recents", true);
1270             editor.putBoolean("auto_hide_navbar", true);
1271          // editor.putBoolean("shortcut_icon", false);
1272             editor.putBoolean("bliss_os_prefs", true);
1273             editor.apply();
1274         }
1275
1276         // Customizations for Android-x86 devices (non-Bliss)
1277         if(BuildConfig.APPLICATION_ID.equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
1278                 && isSystemApp(context)
1279                 && !pref.getBoolean("android_x86_prefs", false)) {
1280             pref.edit()
1281                     .putString("recents_amount", "running_apps_only")
1282                     .putString("refresh_frequency", "0")
1283                     .putString("max_num_of_recents", "2147483647")
1284                     .putString("sort_order", "true")
1285                     .putBoolean("shortcut_icon", false)
1286                     .putBoolean("android_x86_prefs", true)
1287                     .apply();
1288         }
1289     }
1290
1291     public static DisplayInfo getDisplayInfo(Context context) {
1292         return getDisplayInfo(context, false);
1293     }
1294
1295     public static DisplayInfo getDisplayInfo(Context context, boolean fromTaskbar) {
1296         if(LauncherHelper.getInstance().getSecondaryDisplayId() == -1)
1297             context = context.getApplicationContext();
1298
1299         WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
1300         Display disp = wm.getDefaultDisplay();
1301
1302         DisplayMetrics metrics = new DisplayMetrics();
1303         disp.getMetrics(metrics);
1304
1305         DisplayMetrics realMetrics = new DisplayMetrics();
1306         disp.getRealMetrics(realMetrics);
1307
1308         DisplayInfo display = new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);
1309
1310         if(isChromeOs(context)) {
1311             SharedPreferences pref = getSharedPreferences(context);
1312             if(!pref.getBoolean("chrome_os_context_menu_fix", true)) {
1313                 display.width = realMetrics.widthPixels;
1314                 display.height = realMetrics.heightPixels;
1315             }
1316
1317             return display;
1318         }
1319
1320         // Workaround for incorrect display size on devices with notches in landscape mode
1321         if(fromTaskbar && context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)
1322             return display;
1323
1324         boolean sameWidth = metrics.widthPixels == realMetrics.widthPixels;
1325         boolean sameHeight = metrics.heightPixels == realMetrics.heightPixels;
1326
1327         if(sameWidth && !sameHeight) {
1328             display.width = realMetrics.widthPixels;
1329             display.height = realMetrics.heightPixels - getNavbarHeight(context);
1330         }
1331
1332         if(!sameWidth && sameHeight) {
1333             display.width = realMetrics.widthPixels - getNavbarHeight(context);
1334             display.height = realMetrics.heightPixels;
1335         }
1336
1337         return display;
1338     }
1339
1340     public static void pinAppShortcut(Context context) {
1341         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1342             ShortcutManager mShortcutManager = context.getSystemService(ShortcutManager.class);
1343
1344             if(mShortcutManager.isRequestPinShortcutSupported()) {
1345                 ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder(context, "freeform_mode").build();
1346
1347                 mShortcutManager.requestPinShortcut(pinShortcutInfo, null);
1348             } else
1349                 showToastLong(context, R.string.pin_shortcut_not_supported);
1350         } else {
1351             Intent intent = getShortcutIntent(context);
1352             intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
1353             intent.putExtra("duplicate", false);
1354
1355             Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1356             homeIntent.addCategory(Intent.CATEGORY_HOME);
1357             ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
1358
1359             intent.setPackage(defaultLauncher.activityInfo.packageName);
1360             context.sendBroadcast(intent);
1361
1362             showToast(context, R.string.shortcut_created);
1363         }
1364     }
1365
1366     public static boolean shouldCollapse(Context context, boolean pendingAppLaunch) {
1367         SharedPreferences pref = getSharedPreferences(context);
1368         if(pref.getBoolean("hide_taskbar", true)) {
1369             if(isOverridingFreeformHack(context))
1370                 return !LauncherHelper.getInstance().isOnHomeScreen();
1371             else {
1372                 FreeformHackHelper helper = FreeformHackHelper.getInstance();
1373                 if(pendingAppLaunch)
1374                     return !helper.isFreeformHackActive();
1375                 else
1376                     return !helper.isInFreeformWorkspace();
1377             }
1378         } else
1379             return false;
1380     }
1381
1382     public static boolean isOverridingFreeformHack(Context context) {
1383         SharedPreferences pref = getSharedPreferences(context);
1384         return pref.getBoolean("freeform_hack", false)
1385                 && ((isChromeOs(context) && pref.getBoolean("chrome_os_context_menu_fix", true))
1386                 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f));
1387     }
1388
1389     public static boolean isPlayStoreInstalled(Context context) {
1390         try {
1391             context.getPackageManager().getPackageInfo("com.android.vending", 0);
1392             return true;
1393         } catch (PackageManager.NameNotFoundException e) {
1394             return false;
1395         }
1396     }
1397
1398     public static float getCurrentApiVersion() {
1399         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1400             return Float.valueOf(Build.VERSION.SDK_INT + "." + Build.VERSION.PREVIEW_SDK_INT);
1401         else
1402             return (float) Build.VERSION.SDK_INT;
1403     }
1404
1405     public static boolean hasBrokenSetLaunchBoundsApi() {
1406         return getCurrentApiVersion() >= 26.0f
1407                 && getCurrentApiVersion() < 28.0f
1408                 && !isSamsungDevice()
1409                 && !isNvidiaDevice();
1410     }
1411
1412     public static String getSecondScreenPackageName(Context context) {
1413         return getInstalledPackage(context, Arrays.asList(
1414                 "com.farmerbb.secondscreen.free",
1415                 "com.farmerbb.secondscreen"));
1416     }
1417
1418     // Returns the name of an installed package from a list of package names, in order of preference
1419     private static String getInstalledPackage(Context context, List<String> packageNames) {
1420         if(packageNames == null || packageNames.isEmpty())
1421             return null;
1422
1423         List<String> packages = packageNames instanceof ArrayList ? packageNames : new ArrayList<>(packageNames);
1424         String packageName = packages.get(0);
1425
1426         try {
1427             context.getPackageManager().getPackageInfo(packageName, 0);
1428             return packageName;
1429         } catch (PackageManager.NameNotFoundException e) {
1430             packages.remove(0);
1431             return getInstalledPackage(context, packages);
1432         }
1433     }
1434
1435     public static boolean visualFeedbackEnabled(Context context) {
1436         SharedPreferences pref = getSharedPreferences(context);
1437         return (getCurrentApiVersion() < 26.0f || getCurrentApiVersion() >= 28.0f)
1438                 && pref.getBoolean("visual_feedback", true)
1439                 && !isNvidiaDevice();
1440     }
1441
1442     public static void showRecentAppsDialog(Context context) {
1443         showRecentAppsDialog(context, null, null);
1444     }
1445
1446     public static AlertDialog showRecentAppsDialog(Context context, Runnable onError, Runnable onFinish) {
1447         Runnable finalOnFinish = onFinish == null
1448                 ? () -> {}
1449                 : onFinish;
1450
1451         Runnable finalOnError = onError == null
1452                 ? () -> showErrorDialog(context, "GET_USAGE_STATS", finalOnFinish)
1453                 : onError;
1454
1455         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isSystemApp(context)) {
1456             ApplicationInfo applicationInfo = null;
1457             try {
1458                 applicationInfo = context.getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
1459             } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
1460
1461             if(applicationInfo != null) {
1462                 AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
1463                 int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
1464
1465                 if(mode != AppOpsManager.MODE_ALLOWED) {
1466                     AlertDialog.Builder builder = new AlertDialog.Builder(context);
1467                     builder.setTitle(R.string.pref_header_recent_apps)
1468                             .setMessage(R.string.enable_recent_apps)
1469                             .setPositiveButton(R.string.action_ok, (dialog, which) -> {
1470                                 try {
1471                                     context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
1472                                     showToastLong(context, R.string.usage_stats_message);
1473
1474                                     finalOnFinish.run();
1475                                 } catch (ActivityNotFoundException e) {
1476                                     finalOnError.run();
1477                                 }
1478                             })
1479                             .setNegativeButton(R.string.action_cancel, (dialog, which) -> finalOnFinish.run());
1480
1481                     AlertDialog dialog = builder.create();
1482                     dialog.show();
1483                     dialog.setCancelable(false);
1484
1485                     return dialog;
1486                 }
1487             }
1488         }
1489
1490         finalOnFinish.run();
1491         return null;
1492     }
1493
1494     public static Context wrapContext(Context context) {
1495         SharedPreferences pref = getSharedPreferences(context);
1496
1497         int theme = -1;
1498         switch(pref.getString("theme", "light")) {
1499             case "light":
1500                 theme = R.style.AppTheme;
1501                 break;
1502             case "dark":
1503                 theme = R.style.AppTheme_Dark;
1504                 break;
1505         }
1506
1507         return theme > -1 ? new ContextThemeWrapper(context, theme) : context;
1508     }
1509
1510     public static boolean isPlayStoreRelease(Context context) {
1511         return isPlayStoreRelease(context, BuildConfig.APPLICATION_ID);
1512     }
1513
1514     @SuppressLint("PackageManagerGetSignatures")
1515     public static boolean isPlayStoreRelease(Context context, String packageName) {
1516         Signature playStoreSignature = new Signature(context.getString(R.string.signature));
1517         try {
1518             PackageManager pm = context.getPackageManager();
1519             PackageInfo info = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
1520             for(Signature signature : info.signatures) {
1521                 if(signature.equals(playStoreSignature))
1522                     return true;
1523             }
1524         } catch (Exception e) { /* Gracefully fail */ }
1525
1526         return false;
1527     }
1528
1529     public static boolean isExternalAccessDisabled(Context context) {
1530         SharedPreferences pref = getSharedPreferences(context);
1531         return !pref.getBoolean("tasker_enabled", true);
1532     }
1533
1534     public static boolean enableFreeformModeShortcut(Context context) {
1535         return canEnableFreeform()
1536                 && !isOverridingFreeformHack(context)
1537                 && !isChromeOs(context);
1538     }
1539
1540     public static void startForegroundService(Context context, Intent intent) {
1541         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1542             if(Settings.canDrawOverlays(context))
1543                 context.startForegroundService(intent);
1544         } else
1545             context.startService(intent);
1546     }
1547
1548     public static int getOverlayType() {
1549         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
1550                 ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
1551                 : WindowManager.LayoutParams.TYPE_PHONE;
1552     }
1553
1554     public static boolean isDelegatingHomeActivity(Context context) {
1555         Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1556         homeIntent.addCategory(Intent.CATEGORY_HOME);
1557
1558         final List<ResolveInfo> listOfLaunchers = context.getPackageManager().queryIntentActivities(homeIntent, 0);
1559         for(ResolveInfo launcher : listOfLaunchers) {
1560             if(launcher.activityInfo.packageName.equals(BuildConfig.SUPPORT_APPLICATION_ID))
1561                 return true;
1562         }
1563
1564         return false;
1565     }
1566
1567     @SuppressLint("PrivateApi")
1568     private static String getSystemProperty(String key) {
1569         try {
1570             Class<?> cls = Class.forName("android.os.SystemProperties");
1571             return cls.getMethod("get", String.class).invoke(null, key).toString();
1572         } catch (Exception e) {
1573             return null;
1574         }
1575     }
1576
1577     @SuppressWarnings("ResultOfMethodCallIgnored")
1578     public static boolean importCustomStartButtonImage(Context context, Uri uri) {
1579         try {
1580             File imagesDir = new File(context.getFilesDir(), "images");
1581             imagesDir.mkdirs();
1582
1583             File importedFile = new File(imagesDir, "custom_image_new");
1584             if(importedFile.exists()) importedFile.delete();
1585
1586             BufferedInputStream is = new BufferedInputStream(context.getContentResolver().openInputStream(uri));
1587             byte[] data = new byte[is.available()];
1588
1589             if(data.length > 0) {
1590                 BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(importedFile));
1591                 is.read(data);
1592                 os.write(data);
1593                 is.close();
1594                 os.close();
1595             }
1596
1597             File prevFile = new File(imagesDir, "custom_image");
1598             if(prevFile.exists()) prevFile.delete();
1599
1600             importedFile.renameTo(prevFile);
1601             return true;
1602         } catch (IOException e) {
1603             return false;
1604         }
1605     }
1606
1607     public static String getDefaultStartButtonImage(Context context) {
1608         SharedPreferences pref = getSharedPreferences(context);
1609         return pref.getBoolean("app_drawer_icon", false)
1610                 ? "app_logo"
1611                 : "default";
1612     }
1613
1614     private static boolean shouldLaunchTouchAbsorber(Context context) {
1615         return isOverridingFreeformHack(context) && !isChromeOs(context) && getCurrentApiVersion() < 29.0f;
1616     }
1617
1618     public static boolean isDesktopIconsEnabled(Context context) {
1619         return !canBootToFreeform(context) && !shouldLaunchTouchAbsorber(context);
1620     }
1621
1622     public static boolean isSystemTrayEnabled(Context context) {
1623         SharedPreferences pref = getSharedPreferences(context);
1624
1625         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1626                 && pref.getBoolean("sys_tray", context.getResources().getBoolean(R.bool.def_sys_tray))
1627                 && pref.getBoolean("full_length", context.getResources().getBoolean(R.bool.def_full_length))
1628                 && !getTaskbarPosition(context).contains("vertical");
1629     }
1630 }