1 /* 2 * Copyright (C) 2011 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.systemui.statusbar.policy; 18 19 import android.animation.LayoutTransition; 20 import android.animation.ValueAnimator; 21 import android.content.Context; 22 import android.content.res.Configuration; 23 import android.graphics.Rect; 24 import android.util.AttributeSet; 25 import android.util.Log; 26 import android.view.MotionEvent; 27 import android.view.View; 28 import android.view.ViewConfiguration; 29 import android.view.ViewGroup; 30 import android.widget.LinearLayout; 31 32 import com.android.systemui.ExpandHelper; 33 import com.android.systemui.R; 34 import com.android.systemui.SwipeHelper; 35 import com.android.systemui.statusbar.ExpandableNotificationRow; 36 import com.android.systemui.statusbar.NotificationData; 37 38 import java.util.HashMap; 39 40 public class NotificationRowLayout 41 extends LinearLayout 42 implements SwipeHelper.Callback, ExpandHelper.Callback 43 { 44 private static final String TAG = "NotificationRowLayout"; 45 private static final boolean DEBUG = false; 46 private static final boolean SLOW_ANIMATIONS = DEBUG; 47 48 private static final int APPEAR_ANIM_LEN = SLOW_ANIMATIONS ? 5000 : 250; 49 private static final int DISAPPEAR_ANIM_LEN = APPEAR_ANIM_LEN; 50 51 boolean mAnimateBounds = true; 52 53 Rect mTmpRect = new Rect(); 54 55 HashMap<View, ValueAnimator> mAppearingViews = new HashMap<View, ValueAnimator>(); 56 HashMap<View, ValueAnimator> mDisappearingViews = new HashMap<View, ValueAnimator>(); 57 58 private SwipeHelper mSwipeHelper; 59 60 private OnSizeChangedListener mOnSizeChangedListener; 61 62 // Flag set during notification removal animation to avoid causing too much work until 63 // animation is done 64 boolean mRemoveViews = true; 65 66 private LayoutTransition mRealLayoutTransition; 67 68 public NotificationRowLayout(Context context, AttributeSet attrs) { 69 this(context, attrs, 0); 70 } 71 72 public NotificationRowLayout(Context context, AttributeSet attrs, int defStyle) { 73 super(context, attrs, defStyle); 74 75 mRealLayoutTransition = new LayoutTransition(); 76 mRealLayoutTransition.setAnimateParentHierarchy(true); 77 setLayoutTransitionsEnabled(true); 78 79 setOrientation(LinearLayout.VERTICAL); 80 81 if (DEBUG) { 82 setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() { 83 @Override 84 public void onChildViewAdded(View parent, View child) { 85 Log.d(TAG, "view added: " + child + "; new count: " + getChildCount()); 86 } 87 @Override 88 public void onChildViewRemoved(View parent, View child) { 89 Log.d(TAG, "view removed: " + child + "; new count: " + (getChildCount() - 1)); 90 } 91 }); 92 93 setBackgroundColor(0x80FF8000); 94 } 95 96 float densityScale = getResources().getDisplayMetrics().density; 97 float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); 98 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop); 99 } 100 101 public void setLongPressListener(View.OnLongClickListener listener) { 102 mSwipeHelper.setLongPressListener(listener); 103 } 104 105 public void setOnSizeChangedListener(OnSizeChangedListener l) { 106 mOnSizeChangedListener = l; 107 } 108 109 @Override 110 public void onWindowFocusChanged(boolean hasWindowFocus) { 111 super.onWindowFocusChanged(hasWindowFocus); 112 if (!hasWindowFocus) { 113 mSwipeHelper.removeLongPressCallback(); 114 } 115 } 116 117 public void setAnimateBounds(boolean anim) { 118 mAnimateBounds = anim; 119 } 120 121 private void logLayoutTransition() { 122 Log.v(TAG, "layout " + 123 (mRealLayoutTransition.isChangingLayout() ? "is " : "is not ") + 124 "in transition and animations " + 125 (mRealLayoutTransition.isRunning() ? "are " : "are not ") + 126 "running."); 127 } 128 129 @Override 130 public boolean onInterceptTouchEvent(MotionEvent ev) { 131 if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()"); 132 if (DEBUG) logLayoutTransition(); 133 134 return mSwipeHelper.onInterceptTouchEvent(ev) || 135 super.onInterceptTouchEvent(ev); 136 } 137 138 @Override 139 public boolean onTouchEvent(MotionEvent ev) { 140 if (DEBUG) Log.v(TAG, "onTouchEvent()"); 141 if (DEBUG) logLayoutTransition(); 142 143 return mSwipeHelper.onTouchEvent(ev) || 144 super.onTouchEvent(ev); 145 } 146 147 public boolean canChildBeDismissed(View v) { 148 final View veto = v.findViewById(R.id.veto); 149 return (veto != null && veto.getVisibility() != View.GONE); 150 } 151 152 public boolean canChildBeExpanded(View v) { 153 return v instanceof ExpandableNotificationRow 154 && ((ExpandableNotificationRow) v).isExpandable(); 155 } 156 157 public void setUserExpandedChild(View v, boolean userExpanded) { 158 if (v instanceof ExpandableNotificationRow) { 159 ((ExpandableNotificationRow) v).setUserExpanded(userExpanded); 160 } 161 } 162 163 public void setUserLockedChild(View v, boolean userLocked) { 164 if (v instanceof ExpandableNotificationRow) { 165 ((ExpandableNotificationRow) v).setUserLocked(userLocked); 166 } 167 } 168 169 public void onChildDismissed(View v) { 170 if (DEBUG) Log.v(TAG, "onChildDismissed: " + v + " mRemoveViews=" + mRemoveViews); 171 final View veto = v.findViewById(R.id.veto); 172 if (veto != null && veto.getVisibility() != View.GONE && mRemoveViews) { 173 veto.performClick(); 174 } 175 } 176 177 public void onBeginDrag(View v) { 178 // We need to prevent the surrounding ScrollView from intercepting us now; 179 // the scroll position will be locked while we swipe 180 requestDisallowInterceptTouchEvent(true); 181 } 182 183 public void onDragCancelled(View v) { 184 } 185 186 public View getChildAtPosition(MotionEvent ev) { 187 return getChildAtPosition(ev.getX(), ev.getY()); 188 } 189 190 public View getChildAtRawPosition(float touchX, float touchY) { 191 int[] location = new int[2]; 192 getLocationOnScreen(location); 193 return getChildAtPosition((float) (touchX - location[0]), (float) (touchY - location[1])); 194 } 195 196 public View getChildAtPosition(float touchX, float touchY) { 197 // find the view under the pointer, accounting for GONE views 198 final int count = getChildCount(); 199 int y = 0; 200 int childIdx = 0; 201 View slidingChild; 202 for (; childIdx < count; childIdx++) { 203 slidingChild = getChildAt(childIdx); 204 if (slidingChild.getVisibility() == GONE) { 205 continue; 206 } 207 y += slidingChild.getMeasuredHeight(); 208 if (touchY < y) return slidingChild; 209 } 210 return null; 211 } 212 213 public View getChildContentView(View v) { 214 return v; 215 } 216 217 @Override 218 protected void onConfigurationChanged(Configuration newConfig) { 219 super.onConfigurationChanged(newConfig); 220 float densityScale = getResources().getDisplayMetrics().density; 221 mSwipeHelper.setDensityScale(densityScale); 222 float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); 223 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); 224 } 225 226 227 /** 228 * Sets a flag to tell us whether to actually remove views. Removal is delayed by setting this 229 * to false during some animations to smooth out performance. Callers should restore the 230 * flag to true after the animation is done, and then they should make sure that the views 231 * get removed properly. 232 */ 233 public void setViewRemoval(boolean removeViews) { 234 if (DEBUG) Log.v(TAG, "setViewRemoval: " + removeViews); 235 mRemoveViews = removeViews; 236 } 237 238 // Suppress layout transitions for a little while. 239 public void setLayoutTransitionsEnabled(boolean b) { 240 if (b) { 241 setLayoutTransition(mRealLayoutTransition); 242 } else { 243 if (mRealLayoutTransition.isRunning()) { 244 mRealLayoutTransition.cancel(); 245 } 246 setLayoutTransition(null); 247 } 248 } 249 250 public void dismissRowAnimated(View child) { 251 dismissRowAnimated(child, 0); 252 } 253 254 public void dismissRowAnimated(View child, int vel) { 255 mSwipeHelper.dismissChild(child, vel); 256 } 257 258 @Override 259 public void onFinishInflate() { 260 super.onFinishInflate(); 261 if (DEBUG) setWillNotDraw(false); 262 } 263 264 @Override 265 public void onDraw(android.graphics.Canvas c) { 266 super.onDraw(c); 267 if (DEBUG) logLayoutTransition(); 268 if (DEBUG) { 269 //Log.d(TAG, "onDraw: canvas height: " + c.getHeight() + "px; measured height: " 270 // + getMeasuredHeight() + "px"); 271 c.save(); 272 c.clipRect(6, 6, c.getWidth() - 6, getMeasuredHeight() - 6, 273 android.graphics.Region.Op.DIFFERENCE); 274 c.drawColor(0xFFFF8000); 275 c.restore(); 276 } 277 } 278 279 @Override 280 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 281 if (mOnSizeChangedListener != null) { 282 mOnSizeChangedListener.onSizeChanged(this, w, h, oldw, oldh); 283 } 284 } 285 } 286