1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 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 17 package com.android.launcher3.notification; 18 19 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity; 20 21 import android.animation.Animator; 22 import android.animation.ObjectAnimator; 23 import android.annotation.TargetApi; 24 import android.content.Context; 25 import android.content.res.ColorStateList; 26 import android.graphics.drawable.ColorDrawable; 27 import android.graphics.drawable.RippleDrawable; 28 import android.os.Build; 29 import android.text.TextUtils; 30 import android.util.AttributeSet; 31 import android.util.FloatProperty; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.widget.FrameLayout; 35 import android.widget.TextView; 36 37 import com.android.launcher3.ItemInfo; 38 import com.android.launcher3.Launcher; 39 import com.android.launcher3.R; 40 import com.android.launcher3.anim.AnimationSuccessListener; 41 import com.android.launcher3.touch.OverScroll; 42 import com.android.launcher3.touch.SwipeDetector; 43 import com.android.launcher3.userevent.nano.LauncherLogProto; 44 import com.android.launcher3.util.Themes; 45 46 /** 47 * A {@link android.widget.FrameLayout} that contains a single notification, 48 * e.g. icon + title + text. 49 */ 50 @TargetApi(Build.VERSION_CODES.N) 51 public class NotificationMainView extends FrameLayout implements SwipeDetector.Listener { 52 53 private static FloatProperty<NotificationMainView> CONTENT_TRANSLATION = 54 new FloatProperty<NotificationMainView>("contentTranslation") { 55 @Override 56 public void setValue(NotificationMainView view, float v) { 57 view.setContentTranslation(v); 58 } 59 60 @Override 61 public Float get(NotificationMainView view) { 62 return view.mTextAndBackground.getTranslationX(); 63 } 64 }; 65 66 // This is used only to track the notification view, so that it can be properly logged. 67 public static final ItemInfo NOTIFICATION_ITEM_INFO = new ItemInfo(); 68 69 private final ObjectAnimator mContentTranslateAnimator; 70 71 private NotificationInfo mNotificationInfo; 72 private ViewGroup mTextAndBackground; 73 private int mBackgroundColor; 74 private TextView mTitleView; 75 private TextView mTextView; 76 private View mIconView; 77 78 private SwipeDetector mSwipeDetector; 79 NotificationMainView(Context context)80 public NotificationMainView(Context context) { 81 this(context, null, 0); 82 } 83 NotificationMainView(Context context, AttributeSet attrs)84 public NotificationMainView(Context context, AttributeSet attrs) { 85 this(context, attrs, 0); 86 } 87 NotificationMainView(Context context, AttributeSet attrs, int defStyle)88 public NotificationMainView(Context context, AttributeSet attrs, int defStyle) { 89 super(context, attrs, defStyle); 90 91 mContentTranslateAnimator = ObjectAnimator.ofFloat(this, CONTENT_TRANSLATION, 0); 92 } 93 94 @Override onFinishInflate()95 protected void onFinishInflate() { 96 super.onFinishInflate(); 97 98 mTextAndBackground = findViewById(R.id.text_and_background); 99 ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground(); 100 mBackgroundColor = colorBackground.getColor(); 101 RippleDrawable rippleBackground = new RippleDrawable(ColorStateList.valueOf( 102 Themes.getAttrColor(getContext(), android.R.attr.colorControlHighlight)), 103 colorBackground, null); 104 mTextAndBackground.setBackground(rippleBackground); 105 mTitleView = mTextAndBackground.findViewById(R.id.title); 106 mTextView = mTextAndBackground.findViewById(R.id.text); 107 mIconView = findViewById(R.id.popup_item_icon); 108 } 109 setSwipeDetector(SwipeDetector swipeDetector)110 public void setSwipeDetector(SwipeDetector swipeDetector) { 111 mSwipeDetector = swipeDetector; 112 } 113 114 /** 115 * Sets the content of this view, animating it after a new icon shifts up if necessary. 116 */ applyNotificationInfo(NotificationInfo mainNotification, boolean animate)117 public void applyNotificationInfo(NotificationInfo mainNotification, boolean animate) { 118 mNotificationInfo = mainNotification; 119 CharSequence title = mNotificationInfo.title; 120 CharSequence text = mNotificationInfo.text; 121 if (!TextUtils.isEmpty(title) && !TextUtils.isEmpty(text)) { 122 mTitleView.setText(title.toString()); 123 mTextView.setText(text.toString()); 124 } else { 125 mTitleView.setMaxLines(2); 126 mTitleView.setText(TextUtils.isEmpty(title) ? text.toString() : title.toString()); 127 mTextView.setVisibility(GONE); 128 } 129 mIconView.setBackground(mNotificationInfo.getIconForBackground(getContext(), 130 mBackgroundColor)); 131 if (mNotificationInfo.intent != null) { 132 setOnClickListener(mNotificationInfo); 133 } 134 setContentTranslation(0); 135 // Add a dummy ItemInfo so that logging populates the correct container and item types 136 // instead of DEFAULT_CONTAINERTYPE and DEFAULT_ITEMTYPE, respectively. 137 setTag(NOTIFICATION_ITEM_INFO); 138 if (animate) { 139 ObjectAnimator.ofFloat(mTextAndBackground, ALPHA, 0, 1).setDuration(150).start(); 140 } 141 } 142 setContentTranslation(float translation)143 public void setContentTranslation(float translation) { 144 mTextAndBackground.setTranslationX(translation); 145 mIconView.setTranslationX(translation); 146 } 147 setContentVisibility(int visibility)148 public void setContentVisibility(int visibility) { 149 mTextAndBackground.setVisibility(visibility); 150 mIconView.setVisibility(visibility); 151 } 152 getNotificationInfo()153 public NotificationInfo getNotificationInfo() { 154 return mNotificationInfo; 155 } 156 157 canChildBeDismissed()158 public boolean canChildBeDismissed() { 159 return mNotificationInfo != null && mNotificationInfo.dismissable; 160 } 161 onChildDismissed()162 public void onChildDismissed() { 163 Launcher launcher = Launcher.getLauncher(getContext()); 164 launcher.getPopupDataProvider().cancelNotification( 165 mNotificationInfo.notificationKey); 166 launcher.getUserEventDispatcher().logActionOnItem( 167 LauncherLogProto.Action.Touch.SWIPE, 168 LauncherLogProto.Action.Direction.RIGHT, // Assume all swipes are right for logging. 169 LauncherLogProto.ItemType.NOTIFICATION); 170 } 171 172 // SwipeDetector.Listener's 173 @Override onDragStart(boolean start)174 public void onDragStart(boolean start) { } 175 176 177 @Override onDrag(float displacement, float velocity)178 public boolean onDrag(float displacement, float velocity) { 179 setContentTranslation(canChildBeDismissed() 180 ? displacement : OverScroll.dampedScroll(displacement, getWidth())); 181 mContentTranslateAnimator.cancel(); 182 return true; 183 } 184 185 @Override onDragEnd(float velocity, boolean fling)186 public void onDragEnd(float velocity, boolean fling) { 187 final boolean willExit; 188 final float endTranslation; 189 final float startTranslation = mTextAndBackground.getTranslationX(); 190 191 if (!canChildBeDismissed()) { 192 willExit = false; 193 endTranslation = 0; 194 } else if (fling) { 195 willExit = true; 196 endTranslation = velocity < 0 ? - getWidth() : getWidth(); 197 } else if (Math.abs(startTranslation) > getWidth() / 2) { 198 willExit = true; 199 endTranslation = (startTranslation < 0 ? -getWidth() : getWidth()); 200 } else { 201 willExit = false; 202 endTranslation = 0; 203 } 204 205 long duration = SwipeDetector.calculateDuration(velocity, 206 (endTranslation - startTranslation) / getWidth()); 207 208 mContentTranslateAnimator.removeAllListeners(); 209 mContentTranslateAnimator.setDuration(duration) 210 .setInterpolator(scrollInterpolatorForVelocity(velocity)); 211 mContentTranslateAnimator.setFloatValues(startTranslation, endTranslation); 212 mContentTranslateAnimator.addListener(new AnimationSuccessListener() { 213 @Override 214 public void onAnimationSuccess(Animator animator) { 215 mSwipeDetector.finishedScrolling(); 216 if (willExit) { 217 onChildDismissed(); 218 } 219 } 220 }); 221 mContentTranslateAnimator.start(); 222 } 223 } 224