1 /*
2  * Copyright (C) 2022 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 
17 package com.android.systemui.shade;
18 
19 import android.content.ComponentCallbacks2;
20 import android.os.Looper;
21 import android.util.Log;
22 import android.view.MotionEvent;
23 import android.view.ViewTreeObserver;
24 import android.view.WindowManager;
25 import android.view.WindowManagerGlobal;
26 
27 import com.android.systemui.assist.AssistManager;
28 import com.android.systemui.dagger.SysUISingleton;
29 import com.android.systemui.dagger.qualifiers.Main;
30 import com.android.systemui.log.LogBuffer;
31 import com.android.systemui.log.dagger.ShadeTouchLog;
32 import com.android.systemui.plugins.statusbar.StatusBarStateController;
33 import com.android.systemui.statusbar.CommandQueue;
34 import com.android.systemui.statusbar.NotificationPresenter;
35 import com.android.systemui.statusbar.NotificationShadeWindowController;
36 import com.android.systemui.statusbar.StatusBarState;
37 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
38 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
39 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
40 import com.android.systemui.statusbar.policy.KeyguardStateController;
41 import com.android.systemui.statusbar.window.StatusBarWindowController;
42 
43 import dagger.Lazy;
44 
45 import java.util.ArrayList;
46 import java.util.concurrent.Executor;
47 
48 import javax.inject.Inject;
49 
50 /** An implementation of {@link ShadeController}. */
51 @SysUISingleton
52 public final class ShadeControllerImpl implements ShadeController {
53 
54     private static final String TAG = "ShadeControllerImpl";
55     private static final boolean SPEW = false;
56 
57     private final int mDisplayId;
58 
59     private final CommandQueue mCommandQueue;
60     private final Executor mMainExecutor;
61     private final LogBuffer mTouchLog;
62     private final KeyguardStateController mKeyguardStateController;
63     private final NotificationShadeWindowController mNotificationShadeWindowController;
64     private final StatusBarStateController mStatusBarStateController;
65     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
66     private final StatusBarWindowController mStatusBarWindowController;
67     private final DeviceProvisionedController mDeviceProvisionedController;
68 
69     private final Lazy<ShadeViewController> mShadeViewControllerLazy;
70     private final Lazy<AssistManager> mAssistManagerLazy;
71     private final Lazy<NotificationGutsManager> mGutsManager;
72 
73     private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
74 
75     private boolean mExpandedVisible;
76 
77     private NotificationPresenter mPresenter;
78     private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
79     private ShadeVisibilityListener mShadeVisibilityListener;
80 
81     @Inject
ShadeControllerImpl( CommandQueue commandQueue, @Main Executor mainExecutor, @ShadeTouchLog LogBuffer touchLog, KeyguardStateController keyguardStateController, StatusBarStateController statusBarStateController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, StatusBarWindowController statusBarWindowController, DeviceProvisionedController deviceProvisionedController, NotificationShadeWindowController notificationShadeWindowController, WindowManager windowManager, Lazy<ShadeViewController> shadeViewControllerLazy, Lazy<AssistManager> assistManagerLazy, Lazy<NotificationGutsManager> gutsManager )82     public ShadeControllerImpl(
83             CommandQueue commandQueue,
84             @Main Executor mainExecutor,
85             @ShadeTouchLog LogBuffer touchLog,
86             KeyguardStateController keyguardStateController,
87             StatusBarStateController statusBarStateController,
88             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
89             StatusBarWindowController statusBarWindowController,
90             DeviceProvisionedController deviceProvisionedController,
91             NotificationShadeWindowController notificationShadeWindowController,
92             WindowManager windowManager,
93             Lazy<ShadeViewController> shadeViewControllerLazy,
94             Lazy<AssistManager> assistManagerLazy,
95             Lazy<NotificationGutsManager> gutsManager
96     ) {
97         mCommandQueue = commandQueue;
98         mMainExecutor = mainExecutor;
99         mTouchLog = touchLog;
100         mShadeViewControllerLazy = shadeViewControllerLazy;
101         mStatusBarStateController = statusBarStateController;
102         mStatusBarWindowController = statusBarWindowController;
103         mDeviceProvisionedController = deviceProvisionedController;
104         mGutsManager = gutsManager;
105         mNotificationShadeWindowController = notificationShadeWindowController;
106         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
107         mDisplayId = windowManager.getDefaultDisplay().getDisplayId();
108         mKeyguardStateController = keyguardStateController;
109         mAssistManagerLazy = assistManagerLazy;
110     }
111 
112     @Override
instantExpandShade()113     public void instantExpandShade() {
114         // Make our window larger and the panel expanded.
115         makeExpandedVisible(true /* force */);
116         getShadeViewController().expand(false /* animate */);
117         mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
118     }
119 
120     @Override
animateCollapseShade(int flags, boolean force, boolean delayed, float speedUpFactor)121     public void animateCollapseShade(int flags, boolean force, boolean delayed,
122             float speedUpFactor) {
123         if (!force && mStatusBarStateController.getState() != StatusBarState.SHADE) {
124             runPostCollapseRunnables();
125             return;
126         }
127         if (SPEW) {
128             Log.d(TAG,
129                     "animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags);
130         }
131         if (getNotificationShadeWindowView() != null
132                 && getShadeViewController().canBeCollapsed()
133                 && (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) {
134             // release focus immediately to kick off focus change transition
135             mNotificationShadeWindowController.setNotificationShadeFocusable(false);
136 
137             mNotificationShadeWindowViewController.cancelExpandHelper();
138             getShadeViewController().collapse(true, delayed, speedUpFactor);
139         }
140     }
141 
142     @Override
animateExpandShade()143     public void animateExpandShade() {
144         if (!mCommandQueue.panelsEnabled()) {
145             return;
146         }
147         getShadeViewController().expandToNotifications();
148     }
149 
150     @Override
animateExpandQs()151     public void animateExpandQs() {
152         if (!mCommandQueue.panelsEnabled()) {
153             return;
154         }
155         // Settings are not available in setup
156         if (!mDeviceProvisionedController.isCurrentUserSetup()) return;
157 
158         getShadeViewController().expandToQs();
159     }
160 
161     @Override
closeShadeIfOpen()162     public boolean closeShadeIfOpen() {
163         if (!getShadeViewController().isFullyCollapsed()) {
164             mCommandQueue.animateCollapsePanels(
165                     CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
166             notifyVisibilityChanged(false);
167             mAssistManagerLazy.get().hideAssist();
168         }
169         return false;
170     }
171 
172     @Override
isKeyguard()173     public boolean isKeyguard() {
174         return mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
175     }
176 
177     @Override
isShadeFullyOpen()178     public boolean isShadeFullyOpen() {
179         return getShadeViewController().isShadeFullyExpanded();
180     }
181 
182     @Override
isExpandingOrCollapsing()183     public boolean isExpandingOrCollapsing() {
184         return getShadeViewController().isExpandingOrCollapsing();
185     }
186     @Override
postAnimateCollapseShade()187     public void postAnimateCollapseShade() {
188         mMainExecutor.execute(this::animateCollapseShade);
189     }
190 
191     @Override
postAnimateForceCollapseShade()192     public void postAnimateForceCollapseShade() {
193         mMainExecutor.execute(this::animateCollapseShadeForced);
194     }
195 
196     @Override
postAnimateExpandQs()197     public void postAnimateExpandQs() {
198         mMainExecutor.execute(this::animateExpandQs);
199     }
200 
201     @Override
postOnShadeExpanded(Runnable executable)202     public void postOnShadeExpanded(Runnable executable) {
203         getShadeViewController().addOnGlobalLayoutListener(
204                 new ViewTreeObserver.OnGlobalLayoutListener() {
205                     @Override
206                     public void onGlobalLayout() {
207                         if (getNotificationShadeWindowView().isVisibleToUser()) {
208                             getShadeViewController().removeOnGlobalLayoutListener(this);
209                             getShadeViewController().postToView(executable);
210                         }
211                     }
212                 });
213     }
214 
215     @Override
addPostCollapseAction(Runnable action)216     public void addPostCollapseAction(Runnable action) {
217         mPostCollapseRunnables.add(action);
218     }
219 
220     @Override
runPostCollapseRunnables()221     public void runPostCollapseRunnables() {
222         ArrayList<Runnable> clonedList = new ArrayList<>(mPostCollapseRunnables);
223         mPostCollapseRunnables.clear();
224         int size = clonedList.size();
225         for (int i = 0; i < size; i++) {
226             clonedList.get(i).run();
227         }
228         mStatusBarKeyguardViewManager.readyForKeyguardDone();
229     }
230 
231     @Override
collapseShade()232     public boolean collapseShade() {
233         if (!getShadeViewController().isFullyCollapsed()) {
234             // close the shade if it was open
235             animateCollapseShadeForcedDelayed();
236             notifyVisibilityChanged(false);
237 
238             return true;
239         } else {
240             return false;
241         }
242     }
243 
244     @Override
collapseShade(boolean animate)245     public void collapseShade(boolean animate) {
246         if (animate) {
247             boolean willCollapse = collapseShade();
248             if (!willCollapse) {
249                 runPostCollapseRunnables();
250             }
251         } else if (!mPresenter.isPresenterFullyCollapsed()) {
252             instantCollapseShade();
253             notifyVisibilityChanged(false);
254         } else {
255             runPostCollapseRunnables();
256         }
257     }
258 
259     @Override
cancelExpansionAndCollapseShade()260     public void cancelExpansionAndCollapseShade() {
261         if (getShadeViewController().isTracking()) {
262             mNotificationShadeWindowViewController.cancelCurrentTouch();
263         }
264         if (getShadeViewController().isPanelExpanded()
265                 && mStatusBarStateController.getState() == StatusBarState.SHADE) {
266             animateCollapseShade();
267         }
268     }
269 
270     @Override
collapseOnMainThread()271     public void collapseOnMainThread() {
272         if (Looper.getMainLooper().isCurrentThread()) {
273             collapseShade();
274         } else {
275             mMainExecutor.execute(this::collapseShade);
276         }
277     }
278 
279     @Override
onStatusBarTouch(MotionEvent event)280     public void onStatusBarTouch(MotionEvent event) {
281         if (event.getAction() == MotionEvent.ACTION_UP) {
282             if (mExpandedVisible) {
283                 animateCollapseShade();
284             }
285         }
286     }
287 
onClosingFinished()288     private void onClosingFinished() {
289         runPostCollapseRunnables();
290         if (!mPresenter.isPresenterFullyCollapsed()) {
291             // if we set it not to be focusable when collapsing, we have to undo it when we aborted
292             // the closing
293             mNotificationShadeWindowController.setNotificationShadeFocusable(true);
294         }
295     }
296 
297     @Override
onLaunchAnimationCancelled(boolean isLaunchForActivity)298     public void onLaunchAnimationCancelled(boolean isLaunchForActivity) {
299         if (mPresenter.isPresenterFullyCollapsed()
300                 && !mPresenter.isCollapsing()
301                 && isLaunchForActivity) {
302             onClosingFinished();
303         } else {
304             collapseShade(true /* animate */);
305         }
306     }
307 
308     @Override
onLaunchAnimationEnd(boolean launchIsFullScreen)309     public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
310         if (!mPresenter.isCollapsing()) {
311             onClosingFinished();
312         }
313         if (launchIsFullScreen) {
314             instantCollapseShade();
315         }
316     }
317 
318     @Override
instantCollapseShade()319     public void instantCollapseShade() {
320         getShadeViewController().instantCollapse();
321         runPostCollapseRunnables();
322     }
323 
324     @Override
makeExpandedVisible(boolean force)325     public void makeExpandedVisible(boolean force) {
326         if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
327         if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
328             return;
329         }
330 
331         mExpandedVisible = true;
332 
333         // Expand the window to encompass the full screen in anticipation of the drag.
334         // It's only possible to do atomically because the status bar is at the top of the screen!
335         mNotificationShadeWindowController.setPanelVisible(true);
336 
337         notifyVisibilityChanged(true);
338         mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */);
339         notifyExpandedVisibleChanged(true);
340     }
341 
342     @Override
makeExpandedInvisible()343     public void makeExpandedInvisible() {
344         if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible);
345 
346         if (!mExpandedVisible || getNotificationShadeWindowView() == null) {
347             return;
348         }
349 
350         // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
351         getShadeViewController().collapse(false, false, 1.0f);
352 
353         mExpandedVisible = false;
354         notifyVisibilityChanged(false);
355 
356         // Update the visibility of notification shade and status bar window.
357         mNotificationShadeWindowController.setPanelVisible(false);
358         mStatusBarWindowController.setForceStatusBarVisible(false);
359 
360         // Close any guts that might be visible
361         mGutsManager.get().closeAndSaveGuts(
362                 true /* removeLeavebehind */,
363                 true /* force */,
364                 true /* removeControls */,
365                 -1 /* x */,
366                 -1 /* y */,
367                 true /* resetMenu */);
368 
369         runPostCollapseRunnables();
370         notifyExpandedVisibleChanged(false);
371         mCommandQueue.recomputeDisableFlags(
372                 mDisplayId,
373                 getShadeViewController().shouldHideStatusBarIconsWhenExpanded());
374 
375         // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
376         // the bouncer appear animation.
377         if (!mKeyguardStateController.isShowing()) {
378             WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
379         }
380     }
381 
382     @Override
isExpandedVisible()383     public boolean isExpandedVisible() {
384         return mExpandedVisible;
385     }
386 
387     @Override
setVisibilityListener(ShadeVisibilityListener listener)388     public void setVisibilityListener(ShadeVisibilityListener listener) {
389         mShadeVisibilityListener = listener;
390     }
391 
notifyVisibilityChanged(boolean visible)392     private void notifyVisibilityChanged(boolean visible) {
393         mShadeVisibilityListener.visibilityChanged(visible);
394     }
395 
notifyExpandedVisibleChanged(boolean expandedVisible)396     private void notifyExpandedVisibleChanged(boolean expandedVisible) {
397         mShadeVisibilityListener.expandedVisibleChanged(expandedVisible);
398     }
399 
400     @Override
setNotificationPresenter(NotificationPresenter presenter)401     public void setNotificationPresenter(NotificationPresenter presenter) {
402         mPresenter = presenter;
403     }
404 
405     @Override
setNotificationShadeWindowViewController( NotificationShadeWindowViewController controller)406     public void setNotificationShadeWindowViewController(
407             NotificationShadeWindowViewController controller) {
408         mNotificationShadeWindowViewController = controller;
409     }
410 
getNotificationShadeWindowView()411     private NotificationShadeWindowView getNotificationShadeWindowView() {
412         return mNotificationShadeWindowViewController.getView();
413     }
414 
getShadeViewController()415     private ShadeViewController getShadeViewController() {
416         return mShadeViewControllerLazy.get();
417     }
418 
419     @Override
start()420     public void start() {
421         TouchLogger.logTouchesTo(mTouchLog);
422         getShadeViewController().setTrackingStartedListener(this::runPostCollapseRunnables);
423         getShadeViewController().setOpenCloseListener(
424                 new OpenCloseListener() {
425                     @Override
426                     public void onClosingFinished() {
427                         ShadeControllerImpl.this.onClosingFinished();
428                     }
429 
430                     @Override
431                     public void onOpenStarted() {
432                         makeExpandedVisible(false);
433                     }
434                 });
435     }
436 }
437