OSDN Git Service

Use TSL instead to show and hide navbar
[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.hardware.display.DisplayManager;
45 import android.net.Uri;
46 import android.os.Build;
47 import android.os.Bundle;
48 import android.os.Handler;
49 import android.os.Process;
50 import android.os.UserHandle;
51 import android.os.UserManager;
52 import android.provider.Settings;
53 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
54 import androidx.appcompat.view.ContextThemeWrapper;
55 import android.util.DisplayMetrics;
56 import android.view.Display;
57 import android.view.Surface;
58 import android.view.View;
59 import android.view.WindowManager;
60 import android.widget.Toast;
61
62 import com.farmerbb.taskbar.BuildConfig;
63 import com.farmerbb.taskbar.R;
64 import com.farmerbb.taskbar.activity.ContextMenuActivity;
65 import com.farmerbb.taskbar.activity.DummyActivity;
66 import com.farmerbb.taskbar.activity.InvisibleActivityFreeform;
67 import com.farmerbb.taskbar.activity.TouchAbsorberActivity;
68 import com.farmerbb.taskbar.activity.dark.ContextMenuActivityDark;
69 import com.farmerbb.taskbar.service.DashboardService;
70 import com.farmerbb.taskbar.service.NotificationService;
71 import com.farmerbb.taskbar.service.PowerMenuService;
72 import com.farmerbb.taskbar.service.StartMenuService;
73 import com.farmerbb.taskbar.service.TaskbarService;
74
75 import java.io.BufferedInputStream;
76 import java.io.BufferedOutputStream;
77 import java.io.File;
78 import java.io.FileOutputStream;
79 import java.io.IOException;
80 import java.lang.reflect.Method;
81 import java.util.ArrayList;
82 import java.util.Arrays;
83 import java.util.List;
84
85 public class U {
86
87     private U() {}
88
89     private static Integer cachedRotation;
90
91     private static final int MAXIMIZED = 0;
92     private static final int LEFT = -1;
93     private static final int RIGHT = 1;
94
95     public static final int HIDDEN = 0;
96     public static final int TOP_APPS = 1;
97
98     // From android.app.ActivityManager.StackId
99     private static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
100     private static final int FREEFORM_WORKSPACE_STACK_ID = 2;
101
102     // From android.app.WindowConfiguration
103     private static final int WINDOWING_MODE_FULLSCREEN = 1;
104     private static final int WINDOWING_MODE_FREEFORM = 5;
105
106     @SuppressWarnings("deprecation")
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.tb_permission_dialog_title)
127                 .setMessage(R.string.tb_permission_dialog_message)
128                 .setPositiveButton(R.string.tb_action_grant_permission, (dialog, which) -> {
129                     try {
130                         context.startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
131                                 Uri.parse("package:" + context.getPackageName())));
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.tb_error_dialog_title)
157                 .setMessage(context.getString(R.string.tb_error_dialog_message, context.getPackageName(), appopCmd))
158                 .setPositiveButton(R.string.tb_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.tb_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.tb_phone_size_width) / 2;
487         int height1 = display.height / 2;
488         int height2 = context.getResources().getDimensionPixelSize(R.dimen.tb_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.tb_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(context.getPackageName().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=" + context.getPackageName();
630         } else
631             url = "https://f-droid.org/repository/browse/?fdid=" + context.getPackageName();
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(context.getPackageName());
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.tb_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 boolean canEnableFreeform() {
871         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
872     }
873
874     @TargetApi(Build.VERSION_CODES.N)
875     public static boolean hasFreeformSupport(Context context) {
876         return canEnableFreeform()
877                 && (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
878                 || Settings.Global.getInt(context.getContentResolver(), "enable_freeform_support", 0) != 0
879                 || (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
880                 && Settings.Global.getInt(context.getContentResolver(), "force_resizable_activities", 0) != 0));
881     }
882
883     public static boolean canBootToFreeform(Context context) {
884         SharedPreferences pref = getSharedPreferences(context);
885         return hasFreeformSupport(context)
886                 && pref.getBoolean("freeform_hack", false)
887                 && !isOverridingFreeformHack(context);
888     }
889
890     public static boolean isSamsungDevice() {
891         return Build.MANUFACTURER.equalsIgnoreCase("Samsung");
892     }
893
894     private static boolean isNvidiaDevice() {
895         return Build.MANUFACTURER.equalsIgnoreCase("NVIDIA");
896     }
897
898     public static boolean isServiceRunning(Context context, Class<? extends Service> cls) {
899         if(LauncherHelper.getInstance().isOnSecondaryHomeScreen()
900                 && (cls.equals(TaskbarService.class)
901                 || cls.equals(StartMenuService.class)
902                 || cls.equals(DashboardService.class)))
903             return true;
904
905         return isServiceRunning(context, cls.getName());
906     }
907
908     private static boolean isServiceRunning(Context context, String className) {
909         ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
910         for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
911             if(className.equals(service.service.getClassName()))
912                 return true;
913         }
914
915         return false;
916     }
917
918     public static int getBackgroundTint(Context context) {
919         SharedPreferences pref = getSharedPreferences(context);
920
921         // Import old background tint preference
922         if(pref.contains("show_background")) {
923             SharedPreferences.Editor editor = pref.edit();
924
925             if(!pref.getBoolean("show_background", true))
926                 editor.putInt("background_tint", Color.TRANSPARENT).apply();
927
928             editor.remove("show_background");
929             editor.apply();
930         }
931
932         return pref.getInt("background_tint", context.getResources().getInteger(R.integer.tb_translucent_gray));
933     }
934
935     public static int getAccentColor(Context context) {
936         SharedPreferences pref = getSharedPreferences(context);
937         return pref.getInt("accent_color", context.getResources().getInteger(R.integer.tb_translucent_white));
938     }
939
940     @TargetApi(Build.VERSION_CODES.M)
941     public static boolean canDrawOverlays(Context context) {
942         return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context);
943     }
944
945     public static boolean isGame(Context context, String packageName) {
946         SharedPreferences pref = getSharedPreferences(context);
947         if(pref.getBoolean("launch_games_fullscreen", true)) {
948             PackageManager pm = context.getPackageManager();
949
950             try {
951                 ApplicationInfo info = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
952                 return (info.flags & ApplicationInfo.FLAG_IS_GAME) != 0 || (info.metaData != null && info.metaData.getBoolean("isGame", false));
953             } catch (PackageManager.NameNotFoundException e) {
954                 return false;
955             }
956         } else
957             return false;
958     }
959
960     private static ActivityOptions getActivityOptions(View view) {
961         return getActivityOptions(null, null, view);
962     }
963
964     @TargetApi(Build.VERSION_CODES.N)
965     private static ActivityOptions getActivityOptions(Context context, ApplicationType applicationType, View view) {
966         ActivityOptions options;
967         if(view != null)
968             options = ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight());
969         else
970             options = ActivityOptions.makeBasic();
971
972         if(applicationType == null)
973             return options;
974
975         int stackId = -1;
976
977         switch(applicationType) {
978             case APPLICATION:
979                 if(FreeformHackHelper.getInstance().isFreeformHackActive())
980                     stackId = getFreeformWindowModeId();
981                 else
982                     stackId = getFullscreenWindowModeId();
983                 break;
984             case GAME:
985                 stackId = getFullscreenWindowModeId();
986                 break;
987             case FREEFORM_HACK:
988                 stackId = getFreeformWindowModeId();
989                 break;
990             case CONTEXT_MENU:
991                 if(hasBrokenSetLaunchBoundsApi()
992                         || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f))
993                     stackId = getFullscreenWindowModeId();
994                 break;
995         }
996
997         try {
998             Method method = ActivityOptions.class.getMethod(getWindowingModeMethodName(), int.class);
999             method.invoke(options, stackId);
1000         } catch (Exception e) { /* Gracefully fail */ }
1001
1002         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1003             int launchDisplayId = LauncherHelper.getInstance().getSecondaryDisplayId();
1004             if(launchDisplayId != -1)
1005                 options.setLaunchDisplayId(launchDisplayId);
1006         }
1007
1008         return options;
1009     }
1010
1011     private static int getFullscreenWindowModeId() {
1012         if(getCurrentApiVersion() >= 28.0f)
1013             return WINDOWING_MODE_FULLSCREEN;
1014         else
1015             return FULLSCREEN_WORKSPACE_STACK_ID;
1016     }
1017
1018     private static int getFreeformWindowModeId() {
1019         if(getCurrentApiVersion() >= 28.0f)
1020             return WINDOWING_MODE_FREEFORM;
1021         else
1022             return FREEFORM_WORKSPACE_STACK_ID;
1023     }
1024
1025     private static String getWindowingModeMethodName() {
1026         if(getCurrentApiVersion() >= 28.0f)
1027             return "setLaunchWindowingMode";
1028         else
1029             return "setLaunchStackId";
1030     }
1031
1032     public static Bundle getActivityOptionsBundle(Context context, ApplicationType type, View view) {
1033         SharedPreferences pref = getSharedPreferences(context);
1034
1035         return getActivityOptionsBundle(context, type, pref.getString("window_size", context.getString(R.string.tb_def_window_size)), view);
1036     }
1037
1038     private static Bundle getActivityOptionsBundle(Context context, ApplicationType type, String windowSize, View view) {
1039         SharedPreferences pref = getSharedPreferences(context);
1040         if(!canEnableFreeform() || !pref.getBoolean("freeform_hack", false))
1041             return getActivityOptions(view).toBundle();
1042
1043         switch(windowSize) {
1044             case "large":
1045                 return launchMode1(context, type, view);
1046             case "fullscreen":
1047                 return launchMode2(context, MAXIMIZED, type, view);
1048             case "half_left":
1049                 return launchMode2(context, LEFT, type, view);
1050             case "half_right":
1051                 return launchMode2(context, RIGHT, type, view);
1052             case "phone_size":
1053                 return launchMode3(context, type, view);
1054         }
1055
1056         return getActivityOptions(context, type, view).toBundle();
1057     }
1058
1059     private static ApplicationType getApplicationType(Context context, String packageName) {
1060         return isGame(context, packageName) ? ApplicationType.GAME : ApplicationType.APPLICATION;
1061     }
1062
1063     public static boolean isSystemApp(Context context) {
1064         try {
1065             ApplicationInfo info = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
1066             int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
1067             return (info.flags & mask) != 0;
1068         } catch (PackageManager.NameNotFoundException e) {
1069             return false;
1070         }
1071     }
1072
1073     public static boolean isChromeOs(Context context) {
1074         return context.getPackageManager().hasSystemFeature("org.chromium.arc");
1075     }
1076
1077     public static boolean isBlissOs(Context context) {
1078         boolean validBlissOsBuildProp = false;
1079
1080         String blissVersion = getSystemProperty("ro.bliss.version");
1081         if(blissVersion != null && !blissVersion.isEmpty())
1082             validBlissOsBuildProp = true;
1083
1084         String buildUser = getSystemProperty("ro.build.user");
1085         if(buildUser != null && buildUser.equals("electrikjesus"))
1086             validBlissOsBuildProp = true;
1087
1088         return validBlissOsBuildProp
1089                 && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1090                 && isSystemApp(context);
1091     }
1092
1093     public static boolean isLauncherPermanentlyEnabled(Context context) {
1094         if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID))
1095             return true;
1096
1097         PackageManager pm = context.getPackageManager();
1098         try {
1099             pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1100             return pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, context.getPackageName()) == PackageManager.SIGNATURE_MATCH
1101                     && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1102                     && isSystemApp(context);
1103         } catch (PackageManager.NameNotFoundException e) {
1104             return false;
1105         }
1106     }
1107
1108     public static boolean hasSupportLibrary(Context context, int minVersion) {
1109         PackageManager pm = context.getPackageManager();
1110         try {
1111             PackageInfo pInfo = pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
1112             return pInfo.versionCode >= minVersion
1113                     && pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, context.getPackageName()) == PackageManager.SIGNATURE_MATCH
1114                     && context.getPackageName().equals(BuildConfig.BASE_APPLICATION_ID)
1115                     && isSystemApp(context);
1116         } catch (PackageManager.NameNotFoundException e) {
1117             return false;
1118         }
1119     }
1120
1121     public static int getBaseTaskbarSize(Context context) {
1122         return Math.round(getBaseTaskbarSizeFloat(context));
1123     }
1124
1125     private static float getBaseTaskbarSizeFloat(Context context) {
1126         SharedPreferences pref = getSharedPreferences(context);
1127         float baseTaskbarSize = context.getResources().getDimension(R.dimen.tb_base_taskbar_size);
1128         boolean navbarButtonsEnabled = false;
1129
1130         if(pref.getBoolean("dashboard", context.getResources().getBoolean(R.bool.tb_def_dashboard)))
1131             baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_dashboard_button_size);
1132
1133         if(pref.getBoolean("button_back", false)) {
1134             navbarButtonsEnabled = true;
1135             baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1136         }
1137
1138         if(pref.getBoolean("button_home", false)) {
1139             navbarButtonsEnabled = true;
1140             baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1141         }
1142
1143         if(pref.getBoolean("button_recents", false)) {
1144             navbarButtonsEnabled = true;
1145             baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_icon_size);
1146         }
1147
1148         if(navbarButtonsEnabled)
1149             baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_navbar_buttons_margin);
1150
1151         if(isSystemTrayEnabled(context))
1152             baseTaskbarSize += context.getResources().getDimension(R.dimen.tb_systray_size);
1153
1154         return baseTaskbarSize;
1155     }
1156
1157     private static void startTaskbarService(Context context, boolean fullRestart) {
1158         context.startService(new Intent(context, TaskbarService.class));
1159         context.startService(new Intent(context, StartMenuService.class));
1160         context.startService(new Intent(context, DashboardService.class));
1161         if(fullRestart) context.startService(new Intent(context, NotificationService.class));
1162     }
1163
1164     private static void stopTaskbarService(Context context, boolean fullRestart) {
1165         context.stopService(new Intent(context, TaskbarService.class));
1166         context.stopService(new Intent(context, StartMenuService.class));
1167         context.stopService(new Intent(context, DashboardService.class));
1168         if(fullRestart) context.stopService(new Intent(context, NotificationService.class));
1169     }
1170
1171     public static void restartTaskbar(Context context) {
1172         SharedPreferences pref = getSharedPreferences(context);
1173         if(pref.getBoolean("taskbar_active", false) && !pref.getBoolean("is_hidden", false)) {
1174             pref.edit()
1175                     .putBoolean("is_restarting", true)
1176                     .putBoolean("skip_auto_hide_navbar", true)
1177                     .apply();
1178
1179             stopTaskbarService(context, true);
1180             startTaskbarService(context, true);
1181         } else if(isServiceRunning(context, StartMenuService.class)) {
1182             pref.edit().putBoolean("skip_auto_hide_navbar", true).apply();
1183
1184             stopTaskbarService(context, false);
1185             startTaskbarService(context, false);
1186         }
1187
1188         LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("com.farmerbb.taskbar.RESTART"));
1189     }
1190
1191     public static void restartNotificationService(Context context) {
1192         if(isServiceRunning(context, NotificationService.class)) {
1193             SharedPreferences pref = getSharedPreferences(context);
1194             pref.edit().putBoolean("is_restarting", true).apply();
1195
1196             Intent intent = new Intent(context, NotificationService.class);
1197             context.stopService(intent);
1198             context.startService(intent);
1199         }
1200     }
1201
1202     public static void showHideNavigationBar(Context context, boolean show) {
1203         if(hasSupportLibrary(context, 7)) {
1204             Intent intent = new Intent(BuildConfig.SUPPORT_APPLICATION_ID + ".CHANGE_OVERSCAN");
1205             intent.setPackage(BuildConfig.SUPPORT_APPLICATION_ID);
1206
1207             intent.putExtra("display_id", getDisplayID());
1208             intent.putExtra("value", show ? 0 : getNavbarHeight(context) * -1);
1209
1210             context.sendBroadcast(intent);
1211             return;
1212         }
1213
1214         // Show or hide the system navigation bar on Bliss-x86
1215         try {
1216             if(getCurrentApiVersion() >= 28.0f)
1217                 Settings.Secure.putInt(context.getContentResolver(), "navigation_bar_visible", show ? 1 : 0);
1218             else
1219                 Settings.System.putInt(context.getContentResolver(), "navigation_bar_show", show ? 1 : 0);
1220         } catch (Exception e) { /* Gracefully fail */ }
1221     }
1222
1223     public static void initPrefs(Context context) {
1224         // Enable freeform hack automatically on supported devices
1225         SharedPreferences pref = getSharedPreferences(context);
1226         if(canEnableFreeform()) {
1227             if(!pref.getBoolean("freeform_hack_override", false)) {
1228                 pref.edit()
1229                         .putBoolean("freeform_hack", hasFreeformSupport(context) && !isSamsungDevice())
1230                         .putBoolean("save_window_sizes", false)
1231                         .putBoolean("freeform_hack_override", true)
1232                         .apply();
1233             } else if(!hasFreeformSupport(context)) {
1234                 pref.edit().putBoolean("freeform_hack", false).apply();
1235
1236                 stopFreeformHack(context);
1237             }
1238         } else {
1239             boolean freeformWasEnabled = pref.getBoolean("freeform_hack", false)
1240                     || pref.getBoolean("show_freeform_disabled_message", false);
1241
1242             pref.edit()
1243                     .putBoolean("freeform_hack", false)
1244                     .putBoolean("show_freeform_disabled_message", freeformWasEnabled)
1245                     .apply();
1246
1247             SavedWindowSizes.getInstance(context).clear(context);
1248             stopFreeformHack(context);
1249         }
1250
1251         // Customizations for BlissOS
1252         if(isBlissOs(context) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1253                 && !pref.getBoolean("bliss_os_prefs", false)) {
1254             SharedPreferences.Editor editor = pref.edit();
1255
1256             if(hasFreeformSupport(context)) {
1257                 editor.putBoolean("freeform_hack", true);
1258             }
1259
1260             editor.putString("recents_amount", "running_apps_only");
1261             editor.putString("refresh_frequency", "0");
1262             editor.putString("max_num_of_recents", "2147483647");
1263             editor.putString("sort_order", "true");
1264             editor.putString("start_button_image", "app_logo");
1265             editor.putBoolean("button_back", true);
1266             editor.putBoolean("button_home", true);
1267             editor.putBoolean("button_recents", true);
1268             editor.putBoolean("auto_hide_navbar", true);
1269             editor.putBoolean("shortcut_icon", false);
1270             editor.putBoolean("bliss_os_prefs", true);
1271             editor.apply();
1272         }
1273
1274         // Customizations for Android-x86 devices (non-Bliss)
1275         if(context.getPackageName().equals(BuildConfig.ANDROIDX86_APPLICATION_ID)
1276                 && isSystemApp(context)
1277                 && !pref.getBoolean("android_x86_prefs", false)) {
1278             pref.edit()
1279                     .putString("recents_amount", "running_apps_only")
1280                     .putString("refresh_frequency", "0")
1281                     .putString("max_num_of_recents", "2147483647")
1282                     .putString("sort_order", "true")
1283                     .putBoolean("shortcut_icon", false)
1284                     .putBoolean("android_x86_prefs", true)
1285                     .apply();
1286         }
1287     }
1288
1289     public static DisplayInfo getDisplayInfo(Context context) {
1290         return getDisplayInfo(context, false);
1291     }
1292
1293     public static DisplayInfo getDisplayInfo(Context context, boolean fromTaskbar) {
1294         context = context.getApplicationContext();
1295         int displayID = getDisplayID();
1296
1297         DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1298         Display currentDisplay = null;
1299
1300         for(Display display : dm.getDisplays()) {
1301             if(display.getDisplayId() == displayID) {
1302                 currentDisplay = display;
1303                 break;
1304             }
1305         }
1306
1307         if(currentDisplay == null)
1308             return new DisplayInfo(0, 0, 0);
1309
1310         DisplayMetrics metrics = new DisplayMetrics();
1311         currentDisplay.getMetrics(metrics);
1312
1313         DisplayMetrics realMetrics = new DisplayMetrics();
1314         currentDisplay.getRealMetrics(realMetrics);
1315
1316         DisplayInfo info = new DisplayInfo(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);
1317
1318         if(isChromeOs(context)) {
1319             SharedPreferences pref = getSharedPreferences(context);
1320             if(!pref.getBoolean("chrome_os_context_menu_fix", true)) {
1321                 info.width = realMetrics.widthPixels;
1322                 info.height = realMetrics.heightPixels;
1323             }
1324
1325             return info;
1326         }
1327
1328         // Workaround for incorrect display size on devices with notches in landscape mode
1329         if(fromTaskbar && context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)
1330             return info;
1331
1332         boolean sameWidth = metrics.widthPixels == realMetrics.widthPixels;
1333         boolean sameHeight = metrics.heightPixels == realMetrics.heightPixels;
1334
1335         if(sameWidth && !sameHeight) {
1336             info.width = realMetrics.widthPixels;
1337             info.height = realMetrics.heightPixels - getNavbarHeight(context);
1338         }
1339
1340         if(!sameWidth && sameHeight) {
1341             info.width = realMetrics.widthPixels - getNavbarHeight(context);
1342             info.height = realMetrics.heightPixels;
1343         }
1344
1345         return info;
1346     }
1347
1348     private static int getDisplayID() {
1349         LauncherHelper helper = LauncherHelper.getInstance();
1350
1351         if(helper.isOnSecondaryHomeScreen())
1352             return helper.getSecondaryDisplayId();
1353         else
1354             return Display.DEFAULT_DISPLAY;
1355     }
1356
1357     public static void pinAppShortcut(Context context) {
1358         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1359             ShortcutManager mShortcutManager = context.getSystemService(ShortcutManager.class);
1360
1361             if(mShortcutManager.isRequestPinShortcutSupported()) {
1362                 ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder(context, "freeform_mode").build();
1363
1364                 mShortcutManager.requestPinShortcut(pinShortcutInfo, null);
1365             } else
1366                 showToastLong(context, R.string.tb_pin_shortcut_not_supported);
1367         } else {
1368             Intent intent = ShortcutUtils.getShortcutIntent(context);
1369             intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
1370             intent.putExtra("duplicate", false);
1371
1372             Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1373             homeIntent.addCategory(Intent.CATEGORY_HOME);
1374             ResolveInfo defaultLauncher = context.getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
1375
1376             intent.setPackage(defaultLauncher.activityInfo.packageName);
1377             context.sendBroadcast(intent);
1378
1379             showToast(context, R.string.tb_shortcut_created);
1380         }
1381     }
1382
1383     public static boolean shouldCollapse(Context context, boolean pendingAppLaunch) {
1384         SharedPreferences pref = getSharedPreferences(context);
1385         if(pref.getBoolean("hide_taskbar", true)) {
1386             if(isOverridingFreeformHack(context))
1387                 return !LauncherHelper.getInstance().isOnHomeScreen();
1388             else {
1389                 FreeformHackHelper helper = FreeformHackHelper.getInstance();
1390                 if(pendingAppLaunch)
1391                     return !helper.isFreeformHackActive();
1392                 else
1393                     return !helper.isInFreeformWorkspace();
1394             }
1395         } else
1396             return false;
1397     }
1398
1399     public static boolean isOverridingFreeformHack(Context context) {
1400         SharedPreferences pref = getSharedPreferences(context);
1401         return pref.getBoolean("freeform_hack", false)
1402                 && ((isChromeOs(context) && pref.getBoolean("chrome_os_context_menu_fix", true))
1403                 || (!isChromeOs(context) && getCurrentApiVersion() >= 28.0f));
1404     }
1405
1406     public static boolean isPlayStoreInstalled(Context context) {
1407         try {
1408             context.getPackageManager().getPackageInfo("com.android.vending", 0);
1409             return true;
1410         } catch (PackageManager.NameNotFoundException e) {
1411             return false;
1412         }
1413     }
1414
1415     private static float getCurrentApiVersion() {
1416         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1417             return Float.valueOf(Build.VERSION.SDK_INT + "." + Build.VERSION.PREVIEW_SDK_INT);
1418         else
1419             return (float) Build.VERSION.SDK_INT;
1420     }
1421
1422     public static boolean hasBrokenSetLaunchBoundsApi() {
1423         return getCurrentApiVersion() >= 26.0f
1424                 && getCurrentApiVersion() < 28.0f
1425                 && !isSamsungDevice()
1426                 && !isNvidiaDevice();
1427     }
1428
1429     public static String getSecondScreenPackageName(Context context) {
1430         return getInstalledPackage(context, Arrays.asList(
1431                 "com.farmerbb.secondscreen.free",
1432                 "com.farmerbb.secondscreen"));
1433     }
1434
1435     // Returns the name of an installed package from a list of package names, in order of preference
1436     private static String getInstalledPackage(Context context, List<String> packageNames) {
1437         if(packageNames == null || packageNames.isEmpty())
1438             return null;
1439
1440         List<String> packages = packageNames instanceof ArrayList ? packageNames : new ArrayList<>(packageNames);
1441         String packageName = packages.get(0);
1442
1443         try {
1444             context.getPackageManager().getPackageInfo(packageName, 0);
1445             return packageName;
1446         } catch (PackageManager.NameNotFoundException e) {
1447             packages.remove(0);
1448             return getInstalledPackage(context, packages);
1449         }
1450     }
1451
1452     public static boolean visualFeedbackEnabled(Context context) {
1453         SharedPreferences pref = getSharedPreferences(context);
1454         return (getCurrentApiVersion() < 26.0f || getCurrentApiVersion() >= 28.0f)
1455                 && pref.getBoolean("visual_feedback", true)
1456                 && !isNvidiaDevice();
1457     }
1458
1459     public static void showRecentAppsDialog(Context context) {
1460         showRecentAppsDialog(context, null, null);
1461     }
1462
1463     public static AlertDialog showRecentAppsDialog(Context context, Runnable onError, Runnable onFinish) {
1464         Runnable finalOnFinish = onFinish == null
1465                 ? () -> {}
1466                 : onFinish;
1467
1468         Runnable finalOnError = onError == null
1469                 ? () -> showErrorDialog(context, "GET_USAGE_STATS", finalOnFinish)
1470                 : onError;
1471
1472         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isSystemApp(context)) {
1473             ApplicationInfo applicationInfo = null;
1474             try {
1475                 applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
1476             } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
1477
1478             if(applicationInfo != null) {
1479                 AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
1480                 int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
1481
1482                 if(mode != AppOpsManager.MODE_ALLOWED) {
1483                     AlertDialog.Builder builder = new AlertDialog.Builder(context);
1484                     builder.setTitle(R.string.tb_pref_header_recent_apps)
1485                             .setMessage(R.string.tb_enable_recent_apps)
1486                             .setPositiveButton(R.string.tb_action_ok, (dialog, which) -> {
1487                                 try {
1488                                     context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
1489                                     showToastLong(context, R.string.tb_usage_stats_message);
1490
1491                                     finalOnFinish.run();
1492                                 } catch (ActivityNotFoundException e) {
1493                                     finalOnError.run();
1494                                 }
1495                             })
1496                             .setNegativeButton(R.string.tb_action_cancel, (dialog, which) -> finalOnFinish.run());
1497
1498                     AlertDialog dialog = builder.create();
1499                     dialog.show();
1500                     dialog.setCancelable(false);
1501
1502                     return dialog;
1503                 }
1504             }
1505         }
1506
1507         finalOnFinish.run();
1508         return null;
1509     }
1510
1511     public static Context wrapContext(Context context) {
1512         SharedPreferences pref = getSharedPreferences(context);
1513
1514         int theme = -1;
1515         switch(pref.getString("theme", "light")) {
1516             case "light":
1517                 theme = R.style.Taskbar;
1518                 break;
1519             case "dark":
1520                 theme = R.style.Taskbar_Dark;
1521                 break;
1522         }
1523
1524         return theme > -1 ? new ContextThemeWrapper(context, theme) : context;
1525     }
1526
1527     public static boolean isPlayStoreRelease(Context context) {
1528         return isPlayStoreRelease(context, context.getPackageName());
1529     }
1530
1531     @SuppressLint("PackageManagerGetSignatures")
1532     public static boolean isPlayStoreRelease(Context context, String packageName) {
1533         Signature playStoreSignature = new Signature(context.getString(R.string.tb_signature));
1534         try {
1535             PackageManager pm = context.getPackageManager();
1536             PackageInfo info = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
1537             for(Signature signature : info.signatures) {
1538                 if(signature.equals(playStoreSignature))
1539                     return true;
1540             }
1541         } catch (Exception e) { /* Gracefully fail */ }
1542
1543         return false;
1544     }
1545
1546     public static boolean isExternalAccessDisabled(Context context) {
1547         SharedPreferences pref = getSharedPreferences(context);
1548         return !pref.getBoolean("tasker_enabled", true);
1549     }
1550
1551     public static boolean enableFreeformModeShortcut(Context context) {
1552         return canEnableFreeform()
1553                 && !isOverridingFreeformHack(context)
1554                 && !isChromeOs(context);
1555     }
1556
1557     public static void startForegroundService(Context context, Intent intent) {
1558         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1559             if(Settings.canDrawOverlays(context))
1560                 context.startForegroundService(intent);
1561         } else
1562             context.startService(intent);
1563     }
1564
1565     public static int getOverlayType() {
1566         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
1567                 ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
1568                 : WindowManager.LayoutParams.TYPE_PHONE;
1569     }
1570
1571     public static boolean isDelegatingHomeActivity(Context context) {
1572         Intent homeIntent = new Intent(Intent.ACTION_MAIN);
1573         homeIntent.addCategory(Intent.CATEGORY_HOME);
1574
1575         final List<ResolveInfo> listOfLaunchers = context.getPackageManager().queryIntentActivities(homeIntent, 0);
1576         for(ResolveInfo launcher : listOfLaunchers) {
1577             if(launcher.activityInfo.packageName.equals(BuildConfig.SUPPORT_APPLICATION_ID))
1578                 return true;
1579         }
1580
1581         return false;
1582     }
1583
1584     @SuppressLint("PrivateApi")
1585     private static String getSystemProperty(String key) {
1586         try {
1587             Class<?> cls = Class.forName("android.os.SystemProperties");
1588             return cls.getMethod("get", String.class).invoke(null, key).toString();
1589         } catch (Exception e) {
1590             return null;
1591         }
1592     }
1593
1594     @SuppressWarnings("ResultOfMethodCallIgnored")
1595     public static boolean importCustomStartButtonImage(Context context, Uri uri) {
1596         try {
1597             File imagesDir = new File(context.getFilesDir(), "tb_images");
1598             imagesDir.mkdirs();
1599
1600             File importedFile = new File(imagesDir, "custom_image_new");
1601             if(importedFile.exists()) importedFile.delete();
1602
1603             BufferedInputStream is = new BufferedInputStream(context.getContentResolver().openInputStream(uri));
1604             byte[] data = new byte[is.available()];
1605
1606             if(data.length > 0) {
1607                 BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(importedFile));
1608                 is.read(data);
1609                 os.write(data);
1610                 is.close();
1611                 os.close();
1612             }
1613
1614             File prevFile = new File(imagesDir, "custom_image");
1615             if(prevFile.exists()) prevFile.delete();
1616
1617             importedFile.renameTo(prevFile);
1618             return true;
1619         } catch (IOException e) {
1620             return false;
1621         }
1622     }
1623
1624     public static String getDefaultStartButtonImage(Context context) {
1625         SharedPreferences pref = getSharedPreferences(context);
1626         return pref.getBoolean("app_drawer_icon", false)
1627                 ? "app_logo"
1628                 : "default";
1629     }
1630
1631     private static boolean shouldLaunchTouchAbsorber(Context context) {
1632         return isOverridingFreeformHack(context) && !isChromeOs(context) && getCurrentApiVersion() < 29.0f;
1633     }
1634
1635     public static boolean isDesktopIconsEnabled(Context context) {
1636         return !canBootToFreeform(context) && !shouldLaunchTouchAbsorber(context);
1637     }
1638
1639     public static boolean isSystemTrayEnabled(Context context) {
1640         SharedPreferences pref = getSharedPreferences(context);
1641
1642         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
1643                 && pref.getBoolean("sys_tray", context.getResources().getBoolean(R.bool.tb_def_sys_tray))
1644                 && pref.getBoolean("full_length", context.getResources().getBoolean(R.bool.tb_def_full_length))
1645                 && !getTaskbarPosition(context).contains("vertical");
1646     }
1647
1648     @SuppressWarnings("deprecation")
1649     public static boolean isLibrary(Context context) {
1650         return !context.getPackageName().equals(BuildConfig.APPLICATION_ID);
1651     }
1652
1653     public static boolean applyDisplayCutoutModeTo(WindowManager.LayoutParams params) {
1654         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
1655             params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
1656             return true;
1657         }
1658
1659         return false;
1660     }
1661 }