HeadsUpNotificationView.java revision 31ba192dd201df2cad96a8c503f730130ab0d80f
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.setShowingPublic(false); 95 if (mContentHolder == null) { 96 // too soon! 97 return false; 98 } 99 mContentHolder.setX(0); 100 mContentHolder.setVisibility(View.VISIBLE); 101 mContentHolder.setAlpha(mMaxAlpha); 102 mContentHolder.addView(mHeadsUp.row); 103 104 mSwipeHelper.snapChild(mContentHolder, 1f); 105 mStartTouchTime = System.currentTimeMillis() + mTouchSensitivityDelay; 106 107 mHeadsUp.setInterruption(); 108 109 // 2. Animate mHeadsUpNotificationView in 110 mBar.scheduleHeadsUpOpen(); 111 112 // 3. Set alarm to age the notification off 113 mBar.resetHeadsUpDecayTimer(); 114 } 115 return true; 116 } 117 118 public boolean isShowing(String key) { 119 return mHeadsUp != null && mHeadsUp.key.equals(key); 120 } 121 122 /** Discard the Heads Up notification. */ 123 public void clear() { 124 mHeadsUp = null; 125 mBar.scheduleHeadsUpClose(); 126 } 127 128 /** Respond to dismissal of the Heads Up window. */ 129 public void dismiss() { 130 if (mHeadsUp == null) return; 131 if (mHeadsUp.notification.isClearable()) { 132 mBar.onNotificationClear(mHeadsUp.notification); 133 } else { 134 release(); 135 } 136 mHeadsUp = null; 137 mBar.scheduleHeadsUpClose(); 138 } 139 140 /** Push any current Heads Up notification down into the shade. */ 141 public void release() { 142 if (mHeadsUp != null) { 143 mBar.displayNotificationFromHeadsUp(mHeadsUp.notification); 144 } 145 mHeadsUp = null; 146 } 147 148 public void releaseAndClose() { 149 release(); 150 mBar.scheduleHeadsUpClose(); 151 } 152 153 public NotificationData.Entry getEntry() { 154 return mHeadsUp; 155 } 156 157 public boolean isClearable() { 158 return mHeadsUp == null || mHeadsUp.notification.isClearable(); 159 } 160 161 // ViewGroup methods 162 163 private static final ViewOutlineProvider CONTENT_HOLDER_OUTLINE_PROVIDER = 164 new ViewOutlineProvider() { 165 @Override 166 public void getOutline(View view, Outline outline) { 167 int outlineLeft = view.getPaddingLeft(); 168 int outlineTop = view.getPaddingTop(); 169 170 // Apply padding to shadow. 171 outline.setRect(outlineLeft, outlineTop, 172 view.getWidth() - outlineLeft - view.getPaddingRight(), 173 view.getHeight() - outlineTop - view.getPaddingBottom()); 174 } 175 }; 176 177 @Override 178 public void onAttachedToWindow() { 179 float densityScale = getResources().getDisplayMetrics().density; 180 final ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext()); 181 float pagingTouchSlop = viewConfiguration.getScaledPagingTouchSlop(); 182 float touchSlop = viewConfiguration.getScaledTouchSlop(); 183 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop); 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 private class EdgeSwipeHelper implements Gefingerpoken { 344 private static final boolean DEBUG_EDGE_SWIPE = false; 345 private final float mTouchSlop; 346 private boolean mConsuming; 347 private float mFirstY; 348 private float mFirstX; 349 350 public EdgeSwipeHelper(float touchSlop) { 351 mTouchSlop = touchSlop; 352 } 353 354 @Override 355 public boolean onInterceptTouchEvent(MotionEvent ev) { 356 switch (ev.getActionMasked()) { 357 case MotionEvent.ACTION_DOWN: 358 if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action down " + ev.getY()); 359 mFirstX = ev.getX(); 360 mFirstY = ev.getY(); 361 mConsuming = false; 362 break; 363 364 case MotionEvent.ACTION_MOVE: 365 if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action move " + ev.getY()); 366 final float dY = ev.getY() - mFirstY; 367 final float daX = Math.abs(ev.getX() - mFirstX); 368 final float daY = Math.abs(dY); 369 if (!mConsuming && (4f * daX) < daY && daY > mTouchSlop) { 370 if (dY > 0) { 371 if (DEBUG_EDGE_SWIPE) Log.d(TAG, "found an open"); 372 mBar.animateExpandNotificationsPanel(); 373 } 374 if (dY < 0) { 375 if (DEBUG_EDGE_SWIPE) Log.d(TAG, "found a close"); 376 mBar.onHeadsUpDismissed(); 377 } 378 mConsuming = true; 379 } 380 break; 381 382 case MotionEvent.ACTION_UP: 383 case MotionEvent.ACTION_CANCEL: 384 if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action done" ); 385 mConsuming = false; 386 break; 387 } 388 return mConsuming; 389 } 390 391 @Override 392 public boolean onTouchEvent(MotionEvent ev) { 393 return mConsuming; 394 } 395 } 396} 397