1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.taskbar;
17 
18 import static android.content.Context.RECEIVER_NOT_EXPORTED;
19 import static android.view.Display.DEFAULT_DISPLAY;
20 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
21 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
22 
23 import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
24 import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
25 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
26 import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
27 import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
28 import static com.android.launcher3.util.DisplayController.CHANGE_DESKTOP_MODE;
29 import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
30 import static com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING;
31 import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG;
32 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
33 import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
34 import static com.android.quickstep.util.SystemActionConstants.ACTION_SHOW_TASKBAR;
35 import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_TASKBAR;
36 
37 import android.annotation.SuppressLint;
38 import android.app.PendingIntent;
39 import android.content.ComponentCallbacks;
40 import android.content.Context;
41 import android.content.Intent;
42 import android.content.IntentFilter;
43 import android.content.pm.ActivityInfo;
44 import android.content.res.Configuration;
45 import android.hardware.display.DisplayManager;
46 import android.net.Uri;
47 import android.os.Handler;
48 import android.os.Trace;
49 import android.provider.Settings;
50 import android.util.Log;
51 import android.view.Display;
52 import android.view.MotionEvent;
53 import android.view.WindowManager;
54 import android.widget.FrameLayout;
55 
56 import androidx.annotation.NonNull;
57 import androidx.annotation.Nullable;
58 import androidx.annotation.VisibleForTesting;
59 
60 import com.android.launcher3.DeviceProfile;
61 import com.android.launcher3.Launcher;
62 import com.android.launcher3.LauncherAppState;
63 import com.android.launcher3.anim.AnimatorPlaybackController;
64 import com.android.launcher3.statemanager.StatefulActivity;
65 import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks;
66 import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider;
67 import com.android.launcher3.uioverrides.QuickstepLauncher;
68 import com.android.launcher3.util.DisplayController;
69 import com.android.launcher3.util.SettingsCache;
70 import com.android.launcher3.util.SimpleBroadcastReceiver;
71 import com.android.quickstep.AllAppsActionManager;
72 import com.android.quickstep.RecentsActivity;
73 import com.android.quickstep.SystemUiProxy;
74 import com.android.quickstep.util.AssistUtils;
75 import com.android.systemui.shared.system.QuickStepContract;
76 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
77 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
78 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
79 
80 import java.io.PrintWriter;
81 import java.util.StringJoiner;
82 
83 /**
84  * Class to manage taskbar lifecycle
85  */
86 public class TaskbarManager {
87     private static final String TAG = "TaskbarManager";
88     private static final boolean DEBUG = false;
89 
90     /**
91      * All the configurations which do not initiate taskbar recreation.
92      * This includes all the configurations defined in Launcher's manifest entry and
93      * ActivityController#filterConfigChanges
94      */
95     private static final int SKIP_RECREATE_CONFIG_CHANGES = ActivityInfo.CONFIG_WINDOW_CONFIGURATION
96             | ActivityInfo.CONFIG_KEYBOARD
97             | ActivityInfo.CONFIG_KEYBOARD_HIDDEN
98             | ActivityInfo.CONFIG_MCC
99             | ActivityInfo.CONFIG_MNC
100             | ActivityInfo.CONFIG_NAVIGATION
101             | ActivityInfo.CONFIG_ORIENTATION
102             | ActivityInfo.CONFIG_SCREEN_SIZE
103             | ActivityInfo.CONFIG_SCREEN_LAYOUT
104             | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
105 
106     private static final Uri USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(
107             Settings.Secure.USER_SETUP_COMPLETE);
108 
109     private static final Uri NAV_BAR_KIDS_MODE = Settings.Secure.getUriFor(
110             Settings.Secure.NAV_BAR_KIDS_MODE);
111 
112     private final Context mContext;
113     private final @Nullable Context mNavigationBarPanelContext;
114     private WindowManager mWindowManager;
115     private FrameLayout mTaskbarRootLayout;
116     private boolean mAddedWindow;
117     private final TaskbarNavButtonController mNavButtonController;
118     private final ComponentCallbacks mComponentCallbacks;
119 
120     private final SimpleBroadcastReceiver mShutdownReceiver =
121             new SimpleBroadcastReceiver(i -> destroyExistingTaskbar());
122 
123     // The source for this provider is set when Launcher is available
124     // We use 'non-destroyable' version here so the original provider won't be destroyed
125     // as it is tied to the activity lifecycle, not the taskbar lifecycle.
126     // It's destruction/creation will be managed by the activity.
127     private final ScopedUnfoldTransitionProgressProvider mUnfoldProgressProvider =
128             new NonDestroyableScopedUnfoldTransitionProgressProvider();
129 
130     private TaskbarActivityContext mTaskbarActivityContext;
131     private StatefulActivity mActivity;
132     /**
133      * Cache a copy here so we can initialize state whenever taskbar is recreated, since
134      * this class does not get re-initialized w/ new taskbars.
135      */
136     private final TaskbarSharedState mSharedState = new TaskbarSharedState();
137 
138     /**
139      * We use WindowManager's ComponentCallbacks() for internal UI changes (similar to an Activity)
140      * which comes via a different channel
141      */
142     private final RecreationListener mRecreationListener = new RecreationListener();
143 
144     private class RecreationListener implements DisplayController.DisplayInfoChangeListener {
145         @Override
onDisplayInfoChanged(Context context, DisplayController.Info info, int flags)146         public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
147             if ((flags & (CHANGE_DENSITY | CHANGE_NAVIGATION_MODE | CHANGE_DESKTOP_MODE
148                     | CHANGE_TASKBAR_PINNING)) != 0) {
149                 recreateTaskbar();
150             }
151         }
152     }
153     private final SettingsCache.OnChangeListener mOnSettingsChangeListener = c -> recreateTaskbar();
154 
155     private boolean mUserUnlocked = false;
156 
157     private final SimpleBroadcastReceiver mTaskbarBroadcastReceiver =
158             new SimpleBroadcastReceiver(this::showTaskbarFromBroadcast);
159 
160     private final AllAppsActionManager mAllAppsActionManager;
161 
162     private final Runnable mActivityOnDestroyCallback = new Runnable() {
163         @Override
164         public void run() {
165             if (mActivity != null) {
166                 mActivity.removeOnDeviceProfileChangeListener(
167                         mDebugActivityDeviceProfileChanged);
168                 Log.d(TASKBAR_NOT_DESTROYED_TAG,
169                         "unregistering activity lifecycle callbacks from "
170                                 + "onActivityDestroyed.");
171                 mActivity.removeEventCallback(EVENT_DESTROYED, this);
172             }
173             mActivity = null;
174             debugWhyTaskbarNotDestroyed("clearActivity");
175             if (mTaskbarActivityContext != null) {
176                 mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
177             }
178             mUnfoldProgressProvider.setSourceProvider(null);
179         }
180     };
181 
182     UnfoldTransitionProgressProvider.TransitionProgressListener mUnfoldTransitionProgressListener =
183             new UnfoldTransitionProgressProvider.TransitionProgressListener() {
184                 @Override
185                 public void onTransitionStarted() {
186                     Log.d(TASKBAR_NOT_DESTROYED_TAG,
187                             "fold/unfold transition started getting called.");
188                 }
189 
190                 @Override
191                 public void onTransitionProgress(float progress) {
192                     Log.d(TASKBAR_NOT_DESTROYED_TAG,
193                             "fold/unfold transition progress : " + progress);
194                 }
195 
196                 @Override
197                 public void onTransitionFinishing() {
198                     Log.d(TASKBAR_NOT_DESTROYED_TAG,
199                             "fold/unfold transition finishing getting called.");
200 
201                 }
202 
203                 @Override
204                 public void onTransitionFinished() {
205                     Log.d(TASKBAR_NOT_DESTROYED_TAG,
206                             "fold/unfold transition finished getting called.");
207 
208                 }
209             };
210 
211     @SuppressLint("WrongConstant")
TaskbarManager( Context context, AllAppsActionManager allAppsActionManager, TaskbarNavButtonCallbacks navCallbacks)212     public TaskbarManager(
213             Context context,
214             AllAppsActionManager allAppsActionManager,
215             TaskbarNavButtonCallbacks navCallbacks) {
216 
217         Display display =
218                 context.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
219         mContext = context.createWindowContext(display,
220                 ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL,
221                 null);
222         mAllAppsActionManager = allAppsActionManager;
223         mNavigationBarPanelContext = ENABLE_TASKBAR_NAVBAR_UNIFICATION
224                 ? context.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
225                 : null;
226         if (enableTaskbarNoRecreate()) {
227             mWindowManager = mContext.getSystemService(WindowManager.class);
228             mTaskbarRootLayout = new FrameLayout(mContext) {
229                 @Override
230                 public boolean dispatchTouchEvent(MotionEvent ev) {
231                     // The motion events can be outside the view bounds of task bar, and hence
232                     // manually dispatching them to the drag layer here.
233                     if (mTaskbarActivityContext != null
234                             && mTaskbarActivityContext.getDragLayer().isAttachedToWindow()) {
235                         return mTaskbarActivityContext.getDragLayer().dispatchTouchEvent(ev);
236                     }
237                     return super.dispatchTouchEvent(ev);
238                 }
239             };
240         }
241         mNavButtonController = new TaskbarNavButtonController(
242                 context,
243                 navCallbacks,
244                 SystemUiProxy.INSTANCE.get(mContext),
245                 new Handler(),
246                 AssistUtils.newInstance(mContext));
247         mComponentCallbacks = new ComponentCallbacks() {
248             private Configuration mOldConfig = mContext.getResources().getConfiguration();
249 
250             @Override
251             public void onConfigurationChanged(Configuration newConfig) {
252                 Trace.instantForTrack(Trace.TRACE_TAG_APP, "TaskbarManager",
253                         "onConfigurationChanged: " + newConfig);
254                 debugWhyTaskbarNotDestroyed(
255                         "TaskbarManager#mComponentCallbacks.onConfigurationChanged: " + newConfig);
256                 DeviceProfile dp = mUserUnlocked
257                         ? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext)
258                         : null;
259                 int configDiff = mOldConfig.diff(newConfig) & ~SKIP_RECREATE_CONFIG_CHANGES;
260 
261                 if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0) {
262                     // Only recreate for theme changes, not other UI mode changes such as docking.
263                     int oldUiNightMode = (mOldConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
264                     int newUiNightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
265                     if (oldUiNightMode == newUiNightMode) {
266                         configDiff &= ~ActivityInfo.CONFIG_UI_MODE;
267                     }
268                 }
269 
270                 debugWhyTaskbarNotDestroyed("ComponentCallbacks#onConfigurationChanged() "
271                         + "configDiff=" + Configuration.configurationDiffToString(configDiff));
272                 if (configDiff != 0 || mTaskbarActivityContext == null) {
273                     recreateTaskbar();
274                 } else {
275                     // Config change might be handled without re-creating the taskbar
276                     if (dp != null && !isTaskbarEnabled(dp)) {
277                         destroyExistingTaskbar();
278                     } else {
279                         if (dp != null && isTaskbarEnabled(dp)) {
280                             if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
281                                 // Re-initialize for screen size change? Should this be done
282                                 // by looking at screen-size change flag in configDiff in the
283                                 // block above?
284                                 recreateTaskbar();
285                             } else {
286                                 mTaskbarActivityContext.updateDeviceProfile(dp);
287                             }
288                         }
289                         mTaskbarActivityContext.onConfigurationChanged(configDiff);
290                     }
291                 }
292                 mOldConfig = new Configuration(newConfig);
293                 // reset taskbar was pinned value, so we don't automatically unstash taskbar upon
294                 // user unfolding the device.
295                 mSharedState.setTaskbarWasPinned(false);
296             }
297 
298             @Override
299             public void onLowMemory() { }
300         };
301         SettingsCache.INSTANCE.get(mContext)
302                 .register(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
303         SettingsCache.INSTANCE.get(mContext)
304                 .register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
305         Log.d(TASKBAR_NOT_DESTROYED_TAG, "registering component callbacks from constructor.");
306         mContext.registerComponentCallbacks(mComponentCallbacks);
307         mShutdownReceiver.register(mContext, Intent.ACTION_SHUTDOWN);
308         UI_HELPER_EXECUTOR.execute(() -> {
309             mSharedState.taskbarSystemActionPendingIntent = PendingIntent.getBroadcast(
310                     mContext,
311                     SYSTEM_ACTION_ID_TASKBAR,
312                     new Intent(ACTION_SHOW_TASKBAR).setPackage(mContext.getPackageName()),
313                     PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
314             mContext.registerReceiver(
315                     mTaskbarBroadcastReceiver,
316                     new IntentFilter(ACTION_SHOW_TASKBAR),
317                     RECEIVER_NOT_EXPORTED);
318         });
319 
320         debugWhyTaskbarNotDestroyed("TaskbarManager created");
321         recreateTaskbar();
322     }
323 
destroyExistingTaskbar()324     private void destroyExistingTaskbar() {
325         debugWhyTaskbarNotDestroyed("destroyExistingTaskbar: " + mTaskbarActivityContext);
326         if (mTaskbarActivityContext != null) {
327             mTaskbarActivityContext.onDestroy();
328             if (!ENABLE_TASKBAR_NAVBAR_UNIFICATION || enableTaskbarNoRecreate()) {
329                 mTaskbarActivityContext = null;
330             }
331         }
332         DeviceProfile dp = mUserUnlocked ?
333                 LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
334         if (dp == null || !isTaskbarEnabled(dp)) {
335             removeTaskbarRootViewFromWindow();
336         }
337     }
338 
339     /**
340      * Show Taskbar upon receiving broadcast
341      */
showTaskbarFromBroadcast(Intent intent)342     private void showTaskbarFromBroadcast(Intent intent) {
343         if (ACTION_SHOW_TASKBAR.equals(intent.getAction()) && mTaskbarActivityContext != null) {
344             mTaskbarActivityContext.showTaskbarFromBroadcast();
345         }
346     }
347 
348     /**
349      * Toggles All Apps for Taskbar or Launcher depending on the current state.
350      */
toggleAllApps()351     public void toggleAllApps() {
352         if (mTaskbarActivityContext == null || mTaskbarActivityContext.canToggleHomeAllApps()) {
353             // Home All Apps should be toggled from this class, because the controllers are not
354             // initialized when Taskbar is disabled (i.e. TaskbarActivityContext is null).
355             if (mActivity instanceof Launcher l) l.toggleAllAppsSearch();
356         } else {
357             mTaskbarActivityContext.toggleAllAppsSearch();
358         }
359     }
360 
361     /**
362      * Displays a frame of the first Launcher reveal animation.
363      *
364      * This should be used to run a first Launcher reveal animation whose progress matches a swipe
365      * progress.
366      */
createLauncherStartFromSuwAnim(int duration)367     public AnimatorPlaybackController createLauncherStartFromSuwAnim(int duration) {
368         return mTaskbarActivityContext == null
369                 ? null : mTaskbarActivityContext.createLauncherStartFromSuwAnim(duration);
370     }
371 
372     /**
373      * Called when the user is unlocked
374      */
onUserUnlocked()375     public void onUserUnlocked() {
376         mUserUnlocked = true;
377         DisplayController.INSTANCE.get(mContext).addChangeListener(mRecreationListener);
378         recreateTaskbar();
379         addTaskbarRootViewToWindow();
380     }
381 
382     /**
383      * Sets a {@link StatefulActivity} to act as taskbar callback
384      */
setActivity(@onNull StatefulActivity activity)385     public void setActivity(@NonNull StatefulActivity activity) {
386         if (mActivity == activity) {
387             return;
388         }
389         removeActivityCallbacksAndListeners();
390         mActivity = activity;
391         debugWhyTaskbarNotDestroyed("Set mActivity=" + mActivity);
392         mActivity.addOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
393         Log.d(TASKBAR_NOT_DESTROYED_TAG,
394                 "registering activity lifecycle callbacks from setActivity().");
395         mActivity.addEventCallback(EVENT_DESTROYED, mActivityOnDestroyCallback);
396         UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
397                 getUnfoldTransitionProgressProviderForActivity(activity);
398         if (unfoldTransitionProgressProvider != null) {
399             unfoldTransitionProgressProvider.addCallback(mUnfoldTransitionProgressListener);
400         }
401         mUnfoldProgressProvider.setSourceProvider(unfoldTransitionProgressProvider);
402 
403         if (mTaskbarActivityContext != null) {
404             mTaskbarActivityContext.setUIController(
405                     createTaskbarUIControllerForActivity(mActivity));
406         }
407     }
408 
409     /**
410      * Returns an {@link UnfoldTransitionProgressProvider} to use while the given StatefulActivity
411      * is active.
412      */
getUnfoldTransitionProgressProviderForActivity( StatefulActivity activity)413     private UnfoldTransitionProgressProvider getUnfoldTransitionProgressProviderForActivity(
414             StatefulActivity activity) {
415         if (!enableUnfoldStateAnimation()) {
416             if (activity instanceof QuickstepLauncher ql) {
417                 return ql.getUnfoldTransitionProgressProvider();
418             }
419         } else {
420             return SystemUiProxy.INSTANCE.get(mContext).getUnfoldTransitionProvider();
421         }
422         return null;
423     }
424 
425     /**
426      * Creates a {@link TaskbarUIController} to use while the given StatefulActivity is active.
427      */
createTaskbarUIControllerForActivity(StatefulActivity activity)428     private TaskbarUIController createTaskbarUIControllerForActivity(StatefulActivity activity) {
429         if (activity instanceof QuickstepLauncher) {
430             return new LauncherTaskbarUIController((QuickstepLauncher) activity);
431         }
432         if (activity instanceof RecentsActivity) {
433             return new FallbackTaskbarUIController((RecentsActivity) activity);
434         }
435         return TaskbarUIController.DEFAULT;
436     }
437 
438     /**
439      * This method is called multiple times (ex. initial init, then when user unlocks) in which case
440      * we fully want to destroy an existing taskbar and create a new one.
441      * In other case (folding/unfolding) we don't need to remove and add window.
442      */
443     @VisibleForTesting
recreateTaskbar()444     public synchronized void recreateTaskbar() {
445         Trace.beginSection("recreateTaskbar");
446         try {
447             DeviceProfile dp = mUserUnlocked ?
448                 LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
449 
450             // All Apps action is unrelated to navbar unification, so we only need to check DP.
451             final boolean isLargeScreenTaskbar = dp != null && dp.isTaskbarPresent;
452             mAllAppsActionManager.setTaskbarPresent(isLargeScreenTaskbar);
453 
454             destroyExistingTaskbar();
455 
456             boolean isTaskbarEnabled = dp != null && isTaskbarEnabled(dp);
457             debugWhyTaskbarNotDestroyed("recreateTaskbar: isTaskbarEnabled=" + isTaskbarEnabled
458                 + " [dp != null (i.e. mUserUnlocked)]=" + (dp != null)
459                 + " FLAG_HIDE_NAVBAR_WINDOW=" + ENABLE_TASKBAR_NAVBAR_UNIFICATION
460                 + " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent));
461             if (!isTaskbarEnabled) {
462                 SystemUiProxy.INSTANCE.get(mContext)
463                     .notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
464                 return;
465             }
466 
467             if (enableTaskbarNoRecreate() || mTaskbarActivityContext == null) {
468                 mTaskbarActivityContext = new TaskbarActivityContext(mContext,
469                         mNavigationBarPanelContext, dp, mNavButtonController,
470                         mUnfoldProgressProvider);
471             } else {
472                 mTaskbarActivityContext.updateDeviceProfile(dp);
473             }
474             mSharedState.startTaskbarVariantIsTransient =
475                     DisplayController.isTransientTaskbar(mTaskbarActivityContext);
476             mSharedState.allAppsVisible = mSharedState.allAppsVisible && isLargeScreenTaskbar;
477             mTaskbarActivityContext.init(mSharedState);
478 
479             if (mActivity != null) {
480                 mTaskbarActivityContext.setUIController(
481                     createTaskbarUIControllerForActivity(mActivity));
482             }
483 
484             if (enableTaskbarNoRecreate()) {
485                 addTaskbarRootViewToWindow();
486                 mTaskbarRootLayout.removeAllViews();
487                 mTaskbarRootLayout.addView(mTaskbarActivityContext.getDragLayer());
488                 mTaskbarActivityContext.notifyUpdateLayoutParams();
489             }
490         } finally {
491             Trace.endSection();
492         }
493     }
494 
onSystemUiFlagsChanged(@ystemUiStateFlags long systemUiStateFlags)495     public void onSystemUiFlagsChanged(@SystemUiStateFlags long systemUiStateFlags) {
496         if (DEBUG) {
497             Log.d(TAG, "SysUI flags changed: " + formatFlagChange(systemUiStateFlags,
498                     mSharedState.sysuiStateFlags, QuickStepContract::getSystemUiStateString));
499         }
500         mSharedState.sysuiStateFlags = systemUiStateFlags;
501         if (mTaskbarActivityContext != null) {
502             mTaskbarActivityContext.updateSysuiStateFlags(systemUiStateFlags, false /* fromInit */);
503         }
504     }
505 
onLongPressHomeEnabled(boolean assistantLongPressEnabled)506     public void onLongPressHomeEnabled(boolean assistantLongPressEnabled) {
507         if (mNavButtonController != null) {
508             mNavButtonController.setAssistantLongPressEnabled(assistantLongPressEnabled);
509         }
510     }
511 
512     /**
513      * Sets the flag indicating setup UI is visible
514      */
setSetupUIVisible(boolean isVisible)515     public void setSetupUIVisible(boolean isVisible) {
516         mSharedState.setupUIVisible = isVisible;
517         if (mTaskbarActivityContext != null) {
518             mTaskbarActivityContext.setSetupUIVisible(isVisible);
519         }
520     }
521 
isTaskbarEnabled(DeviceProfile deviceProfile)522     private boolean isTaskbarEnabled(DeviceProfile deviceProfile) {
523         return ENABLE_TASKBAR_NAVBAR_UNIFICATION || deviceProfile.isTaskbarPresent;
524     }
525 
onRotationProposal(int rotation, boolean isValid)526     public void onRotationProposal(int rotation, boolean isValid) {
527         if (mTaskbarActivityContext != null) {
528             mTaskbarActivityContext.onRotationProposal(rotation, isValid);
529         }
530     }
531 
disableNavBarElements(int displayId, int state1, int state2, boolean animate)532     public void disableNavBarElements(int displayId, int state1, int state2, boolean animate) {
533         mSharedState.disableNavBarDisplayId = displayId;
534         mSharedState.disableNavBarState1 = state1;
535         mSharedState.disableNavBarState2 = state2;
536         if (mTaskbarActivityContext != null) {
537             mTaskbarActivityContext.disableNavBarElements(displayId, state1, state2, animate);
538         }
539     }
540 
onSystemBarAttributesChanged(int displayId, int behavior)541     public void onSystemBarAttributesChanged(int displayId, int behavior) {
542         mSharedState.systemBarAttrsDisplayId = displayId;
543         mSharedState.systemBarAttrsBehavior = behavior;
544         if (mTaskbarActivityContext != null) {
545             mTaskbarActivityContext.onSystemBarAttributesChanged(displayId, behavior);
546         }
547     }
548 
onNavButtonsDarkIntensityChanged(float darkIntensity)549     public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
550         mSharedState.navButtonsDarkIntensity = darkIntensity;
551         if (mTaskbarActivityContext != null) {
552             mTaskbarActivityContext.onNavButtonsDarkIntensityChanged(darkIntensity);
553         }
554     }
555 
onNavigationBarLumaSamplingEnabled(int displayId, boolean enable)556     public void onNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
557         mSharedState.mLumaSamplingDisplayId = displayId;
558         mSharedState.mIsLumaSamplingEnabled = enable;
559         if (mTaskbarActivityContext != null) {
560             mTaskbarActivityContext.onNavigationBarLumaSamplingEnabled(displayId, enable);
561         }
562     }
563 
removeActivityCallbacksAndListeners()564     private void removeActivityCallbacksAndListeners() {
565         if (mActivity != null) {
566             mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
567             Log.d(TASKBAR_NOT_DESTROYED_TAG,
568                     "unregistering activity lifecycle callbacks from "
569                             + "removeActivityCallbackAndListeners().");
570             mActivity.removeEventCallback(EVENT_DESTROYED, mActivityOnDestroyCallback);
571             UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
572                     getUnfoldTransitionProgressProviderForActivity(mActivity);
573             if (unfoldTransitionProgressProvider != null) {
574                 unfoldTransitionProgressProvider.removeCallback(mUnfoldTransitionProgressListener);
575             }
576         }
577     }
578 
579     /**
580      * Called when the manager is no longer needed
581      */
destroy()582     public void destroy() {
583         debugWhyTaskbarNotDestroyed("TaskbarManager#destroy()");
584         removeActivityCallbacksAndListeners();
585         UI_HELPER_EXECUTOR.execute(
586                 () -> mTaskbarBroadcastReceiver.unregisterReceiverSafely(mContext));
587         destroyExistingTaskbar();
588         removeTaskbarRootViewFromWindow();
589         if (mUserUnlocked) {
590             DisplayController.INSTANCE.get(mContext).removeChangeListener(mRecreationListener);
591         }
592         SettingsCache.INSTANCE.get(mContext)
593                 .unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
594         SettingsCache.INSTANCE.get(mContext)
595                 .unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
596         Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
597         mContext.unregisterComponentCallbacks(mComponentCallbacks);
598         mContext.unregisterReceiver(mShutdownReceiver);
599     }
600 
getCurrentActivityContext()601     public @Nullable TaskbarActivityContext getCurrentActivityContext() {
602         return mTaskbarActivityContext;
603     }
604 
dumpLogs(String prefix, PrintWriter pw)605     public void dumpLogs(String prefix, PrintWriter pw) {
606         pw.println(prefix + "TaskbarManager:");
607         if (mTaskbarActivityContext == null) {
608             pw.println(prefix + "\tTaskbarActivityContext: null");
609         } else {
610             mTaskbarActivityContext.dumpLogs(prefix + "\t", pw);
611         }
612     }
613 
614     @VisibleForTesting
addTaskbarRootViewToWindow()615     void addTaskbarRootViewToWindow() {
616         if (enableTaskbarNoRecreate() && !mAddedWindow && mTaskbarActivityContext != null) {
617             mWindowManager.addView(mTaskbarRootLayout,
618                     mTaskbarActivityContext.getWindowLayoutParams());
619             mAddedWindow = true;
620         }
621     }
622 
623     @VisibleForTesting
removeTaskbarRootViewFromWindow()624     void removeTaskbarRootViewFromWindow() {
625         if (enableTaskbarNoRecreate() && mAddedWindow) {
626             mWindowManager.removeViewImmediate(mTaskbarRootLayout);
627             mAddedWindow = false;
628         }
629     }
630 
631     /** Temp logs for b/254119092. */
debugWhyTaskbarNotDestroyed(String debugReason)632     public void debugWhyTaskbarNotDestroyed(String debugReason) {
633         StringJoiner log = new StringJoiner("\n");
634         log.add(debugReason);
635 
636         boolean activityTaskbarPresent = mActivity != null
637                 && mActivity.getDeviceProfile().isTaskbarPresent;
638         boolean contextTaskbarPresent = mUserUnlocked
639                 && LauncherAppState.getIDP(mContext).getDeviceProfile(mContext).isTaskbarPresent;
640         if (activityTaskbarPresent == contextTaskbarPresent) {
641             log.add("mActivity and mContext agree taskbarIsPresent=" + contextTaskbarPresent);
642             Log.d(TASKBAR_NOT_DESTROYED_TAG, log.toString());
643             return;
644         }
645 
646         log.add("mActivity and mContext device profiles have different values, add more logs.");
647 
648         log.add("\tmActivity logs:");
649         log.add("\t\tmActivity=" + mActivity);
650         if (mActivity != null) {
651             log.add("\t\tmActivity.getResources().getConfiguration()="
652                     + mActivity.getResources().getConfiguration());
653             log.add("\t\tmActivity.getDeviceProfile().isTaskbarPresent="
654                     + activityTaskbarPresent);
655         }
656         log.add("\tmContext logs:");
657         log.add("\t\tmContext=" + mContext);
658         log.add("\t\tmContext.getResources().getConfiguration()="
659                 + mContext.getResources().getConfiguration());
660         if (mUserUnlocked) {
661             log.add("\t\tLauncherAppState.getIDP().getDeviceProfile(mContext).isTaskbarPresent="
662                     + contextTaskbarPresent);
663         } else {
664             log.add("\t\tCouldn't get DeviceProfile because !mUserUnlocked");
665         }
666 
667         Log.d(TASKBAR_NOT_DESTROYED_TAG, log.toString());
668     }
669 
670     private final DeviceProfile.OnDeviceProfileChangeListener mDebugActivityDeviceProfileChanged =
671             dp -> debugWhyTaskbarNotDestroyed("mActivity onDeviceProfileChanged");
672 }
673