OSDN Git Service

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