ExpandableNotificationRow.java revision 31094df5c6e3cb3a4a4faacb091e35eea1f6a5de
1/* 2 * Copyright (C) 2013 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.View; 22import android.view.accessibility.AccessibilityEvent; 23 24import com.android.systemui.R; 25 26public class ExpandableNotificationRow extends ActivatableNotificationView { 27 private int mRowMinHeight; 28 private int mRowMaxHeight; 29 30 /** Does this row contain layouts that can adapt to row expansion */ 31 private boolean mExpandable; 32 /** Has the user actively changed the expansion state of this row */ 33 private boolean mHasUserChangedExpansion; 34 /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */ 35 private boolean mUserExpanded; 36 /** Is the user touching this row */ 37 private boolean mUserLocked; 38 /** Are we showing the "public" version */ 39 private boolean mShowingPublic; 40 private boolean mSensitive; 41 private boolean mShowingPublicInitialized; 42 private boolean mShowingPublicForIntrinsicHeight; 43 44 /** 45 * Is this notification expanded by the system. The expansion state can be overridden by the 46 * user expansion. 47 */ 48 private boolean mIsSystemExpanded; 49 50 /** 51 * Whether the notification expansion is disabled. This is the case on Keyguard. 52 */ 53 private boolean mExpansionDisabled; 54 55 private NotificationContentView mPublicLayout; 56 private NotificationContentView mPrivateLayout; 57 private int mMaxExpandHeight; 58 private View mVetoButton; 59 private boolean mClearable; 60 private ExpansionLogger mLogger; 61 private String mLoggingKey; 62 private boolean mWasReset; 63 64 public interface ExpansionLogger { 65 public void logNotificationExpansion(String key, boolean userAction, boolean expanded); 66 } 67 68 public ExpandableNotificationRow(Context context, AttributeSet attrs) { 69 super(context, attrs); 70 } 71 72 /** 73 * Resets this view so it can be re-used for an updated notification. 74 */ 75 @Override 76 public void reset() { 77 super.reset(); 78 mRowMinHeight = 0; 79 final boolean wasExpanded = isExpanded(); 80 mRowMaxHeight = 0; 81 mExpandable = false; 82 mHasUserChangedExpansion = false; 83 mUserLocked = false; 84 mShowingPublic = false; 85 mSensitive = false; 86 mShowingPublicInitialized = false; 87 mIsSystemExpanded = false; 88 mExpansionDisabled = false; 89 mPublicLayout.reset(); 90 mPrivateLayout.reset(); 91 resetHeight(); 92 logExpansionEvent(false, wasExpanded); 93 } 94 95 public void resetHeight() { 96 mMaxExpandHeight = 0; 97 mWasReset = true; 98 onHeightReset(); 99 } 100 101 @Override 102 protected void onFinishInflate() { 103 super.onFinishInflate(); 104 mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic); 105 mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded); 106 mVetoButton = findViewById(R.id.veto); 107 } 108 109 @Override 110 public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { 111 if (super.onRequestSendAccessibilityEvent(child, event)) { 112 // Add a record for the entire layout since its content is somehow small. 113 // The event comes from a leaf view that is interacted with. 114 AccessibilityEvent record = AccessibilityEvent.obtain(); 115 onInitializeAccessibilityEvent(record); 116 dispatchPopulateAccessibilityEvent(record); 117 event.appendRecord(record); 118 return true; 119 } 120 return false; 121 } 122 123 public void setHeightRange(int rowMinHeight, int rowMaxHeight) { 124 mRowMinHeight = rowMinHeight; 125 mRowMaxHeight = rowMaxHeight; 126 } 127 128 public boolean isExpandable() { 129 return mExpandable; 130 } 131 132 public void setExpandable(boolean expandable) { 133 mExpandable = expandable; 134 } 135 136 /** 137 * @return whether the user has changed the expansion state 138 */ 139 public boolean hasUserChangedExpansion() { 140 return mHasUserChangedExpansion; 141 } 142 143 public boolean isUserExpanded() { 144 return mUserExpanded; 145 } 146 147 /** 148 * Set this notification to be expanded by the user 149 * 150 * @param userExpanded whether the user wants this notification to be expanded 151 */ 152 public void setUserExpanded(boolean userExpanded) { 153 if (userExpanded && !mExpandable) return; 154 final boolean wasExpanded = isExpanded(); 155 mHasUserChangedExpansion = true; 156 mUserExpanded = userExpanded; 157 logExpansionEvent(true, wasExpanded); 158 } 159 160 public void resetUserExpansion() { 161 mHasUserChangedExpansion = false; 162 mUserExpanded = false; 163 } 164 165 public boolean isUserLocked() { 166 return mUserLocked; 167 } 168 169 public void setUserLocked(boolean userLocked) { 170 mUserLocked = userLocked; 171 } 172 173 /** 174 * @return has the system set this notification to be expanded 175 */ 176 public boolean isSystemExpanded() { 177 return mIsSystemExpanded; 178 } 179 180 /** 181 * Set this notification to be expanded by the system. 182 * 183 * @param expand whether the system wants this notification to be expanded. 184 */ 185 public void setSystemExpanded(boolean expand) { 186 if (expand != mIsSystemExpanded) { 187 final boolean wasExpanded = isExpanded(); 188 mIsSystemExpanded = expand; 189 notifyHeightChanged(); 190 logExpansionEvent(false, wasExpanded); 191 } 192 } 193 194 /** 195 * @param expansionDisabled whether to prevent notification expansion 196 */ 197 public void setExpansionDisabled(boolean expansionDisabled) { 198 if (expansionDisabled != mExpansionDisabled) { 199 final boolean wasExpanded = isExpanded(); 200 mExpansionDisabled = expansionDisabled; 201 logExpansionEvent(false, wasExpanded); 202 if (wasExpanded != isExpanded()) { 203 notifyHeightChanged(); 204 } 205 } 206 } 207 208 /** 209 * @return Can the underlying notification be cleared? 210 */ 211 public boolean isClearable() { 212 return mClearable; 213 } 214 215 /** 216 * Set whether the notification can be cleared. 217 * 218 * @param clearable 219 */ 220 public void setClearable(boolean clearable) { 221 mClearable = clearable; 222 updateVetoButton(); 223 } 224 225 /** 226 * Apply an expansion state to the layout. 227 */ 228 public void applyExpansionToLayout() { 229 boolean expand = isExpanded(); 230 if (expand && mExpandable) { 231 setActualHeight(mMaxExpandHeight); 232 } else { 233 setActualHeight(mRowMinHeight); 234 } 235 } 236 237 @Override 238 public int getIntrinsicHeight() { 239 if (isUserLocked()) { 240 return getActualHeight(); 241 } 242 boolean inExpansionState = isExpanded(); 243 if (!inExpansionState) { 244 // not expanded, so we return the collapsed size 245 return mRowMinHeight; 246 } 247 248 return mShowingPublicForIntrinsicHeight ? mRowMinHeight : getMaxExpandHeight(); 249 } 250 251 /** 252 * Check whether the view state is currently expanded. This is given by the system in {@link 253 * #setSystemExpanded(boolean)} and can be overridden by user expansion or 254 * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this 255 * view can differ from this state, if layout params are modified from outside. 256 * 257 * @return whether the view state is currently expanded. 258 */ 259 private boolean isExpanded() { 260 return !mExpansionDisabled 261 && (!hasUserChangedExpansion() && isSystemExpanded() || isUserExpanded()); 262 } 263 264 @Override 265 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 266 super.onLayout(changed, left, top, right, bottom); 267 boolean updateExpandHeight = mMaxExpandHeight == 0 && !mWasReset; 268 mMaxExpandHeight = mPrivateLayout.getMaxHeight(); 269 if (updateExpandHeight) { 270 applyExpansionToLayout(); 271 } 272 mWasReset = false; 273 } 274 275 public void setSensitive(boolean sensitive) { 276 mSensitive = sensitive; 277 } 278 279 public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) { 280 mShowingPublicForIntrinsicHeight = mSensitive && hideSensitive; 281 } 282 283 public void setHideSensitive(boolean hideSensitive, boolean animated, long delay, 284 long duration) { 285 boolean oldShowingPublic = mShowingPublic; 286 mShowingPublic = mSensitive && hideSensitive; 287 if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) { 288 return; 289 } 290 291 // bail out if no public version 292 if (mPublicLayout.getChildCount() == 0) return; 293 294 if (!animated) { 295 mPublicLayout.animate().cancel(); 296 mPrivateLayout.animate().cancel(); 297 mPublicLayout.setAlpha(1f); 298 mPrivateLayout.setAlpha(1f); 299 mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE); 300 mPrivateLayout.setVisibility(mShowingPublic ? View.INVISIBLE : View.VISIBLE); 301 } else { 302 animateShowingPublic(delay, duration); 303 } 304 305 updateVetoButton(); 306 mShowingPublicInitialized = true; 307 } 308 309 private void animateShowingPublic(long delay, long duration) { 310 final View source = mShowingPublic ? mPrivateLayout : mPublicLayout; 311 View target = mShowingPublic ? mPublicLayout : mPrivateLayout; 312 source.setVisibility(View.VISIBLE); 313 target.setVisibility(View.VISIBLE); 314 target.setAlpha(0f); 315 source.animate().cancel(); 316 target.animate().cancel(); 317 source.animate() 318 .alpha(0f) 319 .withLayer() 320 .setStartDelay(delay) 321 .setDuration(duration) 322 .withEndAction(new Runnable() { 323 @Override 324 public void run() { 325 source.setVisibility(View.INVISIBLE); 326 } 327 }); 328 target.animate() 329 .alpha(1f) 330 .withLayer() 331 .setStartDelay(delay) 332 .setDuration(duration); 333 } 334 335 private void updateVetoButton() { 336 // public versions cannot be dismissed 337 mVetoButton.setVisibility(isClearable() && !mShowingPublic ? View.VISIBLE : View.GONE); 338 } 339 340 public int getMaxExpandHeight() { 341 return mShowingPublicForIntrinsicHeight ? mRowMinHeight : mMaxExpandHeight; 342 } 343 344 @Override 345 public boolean isContentExpandable() { 346 NotificationContentView showingLayout = getShowingLayout(); 347 return showingLayout.isContentExpandable(); 348 } 349 350 @Override 351 public void setActualHeight(int height, boolean notifyListeners) { 352 mPrivateLayout.setActualHeight(height); 353 mPublicLayout.setActualHeight(height); 354 invalidate(); 355 super.setActualHeight(height, notifyListeners); 356 } 357 358 @Override 359 public int getMaxHeight() { 360 NotificationContentView showingLayout = getShowingLayout(); 361 return showingLayout.getMaxHeight(); 362 } 363 364 @Override 365 public int getMinHeight() { 366 NotificationContentView showingLayout = getShowingLayout(); 367 return showingLayout.getMinHeight(); 368 } 369 370 @Override 371 public void setClipTopAmount(int clipTopAmount) { 372 super.setClipTopAmount(clipTopAmount); 373 mPrivateLayout.setClipTopAmount(clipTopAmount); 374 mPublicLayout.setClipTopAmount(clipTopAmount); 375 } 376 377 public void notifyContentUpdated() { 378 mPublicLayout.notifyContentUpdated(); 379 mPrivateLayout.notifyContentUpdated(); 380 } 381 382 public boolean isMaxExpandHeightInitialized() { 383 return mMaxExpandHeight != 0; 384 } 385 386 private NotificationContentView getShowingLayout() { 387 return mShowingPublic ? mPublicLayout : mPrivateLayout; 388 } 389 390 public void setExpansionLogger(ExpansionLogger logger, String key) { 391 mLogger = logger; 392 mLoggingKey = key; 393 } 394 395 396 private void logExpansionEvent(boolean userAction, boolean wasExpanded) { 397 final boolean nowExpanded = isExpanded(); 398 if (wasExpanded != nowExpanded && mLogger != null) { 399 mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded) ; 400 } 401 } 402} 403