ActivatableNotificationView.java revision be565dfc1c17b7ddafa9753851b8f82849fd3f42
1/* 2 * Copyright (C) 2014 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; 18 19import android.content.Context; 20import android.util.AttributeSet; 21import android.view.MotionEvent; 22import android.view.View; 23import android.view.ViewConfiguration; 24import android.widget.FrameLayout; 25 26import com.android.internal.R; 27 28/** 29 * Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer} 30 * to implement dimming/activating on Keyguard for the double-tap gesture 31 */ 32public abstract class ActivatableNotificationView extends ExpandableOutlineView { 33 34 private static final long DOUBLETAP_TIMEOUT_MS = 1000; 35 36 private boolean mDimmed; 37 private boolean mLocked; 38 39 private int mBgResId = R.drawable.notification_quantum_bg; 40 private int mDimmedBgResId = R.drawable.notification_quantum_bg_dim; 41 42 /** 43 * Flag to indicate that the notification has been touched once and the second touch will 44 * click it. 45 */ 46 private boolean mActivated; 47 48 private float mDownX; 49 private float mDownY; 50 private final float mTouchSlop; 51 52 private OnActivatedListener mOnActivatedListener; 53 54 public ActivatableNotificationView(Context context, AttributeSet attrs) { 55 super(context, attrs); 56 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 57 updateBackgroundResource(); 58 } 59 60 61 private final Runnable mTapTimeoutRunnable = new Runnable() { 62 @Override 63 public void run() { 64 makeInactive(); 65 } 66 }; 67 68 @Override 69 public void setOnClickListener(OnClickListener l) { 70 super.setOnClickListener(l); 71 } 72 73 @Override 74 public boolean onTouchEvent(MotionEvent event) { 75 if (mLocked) { 76 return handleTouchEventLocked(event); 77 } else { 78 return super.onTouchEvent(event); 79 } 80 } 81 82 private boolean handleTouchEventLocked(MotionEvent event) { 83 int action = event.getActionMasked(); 84 switch (action) { 85 case MotionEvent.ACTION_DOWN: 86 mDownX = event.getX(); 87 mDownY = event.getY(); 88 if (mDownY > getActualHeight()) { 89 return false; 90 } 91 92 // Call the listener tentatively directly, even if we don't know whether the user 93 // will stay within the touch slop, as the listener is implemented as a scale 94 // animation, which is cancellable without jarring effects when swiping away 95 // notifications. 96 if (mOnActivatedListener != null) { 97 mOnActivatedListener.onActivated(this); 98 } 99 break; 100 case MotionEvent.ACTION_MOVE: 101 if (!isWithinTouchSlop(event)) { 102 makeInactive(); 103 return false; 104 } 105 break; 106 case MotionEvent.ACTION_UP: 107 if (isWithinTouchSlop(event)) { 108 if (!mActivated) { 109 makeActive(event.getX(), event.getY()); 110 postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS); 111 } else { 112 performClick(); 113 makeInactive(); 114 } 115 } else { 116 makeInactive(); 117 } 118 break; 119 case MotionEvent.ACTION_CANCEL: 120 makeInactive(); 121 break; 122 default: 123 break; 124 } 125 return true; 126 } 127 128 private void makeActive(float x, float y) { 129 mCustomBackground.setHotspot(0, x, y); 130 mActivated = true; 131 } 132 133 /** 134 * Cancels the hotspot and makes the notification inactive. 135 */ 136 private void makeInactive() { 137 if (mActivated) { 138 // Make sure that we clear the hotspot from the center. 139 mCustomBackground.setHotspot(0, getWidth() / 2, getActualHeight() / 2); 140 mCustomBackground.removeHotspot(0); 141 mActivated = false; 142 } 143 if (mOnActivatedListener != null) { 144 mOnActivatedListener.onReset(this); 145 } 146 removeCallbacks(mTapTimeoutRunnable); 147 } 148 149 private boolean isWithinTouchSlop(MotionEvent event) { 150 return Math.abs(event.getX() - mDownX) < mTouchSlop 151 && Math.abs(event.getY() - mDownY) < mTouchSlop; 152 } 153 154 /** 155 * Sets the notification as dimmed, meaning that it will appear in a more gray variant. 156 */ 157 public void setDimmed(boolean dimmed) { 158 if (mDimmed != dimmed) { 159 mDimmed = dimmed; 160 updateBackgroundResource(); 161 } 162 } 163 164 /** 165 * Sets the notification as locked. In the locked state, the first tap will produce a quantum 166 * ripple to make the notification brighter and only the second tap will cause a click. 167 */ 168 public void setLocked(boolean locked) { 169 mLocked = locked; 170 } 171 172 /** 173 * Sets the resource id for the background of this notification. 174 * 175 * @param bgResId The background resource to use in normal state. 176 * @param dimmedBgResId The background resource to use in dimmed state. 177 */ 178 public void setBackgroundResourceIds(int bgResId, int dimmedBgResId) { 179 mBgResId = bgResId; 180 mDimmedBgResId = dimmedBgResId; 181 updateBackgroundResource(); 182 } 183 184 private void updateBackgroundResource() { 185 setCustomBackgroundResource(mDimmed ? mDimmedBgResId : mBgResId); 186 } 187 188 @Override 189 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 190 super.onLayout(changed, left, top, right, bottom); 191 setPivotX(getWidth()/2); 192 } 193 194 @Override 195 public void setActualHeight(int actualHeight) { 196 super.setActualHeight(actualHeight); 197 setPivotY(actualHeight/2); 198 } 199 200 public void setOnActivatedListener(OnActivatedListener onActivatedListener) { 201 mOnActivatedListener = onActivatedListener; 202 } 203 204 public interface OnActivatedListener { 205 void onActivated(View view); 206 void onReset(View view); 207 } 208} 209