summaryrefslogtreecommitdiffstats
path: root/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java')
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java519
1 files changed, 519 insertions, 0 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
new file mode 100644
index 0000000..920a0a1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -0,0 +1,519 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pools;
+import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Stack;
+import java.util.TreeSet;
+
+public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsListener {
+ private static final String TAG = "HeadsUpManager";
+ private static final boolean DEBUG = false;
+ private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
+
+ private final int mHeadsUpNotificationDecay;
+ private final int mMinimumDisplayTime;
+
+ private final int mTouchSensitivityDelay;
+ private final ArrayMap<String, Long> mSnoozedPackages;
+ private final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>();
+ private final int mDefaultSnoozeLengthMs;
+ private final Handler mHandler = new Handler();
+ private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<HeadsUpEntry>() {
+
+ private Stack<HeadsUpEntry> mPoolObjects = new Stack<>();
+
+ @Override
+ public HeadsUpEntry acquire() {
+ if (!mPoolObjects.isEmpty()) {
+ return mPoolObjects.pop();
+ }
+ return new HeadsUpEntry();
+ }
+
+ @Override
+ public boolean release(HeadsUpEntry instance) {
+ instance.removeAutoCancelCallbacks();
+ mPoolObjects.push(instance);
+ return true;
+ }
+ };
+
+
+ private PhoneStatusBar mBar;
+ private int mSnoozeLengthMs;
+ private ContentObserver mSettingsObserver;
+ private HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
+ private TreeSet<HeadsUpEntry> mSortedEntries = new TreeSet<>();
+ private HashSet<String> mSwipedOutKeys = new HashSet<>();
+ private int mUser;
+ private Clock mClock;
+ private boolean mReleaseOnExpandFinish;
+ private boolean mTrackingHeadsUp;
+ private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>();
+ private boolean mIsExpanded;
+ private boolean mHasPinnedHeadsUp;
+ private int[] mTmpTwoArray = new int[2];
+
+ public HeadsUpManager(final Context context, ViewTreeObserver observer) {
+ Resources resources = context.getResources();
+ mTouchSensitivityDelay = resources.getInteger(R.integer.heads_up_sensitivity_delay);
+ if (DEBUG) Log.v(TAG, "create() " + mTouchSensitivityDelay);
+ mSnoozedPackages = new ArrayMap<>();
+ mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
+ mSnoozeLengthMs = mDefaultSnoozeLengthMs;
+ mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
+ mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay);
+ mClock = new Clock();
+
+ mSnoozeLengthMs = Settings.Global.getInt(context.getContentResolver(),
+ SETTING_HEADS_UP_SNOOZE_LENGTH_MS, mDefaultSnoozeLengthMs);
+ mSettingsObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ final int packageSnoozeLengthMs = Settings.Global.getInt(
+ context.getContentResolver(), SETTING_HEADS_UP_SNOOZE_LENGTH_MS, -1);
+ if (packageSnoozeLengthMs > -1 && packageSnoozeLengthMs != mSnoozeLengthMs) {
+ mSnoozeLengthMs = packageSnoozeLengthMs;
+ if (DEBUG) Log.v(TAG, "mSnoozeLengthMs = " + mSnoozeLengthMs);
+ }
+ }
+ };
+ context.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false,
+ mSettingsObserver);
+ if (DEBUG) Log.v(TAG, "mSnoozeLengthMs = " + mSnoozeLengthMs);
+ observer.addOnComputeInternalInsetsListener(this);
+ }
+
+ public void setBar(PhoneStatusBar bar) {
+ mBar = bar;
+ }
+
+ public void addListener(OnHeadsUpChangedListener listener) {
+ mListeners.add(listener);
+ }
+
+ public PhoneStatusBar getBar() {
+ return mBar;
+ }
+
+ /**
+ * Called when posting a new notification to the heads up.
+ */
+ public void showNotification(NotificationData.Entry headsUp) {
+ if (DEBUG) Log.v(TAG, "showNotification");
+ addHeadsUpEntry(headsUp);
+ updateNotification(headsUp, true);
+ headsUp.setInterruption();
+ }
+
+ /**
+ * Called when updating or posting a notification to the heads up.
+ */
+ public void updateNotification(NotificationData.Entry headsUp, boolean alert) {
+ if (DEBUG) Log.v(TAG, "updateNotification");
+
+ headsUp.row.setChildrenExpanded(false /* expanded */, false /* animated */);
+ headsUp.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+
+ if (alert) {
+ HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(headsUp.key);
+ headsUpEntry.updateEntry();
+ setEntryToShade(headsUpEntry, mIsExpanded, false /* justAdded */, false);
+ }
+ }
+
+ private void addHeadsUpEntry(NotificationData.Entry entry) {
+ HeadsUpEntry headsUpEntry = mEntryPool.acquire();
+
+ // This will also add the entry to the sortedList
+ headsUpEntry.setEntry(entry);
+ mHeadsUpEntries.put(entry.key, headsUpEntry);
+ entry.row.setHeadsUp(true);
+ setEntryToShade(headsUpEntry, mIsExpanded /* inShade */, true /* justAdded */, false);
+ for (OnHeadsUpChangedListener listener : mListeners) {
+ listener.OnHeadsUpStateChanged(entry, true);
+ }
+ entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ }
+
+ private void setEntryToShade(HeadsUpEntry headsUpEntry, boolean inShade, boolean justAdded,
+ boolean forceImmediate) {
+ ExpandableNotificationRow row = headsUpEntry.entry.row;
+ if (row.isInShade() != inShade || justAdded) {
+ row.setInShade(inShade);
+ if (!justAdded || !inShade) {
+ updatePinnedHeadsUpState(forceImmediate);
+ for (OnHeadsUpChangedListener listener : mListeners) {
+ listener.OnHeadsUpPinnedChanged(row, !inShade);
+ }
+ }
+ }
+ }
+
+ private void removeHeadsUpEntry(NotificationData.Entry entry) {
+ HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key);
+ mSortedEntries.remove(remove);
+ mEntryPool.release(remove);
+ entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ entry.row.setHeadsUp(false);
+ setEntryToShade(remove, true /* inShade */, false /* justAdded */,
+ false /* forceImmediate */);
+ for (OnHeadsUpChangedListener listener : mListeners) {
+ listener.OnHeadsUpStateChanged(entry, false);
+ }
+ }
+
+ private void updatePinnedHeadsUpState(boolean forceImmediate) {
+ boolean hasPinnedHeadsUp = hasPinnedHeadsUpInternal();
+ if (hasPinnedHeadsUp == mHasPinnedHeadsUp) {
+ return;
+ }
+ mHasPinnedHeadsUp = hasPinnedHeadsUp;
+ for (OnHeadsUpChangedListener listener :mListeners) {
+ listener.OnPinnedHeadsUpExistChanged(hasPinnedHeadsUp, forceImmediate);
+ }
+ }
+
+ /**
+ * React to the removal of the notification in the heads up.
+ *
+ * @return true if the notification was removed and false if it still needs to be kept around
+ * for a bit since it wasn't shown long enough
+ */
+ public boolean removeNotification(String key) {
+ if (DEBUG) Log.v(TAG, "remove");
+ if (wasShownLongEnough(key)) {
+ releaseImmediately(key);
+ return true;
+ } else {
+ getHeadsUpEntry(key).hideAsSoonAsPossible();
+ return false;
+ }
+ }
+
+ private boolean wasShownLongEnough(String key) {
+ HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
+ HeadsUpEntry topEntry = getTopEntry();
+ if (mSwipedOutKeys.contains(key)) {
+ // We always instantly dismiss views being manually swiped out.
+ mSwipedOutKeys.remove(key);
+ return true;
+ }
+ if (headsUpEntry != topEntry) {
+ return true;
+ }
+ return headsUpEntry.wasShownLongEnough();
+ }
+
+ public boolean isHeadsUp(String key) {
+ return mHeadsUpEntries.containsKey(key);
+ }
+
+
+ /**
+ * Push any current Heads Up notification down into the shade.
+ */
+ public void releaseAllImmediately() {
+ if (DEBUG) Log.v(TAG, "releaseAllImmediately");
+ HashSet<String> keys = new HashSet<>(mHeadsUpEntries.keySet());
+ for (String key: keys) {
+ releaseImmediately(key);
+ }
+ }
+
+ public void releaseImmediately(String key) {
+ HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
+ if (headsUpEntry == null) {
+ return;
+ }
+ NotificationData.Entry shadeEntry = headsUpEntry.entry;
+ removeHeadsUpEntry(shadeEntry);
+ }
+
+ public boolean isSnoozed(String packageName) {
+ final String key = snoozeKey(packageName, mUser);
+ Long snoozedUntil = mSnoozedPackages.get(key);
+ if (snoozedUntil != null) {
+ if (snoozedUntil > SystemClock.elapsedRealtime()) {
+ if (DEBUG) Log.v(TAG, key + " snoozed");
+ return true;
+ }
+ mSnoozedPackages.remove(packageName);
+ }
+ return false;
+ }
+
+ public void snooze() {
+ for (String key: mHeadsUpEntries.keySet()) {
+ HeadsUpEntry entry = mHeadsUpEntries.get(key);
+ String packageName = entry.entry.notification.getPackageName();
+ mSnoozedPackages.put(snoozeKey(packageName, mUser),
+ SystemClock.elapsedRealtime() + mSnoozeLengthMs);
+ }
+ mReleaseOnExpandFinish = true;
+ }
+
+ private static String snoozeKey(String packageName, int user) {
+ return user + "," + packageName;
+ }
+
+ private HeadsUpEntry getHeadsUpEntry(String key) {
+ return mHeadsUpEntries.get(key);
+ }
+
+ public NotificationData.Entry getEntry(String key) {
+ return mHeadsUpEntries.get(key).entry;
+ }
+
+ public TreeSet<HeadsUpEntry> getSortedEntries() {
+ return mSortedEntries;
+ }
+
+ public HeadsUpEntry getTopEntry() {
+ return mSortedEntries.isEmpty() ? null : mSortedEntries.first();
+ }
+
+ /**
+ * @param key the key of the touched notification
+ * @return whether the touch is valid and should not be discarded
+ */
+ public boolean shouldSwallowClick(String key) {
+ if (mClock.currentTimeMillis() < mHeadsUpEntries.get(key).postTime) {
+ return true;
+ }
+ return false;
+ }
+
+ public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
+ if (!mIsExpanded && mHasPinnedHeadsUp) {
+ int minX = Integer.MAX_VALUE;
+ int maxX = 0;
+ int minY = Integer.MAX_VALUE;
+ int maxY = 0;
+ for (HeadsUpEntry entry: mSortedEntries) {
+ ExpandableNotificationRow row = entry.entry.row;
+ if (!row.isInShade()) {
+ row.getLocationOnScreen(mTmpTwoArray);
+ minX = Math.min(minX, mTmpTwoArray[0]);
+ minY = Math.min(minY, 0);
+ maxX = Math.max(maxX, mTmpTwoArray[0] + row.getWidth());
+ maxY = Math.max(maxY, row.getHeadsUpHeight());
+ }
+ }
+
+ info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+ info.touchableRegion.set(minX, minY, maxX, maxY);
+ }
+ }
+
+ public void setUser(int user) {
+ mUser = user;
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("HeadsUpManager state:");
+ pw.print(" mTouchSensitivityDelay="); pw.println(mTouchSensitivityDelay);
+ pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
+ pw.print(" now="); pw.println(SystemClock.elapsedRealtime());
+ pw.print(" mUser="); pw.println(mUser);
+ for (HeadsUpEntry entry: mSortedEntries) {
+ pw.print(" HeadsUpEntry="); pw.println(entry.entry);
+ }
+ int N = mSnoozedPackages.size();
+ pw.println(" snoozed packages: " + N);
+ for (int i = 0; i < N; i++) {
+ pw.print(" "); pw.print(mSnoozedPackages.valueAt(i));
+ pw.print(", "); pw.println(mSnoozedPackages.keyAt(i));
+ }
+ }
+
+ public boolean hasPinnedHeadsUp() {
+ return mHasPinnedHeadsUp;
+ }
+
+ private boolean hasPinnedHeadsUpInternal() {
+ for (String key: mHeadsUpEntries.keySet()) {
+ HeadsUpEntry entry = mHeadsUpEntries.get(key);
+ if (!entry.entry.row.isInShade()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void addSwipedOutKey(String key) {
+ mSwipedOutKeys.add(key);
+ }
+
+ public float getHighestPinnedHeadsUp() {
+ float max = 0;
+ for (HeadsUpEntry entry: mSortedEntries) {
+ if (!entry.entry.row.isInShade()) {
+ max = Math.max(max, entry.entry.row.getActualHeight());
+ }
+ }
+ return max;
+ }
+
+ public void releaseAllToShade() {
+ for (String key: mHeadsUpEntries.keySet()) {
+ HeadsUpEntry entry = mHeadsUpEntries.get(key);
+ setEntryToShade(entry, true /* toShade */, false /* justAdded */,
+ true /* forceImmediate */);
+ }
+ }
+
+ public void onExpandingFinished() {
+ if (mReleaseOnExpandFinish) {
+ releaseAllImmediately();
+ mReleaseOnExpandFinish = false;
+ } else {
+ for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
+ removeHeadsUpEntry(entry);
+ }
+ mEntriesToRemoveAfterExpand.clear();
+ }
+ }
+
+ public void setTrackingHeadsUp(boolean trackingHeadsUp) {
+ mTrackingHeadsUp = trackingHeadsUp;
+ }
+
+ public void setIsExpanded(boolean isExpanded) {
+ if (isExpanded != mIsExpanded) {
+ mIsExpanded = isExpanded;
+ if (isExpanded) {
+ releaseAllToShade();
+ }
+ }
+ }
+
+ public int getTopHeadsUpHeight() {
+ HeadsUpEntry topEntry = getTopEntry();
+ return topEntry != null ? topEntry.entry.row.getHeadsUpHeight() : 0;
+ }
+
+ public int compare(NotificationData.Entry a, NotificationData.Entry b) {
+ HeadsUpEntry aEntry = getHeadsUpEntry(a.key);
+ HeadsUpEntry bEntry = getHeadsUpEntry(b.key);
+ if (aEntry == null || bEntry == null) {
+ return aEntry == null ? 1 : -1;
+ }
+ return aEntry.compareTo(bEntry);
+ }
+
+ public class HeadsUpEntry implements Comparable<HeadsUpEntry> {
+ public NotificationData.Entry entry;
+ public long postTime;
+ public long earliestRemovaltime;
+ private Runnable mRemoveHeadsUpRunnable;
+
+ public void setEntry(final NotificationData.Entry entry) {
+ this.entry = entry;
+
+ // The actual post time will be just after the heads-up really slided in
+ postTime = mClock.currentTimeMillis() + mTouchSensitivityDelay;
+ mRemoveHeadsUpRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (!mTrackingHeadsUp) {
+ removeHeadsUpEntry(entry);
+ } else {
+ mEntriesToRemoveAfterExpand.add(entry);
+ }
+ }
+ };
+ updateEntry();
+ }
+
+ public void updateEntry() {
+ long currentTime = mClock.currentTimeMillis();
+ postTime = Math.max(postTime, currentTime);
+ long finishTime = postTime + mHeadsUpNotificationDecay;
+ long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime);
+ earliestRemovaltime = currentTime + mMinimumDisplayTime;
+ removeAutoCancelCallbacks();
+ mHandler.postDelayed(mRemoveHeadsUpRunnable, removeDelay);
+ updateSortOrder(HeadsUpEntry.this);
+ }
+
+ @Override
+ public int compareTo(HeadsUpEntry o) {
+ return postTime < o.postTime ? 1
+ : postTime == o.postTime ? 0
+ : -1;
+ }
+
+ public void removeAutoCancelCallbacks() {
+ mHandler.removeCallbacks(mRemoveHeadsUpRunnable);
+ }
+
+ public boolean wasShownLongEnough() {
+ return earliestRemovaltime < mClock.currentTimeMillis();
+ }
+
+ public void hideAsSoonAsPossible() {
+ removeAutoCancelCallbacks();
+ mHandler.postDelayed(mRemoveHeadsUpRunnable,
+ earliestRemovaltime - mClock.currentTimeMillis());
+ }
+ }
+
+ /**
+ * Update the sorted heads up order.
+ *
+ * @param headsUpEntry the headsUp that changed
+ */
+ private void updateSortOrder(HeadsUpEntry headsUpEntry) {
+ mSortedEntries.remove(headsUpEntry);
+ mSortedEntries.add(headsUpEntry);
+ }
+
+ public static class Clock {
+ public long currentTimeMillis() {
+ return SystemClock.elapsedRealtime();
+ }
+ }
+
+ public interface OnHeadsUpChangedListener {
+ void OnPinnedHeadsUpExistChanged(boolean exist, boolean changeImmediatly);
+ void OnHeadsUpPinnedChanged(ExpandableNotificationRow headsUp, boolean isHeadsUp);
+ void OnHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp);
+ }
+}