blob: 3fa60659209adad4f08848eba67df04abd7ff3c2 [file] [log] [blame]
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001/*
Selim Cinek684a4422015-04-15 16:18:39 -07002 * Copyright (C) 2015 The Android Open Source Project
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003 *
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.policy;
18
19import android.content.Context;
20import android.content.res.Resources;
21import android.database.ContentObserver;
22import android.os.Handler;
23import android.os.SystemClock;
24import android.provider.Settings;
Selim Cineka7d4f822016-12-06 14:34:47 -080025import android.support.v4.util.ArraySet;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070026import android.util.ArrayMap;
27import android.util.Log;
28import android.util.Pools;
Selim Cinek737bff32015-05-08 16:08:35 -070029import android.view.View;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070030import android.view.ViewTreeObserver;
31import android.view.accessibility.AccessibilityEvent;
32
Chris Wrenb659c4f2015-06-25 17:12:27 -040033import com.android.internal.logging.MetricsLogger;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070034import com.android.systemui.R;
Selim Cineka59ecc32015-04-07 10:51:49 -070035import com.android.systemui.statusbar.ExpandableNotificationRow;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070036import com.android.systemui.statusbar.NotificationData;
Selim Cineka7d4f822016-12-06 14:34:47 -080037import com.android.systemui.statusbar.notification.VisualStabilityManager;
Selim Cinek83bc7832015-10-22 13:26:54 -070038import com.android.systemui.statusbar.phone.NotificationGroupManager;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070039import com.android.systemui.statusbar.phone.PhoneStatusBar;
40
41import java.io.FileDescriptor;
42import java.io.PrintWriter;
Selim Cinek684a4422015-04-15 16:18:39 -070043import java.util.ArrayList;
Selim Cinek3362c132016-02-11 15:43:03 -080044import java.util.Collection;
Selim Cineka59ecc32015-04-07 10:51:49 -070045import java.util.HashMap;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070046import java.util.HashSet;
47import java.util.Stack;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070048
Selim Cinek684a4422015-04-15 16:18:39 -070049/**
50 * A manager which handles heads up notifications which is a special mode where
51 * they simply peek from the top of the screen.
52 */
Selim Cineka7d4f822016-12-06 14:34:47 -080053public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsListener,
54 VisualStabilityManager.Callback {
Selim Cinekb8f09cf2015-03-16 17:09:28 -070055 private static final String TAG = "HeadsUpManager";
56 private static final boolean DEBUG = false;
57 private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
Selim Cinek0fccc722015-07-29 17:04:36 -070058 private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070059
60 private final int mHeadsUpNotificationDecay;
61 private final int mMinimumDisplayTime;
62
Selim Cinek684a4422015-04-15 16:18:39 -070063 private final int mTouchAcceptanceDelay;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070064 private final ArrayMap<String, Long> mSnoozedPackages;
65 private final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>();
66 private final int mDefaultSnoozeLengthMs;
67 private final Handler mHandler = new Handler();
68 private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<HeadsUpEntry>() {
69
70 private Stack<HeadsUpEntry> mPoolObjects = new Stack<>();
71
72 @Override
73 public HeadsUpEntry acquire() {
74 if (!mPoolObjects.isEmpty()) {
75 return mPoolObjects.pop();
76 }
77 return new HeadsUpEntry();
78 }
79
80 @Override
81 public boolean release(HeadsUpEntry instance) {
Selim Cinek684a4422015-04-15 16:18:39 -070082 instance.reset();
Selim Cinekb8f09cf2015-03-16 17:09:28 -070083 mPoolObjects.push(instance);
84 return true;
85 }
86 };
87
Selim Cinek737bff32015-05-08 16:08:35 -070088 private final View mStatusBarWindowView;
89 private final int mStatusBarHeight;
Chris Wrenb659c4f2015-06-25 17:12:27 -040090 private final Context mContext;
Selim Cinek83bc7832015-10-22 13:26:54 -070091 private final NotificationGroupManager mGroupManager;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070092 private PhoneStatusBar mBar;
93 private int mSnoozeLengthMs;
94 private ContentObserver mSettingsObserver;
Selim Cineka59ecc32015-04-07 10:51:49 -070095 private HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
Selim Cinekb8f09cf2015-03-16 17:09:28 -070096 private HashSet<String> mSwipedOutKeys = new HashSet<>();
97 private int mUser;
98 private Clock mClock;
99 private boolean mReleaseOnExpandFinish;
100 private boolean mTrackingHeadsUp;
101 private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>();
Selim Cineka7d4f822016-12-06 14:34:47 -0800102 private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed
103 = new ArraySet<>();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700104 private boolean mIsExpanded;
Selim Cinek684a4422015-04-15 16:18:39 -0700105 private boolean mHasPinnedNotification;
Selim Cineka59ecc32015-04-07 10:51:49 -0700106 private int[] mTmpTwoArray = new int[2];
Selim Cinek737bff32015-05-08 16:08:35 -0700107 private boolean mHeadsUpGoingAway;
108 private boolean mWaitingOnCollapseWhenGoingAway;
109 private boolean mIsObserving;
Adrian Roos1c0ca502015-10-07 12:20:42 -0700110 private boolean mRemoteInputActive;
Selim Cinekd127d792016-11-01 19:11:41 -0700111 private float mExpandedHeight;
Selim Cineka7d4f822016-12-06 14:34:47 -0800112 private VisualStabilityManager mVisualStabilityManager;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700113
Selim Cinek83bc7832015-10-22 13:26:54 -0700114 public HeadsUpManager(final Context context, View statusBarWindowView,
115 NotificationGroupManager groupManager) {
Chris Wrenb659c4f2015-06-25 17:12:27 -0400116 mContext = context;
117 Resources resources = mContext.getResources();
Selim Cinek684a4422015-04-15 16:18:39 -0700118 mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700119 mSnoozedPackages = new ArrayMap<>();
120 mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
121 mSnoozeLengthMs = mDefaultSnoozeLengthMs;
122 mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
Selim Cineke53e6bb2015-04-13 16:14:26 -0700123 mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700124 mClock = new Clock();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700125
126 mSnoozeLengthMs = Settings.Global.getInt(context.getContentResolver(),
127 SETTING_HEADS_UP_SNOOZE_LENGTH_MS, mDefaultSnoozeLengthMs);
128 mSettingsObserver = new ContentObserver(mHandler) {
129 @Override
130 public void onChange(boolean selfChange) {
131 final int packageSnoozeLengthMs = Settings.Global.getInt(
132 context.getContentResolver(), SETTING_HEADS_UP_SNOOZE_LENGTH_MS, -1);
133 if (packageSnoozeLengthMs > -1 && packageSnoozeLengthMs != mSnoozeLengthMs) {
134 mSnoozeLengthMs = packageSnoozeLengthMs;
135 if (DEBUG) Log.v(TAG, "mSnoozeLengthMs = " + mSnoozeLengthMs);
136 }
137 }
138 };
139 context.getContentResolver().registerContentObserver(
140 Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false,
141 mSettingsObserver);
Selim Cinek737bff32015-05-08 16:08:35 -0700142 mStatusBarWindowView = statusBarWindowView;
Selim Cinek83bc7832015-10-22 13:26:54 -0700143 mGroupManager = groupManager;
Selim Cinek737bff32015-05-08 16:08:35 -0700144 mStatusBarHeight = resources.getDimensionPixelSize(
145 com.android.internal.R.dimen.status_bar_height);
146 }
147
148 private void updateTouchableRegionListener() {
149 boolean shouldObserve = mHasPinnedNotification || mHeadsUpGoingAway
150 || mWaitingOnCollapseWhenGoingAway;
151 if (shouldObserve == mIsObserving) {
152 return;
153 }
154 if (shouldObserve) {
155 mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
156 mStatusBarWindowView.requestLayout();
157 } else {
158 mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
159 }
160 mIsObserving = shouldObserve;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700161 }
162
163 public void setBar(PhoneStatusBar bar) {
164 mBar = bar;
165 }
166
167 public void addListener(OnHeadsUpChangedListener listener) {
168 mListeners.add(listener);
169 }
170
171 public PhoneStatusBar getBar() {
172 return mBar;
173 }
174
175 /**
176 * Called when posting a new notification to the heads up.
177 */
178 public void showNotification(NotificationData.Entry headsUp) {
179 if (DEBUG) Log.v(TAG, "showNotification");
180 addHeadsUpEntry(headsUp);
181 updateNotification(headsUp, true);
182 headsUp.setInterruption();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700183 }
184
185 /**
186 * Called when updating or posting a notification to the heads up.
187 */
188 public void updateNotification(NotificationData.Entry headsUp, boolean alert) {
189 if (DEBUG) Log.v(TAG, "updateNotification");
190
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700191 headsUp.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
192
193 if (alert) {
194 HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(headsUp.key);
Selim Cinek967ed2a2016-04-08 18:29:11 -0700195 if (headsUpEntry == null) {
196 // the entry was released before this update (i.e by a listener) This can happen
197 // with the groupmanager
198 return;
199 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700200 headsUpEntry.updateEntry();
Selim Cinek131c1e22015-05-11 19:04:49 -0700201 setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUp));
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700202 }
203 }
204
205 private void addHeadsUpEntry(NotificationData.Entry entry) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700206 HeadsUpEntry headsUpEntry = mEntryPool.acquire();
Selim Cineka59ecc32015-04-07 10:51:49 -0700207
208 // This will also add the entry to the sortedList
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700209 headsUpEntry.setEntry(entry);
210 mHeadsUpEntries.put(entry.key, headsUpEntry);
Selim Cineka59ecc32015-04-07 10:51:49 -0700211 entry.row.setHeadsUp(true);
Selim Cinek131c1e22015-05-11 19:04:49 -0700212 setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(entry));
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700213 for (OnHeadsUpChangedListener listener : mListeners) {
Selim Cinek684a4422015-04-15 16:18:39 -0700214 listener.onHeadsUpStateChanged(entry, true);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700215 }
216 entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700217 }
218
Selim Cinek131c1e22015-05-11 19:04:49 -0700219 private boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) {
220 return !mIsExpanded || hasFullScreenIntent(entry);
221 }
222
223 private boolean hasFullScreenIntent(NotificationData.Entry entry) {
224 return entry.notification.getNotification().fullScreenIntent != null;
225 }
226
Selim Cinek684a4422015-04-15 16:18:39 -0700227 private void setEntryPinned(HeadsUpEntry headsUpEntry, boolean isPinned) {
Selim Cinek1f3f5442015-04-10 17:54:46 -0700228 ExpandableNotificationRow row = headsUpEntry.entry.row;
Selim Cinek684a4422015-04-15 16:18:39 -0700229 if (row.isPinned() != isPinned) {
230 row.setPinned(isPinned);
231 updatePinnedMode();
232 for (OnHeadsUpChangedListener listener : mListeners) {
233 if (isPinned) {
234 listener.onHeadsUpPinned(row);
235 } else {
236 listener.onHeadsUpUnPinned(row);
Selim Cinek1f3f5442015-04-10 17:54:46 -0700237 }
238 }
239 }
240 }
241
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700242 private void removeHeadsUpEntry(NotificationData.Entry entry) {
243 HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700244 entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
245 entry.row.setHeadsUp(false);
Selim Cinek684a4422015-04-15 16:18:39 -0700246 setEntryPinned(remove, false /* isPinned */);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700247 for (OnHeadsUpChangedListener listener : mListeners) {
Selim Cinek684a4422015-04-15 16:18:39 -0700248 listener.onHeadsUpStateChanged(entry, false);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700249 }
Selim Cinek684a4422015-04-15 16:18:39 -0700250 mEntryPool.release(remove);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700251 }
252
Selim Cinek684a4422015-04-15 16:18:39 -0700253 private void updatePinnedMode() {
254 boolean hasPinnedNotification = hasPinnedNotificationInternal();
255 if (hasPinnedNotification == mHasPinnedNotification) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700256 return;
257 }
Selim Cinek684a4422015-04-15 16:18:39 -0700258 mHasPinnedNotification = hasPinnedNotification;
Chris Wren063926b2015-10-16 16:24:20 -0400259 if (mHasPinnedNotification) {
260 MetricsLogger.count(mContext, "note_peek", 1);
261 }
Selim Cinek737bff32015-05-08 16:08:35 -0700262 updateTouchableRegionListener();
Selim Cinek684a4422015-04-15 16:18:39 -0700263 for (OnHeadsUpChangedListener listener : mListeners) {
John Spurlockb349af572015-04-29 12:24:19 -0400264 listener.onHeadsUpPinnedModeChanged(hasPinnedNotification);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700265 }
266 }
267
268 /**
269 * React to the removal of the notification in the heads up.
270 *
271 * @return true if the notification was removed and false if it still needs to be kept around
272 * for a bit since it wasn't shown long enough
273 */
Adrian Roosc721fcc52016-04-29 11:28:37 -0700274 public boolean removeNotification(String key, boolean ignoreEarliestRemovalTime) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700275 if (DEBUG) Log.v(TAG, "remove");
Adrian Roosc721fcc52016-04-29 11:28:37 -0700276 if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700277 releaseImmediately(key);
278 return true;
279 } else {
Selim Cinek684a4422015-04-15 16:18:39 -0700280 getHeadsUpEntry(key).removeAsSoonAsPossible();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700281 return false;
282 }
283 }
284
285 private boolean wasShownLongEnough(String key) {
286 HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
287 HeadsUpEntry topEntry = getTopEntry();
288 if (mSwipedOutKeys.contains(key)) {
289 // We always instantly dismiss views being manually swiped out.
290 mSwipedOutKeys.remove(key);
291 return true;
292 }
293 if (headsUpEntry != topEntry) {
294 return true;
295 }
296 return headsUpEntry.wasShownLongEnough();
297 }
298
299 public boolean isHeadsUp(String key) {
300 return mHeadsUpEntries.containsKey(key);
301 }
302
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700303 /**
304 * Push any current Heads Up notification down into the shade.
305 */
306 public void releaseAllImmediately() {
307 if (DEBUG) Log.v(TAG, "releaseAllImmediately");
Selim Cinek684a4422015-04-15 16:18:39 -0700308 ArrayList<String> keys = new ArrayList<>(mHeadsUpEntries.keySet());
309 for (String key : keys) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700310 releaseImmediately(key);
311 }
312 }
313
314 public void releaseImmediately(String key) {
315 HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
316 if (headsUpEntry == null) {
317 return;
318 }
319 NotificationData.Entry shadeEntry = headsUpEntry.entry;
320 removeHeadsUpEntry(shadeEntry);
321 }
322
323 public boolean isSnoozed(String packageName) {
324 final String key = snoozeKey(packageName, mUser);
325 Long snoozedUntil = mSnoozedPackages.get(key);
326 if (snoozedUntil != null) {
327 if (snoozedUntil > SystemClock.elapsedRealtime()) {
328 if (DEBUG) Log.v(TAG, key + " snoozed");
329 return true;
330 }
331 mSnoozedPackages.remove(packageName);
332 }
333 return false;
334 }
335
336 public void snooze() {
Selim Cinek684a4422015-04-15 16:18:39 -0700337 for (String key : mHeadsUpEntries.keySet()) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700338 HeadsUpEntry entry = mHeadsUpEntries.get(key);
339 String packageName = entry.entry.notification.getPackageName();
340 mSnoozedPackages.put(snoozeKey(packageName, mUser),
341 SystemClock.elapsedRealtime() + mSnoozeLengthMs);
342 }
343 mReleaseOnExpandFinish = true;
344 }
345
346 private static String snoozeKey(String packageName, int user) {
347 return user + "," + packageName;
348 }
349
350 private HeadsUpEntry getHeadsUpEntry(String key) {
351 return mHeadsUpEntries.get(key);
352 }
353
354 public NotificationData.Entry getEntry(String key) {
355 return mHeadsUpEntries.get(key).entry;
356 }
357
Selim Cinek3362c132016-02-11 15:43:03 -0800358 public Collection<HeadsUpEntry> getAllEntries() {
359 return mHeadsUpEntries.values();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700360 }
361
362 public HeadsUpEntry getTopEntry() {
Selim Cinek3362c132016-02-11 15:43:03 -0800363 if (mHeadsUpEntries.isEmpty()) {
364 return null;
365 }
366 HeadsUpEntry topEntry = null;
367 for (HeadsUpEntry entry: mHeadsUpEntries.values()) {
368 if (topEntry == null || entry.compareTo(topEntry) == -1) {
369 topEntry = entry;
370 }
371 }
372 return topEntry;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700373 }
374
375 /**
Selim Cinek684a4422015-04-15 16:18:39 -0700376 * Decides whether a click is invalid for a notification, i.e it has not been shown long enough
377 * that a user might have consciously clicked on it.
378 *
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700379 * @param key the key of the touched notification
Selim Cinek684a4422015-04-15 16:18:39 -0700380 * @return whether the touch is invalid and should be discarded
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700381 */
382 public boolean shouldSwallowClick(String key) {
Selim Cinek2f6b3fb2015-04-20 13:04:47 -0700383 HeadsUpEntry entry = mHeadsUpEntries.get(key);
384 if (entry != null && mClock.currentTimeMillis() < entry.postTime) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700385 return true;
386 }
387 return false;
388 }
389
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700390 public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
Selim Cinek99415392016-09-09 14:58:41 -0700391 if (mIsExpanded || mBar.isBouncerShowing()) {
Selim Cinek131c1e22015-05-11 19:04:49 -0700392 // The touchable region is always the full area when expanded
393 return;
394 }
Selim Cinek737bff32015-05-08 16:08:35 -0700395 if (mHasPinnedNotification) {
Selim Cinek3362c132016-02-11 15:43:03 -0800396 ExpandableNotificationRow topEntry = getTopEntry().entry.row;
397 if (topEntry.isChildInGroup()) {
398 final ExpandableNotificationRow groupSummary
399 = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification());
400 if (groupSummary != null) {
401 topEntry = groupSummary;
Selim Cineka59ecc32015-04-07 10:51:49 -0700402 }
403 }
Selim Cinek3362c132016-02-11 15:43:03 -0800404 topEntry.getLocationOnScreen(mTmpTwoArray);
405 int minX = mTmpTwoArray[0];
406 int maxX = mTmpTwoArray[0] + topEntry.getWidth();
407 int maxY = topEntry.getIntrinsicHeight();
Selim Cineka59ecc32015-04-07 10:51:49 -0700408
409 info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
Selim Cinek33d46142016-01-15 18:58:39 -0800410 info.touchableRegion.set(minX, 0, maxX, maxY);
Selim Cinek737bff32015-05-08 16:08:35 -0700411 } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) {
412 info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
413 info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight);
Selim Cineka59ecc32015-04-07 10:51:49 -0700414 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700415 }
416
417 public void setUser(int user) {
418 mUser = user;
419 }
420
421 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
422 pw.println("HeadsUpManager state:");
Selim Cinek684a4422015-04-15 16:18:39 -0700423 pw.print(" mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700424 pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
425 pw.print(" now="); pw.println(SystemClock.elapsedRealtime());
426 pw.print(" mUser="); pw.println(mUser);
Selim Cinek3362c132016-02-11 15:43:03 -0800427 for (HeadsUpEntry entry: mHeadsUpEntries.values()) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700428 pw.print(" HeadsUpEntry="); pw.println(entry.entry);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700429 }
430 int N = mSnoozedPackages.size();
431 pw.println(" snoozed packages: " + N);
432 for (int i = 0; i < N; i++) {
433 pw.print(" "); pw.print(mSnoozedPackages.valueAt(i));
434 pw.print(", "); pw.println(mSnoozedPackages.keyAt(i));
435 }
436 }
437
438 public boolean hasPinnedHeadsUp() {
Selim Cinek684a4422015-04-15 16:18:39 -0700439 return mHasPinnedNotification;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700440 }
441
Selim Cinek684a4422015-04-15 16:18:39 -0700442 private boolean hasPinnedNotificationInternal() {
443 for (String key : mHeadsUpEntries.keySet()) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700444 HeadsUpEntry entry = mHeadsUpEntries.get(key);
Selim Cinek684a4422015-04-15 16:18:39 -0700445 if (entry.entry.row.isPinned()) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700446 return true;
447 }
448 }
449 return false;
450 }
451
Selim Cinek684a4422015-04-15 16:18:39 -0700452 /**
453 * Notifies that a notification was swiped out and will be removed.
454 *
455 * @param key the notification key
456 */
457 public void addSwipedOutNotification(String key) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700458 mSwipedOutKeys.add(key);
459 }
460
Selim Cinek684a4422015-04-15 16:18:39 -0700461 public void unpinAll() {
462 for (String key : mHeadsUpEntries.keySet()) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700463 HeadsUpEntry entry = mHeadsUpEntries.get(key);
Selim Cinek684a4422015-04-15 16:18:39 -0700464 setEntryPinned(entry, false /* isPinned */);
Selim Cinek31aada42015-12-18 17:51:15 -0800465 // maybe it got un sticky
466 entry.updateEntry(false /* updatePostTime */);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700467 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700468 }
469
470 public void onExpandingFinished() {
471 if (mReleaseOnExpandFinish) {
472 releaseAllImmediately();
473 mReleaseOnExpandFinish = false;
474 } else {
475 for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
Selim Cinekc9e68342015-11-06 11:33:20 -0800476 if (isHeadsUp(entry.key)) {
477 // Maybe the heads-up was removed already
478 removeHeadsUpEntry(entry);
479 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700480 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700481 }
Selim Cinekacd0df62015-07-29 19:09:03 -0700482 mEntriesToRemoveAfterExpand.clear();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700483 }
484
485 public void setTrackingHeadsUp(boolean trackingHeadsUp) {
486 mTrackingHeadsUp = trackingHeadsUp;
487 }
488
Selim Cinek31aada42015-12-18 17:51:15 -0800489 public boolean isTrackingHeadsUp() {
490 return mTrackingHeadsUp;
491 }
492
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700493 public void setIsExpanded(boolean isExpanded) {
Selim Cinek1f3f5442015-04-10 17:54:46 -0700494 if (isExpanded != mIsExpanded) {
495 mIsExpanded = isExpanded;
496 if (isExpanded) {
Selim Cinek737bff32015-05-08 16:08:35 -0700497 // make sure our state is sane
498 mWaitingOnCollapseWhenGoingAway = false;
499 mHeadsUpGoingAway = false;
500 updateTouchableRegionListener();
Selim Cinek1f3f5442015-04-10 17:54:46 -0700501 }
502 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700503 }
504
Selim Cinek31aada42015-12-18 17:51:15 -0800505 /**
506 * @return the height of the top heads up notification when pinned. This is different from the
507 * intrinsic height, which also includes whether the notification is system expanded and
508 * is mainly used when dragging down from a heads up notification.
509 */
510 public int getTopHeadsUpPinnedHeight() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700511 HeadsUpEntry topEntry = getTopEntry();
Selim Cinek5bc852a2015-12-21 12:19:09 -0800512 if (topEntry == null || topEntry.entry == null) {
513 return 0;
514 }
515 ExpandableNotificationRow row = topEntry.entry.row;
516 if (row.isChildInGroup()) {
517 final ExpandableNotificationRow groupSummary
518 = mGroupManager.getGroupSummary(row.getStatusBarNotification());
519 if (groupSummary != null) {
520 row = groupSummary;
521 }
522 }
Selim Cinekd127d792016-11-01 19:11:41 -0700523 return row.getPinnedHeadsUpHeight();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700524 }
525
Selim Cinek684a4422015-04-15 16:18:39 -0700526 /**
527 * Compare two entries and decide how they should be ranked.
528 *
529 * @return -1 if the first argument should be ranked higher than the second, 1 if the second
530 * one should be ranked higher and 0 if they are equal.
531 */
Selim Cinekfbe9a442015-04-13 16:09:49 -0700532 public int compare(NotificationData.Entry a, NotificationData.Entry b) {
533 HeadsUpEntry aEntry = getHeadsUpEntry(a.key);
534 HeadsUpEntry bEntry = getHeadsUpEntry(b.key);
535 if (aEntry == null || bEntry == null) {
536 return aEntry == null ? 1 : -1;
537 }
538 return aEntry.compareTo(bEntry);
539 }
540
Selim Cinek737bff32015-05-08 16:08:35 -0700541 /**
542 * Set that we are exiting the headsUp pinned mode, but some notifications might still be
543 * animating out. This is used to keep the touchable regions in a sane state.
544 */
545 public void setHeadsUpGoingAway(boolean headsUpGoingAway) {
546 if (headsUpGoingAway != mHeadsUpGoingAway) {
547 mHeadsUpGoingAway = headsUpGoingAway;
548 if (!headsUpGoingAway) {
549 waitForStatusBarLayout();
550 }
551 updateTouchableRegionListener();
552 }
553 }
554
555 /**
556 * We need to wait on the whole panel to collapse, before we can remove the touchable region
557 * listener.
558 */
559 private void waitForStatusBarLayout() {
560 mWaitingOnCollapseWhenGoingAway = true;
561 mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
562 @Override
563 public void onLayoutChange(View v, int left, int top, int right, int bottom,
564 int oldLeft,
565 int oldTop, int oldRight, int oldBottom) {
566 if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) {
567 mStatusBarWindowView.removeOnLayoutChangeListener(this);
568 mWaitingOnCollapseWhenGoingAway = false;
569 updateTouchableRegionListener();
570 }
571 }
572 });
573 }
574
Selim Cinek0fccc722015-07-29 17:04:36 -0700575 public static void setIsClickedNotification(View child, boolean clicked) {
576 child.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null);
577 }
578
579 public static boolean isClickedHeadsUpNotification(View child) {
580 Boolean clicked = (Boolean) child.getTag(TAG_CLICKED_NOTIFICATION);
581 return clicked != null && clicked;
582 }
583
Adrian Roos1c0ca502015-10-07 12:20:42 -0700584 public void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive) {
585 HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
586 if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) {
587 headsUpEntry.remoteInputActive = remoteInputActive;
588 if (remoteInputActive) {
589 headsUpEntry.removeAutoRemovalCallbacks();
590 } else {
591 headsUpEntry.updateEntry(false /* updatePostTime */);
592 }
593 }
594 }
595
Selim Cinek684a4422015-04-15 16:18:39 -0700596 /**
Selim Cinek31aada42015-12-18 17:51:15 -0800597 * Set an entry to be expanded and therefore stick in the heads up area if it's pinned
598 * until it's collapsed again.
599 */
600 public void setExpanded(NotificationData.Entry entry, boolean expanded) {
601 HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
602 if (headsUpEntry != null && headsUpEntry.expanded != expanded) {
603 headsUpEntry.expanded = expanded;
604 if (expanded) {
605 headsUpEntry.removeAutoRemovalCallbacks();
606 } else {
607 headsUpEntry.updateEntry(false /* updatePostTime */);
608 }
609 }
610 }
611
Selim Cineka7d4f822016-12-06 14:34:47 -0800612 @Override
613 public void onReorderingAllowed() {
614 for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) {
615 if (isHeadsUp(entry.key)) {
616 // Maybe the heads-up was removed already
617 removeHeadsUpEntry(entry);
618 }
619 }
620 mEntriesToRemoveWhenReorderingAllowed.clear();
621 }
622
623 public void setVisualStabilityManager(VisualStabilityManager visualStabilityManager) {
624 mVisualStabilityManager = visualStabilityManager;
625 }
626
Selim Cinek31aada42015-12-18 17:51:15 -0800627 /**
Selim Cinek684a4422015-04-15 16:18:39 -0700628 * This represents a notification and how long it is in a heads up mode. It also manages its
629 * lifecycle automatically when created.
630 */
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700631 public class HeadsUpEntry implements Comparable<HeadsUpEntry> {
632 public NotificationData.Entry entry;
633 public long postTime;
634 public long earliestRemovaltime;
635 private Runnable mRemoveHeadsUpRunnable;
Adrian Roos1c0ca502015-10-07 12:20:42 -0700636 public boolean remoteInputActive;
Selim Cinek31aada42015-12-18 17:51:15 -0800637 public boolean expanded;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700638
639 public void setEntry(final NotificationData.Entry entry) {
640 this.entry = entry;
641
642 // The actual post time will be just after the heads-up really slided in
Selim Cinek684a4422015-04-15 16:18:39 -0700643 postTime = mClock.currentTimeMillis() + mTouchAcceptanceDelay;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700644 mRemoveHeadsUpRunnable = new Runnable() {
645 @Override
646 public void run() {
Selim Cineka7d4f822016-12-06 14:34:47 -0800647 if (!mVisualStabilityManager.isReorderingAllowed()) {
648 mEntriesToRemoveWhenReorderingAllowed.add(entry);
649 mVisualStabilityManager.addReorderingAllowedCallback(HeadsUpManager.this);
650 } else if (!mTrackingHeadsUp) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700651 removeHeadsUpEntry(entry);
652 } else {
653 mEntriesToRemoveAfterExpand.add(entry);
654 }
655 }
656 };
657 updateEntry();
658 }
659
660 public void updateEntry() {
Adrian Roos1c0ca502015-10-07 12:20:42 -0700661 updateEntry(true);
662 }
663
664 public void updateEntry(boolean updatePostTime) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700665 long currentTime = mClock.currentTimeMillis();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700666 earliestRemovaltime = currentTime + mMinimumDisplayTime;
Adrian Roos1c0ca502015-10-07 12:20:42 -0700667 if (updatePostTime) {
668 postTime = Math.max(postTime, currentTime);
669 }
Selim Cinek684a4422015-04-15 16:18:39 -0700670 removeAutoRemovalCallbacks();
Selim Cinekc9e68342015-11-06 11:33:20 -0800671 if (mEntriesToRemoveAfterExpand.contains(entry)) {
672 mEntriesToRemoveAfterExpand.remove(entry);
673 }
Selim Cineka7d4f822016-12-06 14:34:47 -0800674 if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) {
675 mEntriesToRemoveWhenReorderingAllowed.remove(entry);
676 }
Selim Cinek31aada42015-12-18 17:51:15 -0800677 if (!isSticky()) {
Selim Cinek31d9ef72015-04-15 19:29:49 -0700678 long finishTime = postTime + mHeadsUpNotificationDecay;
679 long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime);
680 mHandler.postDelayed(mRemoveHeadsUpRunnable, removeDelay);
681 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700682 }
683
Selim Cinek31aada42015-12-18 17:51:15 -0800684 private boolean isSticky() {
685 return (entry.row.isPinned() && expanded)
686 || remoteInputActive || hasFullScreenIntent(entry);
687 }
688
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700689 @Override
690 public int compareTo(HeadsUpEntry o) {
Selim Cinek3362c132016-02-11 15:43:03 -0800691 boolean isPinned = entry.row.isPinned();
692 boolean otherPinned = o.entry.row.isPinned();
693 if (isPinned && !otherPinned) {
694 return -1;
695 } else if (!isPinned && otherPinned) {
696 return 1;
697 }
Selim Cinek2ae71072015-10-20 16:07:12 -0700698 boolean selfFullscreen = hasFullScreenIntent(entry);
699 boolean otherFullscreen = hasFullScreenIntent(o.entry);
700 if (selfFullscreen && !otherFullscreen) {
701 return -1;
702 } else if (!selfFullscreen && otherFullscreen) {
703 return 1;
704 }
Adrian Roose211f572016-04-08 14:24:20 -0700705
706 if (remoteInputActive && !o.remoteInputActive) {
707 return -1;
708 } else if (!remoteInputActive && o.remoteInputActive) {
709 return 1;
710 }
711
Selim Cineka59ecc32015-04-07 10:51:49 -0700712 return postTime < o.postTime ? 1
Selim Cinekf87baef2015-06-09 15:56:24 -0700713 : postTime == o.postTime ? entry.key.compareTo(o.entry.key)
Selim Cineka59ecc32015-04-07 10:51:49 -0700714 : -1;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700715 }
716
Selim Cinek684a4422015-04-15 16:18:39 -0700717 public void removeAutoRemovalCallbacks() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700718 mHandler.removeCallbacks(mRemoveHeadsUpRunnable);
719 }
720
721 public boolean wasShownLongEnough() {
722 return earliestRemovaltime < mClock.currentTimeMillis();
723 }
724
Selim Cinek684a4422015-04-15 16:18:39 -0700725 public void removeAsSoonAsPossible() {
726 removeAutoRemovalCallbacks();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700727 mHandler.postDelayed(mRemoveHeadsUpRunnable,
728 earliestRemovaltime - mClock.currentTimeMillis());
729 }
Selim Cinek684a4422015-04-15 16:18:39 -0700730
731 public void reset() {
732 removeAutoRemovalCallbacks();
733 entry = null;
734 mRemoveHeadsUpRunnable = null;
Selim Cinek31aada42015-12-18 17:51:15 -0800735 expanded = false;
736 remoteInputActive = false;
Selim Cinek684a4422015-04-15 16:18:39 -0700737 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700738 }
739
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700740 public static class Clock {
741 public long currentTimeMillis() {
742 return SystemClock.elapsedRealtime();
743 }
744 }
745
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700746}