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.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.LayoutTransition; 22 import android.animation.ObjectAnimator; 23 import android.animation.ValueAnimator; 24 import android.content.Context; 25 import android.content.res.Configuration; 26 import android.content.res.TypedArray; 27 import android.graphics.Rect; 28 import android.util.AttributeSet; 29 import android.util.Log; 30 import android.util.Slog; 31 import android.view.MotionEvent; 32 import android.view.View; 33 import android.view.ViewConfiguration; 34 import android.view.ViewGroup; 35 import android.view.inputmethod.InputMethodManager; 36 import android.widget.LinearLayout; 37 38 import com.android.systemui.ExpandHelper; 39 import com.android.systemui.R; 40 import com.android.systemui.SwipeHelper; 41 import com.android.systemui.statusbar.NotificationData; 42 43 import java.util.HashMap; 44 45 public class NotificationRowLayout 46 extends LinearLayout 47 implements SwipeHelper.Callback, ExpandHelper.Callback 48 { 49 private static final String TAG = "NotificationRowLayout"; 50 private static final boolean DEBUG = false; 51 private static final boolean SLOW_ANIMATIONS = DEBUG; 52 53 private static final int APPEAR_ANIM_LEN = SLOW_ANIMATIONS ? 5000 : 250; 54 private static final int DISAPPEAR_ANIM_LEN = APPEAR_ANIM_LEN; 55 56 boolean mAnimateBounds = true; 57 58 Rect mTmpRect = new Rect(); 59 60 HashMap<View, ValueAnimator> mAppearingViews = new HashMap<View, ValueAnimator>(); 61 HashMap<View, ValueAnimator> mDisappearingViews = new HashMap<View, ValueAnimator>(); 62 63 private SwipeHelper mSwipeHelper; 64 65 private OnSizeChangedListener mOnSizeChangedListener; 66 67 // Flag set during notification removal animation to avoid causing too much work until 68 // animation is done 69 boolean mRemoveViews = true; 70 71 private LayoutTransition mRealLayoutTransition; 72 73 public NotificationRowLayout(Context context, AttributeSet attrs) { 74 this(context, attrs, 0); 75 } 76 77 public NotificationRowLayout(Context context, AttributeSet attrs, int defStyle) { 78 super(context, attrs, defStyle); 79 80 mRealLayoutTransition = new LayoutTransition(); 81 mRealLayoutTransition.setAnimateParentHierarchy(true); 82 setLayoutTransitionsEnabled(true); 83 84 setOrientation(LinearLayout.VERTICAL); 85 86 if (DEBUG) { 87 setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() { 88 @Override 89 public void onChildViewAdded(View parent, View child) { 90 Slog.d(TAG, "view added: " + child + "; new count: " + getChildCount()); 91 } 92 @Override 93 public void onChildViewRemoved(View parent, View child) { 94 Slog.d(TAG, "view removed: " + child + "; new count: " + (getChildCount() - 1)); 95 } 96 }); 97 98 setBackgroundColor(0x80FF8000); 99 } 100 101 float densityScale = getResources().getDisplayMetrics().density; 102 float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); 103 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop); 104 } 105 106 public void setLongPressListener(View.OnLongClickListener listener) { 107 mSwipeHelper.setLongPressListener(listener); 108 } 109 110 public void setOnSizeChangedListener(OnSizeChangedListener l) { 111 mOnSizeChangedListener = l; 112 } 113 114 @Override 115 public void onWindowFocusChanged(boolean hasWindowFocus) { 116 super.onWindowFocusChanged(hasWindowFocus); 117 if (!hasWindowFocus) { 118 mSwipeHelper.removeLongPressCallback(); 119 } 120 } 121 122 public void setAnimateBounds(boolean anim) { 123 mAnimateBounds = anim; 124 } 125 126 private void logLayoutTransition() { 127 Log.v(TAG, "layout " + 128 (mRealLayoutTransition.isChangingLayout() ? "is " : "is not ") + 129 "in transition and animations " + 130 (mRealLayoutTransition.isRunning() ? "are " : "are not ") + 131 "running."); 132 } 133 134 @Override 135 public boolean onInterceptTouchEvent(MotionEvent ev) { 136 if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()"); 137 if (DEBUG) logLayoutTransition(); 138 139 return mSwipeHelper.onInterceptTouchEvent(ev) || 140 super.onInterceptTouchEvent(ev); 141 } 142 143 @Override 144 public boolean onTouchEvent(MotionEvent ev) { 145 if (DEBUG) Log.v(TAG, "onTouchEvent()"); 146 if (DEBUG) logLayoutTransition(); 147 148 return mSwipeHelper.onTouchEvent(ev) || 149 super.onTouchEvent(ev); 150 } 151 152 public boolean canChildBeDismissed(View v) { 153 final View veto = v.findViewById(R.id.veto); 154 return (veto != null && veto.getVisibility() != View.GONE); 155 } 156 157 public boolean canChildBeExpanded(View v) { 158 return NotificationData.getIsExpandable(v); 159 } 160 161 public boolean setUserExpandedChild(View v, boolean userExpanded) { 162 return NotificationData.setUserExpanded(v, userExpanded); 163 } 164 165 public boolean setUserLockedChild(View v, boolean userLocked) { 166 return NotificationData.setUserLocked(v, userLocked); 167 } 168 169 public void onChildDismissed(View v) { 170 if (DEBUG) Slog.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) Slog.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 //Slog.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