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