HeadsUpNotificationView.java revision ae44128776410abd11bd06ae700db9cc4606a773
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 17package com.android.systemui.statusbar.policy; 18 19import android.content.Context; 20import android.content.res.Configuration; 21import android.graphics.Outline; 22import android.graphics.Rect; 23import android.util.AttributeSet; 24import android.util.Log; 25import android.view.MotionEvent; 26import android.view.View; 27import android.view.ViewConfiguration; 28import android.view.ViewGroup; 29import android.view.ViewOutlineProvider; 30import android.view.ViewTreeObserver; 31import android.widget.FrameLayout; 32 33import com.android.systemui.ExpandHelper; 34import com.android.systemui.Gefingerpoken; 35import com.android.systemui.R; 36import com.android.systemui.SwipeHelper; 37import com.android.systemui.statusbar.ExpandableView; 38import com.android.systemui.statusbar.NotificationData; 39import com.android.systemui.statusbar.phone.PhoneStatusBar; 40 41public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.Callback, ExpandHelper.Callback, 42 ViewTreeObserver.OnComputeInternalInsetsListener { 43 private static final String TAG = "HeadsUpNotificationView"; 44 private static final boolean DEBUG = false; 45 private static final boolean SPEW = DEBUG; 46 47 Rect mTmpRect = new Rect(); 48 int[] mTmpTwoArray = new int[2]; 49 50 private final int mTouchSensitivityDelay; 51 private final float mMaxAlpha = 1f; 52 private SwipeHelper mSwipeHelper; 53 private EdgeSwipeHelper mEdgeSwipeHelper; 54 55 private PhoneStatusBar mBar; 56 private ExpandHelper mExpandHelper; 57 58 private long mStartTouchTime; 59 private ViewGroup mContentHolder; 60 61 private NotificationData.Entry mHeadsUp; 62 63 public HeadsUpNotificationView(Context context, AttributeSet attrs) { 64 this(context, attrs, 0); 65 } 66 67 public HeadsUpNotificationView(Context context, AttributeSet attrs, int defStyle) { 68 super(context, attrs, defStyle); 69 mTouchSensitivityDelay = getResources().getInteger(R.integer.heads_up_sensitivity_delay); 70 if (DEBUG) Log.v(TAG, "create() " + mTouchSensitivityDelay); 71 } 72 73 public void setBar(PhoneStatusBar bar) { 74 mBar = bar; 75 } 76 77 public ViewGroup getHolder() { 78 return mContentHolder; 79 } 80 81 public boolean showNotification(NotificationData.Entry headsUp) { 82 if (mHeadsUp != null && headsUp != null && !mHeadsUp.key.equals(headsUp.key)) { 83 // bump any previous heads up back to the shade 84 release(); 85 } 86 87 mHeadsUp = headsUp; 88 if (mContentHolder != null) { 89 mContentHolder.removeAllViews(); 90 } 91 92 if (mHeadsUp != null) { 93 mHeadsUp.row.setSystemExpanded(true); 94 mHeadsUp.row.setSensitive(false); 95 mHeadsUp.row.setHideSensitive( 96 false, false /* animated */, 0 /* delay */, 0 /* duration */); 97 if (mContentHolder == null) { 98 // too soon! 99 return false; 100 } 101 mContentHolder.setX(0); 102 mContentHolder.setVisibility(View.VISIBLE); 103 mContentHolder.setAlpha(mMaxAlpha); 104 mContentHolder.addView(mHeadsUp.row); 105 106 mSwipeHelper.snapChild(mContentHolder, 1f); 107 mStartTouchTime = System.currentTimeMillis() + mTouchSensitivityDelay; 108 109 mHeadsUp.setInterruption(); 110 111 // 2. Animate mHeadsUpNotificationView in 112 mBar.scheduleHeadsUpOpen(); 113 114 // 3. Set alarm to age the notification off 115 mBar.resetHeadsUpDecayTimer(); 116 } 117 return true; 118 } 119 120 public boolean isShowing(String key) { 121 return mHeadsUp != null && mHeadsUp.key.equals(key); 122 } 123 124 /** Discard the Heads Up notification. */ 125 public void clear() { 126 mHeadsUp = null; 127 mBar.scheduleHeadsUpClose(); 128 } 129 130 /** Respond to dismissal of the Heads Up window. */ 131 public void dismiss() { 132 if (mHeadsUp == null) return; 133 if (mHeadsUp.notification.isClearable()) { 134 mBar.onNotificationClear(mHeadsUp.notification); 135 } else { 136 release(); 137 } 138 mHeadsUp = null; 139 mBar.scheduleHeadsUpClose(); 140 } 141 142 /** Push any current Heads Up notification down into the shade. */ 143 public void release() { 144 if (mHeadsUp != null) { 145 mBar.displayNotificationFromHeadsUp(mHeadsUp.notification); 146 } 147 mHeadsUp = null; 148 } 149 150 public void releaseAndClose() { 151 release(); 152 mBar.scheduleHeadsUpClose(); 153 } 154 155 public NotificationData.Entry getEntry() { 156 return mHeadsUp; 157 } 158 159 public boolean isClearable() { 160 return mHeadsUp == null || mHeadsUp.notification.isClearable(); 161 } 162 163 // ViewGroup methods 164 165 private static final ViewOutlineProvider CONTENT_HOLDER_OUTLINE_PROVIDER = 166 new ViewOutlineProvider() { 167 @Override 168 public void getOutline(View view, Outline outline) { 169 int outlineLeft = view.getPaddingLeft(); 170 int outlineTop = view.getPaddingTop(); 171 172 // Apply padding to shadow. 173 outline.setRect(outlineLeft, outlineTop, 174 view.getWidth() - outlineLeft - view.getPaddingRight(), 175 view.getHeight() - outlineTop - view.getPaddingBottom()); 176 } 177 }; 178 179 @Override 180 public void onAttachedToWindow() { 181 final ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext()); 182 float touchSlop = viewConfiguration.getScaledTouchSlop(); 183 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext()); 184 mSwipeHelper.setMaxSwipeProgress(mMaxAlpha); 185 mEdgeSwipeHelper = new EdgeSwipeHelper(touchSlop); 186 187 int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height); 188 int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height); 189 mExpandHelper = new ExpandHelper(getContext(), this, minHeight, maxHeight); 190 191 mContentHolder = (ViewGroup) findViewById(R.id.content_holder); 192 mContentHolder.setOutlineProvider(CONTENT_HOLDER_OUTLINE_PROVIDER); 193 194 if (mHeadsUp != null) { 195 // whoops, we're on already! 196 showNotification(mHeadsUp); 197 } 198 199 getViewTreeObserver().addOnComputeInternalInsetsListener(this); 200 } 201 202 @Override 203 public boolean onInterceptTouchEvent(MotionEvent ev) { 204 if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()"); 205 if (System.currentTimeMillis() < mStartTouchTime) { 206 return true; 207 } 208 return mEdgeSwipeHelper.onInterceptTouchEvent(ev) 209 || mSwipeHelper.onInterceptTouchEvent(ev) 210 || mExpandHelper.onInterceptTouchEvent(ev) 211 || super.onInterceptTouchEvent(ev); 212 } 213 214 // View methods 215 216 @Override 217 public void onDraw(android.graphics.Canvas c) { 218 super.onDraw(c); 219 if (DEBUG) { 220 //Log.d(TAG, "onDraw: canvas height: " + c.getHeight() + "px; measured height: " 221 // + getMeasuredHeight() + "px"); 222 c.save(); 223 c.clipRect(6, 6, c.getWidth() - 6, getMeasuredHeight() - 6, 224 android.graphics.Region.Op.DIFFERENCE); 225 c.drawColor(0xFFcc00cc); 226 c.restore(); 227 } 228 } 229 230 @Override 231 public boolean onTouchEvent(MotionEvent ev) { 232 if (System.currentTimeMillis() < mStartTouchTime) { 233 return false; 234 } 235 mBar.resetHeadsUpDecayTimer(); 236 return mEdgeSwipeHelper.onTouchEvent(ev) 237 || mSwipeHelper.onTouchEvent(ev) 238 || mExpandHelper.onTouchEvent(ev) 239 || super.onTouchEvent(ev); 240 } 241 242 @Override 243 protected void onConfigurationChanged(Configuration newConfig) { 244 super.onConfigurationChanged(newConfig); 245 float densityScale = getResources().getDisplayMetrics().density; 246 mSwipeHelper.setDensityScale(densityScale); 247 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); 248 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); 249 } 250 251 // ExpandHelper.Callback methods 252 253 @Override 254 public ExpandableView getChildAtRawPosition(float x, float y) { 255 return getChildAtPosition(x, y); 256 } 257 258 @Override 259 public ExpandableView getChildAtPosition(float x, float y) { 260 return mHeadsUp == null ? null : mHeadsUp.row; 261 } 262 263 @Override 264 public boolean canChildBeExpanded(View v) { 265 return mHeadsUp != null && mHeadsUp.row == v && mHeadsUp.row.isExpandable(); 266 } 267 268 @Override 269 public void setUserExpandedChild(View v, boolean userExpanded) { 270 if (mHeadsUp != null && mHeadsUp.row == v) { 271 mHeadsUp.row.setUserExpanded(userExpanded); 272 } 273 } 274 275 @Override 276 public void setUserLockedChild(View v, boolean userLocked) { 277 if (mHeadsUp != null && mHeadsUp.row == v) { 278 mHeadsUp.row.setUserLocked(userLocked); 279 } 280 } 281 282 @Override 283 public void expansionStateChanged(boolean isExpanding) { 284 285 } 286 287 // SwipeHelper.Callback methods 288 289 @Override 290 public boolean canChildBeDismissed(View v) { 291 return true; 292 } 293 294 @Override 295 public void onChildDismissed(View v) { 296 Log.v(TAG, "User swiped heads up to dismiss"); 297 mBar.onHeadsUpDismissed(); 298 } 299 300 @Override 301 public void onBeginDrag(View v) { 302 } 303 304 @Override 305 public void onDragCancelled(View v) { 306 mContentHolder.setAlpha(mMaxAlpha); // sometimes this isn't quite reset 307 } 308 309 @Override 310 public void onChildSnappedBack(View animView) { 311 } 312 313 @Override 314 public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) { 315 getBackground().setAlpha((int) (255 * swipeProgress)); 316 return false; 317 } 318 319 @Override 320 public View getChildAtPosition(MotionEvent ev) { 321 return mContentHolder; 322 } 323 324 @Override 325 public View getChildContentView(View v) { 326 return mContentHolder; 327 } 328 329 @Override 330 public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { 331 mContentHolder.getLocationOnScreen(mTmpTwoArray); 332 333 info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); 334 info.touchableRegion.set(mTmpTwoArray[0], mTmpTwoArray[1], 335 mTmpTwoArray[0] + mContentHolder.getWidth(), 336 mTmpTwoArray[1] + mContentHolder.getHeight()); 337 } 338 339 public void escalate() { 340 mBar.scheduleHeadsUpEscalation(); 341 } 342 343 public String getKey() { 344 return mHeadsUp == null ? null : mHeadsUp.notification.getKey(); 345 } 346 347 private class EdgeSwipeHelper implements Gefingerpoken { 348 private static final boolean DEBUG_EDGE_SWIPE = false; 349 private final float mTouchSlop; 350 private boolean mConsuming; 351 private float mFirstY; 352 private float mFirstX; 353 354 public EdgeSwipeHelper(float touchSlop) { 355 mTouchSlop = touchSlop; 356 } 357 358 @Override 359 public boolean onInterceptTouchEvent(MotionEvent ev) { 360 switch (ev.getActionMasked()) { 361 case MotionEvent.ACTION_DOWN: 362 if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action down " + ev.getY()); 363 mFirstX = ev.getX(); 364 mFirstY = ev.getY(); 365 mConsuming = false; 366 break; 367 368 case MotionEvent.ACTION_MOVE: 369 if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action move " + ev.getY()); 370 final float dY = ev.getY() - mFirstY; 371 final float daX = Math.abs(ev.getX() - mFirstX); 372 final float daY = Math.abs(dY); 373 if (!mConsuming && (4f * daX) < daY && daY > mTouchSlop) { 374 if (dY > 0) { 375 if (DEBUG_EDGE_SWIPE) Log.d(TAG, "found an open"); 376 mBar.animateExpandNotificationsPanel(); 377 } 378 if (dY < 0) { 379 if (DEBUG_EDGE_SWIPE) Log.d(TAG, "found a close"); 380 mBar.onHeadsUpDismissed(); 381 } 382 mConsuming = true; 383 } 384 break; 385 386 case MotionEvent.ACTION_UP: 387 case MotionEvent.ACTION_CANCEL: 388 if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action done" ); 389 mConsuming = false; 390 break; 391 } 392 return mConsuming; 393 } 394 395 @Override 396 public boolean onTouchEvent(MotionEvent ev) { 397 return mConsuming; 398 } 399 } 400} 401