blob: 1daf48492ea080cb813bfa69743c68bccf048d9b [file] [log] [blame]
Selim Cineka7d4f822016-12-06 14:34:47 -08001/*
2 * Copyright (C) 2016 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.notification;
18
Ned Burns9512e0c2019-05-30 19:36:04 -040019import android.os.Handler;
20import android.os.SystemClock;
Selim Cineka7d4f822016-12-06 14:34:47 -080021import android.view.View;
22
Gus Prevasab336792018-11-14 13:52:20 -050023import androidx.collection.ArraySet;
24
Ned Burns9512e0c2019-05-30 19:36:04 -040025import com.android.systemui.Dumpable;
Dave Mankofff4736812019-10-18 17:25:50 -040026import com.android.systemui.dagger.qualifiers.MainHandler;
Gus Prevas5b9098dc2018-12-21 17:07:15 -050027import com.android.systemui.statusbar.NotificationPresenter;
Ned Burnsf81c4c42019-01-07 14:10:43 -050028import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Rohan Shah20790b82018-07-02 17:21:04 -070029import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
Selim Cineka7d4f822016-12-06 14:34:47 -080030import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
31
Ned Burns9512e0c2019-05-30 19:36:04 -040032import java.io.FileDescriptor;
33import java.io.PrintWriter;
Selim Cineka7d4f822016-12-06 14:34:47 -080034import java.util.ArrayList;
35
Jason Monk27d01a622018-12-10 15:57:09 -050036import javax.inject.Inject;
37import javax.inject.Singleton;
38
Selim Cineka7d4f822016-12-06 14:34:47 -080039/**
40 * A manager that ensures that notifications are visually stable. It will suppress reorderings
41 * and reorder at the right time when they are out of view.
42 */
Jason Monk27d01a622018-12-10 15:57:09 -050043@Singleton
Ned Burns9512e0c2019-05-30 19:36:04 -040044public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpable {
45
46 private static final long TEMPORARY_REORDERING_ALLOWED_DURATION = 1000;
Selim Cineka7d4f822016-12-06 14:34:47 -080047
48 private final ArrayList<Callback> mCallbacks = new ArrayList<>();
Ned Burns9512e0c2019-05-30 19:36:04 -040049 private final Handler mHandler;
Selim Cineka7d4f822016-12-06 14:34:47 -080050
Gus Prevas5b9098dc2018-12-21 17:07:15 -050051 private NotificationPresenter mPresenter;
Selim Cineka7d4f822016-12-06 14:34:47 -080052 private boolean mPanelExpanded;
53 private boolean mScreenOn;
54 private boolean mReorderingAllowed;
Ned Burns9512e0c2019-05-30 19:36:04 -040055 private boolean mIsTemporaryReorderingAllowed;
56 private long mTemporaryReorderingStart;
Selim Cineka7d4f822016-12-06 14:34:47 -080057 private VisibilityLocationProvider mVisibilityLocationProvider;
58 private ArraySet<View> mAllowedReorderViews = new ArraySet<>();
Ned Burnsa13b6f12019-02-11 20:42:58 -050059 private ArraySet<NotificationEntry> mLowPriorityReorderingViews = new ArraySet<>();
Selim Cineka7d4f822016-12-06 14:34:47 -080060 private ArraySet<View> mAddedChildren = new ArraySet<>();
Adrian Roos4b820e42017-01-13 17:40:43 -080061 private boolean mPulsing;
Selim Cineka7d4f822016-12-06 14:34:47 -080062
Jason Monk27d01a622018-12-10 15:57:09 -050063 @Inject
Ned Burns9512e0c2019-05-30 19:36:04 -040064 public VisualStabilityManager(
Dave Mankofff4736812019-10-18 17:25:50 -040065 NotificationEntryManager notificationEntryManager, @MainHandler Handler handler) {
Ned Burns9512e0c2019-05-30 19:36:04 -040066
67 mHandler = handler;
68
Gus Prevas5b9098dc2018-12-21 17:07:15 -050069 notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
70 @Override
Ned Burnsa13b6f12019-02-11 20:42:58 -050071 public void onPreEntryUpdated(NotificationEntry entry) {
72 final boolean mAmbientStateHasChanged =
Ned Burns60e94592019-09-06 14:47:25 -040073 entry.isAmbient() != entry.getRow().isLowPriority();
Ned Burnsa13b6f12019-02-11 20:42:58 -050074 if (mAmbientStateHasChanged) {
75 mLowPriorityReorderingViews.add(entry);
Gus Prevas5b9098dc2018-12-21 17:07:15 -050076 }
77 }
Ned Burnsa13b6f12019-02-11 20:42:58 -050078
79 @Override
80 public void onPostEntryUpdated(NotificationEntry entry) {
81 // This line is technically not required as we'll get called as the hierarchy
82 // manager will call onReorderingFinished() immediately before this.
83 // TODO: Find a way to make this relationship more explicit
84 mLowPriorityReorderingViews.remove(entry);
85 }
Gus Prevas5b9098dc2018-12-21 17:07:15 -050086 });
87 }
88
89 public void setUpWithPresenter(NotificationPresenter presenter) {
90 mPresenter = presenter;
Jason Monk27d01a622018-12-10 15:57:09 -050091 }
92
Selim Cineka7d4f822016-12-06 14:34:47 -080093 /**
94 * Add a callback to invoke when reordering is allowed again.
95 * @param callback
96 */
97 public void addReorderingAllowedCallback(Callback callback) {
98 if (mCallbacks.contains(callback)) {
99 return;
100 }
101 mCallbacks.add(callback);
102 }
103
104 /**
105 * Set the panel to be expanded.
106 */
107 public void setPanelExpanded(boolean expanded) {
108 mPanelExpanded = expanded;
109 updateReorderingAllowed();
110 }
111
112 /**
113 * @param screenOn whether the screen is on
114 */
115 public void setScreenOn(boolean screenOn) {
116 mScreenOn = screenOn;
117 updateReorderingAllowed();
118 }
119
Adrian Roos4b820e42017-01-13 17:40:43 -0800120 /**
121 * @param pulsing whether we are currently pulsing for ambient display.
122 */
123 public void setPulsing(boolean pulsing) {
Adrian Roosb2a87292017-02-13 15:05:03 +0100124 if (mPulsing == pulsing) {
125 return;
126 }
Adrian Roos4b820e42017-01-13 17:40:43 -0800127 mPulsing = pulsing;
128 updateReorderingAllowed();
129 }
130
Selim Cineka7d4f822016-12-06 14:34:47 -0800131 private void updateReorderingAllowed() {
Ned Burns9512e0c2019-05-30 19:36:04 -0400132 boolean reorderingAllowed =
133 (!mScreenOn || !mPanelExpanded || mIsTemporaryReorderingAllowed) && !mPulsing;
134 boolean changedToTrue = reorderingAllowed && !mReorderingAllowed;
Selim Cineka7d4f822016-12-06 14:34:47 -0800135 mReorderingAllowed = reorderingAllowed;
Ned Burns9512e0c2019-05-30 19:36:04 -0400136 if (changedToTrue) {
Selim Cineka7d4f822016-12-06 14:34:47 -0800137 notifyCallbacks();
138 }
139 }
140
141 private void notifyCallbacks() {
142 for (int i = 0; i < mCallbacks.size(); i++) {
143 Callback callback = mCallbacks.get(i);
144 callback.onReorderingAllowed();
145 }
146 mCallbacks.clear();
147 }
148
149 /**
150 * @return whether reordering is currently allowed in general.
151 */
152 public boolean isReorderingAllowed() {
153 return mReorderingAllowed;
154 }
155
156 /**
157 * @return whether a specific notification is allowed to reorder. Certain notifications are
158 * allowed to reorder even if {@link #isReorderingAllowed()} returns false, like newly added
159 * notifications or heads-up notifications that are out of view.
160 */
161 public boolean canReorderNotification(ExpandableNotificationRow row) {
162 if (mReorderingAllowed) {
163 return true;
164 }
165 if (mAddedChildren.contains(row)) {
166 return true;
167 }
Ned Burnsa13b6f12019-02-11 20:42:58 -0500168 if (mLowPriorityReorderingViews.contains(row.getEntry())) {
Selim Cinek55a3e732017-05-25 18:30:10 -0700169 return true;
170 }
Selim Cineka7d4f822016-12-06 14:34:47 -0800171 if (mAllowedReorderViews.contains(row)
Evan Laird94492852018-10-25 13:43:01 -0400172 && !mVisibilityLocationProvider.isInVisibleLocation(row.getEntry())) {
Selim Cineka7d4f822016-12-06 14:34:47 -0800173 return true;
174 }
175 return false;
176 }
177
178 public void setVisibilityLocationProvider(
179 VisibilityLocationProvider visibilityLocationProvider) {
180 mVisibilityLocationProvider = visibilityLocationProvider;
181 }
182
183 public void onReorderingFinished() {
184 mAllowedReorderViews.clear();
185 mAddedChildren.clear();
Selim Cinek55a3e732017-05-25 18:30:10 -0700186 mLowPriorityReorderingViews.clear();
Selim Cineka7d4f822016-12-06 14:34:47 -0800187 }
188
189 @Override
Ned Burnsf81c4c42019-01-07 14:10:43 -0500190 public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
Selim Cineka7d4f822016-12-06 14:34:47 -0800191 if (isHeadsUp) {
192 // Heads up notifications should in general be allowed to reorder if they are out of
193 // view and stay at the current location if they aren't.
Evan Laird94492852018-10-25 13:43:01 -0400194 mAllowedReorderViews.add(entry.getRow());
Selim Cineka7d4f822016-12-06 14:34:47 -0800195 }
196 }
197
Selim Cineka7d4f822016-12-06 14:34:47 -0800198 /**
Ned Burns9512e0c2019-05-30 19:36:04 -0400199 * Temporarily allows reordering of the entire shade for a period of 1000ms. Subsequent calls
200 * to this method will extend the timer.
201 */
202 public void temporarilyAllowReordering() {
203 mHandler.removeCallbacks(mOnTemporaryReorderingExpired);
204 mHandler.postDelayed(mOnTemporaryReorderingExpired, TEMPORARY_REORDERING_ALLOWED_DURATION);
205 if (!mIsTemporaryReorderingAllowed) {
206 mTemporaryReorderingStart = SystemClock.elapsedRealtime();
207 }
208 mIsTemporaryReorderingAllowed = true;
209 updateReorderingAllowed();
210 }
211
212 private final Runnable mOnTemporaryReorderingExpired = () -> {
213 mIsTemporaryReorderingAllowed = false;
214 updateReorderingAllowed();
215 };
216
217 /**
Selim Cineka7d4f822016-12-06 14:34:47 -0800218 * Notify the visual stability manager that a new view was added and should be allowed to
219 * reorder next time.
220 */
221 public void notifyViewAddition(View view) {
222 mAddedChildren.add(view);
223 }
224
Ned Burns9512e0c2019-05-30 19:36:04 -0400225 @Override
226 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
227 pw.println("VisualStabilityManager state:");
228 pw.print(" mIsTemporaryReorderingAllowed="); pw.println(mIsTemporaryReorderingAllowed);
229 pw.print(" mTemporaryReorderingStart="); pw.println(mTemporaryReorderingStart);
230
231 long now = SystemClock.elapsedRealtime();
232 pw.print(" Temporary reordering window has been open for ");
233 pw.print(now - (mIsTemporaryReorderingAllowed ? mTemporaryReorderingStart : now));
234 pw.println("ms");
235
236 pw.println();
237 }
238
Selim Cineka7d4f822016-12-06 14:34:47 -0800239 public interface Callback {
240 /**
241 * Called when reordering is allowed again.
242 */
243 void onReorderingAllowed();
244 }
245
246}