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