OSDN Git Service

Couple more fixes
[android-x86/packages-apps-Taskbar.git] / app / src / main / java / com / farmerbb / taskbar / ui / StartMenuController.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.ui;
17
18 import android.annotation.SuppressLint;
19 import android.annotation.TargetApi;
20 import android.app.SearchManager;
21 import android.content.ActivityNotFoundException;
22 import android.content.BroadcastReceiver;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.SharedPreferences;
27 import android.content.pm.LauncherActivityInfo;
28 import android.content.pm.LauncherApps;
29 import android.content.pm.PackageManager;
30 import android.content.res.Configuration;
31 import android.graphics.drawable.Drawable;
32 import android.net.Uri;
33 import android.os.Build;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.os.UserHandle;
37 import android.os.UserManager;
38
39 import androidx.annotation.VisibleForTesting;
40 import androidx.appcompat.widget.SearchView;
41 import android.util.Patterns;
42 import android.view.Gravity;
43 import android.view.LayoutInflater;
44 import android.view.MotionEvent;
45 import android.view.View;
46 import android.view.ViewGroup;
47 import android.view.WindowManager;
48 import android.view.inputmethod.EditorInfo;
49 import android.view.inputmethod.InputMethodManager;
50 import android.webkit.URLUtil;
51 import android.widget.EditText;
52 import android.widget.FrameLayout;
53 import android.widget.GridView;
54 import android.widget.LinearLayout;
55 import android.widget.ListAdapter;
56 import android.widget.TextView;
57
58 import com.farmerbb.taskbar.R;
59 import com.farmerbb.taskbar.activity.InvisibleActivity;
60 import com.farmerbb.taskbar.activity.InvisibleActivityAlt;
61 import com.farmerbb.taskbar.adapter.StartMenuAdapter;
62 import com.farmerbb.taskbar.util.TaskbarPosition;
63 import com.farmerbb.taskbar.util.AppEntry;
64 import com.farmerbb.taskbar.util.Blacklist;
65 import com.farmerbb.taskbar.helper.FreeformHackHelper;
66 import com.farmerbb.taskbar.util.IconCache;
67 import com.farmerbb.taskbar.helper.LauncherHelper;
68 import com.farmerbb.taskbar.helper.MenuHelper;
69 import com.farmerbb.taskbar.util.TopApps;
70 import com.farmerbb.taskbar.util.U;
71 import com.farmerbb.taskbar.widget.StartMenuLayout;
72
73 import java.text.Collator;
74 import java.util.ArrayList;
75 import java.util.Collections;
76 import java.util.Comparator;
77 import java.util.List;
78
79 import static com.farmerbb.taskbar.util.Constants.*;
80
81 public class StartMenuController extends UIController {
82
83     private StartMenuLayout layout;
84     private GridView startMenu;
85     private SearchView searchView;
86     private TextView textView;
87     private PackageManager pm;
88     private StartMenuAdapter adapter;
89
90     private Handler handler;
91     private Thread thread;
92
93     private boolean hasSubmittedQuery = false;
94     private boolean hasHardwareKeyboard = false;
95     private boolean searchViewClicked = false;
96
97     private List<String> currentStartMenuIds = new ArrayList<>();
98
99     private final View.OnClickListener ocl = view -> toggleStartMenu();
100
101     private final BroadcastReceiver toggleReceiver = new BroadcastReceiver() {
102         @Override
103         public void onReceive(Context context, Intent intent) {
104             toggleStartMenu();
105         }
106     };
107
108     private final BroadcastReceiver showSpaceReceiver = new BroadcastReceiver() {
109         @Override
110         public void onReceive(Context context, Intent intent) {
111             layout.findViewById(R.id.start_menu_space).setVisibility(View.VISIBLE);
112         }
113     };
114
115     private final BroadcastReceiver hideSpaceReceiver = new BroadcastReceiver() {
116         @Override
117         public void onReceive(Context context, Intent intent) {
118             layout.findViewById(R.id.start_menu_space).setVisibility(View.GONE);
119         }
120     };
121
122     private final BroadcastReceiver hideReceiver = new BroadcastReceiver() {
123         @Override
124         public void onReceive(Context context, Intent intent) {
125             hideStartMenu(true);
126         }
127     };
128
129     private final BroadcastReceiver hideReceiverNoReset = new BroadcastReceiver() {
130         @Override
131         public void onReceive(Context context, Intent intent) {
132             hideStartMenu(false);
133         }
134     };
135
136     private final BroadcastReceiver resetReceiver = new BroadcastReceiver() {
137         @Override
138         public void onReceive(Context context, Intent intent) {
139             startMenu.setSelection(0);
140         }
141     };
142
143     private final Comparator<LauncherActivityInfo> comparator = (ai1, ai2) -> {
144         String label1;
145         String label2;
146
147         try {
148             label1 = ai1.getLabel().toString();
149             label2 = ai2.getLabel().toString();
150         } catch (OutOfMemoryError e) {
151             System.gc();
152
153             label1 = ai1.getApplicationInfo().packageName;
154             label2 = ai2.getApplicationInfo().packageName;
155         }
156
157         return Collator.getInstance().compare(label1, label2);
158     };
159
160     public StartMenuController(Context context) {
161         super(context);
162     }
163
164     @TargetApi(Build.VERSION_CODES.M)
165     @Override
166     public void onCreateHost(UIHost host) {
167         hasHardwareKeyboard = context.getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS;
168
169         init(context, host, () -> drawStartMenu(host));
170     }
171
172     private void drawStartMenu(UIHost host) {
173         IconCache.getInstance(context).clearCache();
174
175         final SharedPreferences pref = U.getSharedPreferences(context);
176         boolean shouldShowSearchBox = shouldShowSearchBox(pref, hasHardwareKeyboard);
177
178         // Initialize layout params
179         WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
180         TaskbarPosition.setCachedRotation(windowManager.getDefaultDisplay().getRotation());
181
182         final ViewParams params = new ViewParams(
183                 WindowManager.LayoutParams.WRAP_CONTENT,
184                 WindowManager.LayoutParams.WRAP_CONTENT,
185                 -1,
186                 shouldShowSearchBox ? 0 : WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
187                 getBottomMargin(context, host)
188         );
189
190         // Determine where to show the start menu on screen
191         String taskbarPosition = TaskbarPosition.getTaskbarPosition(context);
192         int layoutId = getStartMenuLayoutId(taskbarPosition);
193         params.gravity = getStartMenuGravity(taskbarPosition);
194
195         // Initialize views
196         layout = (StartMenuLayout) LayoutInflater.from(U.wrapContext(context)).inflate(layoutId, null);
197         layout.setAlpha(0);
198
199         startMenu = layout.findViewById(R.id.start_menu);
200
201         if((shouldShowSearchBox && !hasHardwareKeyboard) || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1)
202             layout.viewHandlesBackButton();
203
204         boolean scrollbar = pref.getBoolean(PREF_SCROLLBAR, false);
205         startMenu.setFastScrollEnabled(scrollbar);
206         startMenu.setFastScrollAlwaysVisible(scrollbar);
207         startMenu.setScrollBarStyle(scrollbar ? View.SCROLLBARS_OUTSIDE_INSET : View.SCROLLBARS_INSIDE_OVERLAY);
208
209         if(pref.getBoolean(PREF_TRANSPARENT_START_MENU, false))
210             startMenu.setBackgroundColor(0);
211
212         if(pref.getBoolean(PREF_VISUAL_FEEDBACK, true))
213             startMenu.setRecyclerListener(view -> view.setBackgroundColor(0));
214
215         int columns = context.getResources().getInteger(R.integer.tb_start_menu_columns);
216         boolean isGrid = pref.getString(PREF_START_MENU_LAYOUT, "grid").equals("grid");
217
218         if(isGrid) {
219             ViewGroup.LayoutParams startMenuParams = startMenu.getLayoutParams();
220             startMenuParams.width = (int) (startMenuParams.width * (columns / 3f));
221             startMenu.setLayoutParams(startMenuParams);
222         }
223
224         searchView = layout.findViewById(R.id.search);
225         searchViewClicked = false;
226
227         int backgroundTint = U.getBackgroundTint(context);
228
229         FrameLayout startMenuFrame = layout.findViewById(R.id.start_menu_frame);
230         FrameLayout searchViewLayout = layout.findViewById(R.id.search_view_layout);
231         startMenuFrame.setBackgroundColor(backgroundTint);
232         searchViewLayout.setBackgroundColor(backgroundTint);
233
234         if(shouldShowSearchBox) {
235             if(!hasHardwareKeyboard) searchView.setIconifiedByDefault(true);
236
237             searchView.setOnTouchListener((v, event) -> {
238                 searchViewClicked = true;
239                 return false;
240             });
241
242             searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
243                 @Override
244                 public boolean onQueryTextSubmit(String query) {
245                     if(!hasSubmittedQuery) {
246                         ListAdapter adapter = startMenu.getAdapter();
247                         if(adapter != null) {
248                             hasSubmittedQuery = true;
249
250                             if(adapter.getCount() > 0) {
251                                 View view = adapter.getView(0, null, startMenu);
252                                 LinearLayout layout = view.findViewById(R.id.entry);
253                                 layout.performClick();
254                             } else {
255                                 if(U.shouldCollapse(context, true)) {
256                                     U.sendBroadcast(context, ACTION_HIDE_TASKBAR);
257                                 } else {
258                                     hideStartMenu(true);
259                                 }
260
261                                 Intent intent = generateQueryWebSearchIntent(query);
262                                 if(intent.resolveActivity(context.getPackageManager()) != null) {
263                                     context.startActivity(intent);
264                                 } else {
265                                     intent = generateQueryGoogleIntent(query);
266                                     try {
267                                         context.startActivity(intent);
268                                     } catch (ActivityNotFoundException ignored) {}
269                                 }
270                             }
271                         }
272                     }
273                     return true;
274                 }
275
276                 @Override
277                 public boolean onQueryTextChange(String newText) {
278                     searchView.setIconified(false);
279
280                     View closeButton = searchView.findViewById(R.id.search_close_btn);
281                     if(closeButton != null) closeButton.setVisibility(View.GONE);
282
283                     refreshApps(newText, false);
284
285                     if(Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
286                         U.newHandler().postDelayed(() -> {
287                             EditText editText = searchView.findViewById(R.id.search_src_text);
288                             if(editText != null) {
289                                 editText.requestFocus();
290                                 editText.setSelection(editText.getText().length());
291                             }
292                         }, 50);
293                     }
294
295                     return true;
296                 }
297             });
298
299             searchView.setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
300
301             LinearLayout powerButton = layout.findViewById(R.id.power_button);
302             powerButton.setOnClickListener(view -> {
303                 int[] location = new int[2];
304                 view.getLocationOnScreen(location);
305                 openContextMenu(location);
306             });
307
308             powerButton.setOnGenericMotionListener((view, motionEvent) -> {
309                 if(motionEvent.getAction() == MotionEvent.ACTION_BUTTON_PRESS
310                         && motionEvent.getButtonState() == MotionEvent.BUTTON_SECONDARY) {
311                     int[] location = new int[2];
312                     view.getLocationOnScreen(location);
313                     openContextMenu(location);
314                 }
315                 return false;
316             });
317
318             searchViewLayout.setOnClickListener(view -> searchView.setIconified(false));
319
320             startMenu.setOnItemClickListener((viewParent, view, position, id) -> {
321                 hideStartMenu(true);
322
323                 AppEntry entry = (AppEntry) viewParent.getAdapter().getItem(position);
324                 U.launchApp(context, entry, null, false, false, view);
325             });
326
327             View childLayout = layout.findViewById(R.id.search_view_child_layout);
328             if(pref.getBoolean(PREF_TRANSPARENT_START_MENU, false))
329                 childLayout.setBackgroundColor(0);
330
331             if(isGrid) {
332                 ViewGroup.LayoutParams childLayoutParams = childLayout.getLayoutParams();
333                 childLayoutParams.width = (int) (childLayoutParams.width * (columns / 3f));
334                 childLayout.setLayoutParams(childLayoutParams);
335             }
336         } else
337             searchViewLayout.setVisibility(View.GONE);
338
339         applyMarginFix(host, layout, params);
340
341         textView = layout.findViewById(R.id.no_apps_found);
342
343         U.registerReceiver(context, toggleReceiver, ACTION_TOGGLE_START_MENU);
344         U.registerReceiver(context, hideReceiver, ACTION_HIDE_START_MENU);
345         U.registerReceiver(context, hideReceiverNoReset, ACTION_HIDE_START_MENU_NO_RESET);
346         U.registerReceiver(context, showSpaceReceiver, ACTION_SHOW_START_MENU_SPACE);
347         U.registerReceiver(context, hideSpaceReceiver, ACTION_HIDE_START_MENU_SPACE);
348         U.registerReceiver(context, resetReceiver, ACTION_RESET_START_MENU);
349
350         handler = U.newHandler();
351         refreshApps(true);
352
353         host.addView(layout, params);
354     }
355
356     @VisibleForTesting
357     boolean shouldShowSearchBox(SharedPreferences pref, boolean hasHardwareKeyboard) {
358         boolean shouldShowSearchBox;
359         switch(pref.getString(PREF_SHOW_SEARCH_BAR, "always")) {
360             case "always":
361                 shouldShowSearchBox = true;
362                 break;
363             case "keyboard":
364                 shouldShowSearchBox = hasHardwareKeyboard;
365                 break;
366             default:
367                 shouldShowSearchBox = false;
368                 break;
369         }
370         return shouldShowSearchBox;
371     }
372
373     @VisibleForTesting
374     int getStartMenuLayoutId(String taskbarPosition) {
375         switch(taskbarPosition) {
376             case POSITION_BOTTOM_LEFT:
377             default:
378                 return R.layout.tb_start_menu_left;
379             case POSITION_BOTTOM_RIGHT:
380                 return R.layout.tb_start_menu_right;
381             case POSITION_TOP_LEFT:
382                 return R.layout.tb_start_menu_top_left;
383             case POSITION_TOP_VERTICAL_LEFT:
384             case POSITION_BOTTOM_VERTICAL_LEFT:
385                 return R.layout.tb_start_menu_vertical_left;
386             case POSITION_TOP_RIGHT:
387                 return R.layout.tb_start_menu_top_right;
388             case POSITION_TOP_VERTICAL_RIGHT:
389             case POSITION_BOTTOM_VERTICAL_RIGHT:
390                 return R.layout.tb_start_menu_vertical_right;
391         }
392     }
393
394     @VisibleForTesting
395     @SuppressLint("RtlHardcoded")
396     int getStartMenuGravity(String taskbarPosition) {
397         switch(taskbarPosition) {
398             case POSITION_BOTTOM_LEFT:
399             case POSITION_BOTTOM_VERTICAL_LEFT:
400             default:
401                 return Gravity.BOTTOM | Gravity.LEFT;
402             case POSITION_BOTTOM_RIGHT:
403             case POSITION_BOTTOM_VERTICAL_RIGHT:
404                 return Gravity.BOTTOM | Gravity.RIGHT;
405             case POSITION_TOP_LEFT:
406             case POSITION_TOP_VERTICAL_LEFT:
407                 return Gravity.TOP | Gravity.LEFT;
408             case POSITION_TOP_RIGHT:
409             case POSITION_TOP_VERTICAL_RIGHT:
410                 return Gravity.TOP | Gravity.RIGHT;
411         }
412     }
413
414     @VisibleForTesting
415     Intent generateQueryWebSearchIntent(String query) {
416         Intent intent;
417         if(Patterns.WEB_URL.matcher(query).matches()) {
418             intent = new Intent(Intent.ACTION_VIEW);
419             intent.setData(Uri.parse(URLUtil.guessUrl(query)));
420         } else {
421             intent = new Intent(Intent.ACTION_WEB_SEARCH);
422             intent.putExtra(SearchManager.QUERY, query);
423         }
424
425         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
426         return intent;
427     }
428
429     @VisibleForTesting
430     Intent generateQueryGoogleIntent(String query) {
431         Intent intent;
432         Uri uri = new Uri.Builder()
433                 .scheme("https")
434                 .authority("www.google.com")
435                 .path("search")
436                 .appendQueryParameter("q", query)
437                 .build();
438
439         intent = new Intent(Intent.ACTION_VIEW);
440         intent.setData(uri);
441         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
442         return intent;
443     }
444
445     private void refreshApps(boolean firstDraw) {
446         refreshApps(null, firstDraw);
447     }
448
449     private void refreshApps(final String query, final boolean firstDraw) {
450         if(thread != null) thread.interrupt();
451
452         handler = U.newHandler();
453         thread = new Thread(() -> {
454             if(pm == null) pm = context.getPackageManager();
455
456             UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
457             LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
458
459             final List<UserHandle> userHandles = userManager.getUserProfiles();
460             final List<LauncherActivityInfo> unfilteredList = new ArrayList<>();
461
462             for(UserHandle handle : userHandles) {
463                 unfilteredList.addAll(launcherApps.getActivityList(null, handle));
464             }
465
466             final List<LauncherActivityInfo> topAppsList = new ArrayList<>();
467             final List<LauncherActivityInfo> allAppsList = new ArrayList<>();
468             final List<LauncherActivityInfo> list = new ArrayList<>();
469
470             TopApps topApps = TopApps.getInstance(context);
471             for(LauncherActivityInfo appInfo : unfilteredList) {
472                 String userSuffix = ":" + userManager.getSerialNumberForUser(appInfo.getUser());
473                 if(topApps.isTopApp(appInfo.getComponentName().flattenToString() + userSuffix)
474                         || topApps.isTopApp(appInfo.getComponentName().flattenToString())
475                         || topApps.isTopApp(appInfo.getName()))
476                     topAppsList.add(appInfo);
477             }
478
479             Blacklist blacklist = Blacklist.getInstance(context);
480             for(LauncherActivityInfo appInfo : unfilteredList) {
481                 String userSuffix = ":" + userManager.getSerialNumberForUser(appInfo.getUser());
482                 if(!(blacklist.isBlocked(appInfo.getComponentName().flattenToString() + userSuffix)
483                         || blacklist.isBlocked(appInfo.getComponentName().flattenToString())
484                         || blacklist.isBlocked(appInfo.getName()))
485                         && !(topApps.isTopApp(appInfo.getComponentName().flattenToString() + userSuffix)
486                         || topApps.isTopApp(appInfo.getComponentName().flattenToString())
487                         || topApps.isTopApp(appInfo.getName())))
488                     allAppsList.add(appInfo);
489             }
490
491             Collections.sort(topAppsList, comparator);
492             Collections.sort(allAppsList, comparator);
493
494             list.addAll(topAppsList);
495             list.addAll(allAppsList);
496
497             topAppsList.clear();
498             allAppsList.clear();
499
500             List<LauncherActivityInfo> queryList;
501             if(query == null)
502                 queryList = list;
503             else {
504                 queryList = new ArrayList<>();
505                 for(LauncherActivityInfo appInfo : list) {
506                     if(appInfo.getLabel().toString().toLowerCase().contains(query.toLowerCase()))
507                         queryList.add(appInfo);
508                 }
509             }
510
511             // Now that we've generated the list of apps,
512             // we need to determine if we need to redraw the start menu or not
513             boolean shouldRedrawStartMenu = false;
514             List<String> finalApplicationIds = new ArrayList<>();
515
516             if(query == null && !firstDraw) {
517                 for(LauncherActivityInfo appInfo : queryList) {
518                     finalApplicationIds.add(appInfo.getApplicationInfo().packageName);
519                 }
520
521                 if(finalApplicationIds.size() != currentStartMenuIds.size())
522                     shouldRedrawStartMenu = true;
523                 else {
524                     for(int i = 0; i < finalApplicationIds.size(); i++) {
525                         if(!finalApplicationIds.get(i).equals(currentStartMenuIds.get(i))) {
526                             shouldRedrawStartMenu = true;
527                             break;
528                         }
529                     }
530                 }
531             } else shouldRedrawStartMenu = true;
532
533             if(shouldRedrawStartMenu) {
534                 if(query == null) currentStartMenuIds = finalApplicationIds;
535
536                 final List<AppEntry> entries =
537                         generateAppEntries(context, userManager, pm, queryList);
538
539                 handler.post(() -> {
540                     String queryText = searchView.getQuery().toString();
541                     if(query == null && queryText.length() == 0
542                             || query != null && query.equals(queryText)) {
543
544                         if(firstDraw) {
545                             SharedPreferences pref = U.getSharedPreferences(context);
546                             if(pref.getString(PREF_START_MENU_LAYOUT, "grid").equals("grid")) {
547                                 startMenu.setNumColumns(context.getResources().getInteger(R.integer.tb_start_menu_columns));
548                                 adapter = new StartMenuAdapter(context, R.layout.tb_row_alt, entries);
549                             } else
550                                 adapter = new StartMenuAdapter(context, R.layout.tb_row, entries);
551
552                             startMenu.setAdapter(adapter);
553                         }
554
555                         int position = startMenu.getFirstVisiblePosition();
556
557                         if(!firstDraw && adapter != null)
558                             adapter.updateList(entries);
559
560                         startMenu.setSelection(position);
561
562                         if(adapter != null && adapter.getCount() > 0)
563                             textView.setText(null);
564                         else if(query != null)
565                             textView.setText(context.getString(Patterns.WEB_URL.matcher(query).matches() ? R.string.tb_press_enter_alt : R.string.tb_press_enter));
566                         else
567                             textView.setText(context.getString(R.string.tb_nothing_to_see_here));
568                     }
569                 });
570             }
571         });
572
573         thread.start();
574     }
575
576     @VisibleForTesting
577     List<AppEntry> generateAppEntries(Context context,
578                                       UserManager userManager,
579                                       PackageManager pm,
580                                       List<LauncherActivityInfo> queryList) {
581         final List<AppEntry> entries = new ArrayList<>();
582         Drawable defaultIcon = pm.getDefaultActivityIcon();
583         for(LauncherActivityInfo appInfo : queryList) {
584             // Attempt to work around frequently reported OutOfMemoryErrors
585             String label;
586             Drawable icon;
587
588             try {
589                 label = appInfo.getLabel().toString();
590                 icon = IconCache.getInstance(context).getIcon(context, pm, appInfo);
591             } catch (OutOfMemoryError e) {
592                 System.gc();
593
594                 label = appInfo.getApplicationInfo().packageName;
595                 icon = defaultIcon;
596             }
597
598             String packageName = appInfo.getApplicationInfo().packageName;
599             ComponentName componentName = new ComponentName(packageName, appInfo.getName());
600             AppEntry newEntry =
601                     new AppEntry(packageName, componentName.flattenToString(), label, icon, false);
602
603             newEntry.setUserId(userManager.getSerialNumberForUser(appInfo.getUser()));
604             entries.add(newEntry);
605         }
606         return entries;
607     }
608
609     private void toggleStartMenu() {
610         if(layout.getVisibility() == View.GONE)
611             showStartMenu();
612         else
613             hideStartMenu(true);
614     }
615
616     @TargetApi(Build.VERSION_CODES.N)
617     private void showStartMenu() {
618         if(layout.getVisibility() == View.GONE) {
619             layout.setOnClickListener(ocl);
620             layout.setVisibility(View.VISIBLE);
621
622             if(Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1 && !hasHardwareKeyboard)
623                 layout.setAlpha(1);
624
625             MenuHelper.getInstance().setStartMenuOpen(true);
626
627             U.sendBroadcast(context, ACTION_START_MENU_APPEARING);
628
629             boolean onHomeScreen = LauncherHelper.getInstance().isOnHomeScreen(context);
630             boolean inFreeformMode = FreeformHackHelper.getInstance().isInFreeformWorkspace();
631
632             if(!U.isChromeOs(context) && (!onHomeScreen || inFreeformMode)) {
633                 Class<?> clazz = inFreeformMode && !U.hasBrokenSetLaunchBoundsApi()
634                         ? InvisibleActivityAlt.class
635                         : InvisibleActivity.class;
636
637                 Intent intent = new Intent(context, clazz);
638                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
639                 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
640
641                 if(inFreeformMode) {
642                     if(clazz.equals(InvisibleActivity.class))
643                         U.startActivityLowerRight(context, intent);
644                     else if(clazz.equals(InvisibleActivityAlt.class))
645                         U.startActivityMaximized(context, intent);
646                 } else
647                     context.startActivity(intent);
648             }
649
650             EditText editText = searchView.findViewById(R.id.search_src_text);
651             if(searchView.getVisibility() == View.VISIBLE) {
652                 if(hasHardwareKeyboard) {
653                     searchView.setIconifiedByDefault(true);
654
655                     if(editText != null)
656                         editText.setShowSoftInputOnFocus(false);
657                 } else
658                     searchView.requestFocus();
659             }
660
661             refreshApps(false);
662
663             U.newHandler().postDelayed(() -> {
664                 if(Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1 || hasHardwareKeyboard)
665                     layout.setAlpha(1);
666
667                 if(hasHardwareKeyboard) {
668                     searchView.setIconifiedByDefault(false);
669                     if(editText != null)
670                         editText.setShowSoftInputOnFocus(true);
671
672                     searchView.requestFocus();
673                 }
674
675                 searchView.setOnQueryTextFocusChangeListener((view, b) -> {
676                     if(!hasHardwareKeyboard) {
677                         ViewGroup.LayoutParams params1 = startMenu.getLayoutParams();
678                         params1.height = context.getResources().getDimensionPixelSize(
679                                 b && isImeFixDisabled()
680                                         ? R.dimen.tb_start_menu_height_half
681                                         : R.dimen.tb_start_menu_height);
682                         startMenu.setLayoutParams(params1);
683                     }
684
685                     if(!b && !(U.isBlissOs(context) && Build.VERSION.SDK_INT == Build.VERSION_CODES.P)) {
686                         if(hasHardwareKeyboard && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
687                             hideStartMenu(true);
688                         } else {
689                             InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
690                             imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
691                         }
692                     }
693                 });
694
695                 InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
696                 imm.hideSoftInputFromWindow(layout.getWindowToken(), 0);
697             }, 100);
698         }
699     }
700
701     private void hideStartMenu(boolean shouldReset) {
702         if(layout.getVisibility() == View.VISIBLE) {
703             layout.setOnClickListener(null);
704             layout.setAlpha(0);
705
706             MenuHelper.getInstance().setStartMenuOpen(false);
707
708             U.sendBroadcast(context, ACTION_START_MENU_DISAPPEARING);
709
710             layout.postDelayed(() -> {
711                 layout.setVisibility(View.GONE);
712
713                 if(searchViewClicked || hasHardwareKeyboard) {
714                     if(!hasHardwareKeyboard)
715                         searchView.setQuery(null, false);
716
717                     searchView.setIconified(true);
718                 }
719
720                 searchView.setOnQueryTextFocusChangeListener(null);
721                 hasSubmittedQuery = false;
722
723                 if(shouldReset) {
724                     startMenu.smoothScrollBy(0, 0);
725                     startMenu.setSelection(0);
726                 }
727
728                 InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
729                 imm.hideSoftInputFromWindow(layout.getWindowToken(), 0);
730             }, 100);
731         }
732     }
733
734     @Override
735     public void onDestroyHost(UIHost host) {
736         if(layout != null)
737             try {
738                 host.removeView(layout);
739             } catch (IllegalArgumentException ignored) {}
740
741         U.unregisterReceiver(context, toggleReceiver);
742         U.unregisterReceiver(context, hideReceiver);
743         U.unregisterReceiver(context, hideReceiverNoReset);
744         U.unregisterReceiver(context, showSpaceReceiver);
745         U.unregisterReceiver(context, hideSpaceReceiver);
746         U.unregisterReceiver(context, resetReceiver);
747
748         U.sendBroadcast(context, ACTION_START_MENU_DISAPPEARING);
749     }
750
751     @TargetApi(Build.VERSION_CODES.M)
752     @Override
753     public void onRecreateHost(UIHost host) {
754         if(layout != null) {
755             try {
756                 host.removeView(layout);
757             } catch (IllegalArgumentException ignored) {}
758
759             if(U.canDrawOverlays(context))
760                 drawStartMenu(host);
761             else {
762                 SharedPreferences pref = U.getSharedPreferences(context);
763                 pref.edit().putBoolean(PREF_TASKBAR_ACTIVE, false).apply();
764
765                 host.terminate();
766             }
767         }
768     }
769
770     private void openContextMenu(final int[] location) {
771         hideStartMenu(false);
772
773         Bundle args = new Bundle();
774         args.putBoolean("launched_from_start_menu", true);
775         args.putBoolean("is_overflow_menu", true);
776         args.putInt("x", location[0]);
777         args.putInt("y", location[1]);
778
779         U.newHandler().postDelayed(() -> U.startContextMenuActivity(context, args), shouldDelay() ? 100 : 0);
780     }
781
782     private boolean shouldDelay() {
783         return U.hasFreeformSupport(context)
784                 && U.isFreeformModeEnabled(context)
785                 && !FreeformHackHelper.getInstance().isFreeformHackActive();
786     }
787 }