blob: 6694b93a34f7e8eae386314c0a4642a7fcb16636 [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
yoshiki iguchi4e30e762018-02-06 12:09:23 +090019import android.annotation.NonNull;
20import android.annotation.Nullable;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070021import android.content.Context;
22import android.content.res.Resources;
23import android.database.ContentObserver;
yoshiki iguchi4e30e762018-02-06 12:09:23 +090024import android.os.SystemClock;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070025import android.os.Handler;
Selim Cinek5cf1d052017-06-01 17:36:46 -070026import android.os.Looper;
yoshiki iguchi0adf6a62018-02-01 13:46:26 +090027import android.util.ArrayMap;
yoshiki iguchi4e30e762018-02-06 12:09:23 +090028import android.provider.Settings;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070029import android.util.Log;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070030import android.view.accessibility.AccessibilityEvent;
31
Chris Wrenb659c4f2015-06-25 17:12:27 -040032import com.android.internal.logging.MetricsLogger;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070033import com.android.systemui.R;
Rohan Shah20790b82018-07-02 17:21:04 -070034import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
35import com.android.systemui.statusbar.notification.NotificationData;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070036
37import java.io.FileDescriptor;
38import java.io.PrintWriter;
yoshiki iguchi4e30e762018-02-06 12:09:23 +090039import java.util.Iterator;
40import java.util.stream.Stream;
Selim Cineka59ecc32015-04-07 10:51:49 -070041import java.util.HashMap;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070042import java.util.HashSet;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070043
Selim Cinek684a4422015-04-15 16:18:39 -070044/**
45 * A manager which handles heads up notifications which is a special mode where
46 * they simply peek from the top of the screen.
47 */
yoshiki iguchi4e30e762018-02-06 12:09:23 +090048public class HeadsUpManager {
Selim Cinekb8f09cf2015-03-16 17:09:28 -070049 private static final String TAG = "HeadsUpManager";
50 private static final boolean DEBUG = false;
51 private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
52
yoshiki iguchi4e30e762018-02-06 12:09:23 +090053 protected final Clock mClock = new Clock();
54 protected final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>();
55 protected final Handler mHandler = new Handler(Looper.getMainLooper());
Selim Cinekb8f09cf2015-03-16 17:09:28 -070056
yoshiki iguchi4e30e762018-02-06 12:09:23 +090057 protected final Context mContext;
58
59 protected int mHeadsUpNotificationDecay;
60 protected int mMinimumDisplayTime;
61 protected int mTouchAcceptanceDelay;
62 protected int mSnoozeLengthMs;
63 protected boolean mHasPinnedNotification;
64 protected int mUser;
65
66 private final HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
Selim Cinekb8f09cf2015-03-16 17:09:28 -070067 private final ArrayMap<String, Long> mSnoozedPackages;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070068
yoshiki iguchi4e30e762018-02-06 12:09:23 +090069 public HeadsUpManager(@NonNull final Context context) {
Chris Wrenb659c4f2015-06-25 17:12:27 -040070 mContext = context;
yoshiki iguchi4e30e762018-02-06 12:09:23 +090071 Resources resources = context.getResources();
yoshiki iguchi0adf6a62018-02-01 13:46:26 +090072 mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
73 mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay);
yoshiki iguchi4e30e762018-02-06 12:09:23 +090074 mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay);
75 mSnoozedPackages = new ArrayMap<>();
76 int defaultSnoozeLengthMs =
77 resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
Selim Cinekb8f09cf2015-03-16 17:09:28 -070078
79 mSnoozeLengthMs = Settings.Global.getInt(context.getContentResolver(),
yoshiki iguchi4e30e762018-02-06 12:09:23 +090080 SETTING_HEADS_UP_SNOOZE_LENGTH_MS, defaultSnoozeLengthMs);
81 ContentObserver settingsObserver = new ContentObserver(mHandler) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -070082 @Override
83 public void onChange(boolean selfChange) {
84 final int packageSnoozeLengthMs = Settings.Global.getInt(
85 context.getContentResolver(), SETTING_HEADS_UP_SNOOZE_LENGTH_MS, -1);
86 if (packageSnoozeLengthMs > -1 && packageSnoozeLengthMs != mSnoozeLengthMs) {
87 mSnoozeLengthMs = packageSnoozeLengthMs;
88 if (DEBUG) Log.v(TAG, "mSnoozeLengthMs = " + mSnoozeLengthMs);
89 }
90 }
91 };
92 context.getContentResolver().registerContentObserver(
93 Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false,
yoshiki iguchi4e30e762018-02-06 12:09:23 +090094 settingsObserver);
Selim Cinek737bff32015-05-08 16:08:35 -070095 }
96
yoshiki iguchi4e30e762018-02-06 12:09:23 +090097 /**
98 * Adds an OnHeadUpChangedListener to observe events.
99 */
100 public void addListener(@NonNull OnHeadsUpChangedListener listener) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700101 mListeners.add(listener);
102 }
103
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900104 /**
105 * Removes the OnHeadUpChangedListener from the observer list.
106 */
107 public void removeListener(@NonNull OnHeadsUpChangedListener listener) {
Jason Monk7ebba0b2017-04-20 14:10:20 -0400108 mListeners.remove(listener);
109 }
110
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700111 /**
112 * Called when posting a new notification to the heads up.
113 */
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900114 public void showNotification(@NonNull NotificationData.Entry headsUp) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700115 if (DEBUG) Log.v(TAG, "showNotification");
116 addHeadsUpEntry(headsUp);
117 updateNotification(headsUp, true);
118 headsUp.setInterruption();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700119 }
120
121 /**
122 * Called when updating or posting a notification to the heads up.
123 */
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900124 public void updateNotification(@NonNull NotificationData.Entry headsUp, boolean alert) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700125 if (DEBUG) Log.v(TAG, "updateNotification");
126
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700127 headsUp.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
128
129 if (alert) {
130 HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(headsUp.key);
Selim Cinek967ed2a2016-04-08 18:29:11 -0700131 if (headsUpEntry == null) {
132 // the entry was released before this update (i.e by a listener) This can happen
133 // with the groupmanager
134 return;
135 }
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900136 headsUpEntry.updateEntry(true /* updatePostTime */);
Selim Cinek131c1e22015-05-11 19:04:49 -0700137 setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUp));
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700138 }
139 }
140
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900141 private void addHeadsUpEntry(@NonNull NotificationData.Entry entry) {
142 HeadsUpEntry headsUpEntry = createHeadsUpEntry();
Selim Cineka59ecc32015-04-07 10:51:49 -0700143 // This will also add the entry to the sortedList
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700144 headsUpEntry.setEntry(entry);
145 mHeadsUpEntries.put(entry.key, headsUpEntry);
Selim Cineka59ecc32015-04-07 10:51:49 -0700146 entry.row.setHeadsUp(true);
Selim Cinek131c1e22015-05-11 19:04:49 -0700147 setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(entry));
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700148 for (OnHeadsUpChangedListener listener : mListeners) {
Selim Cinek684a4422015-04-15 16:18:39 -0700149 listener.onHeadsUpStateChanged(entry, true);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700150 }
151 entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700152 }
153
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900154 protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationData.Entry entry) {
155 return hasFullScreenIntent(entry);
Selim Cinek131c1e22015-05-11 19:04:49 -0700156 }
157
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900158 protected boolean hasFullScreenIntent(@NonNull NotificationData.Entry entry) {
Selim Cinek131c1e22015-05-11 19:04:49 -0700159 return entry.notification.getNotification().fullScreenIntent != null;
160 }
161
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900162 protected void setEntryPinned(
163 @NonNull HeadsUpManager.HeadsUpEntry headsUpEntry, boolean isPinned) {
164 if (DEBUG) Log.v(TAG, "setEntryPinned: " + isPinned);
Selim Cinek1f3f5442015-04-10 17:54:46 -0700165 ExpandableNotificationRow row = headsUpEntry.entry.row;
Selim Cinek684a4422015-04-15 16:18:39 -0700166 if (row.isPinned() != isPinned) {
167 row.setPinned(isPinned);
168 updatePinnedMode();
169 for (OnHeadsUpChangedListener listener : mListeners) {
170 if (isPinned) {
171 listener.onHeadsUpPinned(row);
172 } else {
173 listener.onHeadsUpUnPinned(row);
Selim Cinek1f3f5442015-04-10 17:54:46 -0700174 }
175 }
176 }
177 }
178
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900179 protected void removeHeadsUpEntry(@NonNull NotificationData.Entry entry) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700180 HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key);
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900181 onHeadsUpEntryRemoved(remove);
182 }
183
184 protected void onHeadsUpEntryRemoved(@NonNull HeadsUpEntry remove) {
185 NotificationData.Entry entry = remove.entry;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700186 entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
187 entry.row.setHeadsUp(false);
Selim Cinek684a4422015-04-15 16:18:39 -0700188 setEntryPinned(remove, false /* isPinned */);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700189 for (OnHeadsUpChangedListener listener : mListeners) {
Selim Cinek684a4422015-04-15 16:18:39 -0700190 listener.onHeadsUpStateChanged(entry, false);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700191 }
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900192 releaseHeadsUpEntry(remove);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700193 }
194
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900195 protected void updatePinnedMode() {
Selim Cinek684a4422015-04-15 16:18:39 -0700196 boolean hasPinnedNotification = hasPinnedNotificationInternal();
197 if (hasPinnedNotification == mHasPinnedNotification) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700198 return;
199 }
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900200 if (DEBUG) {
201 Log.v(TAG, "Pinned mode changed: " + mHasPinnedNotification + " -> " +
202 hasPinnedNotification);
203 }
Selim Cinek684a4422015-04-15 16:18:39 -0700204 mHasPinnedNotification = hasPinnedNotification;
Chris Wren063926b2015-10-16 16:24:20 -0400205 if (mHasPinnedNotification) {
206 MetricsLogger.count(mContext, "note_peek", 1);
207 }
Selim Cinek684a4422015-04-15 16:18:39 -0700208 for (OnHeadsUpChangedListener listener : mListeners) {
John Spurlockb349af572015-04-29 12:24:19 -0400209 listener.onHeadsUpPinnedModeChanged(hasPinnedNotification);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700210 }
211 }
212
213 /**
214 * React to the removal of the notification in the heads up.
215 *
216 * @return true if the notification was removed and false if it still needs to be kept around
217 * for a bit since it wasn't shown long enough
218 */
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900219 public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) {
220 if (DEBUG) Log.v(TAG, "removeNotification");
221 releaseImmediately(key);
222 return true;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700223 }
224
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900225 /**
226 * Returns if the given notification is in the Heads Up Notification list or not.
227 */
228 public boolean isHeadsUp(@NonNull String key) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700229 return mHeadsUpEntries.containsKey(key);
230 }
231
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700232 /**
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900233 * Pushes any current Heads Up notification down into the shade.
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700234 */
235 public void releaseAllImmediately() {
236 if (DEBUG) Log.v(TAG, "releaseAllImmediately");
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900237 Iterator<HeadsUpEntry> iterator = mHeadsUpEntries.values().iterator();
238 while (iterator.hasNext()) {
239 HeadsUpEntry entry = iterator.next();
240 iterator.remove();
241 onHeadsUpEntryRemoved(entry);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700242 }
243 }
244
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900245 /**
246 * Pushes the given Heads Up notification down into the shade.
247 */
248 public void releaseImmediately(@NonNull String key) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700249 HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
250 if (headsUpEntry == null) {
251 return;
252 }
253 NotificationData.Entry shadeEntry = headsUpEntry.entry;
254 removeHeadsUpEntry(shadeEntry);
255 }
256
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900257 /**
258 * Returns if the given notification is snoozed or not.
259 */
260 public boolean isSnoozed(@NonNull String packageName) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700261 final String key = snoozeKey(packageName, mUser);
262 Long snoozedUntil = mSnoozedPackages.get(key);
263 if (snoozedUntil != null) {
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900264 if (snoozedUntil > mClock.currentTimeMillis()) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700265 if (DEBUG) Log.v(TAG, key + " snoozed");
266 return true;
267 }
268 mSnoozedPackages.remove(packageName);
269 }
270 return false;
271 }
272
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900273 /**
274 * Snoozes all current Heads Up Notifications.
275 */
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700276 public void snooze() {
Selim Cinek684a4422015-04-15 16:18:39 -0700277 for (String key : mHeadsUpEntries.keySet()) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700278 HeadsUpEntry entry = mHeadsUpEntries.get(key);
279 String packageName = entry.entry.notification.getPackageName();
280 mSnoozedPackages.put(snoozeKey(packageName, mUser),
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900281 mClock.currentTimeMillis() + mSnoozeLengthMs);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700282 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700283 }
284
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900285 @NonNull
286 private static String snoozeKey(@NonNull String packageName, int user) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700287 return user + "," + packageName;
288 }
289
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900290 @Nullable
291 protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700292 return mHeadsUpEntries.get(key);
293 }
294
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900295 /**
296 * Returns the entry of given Heads Up Notification.
297 *
298 * @param key Key of heads up notification
299 */
300 @Nullable
301 public NotificationData.Entry getEntry(@NonNull String key) {
302 HeadsUpEntry entry = mHeadsUpEntries.get(key);
303 return entry != null ? entry.entry : null;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700304 }
305
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900306 /**
307 * Returns the stream of all current Heads Up Notifications.
308 */
309 @NonNull
310 public Stream<NotificationData.Entry> getAllEntries() {
311 return mHeadsUpEntries.values().stream().map(headsUpEntry -> headsUpEntry.entry);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700312 }
313
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900314 /**
315 * Returns the top Heads Up Notification, which appeares to show at first.
316 */
317 @Nullable
318 public NotificationData.Entry getTopEntry() {
319 HeadsUpEntry topEntry = getTopHeadsUpEntry();
320 return (topEntry != null) ? topEntry.entry : null;
321 }
322
323 /**
324 * Returns if any heads up notification is available or not.
325 */
326 public boolean hasHeadsUpNotifications() {
327 return !mHeadsUpEntries.isEmpty();
328 }
329
330 @Nullable
331 protected HeadsUpEntry getTopHeadsUpEntry() {
Selim Cinek3362c132016-02-11 15:43:03 -0800332 if (mHeadsUpEntries.isEmpty()) {
333 return null;
334 }
335 HeadsUpEntry topEntry = null;
336 for (HeadsUpEntry entry: mHeadsUpEntries.values()) {
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900337 if (topEntry == null || entry.compareTo(topEntry) < 0) {
Selim Cinek3362c132016-02-11 15:43:03 -0800338 topEntry = entry;
339 }
340 }
341 return topEntry;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700342 }
343
344 /**
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900345 * Sets the current user.
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700346 */
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700347 public void setUser(int user) {
348 mUser = user;
349 }
350
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900351 public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700352 pw.println("HeadsUpManager state:");
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900353 dumpInternal(fd, pw, args);
354 }
355
356 protected void dumpInternal(
357 @NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
Selim Cinek684a4422015-04-15 16:18:39 -0700358 pw.print(" mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700359 pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900360 pw.print(" now="); pw.println(mClock.currentTimeMillis());
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700361 pw.print(" mUser="); pw.println(mUser);
Selim Cinek3362c132016-02-11 15:43:03 -0800362 for (HeadsUpEntry entry: mHeadsUpEntries.values()) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700363 pw.print(" HeadsUpEntry="); pw.println(entry.entry);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700364 }
365 int N = mSnoozedPackages.size();
366 pw.println(" snoozed packages: " + N);
367 for (int i = 0; i < N; i++) {
368 pw.print(" "); pw.print(mSnoozedPackages.valueAt(i));
369 pw.print(", "); pw.println(mSnoozedPackages.keyAt(i));
370 }
371 }
372
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900373 /**
374 * Returns if there are any pinned Heads Up Notifications or not.
375 */
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700376 public boolean hasPinnedHeadsUp() {
Selim Cinek684a4422015-04-15 16:18:39 -0700377 return mHasPinnedNotification;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700378 }
379
Selim Cinek684a4422015-04-15 16:18:39 -0700380 private boolean hasPinnedNotificationInternal() {
381 for (String key : mHeadsUpEntries.keySet()) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700382 HeadsUpEntry entry = mHeadsUpEntries.get(key);
Selim Cinek684a4422015-04-15 16:18:39 -0700383 if (entry.entry.row.isPinned()) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700384 return true;
385 }
386 }
387 return false;
388 }
389
Selim Cinek684a4422015-04-15 16:18:39 -0700390 /**
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900391 * Unpins all pinned Heads Up Notifications.
Selim Cinek684a4422015-04-15 16:18:39 -0700392 */
Selim Cinek684a4422015-04-15 16:18:39 -0700393 public void unpinAll() {
394 for (String key : mHeadsUpEntries.keySet()) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700395 HeadsUpEntry entry = mHeadsUpEntries.get(key);
Selim Cinek684a4422015-04-15 16:18:39 -0700396 setEntryPinned(entry, false /* isPinned */);
Selim Cinek31aada42015-12-18 17:51:15 -0800397 // maybe it got un sticky
398 entry.updateEntry(false /* updatePostTime */);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700399 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700400 }
401
yoshiki iguchi0adf6a62018-02-01 13:46:26 +0900402 /**
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900403 * Returns the value of the tracking-heads-up flag. See the doc of {@code setTrackingHeadsUp} as
404 * well.
yoshiki iguchi0adf6a62018-02-01 13:46:26 +0900405 */
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900406 public boolean isTrackingHeadsUp() {
407 // Might be implemented in subclass.
408 return false;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700409 }
410
Selim Cinek684a4422015-04-15 16:18:39 -0700411 /**
412 * Compare two entries and decide how they should be ranked.
413 *
414 * @return -1 if the first argument should be ranked higher than the second, 1 if the second
415 * one should be ranked higher and 0 if they are equal.
416 */
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900417 public int compare(@NonNull NotificationData.Entry a, @NonNull NotificationData.Entry b) {
Selim Cinekfbe9a442015-04-13 16:09:49 -0700418 HeadsUpEntry aEntry = getHeadsUpEntry(a.key);
419 HeadsUpEntry bEntry = getHeadsUpEntry(b.key);
420 if (aEntry == null || bEntry == null) {
421 return aEntry == null ? 1 : -1;
422 }
423 return aEntry.compareTo(bEntry);
424 }
425
Selim Cinek737bff32015-05-08 16:08:35 -0700426 /**
Selim Cinek31aada42015-12-18 17:51:15 -0800427 * Set an entry to be expanded and therefore stick in the heads up area if it's pinned
428 * until it's collapsed again.
429 */
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900430 public void setExpanded(@NonNull NotificationData.Entry entry, boolean expanded) {
431 HeadsUpManager.HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
432 if (headsUpEntry != null && entry.row.isPinned()) {
433 headsUpEntry.expanded(expanded);
Selim Cinek31aada42015-12-18 17:51:15 -0800434 }
435 }
436
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900437 @NonNull
438 protected HeadsUpEntry createHeadsUpEntry() {
439 return new HeadsUpEntry();
Selim Cineka7d4f822016-12-06 14:34:47 -0800440 }
441
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900442 protected void releaseHeadsUpEntry(@NonNull HeadsUpEntry entry) {
443 entry.reset();
Selim Cinek01ee2cd52016-12-21 18:23:11 +0100444 }
445
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800446 public void onDensityOrFontScaleChanged() {
447 }
448
Selim Cinek31aada42015-12-18 17:51:15 -0800449 /**
Selim Cinek684a4422015-04-15 16:18:39 -0700450 * This represents a notification and how long it is in a heads up mode. It also manages its
451 * lifecycle automatically when created.
452 */
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900453 protected class HeadsUpEntry implements Comparable<HeadsUpEntry> {
454 @Nullable public NotificationData.Entry entry;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700455 public long postTime;
yoshiki iguchi0adf6a62018-02-01 13:46:26 +0900456 public boolean remoteInputActive;
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900457 public long earliestRemovaltime;
Selim Cinek31aada42015-12-18 17:51:15 -0800458 public boolean expanded;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700459
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900460 @Nullable private Runnable mRemoveHeadsUpRunnable;
461
462 public void setEntry(@Nullable final NotificationData.Entry entry) {
463 setEntry(entry, null);
464 }
465
466 public void setEntry(@Nullable final NotificationData.Entry entry,
467 @Nullable Runnable removeHeadsUpRunnable) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700468 this.entry = entry;
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900469 this.mRemoveHeadsUpRunnable = removeHeadsUpRunnable;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700470
471 // The actual post time will be just after the heads-up really slided in
Selim Cinek684a4422015-04-15 16:18:39 -0700472 postTime = mClock.currentTimeMillis() + mTouchAcceptanceDelay;
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900473 updateEntry(true /* updatePostTime */);
Adrian Roos1c0ca502015-10-07 12:20:42 -0700474 }
475
476 public void updateEntry(boolean updatePostTime) {
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900477 if (DEBUG) Log.v(TAG, "updateEntry");
478
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700479 long currentTime = mClock.currentTimeMillis();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700480 earliestRemovaltime = currentTime + mMinimumDisplayTime;
Adrian Roos1c0ca502015-10-07 12:20:42 -0700481 if (updatePostTime) {
482 postTime = Math.max(postTime, currentTime);
483 }
Selim Cinek684a4422015-04-15 16:18:39 -0700484 removeAutoRemovalCallbacks();
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900485
Selim Cinek31aada42015-12-18 17:51:15 -0800486 if (!isSticky()) {
Selim Cinek31d9ef72015-04-15 19:29:49 -0700487 long finishTime = postTime + mHeadsUpNotificationDecay;
488 long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime);
489 mHandler.postDelayed(mRemoveHeadsUpRunnable, removeDelay);
490 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700491 }
492
Selim Cinek31aada42015-12-18 17:51:15 -0800493 private boolean isSticky() {
494 return (entry.row.isPinned() && expanded)
495 || remoteInputActive || hasFullScreenIntent(entry);
496 }
497
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700498 @Override
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900499 public int compareTo(@NonNull HeadsUpEntry o) {
Selim Cinek3362c132016-02-11 15:43:03 -0800500 boolean isPinned = entry.row.isPinned();
501 boolean otherPinned = o.entry.row.isPinned();
502 if (isPinned && !otherPinned) {
503 return -1;
504 } else if (!isPinned && otherPinned) {
505 return 1;
506 }
Selim Cinek2ae71072015-10-20 16:07:12 -0700507 boolean selfFullscreen = hasFullScreenIntent(entry);
508 boolean otherFullscreen = hasFullScreenIntent(o.entry);
509 if (selfFullscreen && !otherFullscreen) {
510 return -1;
511 } else if (!selfFullscreen && otherFullscreen) {
512 return 1;
513 }
Adrian Roose211f572016-04-08 14:24:20 -0700514
515 if (remoteInputActive && !o.remoteInputActive) {
516 return -1;
517 } else if (!remoteInputActive && o.remoteInputActive) {
518 return 1;
519 }
520
Selim Cineka59ecc32015-04-07 10:51:49 -0700521 return postTime < o.postTime ? 1
Selim Cinekf87baef2015-06-09 15:56:24 -0700522 : postTime == o.postTime ? entry.key.compareTo(o.entry.key)
Selim Cineka59ecc32015-04-07 10:51:49 -0700523 : -1;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700524 }
525
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900526 public void expanded(boolean expanded) {
527 this.expanded = expanded;
yoshiki iguchi0adf6a62018-02-01 13:46:26 +0900528 }
529
530 public void reset() {
yoshiki iguchi0adf6a62018-02-01 13:46:26 +0900531 entry = null;
yoshiki iguchi0adf6a62018-02-01 13:46:26 +0900532 expanded = false;
533 remoteInputActive = false;
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900534 removeAutoRemovalCallbacks();
535 mRemoveHeadsUpRunnable = null;
536 }
537
538 public void removeAutoRemovalCallbacks() {
539 if (mRemoveHeadsUpRunnable != null)
540 mHandler.removeCallbacks(mRemoveHeadsUpRunnable);
541 }
542
543 public void removeAsSoonAsPossible() {
544 if (mRemoveHeadsUpRunnable != null) {
545 removeAutoRemovalCallbacks();
546 mHandler.postDelayed(mRemoveHeadsUpRunnable,
547 earliestRemovaltime - mClock.currentTimeMillis());
548 }
Selim Cinek684a4422015-04-15 16:18:39 -0700549 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700550 }
551
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700552 public static class Clock {
553 public long currentTimeMillis() {
554 return SystemClock.elapsedRealtime();
555 }
556 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700557}