blob: 7ac59954cb576ac00e6beba09e43d71cf8de81fa [file] [log] [blame]
/*
* Copyright (C) 2016 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.notification;
import android.os.Handler;
import android.os.SystemClock;
import android.view.View;
import androidx.collection.ArraySet;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
/**
* A manager that ensures that notifications are visually stable. It will suppress reorderings
* and reorder at the right time when they are out of view.
*/
public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpable {
private static final long TEMPORARY_REORDERING_ALLOWED_DURATION = 1000;
private final ArrayList<Callback> mReorderingAllowedCallbacks = new ArrayList<>();
private final ArrayList<Callback> mGroupChangesAllowedCallbacks = new ArrayList<>();
private final Handler mHandler;
private boolean mPanelExpanded;
private boolean mScreenOn;
private boolean mReorderingAllowed;
private boolean mGroupChangedAllowed;
private boolean mIsTemporaryReorderingAllowed;
private long mTemporaryReorderingStart;
private VisibilityLocationProvider mVisibilityLocationProvider;
private ArraySet<View> mAllowedReorderViews = new ArraySet<>();
private ArraySet<NotificationEntry> mLowPriorityReorderingViews = new ArraySet<>();
private ArraySet<View> mAddedChildren = new ArraySet<>();
private boolean mPulsing;
/**
* Injected constructor. See {@link NotificationsModule}.
*/
public VisualStabilityManager(
NotificationEntryManager notificationEntryManager,
@Main Handler handler) {
mHandler = handler;
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
public void onPreEntryUpdated(NotificationEntry entry) {
final boolean ambientStateHasChanged =
entry.isAmbient() != entry.getRow().isLowPriority();
if (ambientStateHasChanged) {
// note: entries are removed in onReorderingFinished
mLowPriorityReorderingViews.add(entry);
}
}
});
}
public void setUpWithPresenter(NotificationPresenter presenter) {
}
/**
* Add a callback to invoke when reordering is allowed again.
*/
public void addReorderingAllowedCallback(Callback callback) {
if (mReorderingAllowedCallbacks.contains(callback)) {
return;
}
mReorderingAllowedCallbacks.add(callback);
}
/**
* Add a callback to invoke when group changes are allowed again.
*/
public void addGroupChangesAllowedCallback(Callback callback) {
if (mGroupChangesAllowedCallbacks.contains(callback)) {
return;
}
mGroupChangesAllowedCallbacks.add(callback);
}
/**
* Set the panel to be expanded.
*/
public void setPanelExpanded(boolean expanded) {
mPanelExpanded = expanded;
updateAllowedStates();
}
/**
* @param screenOn whether the screen is on
*/
public void setScreenOn(boolean screenOn) {
mScreenOn = screenOn;
updateAllowedStates();
}
/**
* @param pulsing whether we are currently pulsing for ambient display.
*/
public void setPulsing(boolean pulsing) {
if (mPulsing == pulsing) {
return;
}
mPulsing = pulsing;
updateAllowedStates();
}
private void updateAllowedStates() {
boolean reorderingAllowed =
(!mScreenOn || !mPanelExpanded || mIsTemporaryReorderingAllowed) && !mPulsing;
boolean changedToTrue = reorderingAllowed && !mReorderingAllowed;
mReorderingAllowed = reorderingAllowed;
if (changedToTrue) {
notifyChangeAllowed(mReorderingAllowedCallbacks);
}
boolean groupChangesAllowed = (!mScreenOn || !mPanelExpanded) && !mPulsing;
changedToTrue = groupChangesAllowed && !mGroupChangedAllowed;
mGroupChangedAllowed = groupChangesAllowed;
if (changedToTrue) {
notifyChangeAllowed(mGroupChangesAllowedCallbacks);
}
}
private void notifyChangeAllowed(ArrayList<Callback> callbacks) {
for (int i = 0; i < callbacks.size(); i++) {
callbacks.get(i).onChangeAllowed();
}
callbacks.clear();
}
/**
* @return whether reordering is currently allowed in general.
*/
public boolean isReorderingAllowed() {
return mReorderingAllowed;
}
/**
* @return whether changes in the grouping should be allowed right now.
*/
public boolean areGroupChangesAllowed() {
return mGroupChangedAllowed;
}
/**
* @return whether a specific notification is allowed to reorder. Certain notifications are
* allowed to reorder even if {@link #isReorderingAllowed()} returns false, like newly added
* notifications or heads-up notifications that are out of view.
*/
public boolean canReorderNotification(ExpandableNotificationRow row) {
if (mReorderingAllowed) {
return true;
}
if (mAddedChildren.contains(row)) {
return true;
}
if (mLowPriorityReorderingViews.contains(row.getEntry())) {
return true;
}
if (mAllowedReorderViews.contains(row)
&& !mVisibilityLocationProvider.isInVisibleLocation(row.getEntry())) {
return true;
}
return false;
}
public void setVisibilityLocationProvider(
VisibilityLocationProvider visibilityLocationProvider) {
mVisibilityLocationProvider = visibilityLocationProvider;
}
public void onReorderingFinished() {
mAllowedReorderViews.clear();
mAddedChildren.clear();
mLowPriorityReorderingViews.clear();
}
@Override
public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
if (isHeadsUp) {
// Heads up notifications should in general be allowed to reorder if they are out of
// view and stay at the current location if they aren't.
mAllowedReorderViews.add(entry.getRow());
}
}
/**
* Temporarily allows reordering of the entire shade for a period of 1000ms. Subsequent calls
* to this method will extend the timer.
*/
public void temporarilyAllowReordering() {
mHandler.removeCallbacks(mOnTemporaryReorderingExpired);
mHandler.postDelayed(mOnTemporaryReorderingExpired, TEMPORARY_REORDERING_ALLOWED_DURATION);
if (!mIsTemporaryReorderingAllowed) {
mTemporaryReorderingStart = SystemClock.elapsedRealtime();
}
mIsTemporaryReorderingAllowed = true;
updateAllowedStates();
}
private final Runnable mOnTemporaryReorderingExpired = () -> {
mIsTemporaryReorderingAllowed = false;
updateAllowedStates();
};
/**
* Notify the visual stability manager that a new view was added and should be allowed to
* reorder next time.
*/
public void notifyViewAddition(View view) {
mAddedChildren.add(view);
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("VisualStabilityManager state:");
pw.print(" mIsTemporaryReorderingAllowed="); pw.println(mIsTemporaryReorderingAllowed);
pw.print(" mTemporaryReorderingStart="); pw.println(mTemporaryReorderingStart);
long now = SystemClock.elapsedRealtime();
pw.print(" Temporary reordering window has been open for ");
pw.print(now - (mIsTemporaryReorderingAllowed ? mTemporaryReorderingStart : now));
pw.println("ms");
pw.println();
}
public interface Callback {
/**
* Called when changing is allowed again.
*/
void onChangeAllowed();
}
}