f3bbd98bf8
Bug: 193014051
Test: - dismiss notifications (left/right)
- open popup, dismiss notification from shade
and ensure popup gets updates
- open popup, trigger new notification
and ensure popup gets updated
Change-Id: Iea4d458218cbf5cb22f5f89aa0a4cc1bee18cc73
284 lines
10 KiB
Java
284 lines
10 KiB
Java
/*
|
|
* Copyright (C) 2021 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.launcher3.notification;
|
|
|
|
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
|
|
import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
|
|
|
|
import android.animation.Animator;
|
|
import android.animation.AnimatorSet;
|
|
import android.animation.ObjectAnimator;
|
|
import android.content.Context;
|
|
import android.graphics.Rect;
|
|
import android.util.AttributeSet;
|
|
import android.util.FloatProperty;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.widget.FrameLayout;
|
|
|
|
import com.android.launcher3.R;
|
|
import com.android.launcher3.anim.AnimationSuccessListener;
|
|
import com.android.launcher3.popup.PopupContainerWithArrow;
|
|
import com.android.launcher3.touch.BaseSwipeDetector;
|
|
import com.android.launcher3.touch.OverScroll;
|
|
import com.android.launcher3.touch.SingleAxisSwipeDetector;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* Class to manage the notification UI in a {@link PopupContainerWithArrow}.
|
|
*
|
|
* - Has two {@link NotificationMainView} that represent the top two notifications
|
|
* - Handles dismissing a notification
|
|
*/
|
|
public class NotificationContainer extends FrameLayout implements SingleAxisSwipeDetector.Listener {
|
|
|
|
private static final FloatProperty<NotificationContainer> DRAG_TRANSLATION_X =
|
|
new FloatProperty<NotificationContainer>("notificationProgress") {
|
|
@Override
|
|
public void setValue(NotificationContainer view, float transX) {
|
|
view.setDragTranslationX(transX);
|
|
}
|
|
|
|
@Override
|
|
public Float get(NotificationContainer view) {
|
|
return view.mDragTranslationX;
|
|
}
|
|
};
|
|
|
|
private static final Rect sTempRect = new Rect();
|
|
|
|
private final SingleAxisSwipeDetector mSwipeDetector;
|
|
private final List<NotificationInfo> mNotificationInfos = new ArrayList<>();
|
|
private boolean mIgnoreTouch = false;
|
|
|
|
private final ObjectAnimator mContentTranslateAnimator;
|
|
private float mDragTranslationX = 0;
|
|
|
|
private final NotificationMainView mPrimaryView;
|
|
private final NotificationMainView mSecondaryView;
|
|
private PopupContainerWithArrow mPopupContainer;
|
|
|
|
public NotificationContainer(Context context) {
|
|
this(context, null, 0);
|
|
}
|
|
|
|
public NotificationContainer(Context context, AttributeSet attrs) {
|
|
this(context, attrs, 0);
|
|
}
|
|
|
|
public NotificationContainer(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
super(context, attrs, defStyleAttr);
|
|
mSwipeDetector = new SingleAxisSwipeDetector(getContext(), this, HORIZONTAL);
|
|
mSwipeDetector.setDetectableScrollConditions(SingleAxisSwipeDetector.DIRECTION_BOTH, false);
|
|
mContentTranslateAnimator = ObjectAnimator.ofFloat(this, DRAG_TRANSLATION_X, 0);
|
|
|
|
mPrimaryView = (NotificationMainView) View.inflate(getContext(),
|
|
R.layout.notification_content, null);
|
|
mSecondaryView = (NotificationMainView) View.inflate(getContext(),
|
|
R.layout.notification_content, null);
|
|
mSecondaryView.setAlpha(0);
|
|
|
|
addView(mSecondaryView);
|
|
addView(mPrimaryView);
|
|
|
|
}
|
|
|
|
public void setPopupView(PopupContainerWithArrow popupView) {
|
|
mPopupContainer = popupView;
|
|
}
|
|
|
|
/**
|
|
* Returns true if we should intercept the swipe.
|
|
*/
|
|
public boolean onInterceptSwipeEvent(MotionEvent ev) {
|
|
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
|
sTempRect.set(getLeft(), getTop(), getRight(), getBottom());
|
|
mIgnoreTouch = !sTempRect.contains((int) ev.getX(), (int) ev.getY());
|
|
if (!mIgnoreTouch) {
|
|
mPopupContainer.getParent().requestDisallowInterceptTouchEvent(true);
|
|
}
|
|
}
|
|
if (mIgnoreTouch) {
|
|
return false;
|
|
}
|
|
if (mPrimaryView.getNotificationInfo() == null) {
|
|
// The notification hasn't been populated yet.
|
|
return false;
|
|
}
|
|
|
|
mSwipeDetector.onTouchEvent(ev);
|
|
return mSwipeDetector.isDraggingOrSettling();
|
|
}
|
|
|
|
/**
|
|
* Returns true when we should handle the swipe.
|
|
*/
|
|
public boolean onSwipeEvent(MotionEvent ev) {
|
|
if (mIgnoreTouch) {
|
|
return false;
|
|
}
|
|
if (mPrimaryView.getNotificationInfo() == null) {
|
|
// The notification hasn't been populated yet.
|
|
return false;
|
|
}
|
|
return mSwipeDetector.onTouchEvent(ev);
|
|
}
|
|
|
|
/**
|
|
* Applies the list of @param notificationInfos to this container.
|
|
*/
|
|
public void applyNotificationInfos(final List<NotificationInfo> notificationInfos) {
|
|
mNotificationInfos.clear();
|
|
if (notificationInfos.isEmpty()) {
|
|
mPrimaryView.applyNotificationInfo(null);
|
|
mSecondaryView.applyNotificationInfo(null);
|
|
return;
|
|
}
|
|
mNotificationInfos.addAll(notificationInfos);
|
|
|
|
NotificationInfo mainNotification = notificationInfos.get(0);
|
|
mPrimaryView.applyNotificationInfo(mainNotification);
|
|
mSecondaryView.applyNotificationInfo(notificationInfos.size() > 1
|
|
? notificationInfos.get(1)
|
|
: null);
|
|
}
|
|
|
|
/**
|
|
* Trims the notifications.
|
|
* @param notificationKeys List of all valid notification keys.
|
|
*/
|
|
public void trimNotifications(final List<String> notificationKeys) {
|
|
Iterator<NotificationInfo> iterator = mNotificationInfos.iterator();
|
|
while (iterator.hasNext()) {
|
|
if (!notificationKeys.contains(iterator.next().notificationKey)) {
|
|
iterator.remove();
|
|
}
|
|
}
|
|
|
|
NotificationInfo primaryInfo = mNotificationInfos.size() > 0
|
|
? mNotificationInfos.get(0)
|
|
: null;
|
|
NotificationInfo secondaryInfo = mNotificationInfos.size() > 1
|
|
? mNotificationInfos.get(1)
|
|
: null;
|
|
|
|
mPrimaryView.applyNotificationInfo(primaryInfo);
|
|
mSecondaryView.applyNotificationInfo(secondaryInfo);
|
|
|
|
mPrimaryView.onPrimaryDrag(0);
|
|
mSecondaryView.onSecondaryDrag(0);
|
|
}
|
|
|
|
private void setDragTranslationX(float translationX) {
|
|
mDragTranslationX = translationX;
|
|
|
|
float progress = translationX / getWidth();
|
|
mPrimaryView.onPrimaryDrag(progress);
|
|
if (mSecondaryView.getNotificationInfo() == null) {
|
|
mSecondaryView.setAlpha(0f);
|
|
} else {
|
|
mSecondaryView.onSecondaryDrag(progress);
|
|
}
|
|
}
|
|
|
|
// SingleAxisSwipeDetector.Listener's
|
|
@Override
|
|
public void onDragStart(boolean start, float startDisplacement) {
|
|
mPopupContainer.showArrow(false);
|
|
}
|
|
|
|
@Override
|
|
public boolean onDrag(float displacement) {
|
|
if (!mPrimaryView.canChildBeDismissed()) {
|
|
displacement = OverScroll.dampedScroll(displacement, getWidth());
|
|
}
|
|
|
|
float progress = displacement / getWidth();
|
|
mPrimaryView.onPrimaryDrag(progress);
|
|
if (mSecondaryView.getNotificationInfo() == null) {
|
|
mSecondaryView.setAlpha(0f);
|
|
} else {
|
|
mSecondaryView.onSecondaryDrag(progress);
|
|
}
|
|
mContentTranslateAnimator.cancel();
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void onDragEnd(float velocity) {
|
|
final boolean willExit;
|
|
final float endTranslation;
|
|
final float startTranslation = mPrimaryView.getTranslationX();
|
|
final float width = getWidth();
|
|
|
|
if (!mPrimaryView.canChildBeDismissed()) {
|
|
willExit = false;
|
|
endTranslation = 0;
|
|
} else if (mSwipeDetector.isFling(velocity)) {
|
|
willExit = true;
|
|
endTranslation = velocity < 0 ? -width : width;
|
|
} else if (Math.abs(startTranslation) > width / 2f) {
|
|
willExit = true;
|
|
endTranslation = (startTranslation < 0 ? -width : width);
|
|
} else {
|
|
willExit = false;
|
|
endTranslation = 0;
|
|
}
|
|
|
|
long duration = BaseSwipeDetector.calculateDuration(velocity,
|
|
(endTranslation - startTranslation) / width);
|
|
|
|
mContentTranslateAnimator.removeAllListeners();
|
|
mContentTranslateAnimator.setDuration(duration)
|
|
.setInterpolator(scrollInterpolatorForVelocity(velocity));
|
|
mContentTranslateAnimator.setFloatValues(startTranslation, endTranslation);
|
|
|
|
NotificationMainView current = mPrimaryView;
|
|
mContentTranslateAnimator.addListener(new AnimationSuccessListener() {
|
|
@Override
|
|
public void onAnimationSuccess(Animator animator) {
|
|
mSwipeDetector.finishedScrolling();
|
|
if (willExit) {
|
|
current.onChildDismissed();
|
|
}
|
|
mPopupContainer.showArrow(true);
|
|
}
|
|
});
|
|
mContentTranslateAnimator.start();
|
|
}
|
|
|
|
/**
|
|
* Animates the background color to a new color.
|
|
* @param color The color to change to.
|
|
* @param animatorSetOut The AnimatorSet where we add the color animator to.
|
|
*/
|
|
public void updateBackgroundColor(int color, AnimatorSet animatorSetOut) {
|
|
mPrimaryView.updateBackgroundColor(color, animatorSetOut);
|
|
mSecondaryView.updateBackgroundColor(color, animatorSetOut);
|
|
}
|
|
|
|
/**
|
|
* Updates the header with a new @param notificationCount.
|
|
*/
|
|
public void updateHeader(int notificationCount) {
|
|
mPrimaryView.updateHeader(notificationCount);
|
|
mSecondaryView.updateHeader(notificationCount - 1);
|
|
}
|
|
}
|