blob: 409a7839197562424fb9ff60c3cfab9827b96b88 [file] [log] [blame]
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001/*
2 * Copyright (C) 2018 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
17package com.android.systemui.statusbar.phone;
18
felkachang3d00f352018-05-22 12:53:50 +080019import android.graphics.Point;
Selim Cinekaa9db1f2018-02-27 17:35:47 -080020import android.graphics.Rect;
Selim Cinekaa9db1f2018-02-27 17:35:47 -080021import android.view.View;
felkachang3d00f352018-05-22 12:53:50 +080022import android.view.WindowInsets;
Selim Cinekaa9db1f2018-02-27 17:35:47 -080023
24import com.android.internal.annotations.VisibleForTesting;
25import com.android.systemui.Dependency;
26import com.android.systemui.R;
Selim Cinekd03518c2018-03-15 12:13:51 -070027import com.android.systemui.statusbar.CrossFadeHelper;
Selim Cinekaa9db1f2018-02-27 17:35:47 -080028import com.android.systemui.statusbar.ExpandableNotificationRow;
29import com.android.systemui.statusbar.HeadsUpStatusBarView;
30import com.android.systemui.statusbar.NotificationData;
31import com.android.systemui.statusbar.policy.DarkIconDispatcher;
32import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
33import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
34
Selim Cinek60ffea62018-03-22 13:16:44 -070035import java.util.function.BiConsumer;
36import java.util.function.Consumer;
37
Selim Cinekaa9db1f2018-02-27 17:35:47 -080038/**
39 * Controls the appearance of heads up notifications in the icon area and the header itself.
40 */
Selim Cinekf0c79e12018-05-14 17:17:31 -070041public class HeadsUpAppearanceController implements OnHeadsUpChangedListener,
Selim Cinekaa9db1f2018-02-27 17:35:47 -080042 DarkIconDispatcher.DarkReceiver {
Selim Cinekd03518c2018-03-15 12:13:51 -070043 public static final int CONTENT_FADE_DURATION = 110;
44 public static final int CONTENT_FADE_DELAY = 100;
Selim Cinekaa9db1f2018-02-27 17:35:47 -080045 private final NotificationIconAreaController mNotificationIconAreaController;
46 private final HeadsUpManagerPhone mHeadsUpManager;
47 private final NotificationStackScrollLayout mStackScroller;
48 private final HeadsUpStatusBarView mHeadsUpStatusBarView;
49 private final View mClockView;
50 private final DarkIconDispatcher mDarkIconDispatcher;
Selim Cinek60ffea62018-03-22 13:16:44 -070051 private final NotificationPanelView mPanelView;
52 private final Consumer<ExpandableNotificationRow>
53 mSetTrackingHeadsUp = this::setTrackingHeadsUp;
54 private final Runnable mUpdatePanelTranslation = this::updatePanelTranslation;
55 private final BiConsumer<Float, Float> mSetExpandedHeight = this::setExpandedHeight;
Selim Cinekaa9db1f2018-02-27 17:35:47 -080056 private float mExpandedHeight;
57 private boolean mIsExpanded;
58 private float mExpandFraction;
59 private ExpandableNotificationRow mTrackedChild;
60 private boolean mShown;
Selim Cinek60ffea62018-03-22 13:16:44 -070061 private final View.OnLayoutChangeListener mStackScrollLayoutChangeListener =
62 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom)
63 -> updatePanelTranslation();
felkachang3d00f352018-05-22 12:53:50 +080064 Point mPoint;
Selim Cinekaa9db1f2018-02-27 17:35:47 -080065
66 public HeadsUpAppearanceController(
67 NotificationIconAreaController notificationIconAreaController,
68 HeadsUpManagerPhone headsUpManager,
69 View statusbarView) {
70 this(notificationIconAreaController, headsUpManager,
71 statusbarView.findViewById(R.id.heads_up_status_bar_view),
72 statusbarView.findViewById(R.id.notification_stack_scroller),
73 statusbarView.findViewById(R.id.notification_panel),
74 statusbarView.findViewById(R.id.clock));
75 }
76
77 @VisibleForTesting
78 public HeadsUpAppearanceController(
79 NotificationIconAreaController notificationIconAreaController,
80 HeadsUpManagerPhone headsUpManager,
81 HeadsUpStatusBarView headsUpStatusBarView,
82 NotificationStackScrollLayout stackScroller,
83 NotificationPanelView panelView,
84 View clockView) {
85 mNotificationIconAreaController = notificationIconAreaController;
86 mHeadsUpManager = headsUpManager;
87 mHeadsUpManager.addListener(this);
88 mHeadsUpStatusBarView = headsUpStatusBarView;
Selim Cinek332c23f2018-03-16 17:37:50 -070089 headsUpStatusBarView.setOnDrawingRectChangedListener(
90 () -> updateIsolatedIconLocation(true /* requireUpdate */));
Selim Cinekaa9db1f2018-02-27 17:35:47 -080091 mStackScroller = stackScroller;
Selim Cinek60ffea62018-03-22 13:16:44 -070092 mPanelView = panelView;
93 panelView.addTrackingHeadsUpListener(mSetTrackingHeadsUp);
94 panelView.addVerticalTranslationListener(mUpdatePanelTranslation);
Selim Cinek332c23f2018-03-16 17:37:50 -070095 panelView.setHeadsUpAppearanceController(this);
Selim Cinek60ffea62018-03-22 13:16:44 -070096 mStackScroller.addOnExpandedHeightListener(mSetExpandedHeight);
97 mStackScroller.addOnLayoutChangeListener(mStackScrollLayoutChangeListener);
Selim Cinekf0c79e12018-05-14 17:17:31 -070098 mStackScroller.setHeadsUpAppearanceController(this);
Selim Cinekaa9db1f2018-02-27 17:35:47 -080099 mClockView = clockView;
100 mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
101 mDarkIconDispatcher.addDarkReceiver(this);
102 }
103
Selim Cinek60ffea62018-03-22 13:16:44 -0700104
105 public void destroy() {
106 mHeadsUpManager.removeListener(this);
107 mHeadsUpStatusBarView.setOnDrawingRectChangedListener(null);
108 mPanelView.removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
109 mPanelView.removeVerticalTranslationListener(mUpdatePanelTranslation);
110 mPanelView.setHeadsUpAppearanceController(null);
111 mStackScroller.removeOnExpandedHeightListener(mSetExpandedHeight);
112 mStackScroller.removeOnLayoutChangeListener(mStackScrollLayoutChangeListener);
113 mDarkIconDispatcher.removeDarkReceiver(this);
114 }
115
Selim Cinek332c23f2018-03-16 17:37:50 -0700116 private void updateIsolatedIconLocation(boolean requireStateUpdate) {
117 mNotificationIconAreaController.setIsolatedIconLocation(
118 mHeadsUpStatusBarView.getIconDrawingRect(), requireStateUpdate);
119 }
120
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800121 @Override
122 public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
123 updateTopEntry();
124 updateHeader(headsUp.getEntry());
125 }
126
felkachang3d00f352018-05-22 12:53:50 +0800127 /** To count the distance from the window right boundary to scroller right boundary. The
128 * distance formula is the following:
129 * Y = screenSize - (SystemWindow's width + Scroller.getRight())
130 * There are four modes MUST to be considered in Cut Out of RTL.
131 * No Cut Out:
132 * Scroller + NB
133 * NB + Scroller
134 * => SystemWindow = NavigationBar's width
135 * => Y = screenSize - (SystemWindow's width + Scroller.getRight())
136 * Corner Cut Out or Tall Cut Out:
137 * cut out + Scroller + NB
138 * NB + Scroller + cut out
139 * => SystemWindow = NavigationBar's width
140 * => Y = screenSize - (SystemWindow's width + Scroller.getRight())
141 * Double Cut Out:
142 * cut out left + Scroller + (NB + cut out right)
143 * SystemWindow = NavigationBar's width + cut out right width
144 * => Y = screenSize - (SystemWindow's width + Scroller.getRight())
145 * (cut out left + NB) + Scroller + cut out right
146 * SystemWindow = NavigationBar's width + cut out left width
147 * => Y = screenSize - (SystemWindow's width + Scroller.getRight())
148 * @return the translation X value for RTL. In theory, it should be negative. i.e. -Y
149 */
150 private int getRtlTranslation() {
felkachang3d00f352018-05-22 12:53:50 +0800151 if (mPoint == null) {
152 mPoint = new Point();
153 }
154
155 int realDisplaySize = 0;
156 if (mStackScroller.getDisplay() != null) {
157 mStackScroller.getDisplay().getRealSize(mPoint);
158 realDisplaySize = mPoint.x;
159 }
160
161 WindowInsets windowInset = mStackScroller.getRootWindowInsets();
162 return windowInset.getSystemWindowInsetLeft() + mStackScroller.getRight()
163 + windowInset.getSystemWindowInsetRight() - realDisplaySize;
164 }
165
Selim Cinek332c23f2018-03-16 17:37:50 -0700166 public void updatePanelTranslation() {
felkachang3d00f352018-05-22 12:53:50 +0800167 float newTranslation;
168 if (mStackScroller.isLayoutRtl()) {
169 newTranslation = getRtlTranslation();
170 } else {
171 newTranslation = mStackScroller.getLeft();
172 }
173 newTranslation += mStackScroller.getTranslationX();
felkachange8a35362018-05-18 20:11:38 +0800174 mHeadsUpStatusBarView.setPanelTranslation(newTranslation);
Selim Cinek332c23f2018-03-16 17:37:50 -0700175 }
176
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800177 private void updateTopEntry() {
178 NotificationData.Entry newEntry = null;
179 if (!mIsExpanded && mHeadsUpManager.hasPinnedHeadsUp()) {
180 newEntry = mHeadsUpManager.getTopEntry();
181 }
182 NotificationData.Entry previousEntry = mHeadsUpStatusBarView.getShowingEntry();
183 mHeadsUpStatusBarView.setEntry(newEntry);
184 if (newEntry != previousEntry) {
Selim Cinekd03518c2018-03-15 12:13:51 -0700185 boolean animateIsolation = false;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800186 if (newEntry == null) {
187 // no heads up anymore, lets start the disappear animation
188
189 setShown(false);
Selim Cinekd03518c2018-03-15 12:13:51 -0700190 animateIsolation = !mIsExpanded;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800191 } else if (previousEntry == null) {
192 // We now have a headsUp and didn't have one before. Let's start the disappear
193 // animation
194 setShown(true);
Selim Cinek332c23f2018-03-16 17:37:50 -0700195 animateIsolation = !mIsExpanded;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800196 }
Selim Cinek332c23f2018-03-16 17:37:50 -0700197 updateIsolatedIconLocation(false /* requireUpdate */);
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800198 mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
Selim Cinek332c23f2018-03-16 17:37:50 -0700199 : newEntry.icon, animateIsolation);
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800200 }
201 }
202
203 private void setShown(boolean isShown) {
Selim Cinekd03518c2018-03-15 12:13:51 -0700204 if (mShown != isShown) {
205 mShown = isShown;
206 if (isShown) {
207 mHeadsUpStatusBarView.setVisibility(View.VISIBLE);
208 CrossFadeHelper.fadeIn(mHeadsUpStatusBarView, CONTENT_FADE_DURATION /* duration */,
209 CONTENT_FADE_DELAY /* delay */);
210 CrossFadeHelper.fadeOut(mClockView, CONTENT_FADE_DURATION/* duration */,
211 0 /* delay */, () -> mClockView.setVisibility(View.INVISIBLE));
212 } else {
213 CrossFadeHelper.fadeIn(mClockView, CONTENT_FADE_DURATION /* duration */,
214 CONTENT_FADE_DELAY /* delay */);
215 CrossFadeHelper.fadeOut(mHeadsUpStatusBarView, CONTENT_FADE_DURATION/* duration */,
216 0 /* delay */, () -> mHeadsUpStatusBarView.setVisibility(View.GONE));
217
218 }
219 }
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800220 }
221
222 @VisibleForTesting
223 public boolean isShown() {
224 return mShown;
225 }
226
Selim Cinek332c23f2018-03-16 17:37:50 -0700227 /**
228 * Should the headsup status bar view be visible right now? This may be different from isShown,
229 * since the headsUp manager might not have notified us yet of the state change.
230 *
231 * @return if the heads up status bar view should be shown
232 */
233 public boolean shouldBeVisible() {
234 return !mIsExpanded && mHeadsUpManager.hasPinnedHeadsUp();
235 }
236
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800237 @Override
238 public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
239 updateTopEntry();
240 updateHeader(headsUp.getEntry());
241 }
242
243 public void setExpandedHeight(float expandedHeight, float appearFraction) {
244 boolean changedHeight = expandedHeight != mExpandedHeight;
245 mExpandedHeight = expandedHeight;
246 mExpandFraction = appearFraction;
247 boolean isExpanded = expandedHeight > 0;
248 if (changedHeight) {
249 updateHeadsUpHeaders();
250 }
251 if (isExpanded != mIsExpanded) {
252 mIsExpanded = isExpanded;
253 updateTopEntry();
254 }
255 }
256
257 /**
258 * Set a headsUp to be tracked, meaning that it is currently being pulled down after being
259 * in a pinned state on the top. The expand animation is different in that case and we need
260 * to update the header constantly afterwards.
261 *
262 * @param trackedChild the tracked headsUp or null if it's not tracking anymore.
263 */
264 public void setTrackingHeadsUp(ExpandableNotificationRow trackedChild) {
265 ExpandableNotificationRow previousTracked = mTrackedChild;
266 mTrackedChild = trackedChild;
267 if (previousTracked != null) {
268 updateHeader(previousTracked.getEntry());
269 }
270 }
271
272 private void updateHeadsUpHeaders() {
273 mHeadsUpManager.getAllEntries().forEach(entry -> {
274 updateHeader(entry);
275 });
276 }
277
Selim Cinekf0c79e12018-05-14 17:17:31 -0700278 public void updateHeader(NotificationData.Entry entry) {
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800279 ExpandableNotificationRow row = entry.row;
280 float headerVisibleAmount = 1.0f;
Selim Cinekf0c79e12018-05-14 17:17:31 -0700281 if (row.isPinned() || row.isHeadsUpAnimatingAway() || row == mTrackedChild) {
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800282 headerVisibleAmount = mExpandFraction;
283 }
284 row.setHeaderVisibleAmount(headerVisibleAmount);
285 }
286
287 @Override
288 public void onDarkChanged(Rect area, float darkIntensity, int tint) {
289 mHeadsUpStatusBarView.onDarkChanged(area, darkIntensity, tint);
290 }
291
292 public void setPublicMode(boolean publicMode) {
293 mHeadsUpStatusBarView.setPublicMode(publicMode);
294 updateTopEntry();
295 }
296}