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 NotificationListener listener = NotificationListener.getInstanceIfConnected(); 120 if (listener != null) { 121 listener.setNotificationsShown(new String[] {mNotificationInfo.notificationKey}); 122 } 123 CharSequence title = mNotificationInfo.title; 124 CharSequence text = mNotificationInfo.text; 125 if (!TextUtils.isEmpty(title) && !TextUtils.isEmpty(text)) { 126 mTitleView.setText(title.toString()); 127 mTextView.setText(text.toString()); 128 } else { 129 mTitleView.setMaxLines(2); 130 mTitleView.setText(TextUtils.isEmpty(title) ? text.toString() : title.toString()); 131 mTextView.setVisibility(GONE); 132 } 133 mIconView.setBackground(mNotificationInfo.getIconForBackground(getContext(), 134 mBackgroundColor)); 135 if (mNotificationInfo.intent != null) { 136 setOnClickListener(mNotificationInfo); 137 } 138 setContentTranslation(0); 139 // Add a dummy ItemInfo so that logging populates the correct container and item types 140 // instead of DEFAULT_CONTAINERTYPE and DEFAULT_ITEMTYPE, respectively. 141 setTag(NOTIFICATION_ITEM_INFO); 142 if (animate) { 143 ObjectAnimator.ofFloat(mTextAndBackground, ALPHA, 0, 1).setDuration(150).start(); 144 } 145 } 146 setContentTranslation(float translation)147 public void setContentTranslation(float translation) { 148 mTextAndBackground.setTranslationX(translation); 149 mIconView.setTranslationX(translation); 150 } 151 setContentVisibility(int visibility)152 public void setContentVisibility(int visibility) { 153 mTextAndBackground.setVisibility(visibility); 154 mIconView.setVisibility(visibility); 155 } 156 getNotificationInfo()157 public NotificationInfo getNotificationInfo() { 158 return mNotificationInfo; 159 } 160 161 canChildBeDismissed()162 public boolean canChildBeDismissed() { 163 return mNotificationInfo != null && mNotificationInfo.dismissable; 164 } 165 onChildDismissed()166 public void onChildDismissed() { 167 Launcher launcher = Launcher.getLauncher(getContext()); 168 launcher.getPopupDataProvider().cancelNotification( 169 mNotificationInfo.notificationKey); 170 launcher.getUserEventDispatcher().logActionOnItem( 171 LauncherLogProto.Action.Touch.SWIPE, 172 LauncherLogProto.Action.Direction.RIGHT, // Assume all swipes are right for logging. 173 LauncherLogProto.ItemType.NOTIFICATION); 174 } 175 176 // SwipeDetector.Listener's 177 @Override onDragStart(boolean start)178 public void onDragStart(boolean start) { } 179 180 181 @Override onDrag(float displacement)182 public boolean onDrag(float displacement) { 183 setContentTranslation(canChildBeDismissed() 184 ? displacement : OverScroll.dampedScroll(displacement, getWidth())); 185 mContentTranslateAnimator.cancel(); 186 return true; 187 } 188 189 @Override onDragEnd(float velocity, boolean fling)190 public void onDragEnd(float velocity, boolean fling) { 191 final boolean willExit; 192 final float endTranslation; 193 final float startTranslation = mTextAndBackground.getTranslationX(); 194 195 if (!canChildBeDismissed()) { 196 willExit = false; 197 endTranslation = 0; 198 } else if (fling) { 199 willExit = true; 200 endTranslation = velocity < 0 ? - getWidth() : getWidth(); 201 } else if (Math.abs(startTranslation) > getWidth() / 2) { 202 willExit = true; 203 endTranslation = (startTranslation < 0 ? -getWidth() : getWidth()); 204 } else { 205 willExit = false; 206 endTranslation = 0; 207 } 208 209 long duration = SwipeDetector.calculateDuration(velocity, 210 (endTranslation - startTranslation) / getWidth()); 211 212 mContentTranslateAnimator.removeAllListeners(); 213 mContentTranslateAnimator.setDuration(duration) 214 .setInterpolator(scrollInterpolatorForVelocity(velocity)); 215 mContentTranslateAnimator.setFloatValues(startTranslation, endTranslation); 216 mContentTranslateAnimator.addListener(new AnimationSuccessListener() { 217 @Override 218 public void onAnimationSuccess(Animator animator) { 219 mSwipeDetector.finishedScrolling(); 220 if (willExit) { 221 onChildDismissed(); 222 } 223 } 224 }); 225 mContentTranslateAnimator.start(); 226 } 227 } 228