1 /* 2 * Copyright (C) 2017 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 17 package com.android.systemui.statusbar.notification; 18 19 import android.content.res.Resources; 20 import android.util.Pools; 21 import android.view.View; 22 import android.view.ViewGroup; 23 24 import com.android.internal.widget.MessagingGroup; 25 import com.android.internal.widget.MessagingImageMessage; 26 import com.android.internal.widget.MessagingLayout; 27 import com.android.internal.widget.MessagingLinearLayout; 28 import com.android.internal.widget.MessagingMessage; 29 import com.android.internal.widget.MessagingPropertyAnimator; 30 import com.android.systemui.Interpolators; 31 32 import java.util.ArrayList; 33 import java.util.HashMap; 34 import java.util.List; 35 36 /** 37 * A transform state of the action list 38 */ 39 public class MessagingLayoutTransformState extends TransformState { 40 41 private static Pools.SimplePool<MessagingLayoutTransformState> sInstancePool 42 = new Pools.SimplePool<>(40); 43 private MessagingLinearLayout mMessageContainer; 44 private MessagingLayout mMessagingLayout; 45 private HashMap<MessagingGroup, MessagingGroup> mGroupMap = new HashMap<>(); 46 private float mRelativeTranslationOffset; 47 obtain()48 public static MessagingLayoutTransformState obtain() { 49 MessagingLayoutTransformState instance = sInstancePool.acquire(); 50 if (instance != null) { 51 return instance; 52 } 53 return new MessagingLayoutTransformState(); 54 } 55 56 @Override initFrom(View view, TransformInfo transformInfo)57 public void initFrom(View view, TransformInfo transformInfo) { 58 super.initFrom(view, transformInfo); 59 if (mTransformedView instanceof MessagingLinearLayout) { 60 mMessageContainer = (MessagingLinearLayout) mTransformedView; 61 mMessagingLayout = mMessageContainer.getMessagingLayout(); 62 Resources resources = view.getContext().getResources(); 63 mRelativeTranslationOffset = resources.getDisplayMetrics().density * 8; 64 } 65 } 66 67 @Override transformViewTo(TransformState otherState, float transformationAmount)68 public boolean transformViewTo(TransformState otherState, float transformationAmount) { 69 if (otherState instanceof MessagingLayoutTransformState) { 70 // It's a party! Let's transform between these two layouts! 71 transformViewInternal((MessagingLayoutTransformState) otherState, transformationAmount, 72 true /* to */); 73 return true; 74 } else { 75 return super.transformViewTo(otherState, transformationAmount); 76 } 77 } 78 79 @Override transformViewFrom(TransformState otherState, float transformationAmount)80 public void transformViewFrom(TransformState otherState, float transformationAmount) { 81 if (otherState instanceof MessagingLayoutTransformState) { 82 // It's a party! Let's transform between these two layouts! 83 transformViewInternal((MessagingLayoutTransformState) otherState, transformationAmount, 84 false /* to */); 85 } else { 86 super.transformViewFrom(otherState, transformationAmount); 87 } 88 } 89 transformViewInternal(MessagingLayoutTransformState mlt, float transformationAmount, boolean to)90 private void transformViewInternal(MessagingLayoutTransformState mlt, 91 float transformationAmount, boolean to) { 92 ensureVisible(); 93 ArrayList<MessagingGroup> ownGroups = filterHiddenGroups( 94 mMessagingLayout.getMessagingGroups()); 95 ArrayList<MessagingGroup> otherGroups = filterHiddenGroups( 96 mlt.mMessagingLayout.getMessagingGroups()); 97 HashMap<MessagingGroup, MessagingGroup> pairs = findPairs(ownGroups, otherGroups); 98 MessagingGroup lastPairedGroup = null; 99 float currentTranslation = 0; 100 for (int i = ownGroups.size() - 1; i >= 0; i--) { 101 MessagingGroup ownGroup = ownGroups.get(i); 102 MessagingGroup matchingGroup = pairs.get(ownGroup); 103 if (!isGone(ownGroup)) { 104 if (matchingGroup != null) { 105 transformGroups(ownGroup, matchingGroup, transformationAmount, to); 106 if (lastPairedGroup == null) { 107 lastPairedGroup = ownGroup; 108 if (to){ 109 float totalTranslation = ownGroup.getTop() - matchingGroup.getTop(); 110 currentTranslation = matchingGroup.getAvatar().getTranslationY() 111 - totalTranslation; 112 } else { 113 currentTranslation = ownGroup.getAvatar().getTranslationY(); 114 } 115 } 116 } else { 117 float groupTransformationAmount = transformationAmount; 118 if (lastPairedGroup != null) { 119 adaptGroupAppear(ownGroup, transformationAmount, currentTranslation, 120 to); 121 float newPosition = ownGroup.getTop() + currentTranslation; 122 123 if (!mTransformInfo.isAnimating()) { 124 // We fade the group away as soon as 1/2 of it is translated away on top 125 float fadeStart = -ownGroup.getHeight() * 0.5f; 126 groupTransformationAmount = (newPosition - fadeStart) 127 / Math.abs(fadeStart); 128 } else { 129 float fadeStart = -ownGroup.getHeight() * 0.75f; 130 // We want to fade out as soon as the animation starts, let's add the 131 // complete top in addition 132 groupTransformationAmount = (newPosition - fadeStart) 133 / (Math.abs(fadeStart) + ownGroup.getTop()); 134 } 135 groupTransformationAmount = Math.max(0.0f, Math.min(1.0f, 136 groupTransformationAmount)); 137 if (to) { 138 groupTransformationAmount = 1.0f - groupTransformationAmount; 139 } 140 } 141 if (to) { 142 disappear(ownGroup, groupTransformationAmount); 143 } else { 144 appear(ownGroup, groupTransformationAmount); 145 } 146 } 147 } 148 } 149 } 150 appear(MessagingGroup ownGroup, float transformationAmount)151 private void appear(MessagingGroup ownGroup, float transformationAmount) { 152 MessagingLinearLayout ownMessages = ownGroup.getMessageContainer(); 153 for (int j = 0; j < ownMessages.getChildCount(); j++) { 154 View child = ownMessages.getChildAt(j); 155 if (isGone(child)) { 156 continue; 157 } 158 appear(child, transformationAmount); 159 setClippingDeactivated(child, true); 160 } 161 appear(ownGroup.getAvatar(), transformationAmount); 162 appear(ownGroup.getSenderView(), transformationAmount); 163 appear(ownGroup.getIsolatedMessage(), transformationAmount); 164 setClippingDeactivated(ownGroup.getSenderView(), true); 165 setClippingDeactivated(ownGroup.getAvatar(), true); 166 } 167 adaptGroupAppear(MessagingGroup ownGroup, float transformationAmount, float overallTranslation, boolean to)168 private void adaptGroupAppear(MessagingGroup ownGroup, float transformationAmount, 169 float overallTranslation, boolean to) { 170 float relativeOffset; 171 if (to) { 172 relativeOffset = transformationAmount * mRelativeTranslationOffset; 173 } else { 174 relativeOffset = (1.0f - transformationAmount) * mRelativeTranslationOffset; 175 } 176 if (ownGroup.getSenderView().getVisibility() != View.GONE) { 177 relativeOffset *= 0.5f; 178 } 179 ownGroup.getMessageContainer().setTranslationY(relativeOffset); 180 ownGroup.getSenderView().setTranslationY(relativeOffset); 181 ownGroup.setTranslationY(overallTranslation * 0.9f); 182 } 183 disappear(MessagingGroup ownGroup, float transformationAmount)184 private void disappear(MessagingGroup ownGroup, float transformationAmount) { 185 MessagingLinearLayout ownMessages = ownGroup.getMessageContainer(); 186 for (int j = 0; j < ownMessages.getChildCount(); j++) { 187 View child = ownMessages.getChildAt(j); 188 if (isGone(child)) { 189 continue; 190 } 191 disappear(child, transformationAmount); 192 setClippingDeactivated(child, true); 193 } 194 disappear(ownGroup.getAvatar(), transformationAmount); 195 disappear(ownGroup.getSenderView(), transformationAmount); 196 disappear(ownGroup.getIsolatedMessage(), transformationAmount); 197 setClippingDeactivated(ownGroup.getSenderView(), true); 198 setClippingDeactivated(ownGroup.getAvatar(), true); 199 } 200 appear(View child, float transformationAmount)201 private void appear(View child, float transformationAmount) { 202 if (child == null || child.getVisibility() == View.GONE) { 203 return; 204 } 205 TransformState ownState = TransformState.createFrom(child, mTransformInfo); 206 ownState.appear(transformationAmount, null); 207 ownState.recycle(); 208 } 209 disappear(View child, float transformationAmount)210 private void disappear(View child, float transformationAmount) { 211 if (child == null || child.getVisibility() == View.GONE) { 212 return; 213 } 214 TransformState ownState = TransformState.createFrom(child, mTransformInfo); 215 ownState.disappear(transformationAmount, null); 216 ownState.recycle(); 217 } 218 filterHiddenGroups( ArrayList<MessagingGroup> groups)219 private ArrayList<MessagingGroup> filterHiddenGroups( 220 ArrayList<MessagingGroup> groups) { 221 ArrayList<MessagingGroup> result = new ArrayList<>(groups); 222 for (int i = 0; i < result.size(); i++) { 223 MessagingGroup messagingGroup = result.get(i); 224 if (isGone(messagingGroup)) { 225 result.remove(i); 226 i--; 227 } 228 } 229 return result; 230 } 231 transformGroups(MessagingGroup ownGroup, MessagingGroup otherGroup, float transformationAmount, boolean to)232 private void transformGroups(MessagingGroup ownGroup, MessagingGroup otherGroup, 233 float transformationAmount, boolean to) { 234 boolean useLinearTransformation = 235 otherGroup.getIsolatedMessage() == null && !mTransformInfo.isAnimating(); 236 transformView(transformationAmount, to, ownGroup.getSenderView(), otherGroup.getSenderView(), 237 true /* sameAsAny */, useLinearTransformation); 238 transformView(transformationAmount, to, ownGroup.getAvatar(), otherGroup.getAvatar(), 239 true /* sameAsAny */, useLinearTransformation); 240 List<MessagingMessage> ownMessages = ownGroup.getMessages(); 241 List<MessagingMessage> otherMessages = otherGroup.getMessages(); 242 float previousTranslation = 0; 243 for (int i = 0; i < ownMessages.size(); i++) { 244 View child = ownMessages.get(ownMessages.size() - 1 - i).getView(); 245 if (isGone(child)) { 246 continue; 247 } 248 int otherIndex = otherMessages.size() - 1 - i; 249 View otherChild = null; 250 if (otherIndex >= 0) { 251 otherChild = otherMessages.get(otherIndex).getView(); 252 if (isGone(otherChild)) { 253 otherChild = null; 254 } 255 } 256 if (otherChild == null && previousTranslation < 0) { 257 // Let's fade out as we approach the top of the screen. We can only do this if 258 // we're actually moving up 259 float distanceToTop = child.getTop() + child.getHeight() + previousTranslation; 260 transformationAmount = distanceToTop / child.getHeight(); 261 transformationAmount = Math.max(0.0f, Math.min(1.0f, transformationAmount)); 262 if (to) { 263 transformationAmount = 1.0f - transformationAmount; 264 } 265 } 266 transformView(transformationAmount, to, child, otherChild, false, /* sameAsAny */ 267 useLinearTransformation); 268 boolean otherIsIsolated = otherGroup.getIsolatedMessage() == otherChild; 269 if (transformationAmount == 0.0f && otherIsIsolated) { 270 ownGroup.setTransformingImages(true); 271 } 272 if (otherChild == null) { 273 child.setTranslationY(previousTranslation); 274 setClippingDeactivated(child, true); 275 } else if (ownGroup.getIsolatedMessage() == child || otherIsIsolated) { 276 // We don't want to add any translation for the image that is transforming 277 } else if (to) { 278 float totalTranslation = child.getTop() + ownGroup.getTop() 279 - otherChild.getTop() - otherChild.getTop(); 280 previousTranslation = otherChild.getTranslationY() - totalTranslation; 281 } else { 282 previousTranslation = child.getTranslationY(); 283 } 284 } 285 ownGroup.updateClipRect(); 286 } 287 transformView(float transformationAmount, boolean to, View ownView, View otherView, boolean sameAsAny, boolean useLinearTransformation)288 private void transformView(float transformationAmount, boolean to, View ownView, 289 View otherView, boolean sameAsAny, boolean useLinearTransformation) { 290 TransformState ownState = TransformState.createFrom(ownView, mTransformInfo); 291 if (useLinearTransformation) { 292 ownState.setDefaultInterpolator(Interpolators.LINEAR); 293 } 294 ownState.setIsSameAsAnyView(sameAsAny); 295 if (to) { 296 if (otherView != null) { 297 TransformState otherState = TransformState.createFrom(otherView, mTransformInfo); 298 ownState.transformViewTo(otherState, transformationAmount); 299 otherState.recycle(); 300 } else { 301 ownState.disappear(transformationAmount, null); 302 } 303 } else { 304 if (otherView != null) { 305 TransformState otherState = TransformState.createFrom(otherView, mTransformInfo); 306 ownState.transformViewFrom(otherState, transformationAmount); 307 otherState.recycle(); 308 } else { 309 ownState.appear(transformationAmount, null); 310 } 311 } 312 ownState.recycle(); 313 } 314 findPairs(ArrayList<MessagingGroup> ownGroups, ArrayList<MessagingGroup> otherGroups)315 private HashMap<MessagingGroup, MessagingGroup> findPairs(ArrayList<MessagingGroup> ownGroups, 316 ArrayList<MessagingGroup> otherGroups) { 317 mGroupMap.clear(); 318 int lastMatch = Integer.MAX_VALUE; 319 for (int i = ownGroups.size() - 1; i >= 0; i--) { 320 MessagingGroup ownGroup = ownGroups.get(i); 321 MessagingGroup bestMatch = null; 322 int bestCompatibility = 0; 323 for (int j = Math.min(otherGroups.size(), lastMatch) - 1; j >= 0; j--) { 324 MessagingGroup otherGroup = otherGroups.get(j); 325 int compatibility = ownGroup.calculateGroupCompatibility(otherGroup); 326 if (compatibility > bestCompatibility) { 327 bestCompatibility = compatibility; 328 bestMatch = otherGroup; 329 lastMatch = j; 330 } 331 } 332 if (bestMatch != null) { 333 mGroupMap.put(ownGroup, bestMatch); 334 } 335 } 336 return mGroupMap; 337 } 338 isGone(View view)339 private boolean isGone(View view) { 340 if (view.getVisibility() == View.GONE) { 341 return true; 342 } 343 final ViewGroup.LayoutParams lp = view.getLayoutParams(); 344 if (lp instanceof MessagingLinearLayout.LayoutParams 345 && ((MessagingLinearLayout.LayoutParams) lp).hide) { 346 return true; 347 } 348 return false; 349 } 350 351 @Override setVisible(boolean visible, boolean force)352 public void setVisible(boolean visible, boolean force) { 353 super.setVisible(visible, force); 354 resetTransformedView(); 355 ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups(); 356 for (int i = 0; i < ownGroups.size(); i++) { 357 MessagingGroup ownGroup = ownGroups.get(i); 358 if (!isGone(ownGroup)) { 359 MessagingLinearLayout ownMessages = ownGroup.getMessageContainer(); 360 for (int j = 0; j < ownMessages.getChildCount(); j++) { 361 View child = ownMessages.getChildAt(j); 362 setVisible(child, visible, force); 363 } 364 setVisible(ownGroup.getAvatar(), visible, force); 365 setVisible(ownGroup.getSenderView(), visible, force); 366 MessagingImageMessage isolatedMessage = ownGroup.getIsolatedMessage(); 367 if (isolatedMessage != null) { 368 setVisible(isolatedMessage, visible, force); 369 } 370 } 371 } 372 } 373 setVisible(View child, boolean visible, boolean force)374 private void setVisible(View child, boolean visible, boolean force) { 375 if (isGone(child) || MessagingPropertyAnimator.isAnimatingAlpha(child)) { 376 return; 377 } 378 TransformState ownState = TransformState.createFrom(child, mTransformInfo); 379 ownState.setVisible(visible, force); 380 ownState.recycle(); 381 } 382 383 @Override resetTransformedView()384 protected void resetTransformedView() { 385 super.resetTransformedView(); 386 ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups(); 387 for (int i = 0; i < ownGroups.size(); i++) { 388 MessagingGroup ownGroup = ownGroups.get(i); 389 if (!isGone(ownGroup)) { 390 MessagingLinearLayout ownMessages = ownGroup.getMessageContainer(); 391 for (int j = 0; j < ownMessages.getChildCount(); j++) { 392 View child = ownMessages.getChildAt(j); 393 if (isGone(child)) { 394 continue; 395 } 396 resetTransformedView(child); 397 setClippingDeactivated(child, false); 398 } 399 resetTransformedView(ownGroup.getAvatar()); 400 resetTransformedView(ownGroup.getSenderView()); 401 MessagingImageMessage isolatedMessage = ownGroup.getIsolatedMessage(); 402 if (isolatedMessage != null) { 403 resetTransformedView(isolatedMessage); 404 } 405 setClippingDeactivated(ownGroup.getAvatar(), false); 406 setClippingDeactivated(ownGroup.getSenderView(), false); 407 ownGroup.setTranslationY(0); 408 ownGroup.getMessageContainer().setTranslationY(0); 409 ownGroup.getSenderView().setTranslationY(0); 410 } 411 ownGroup.setTransformingImages(false); 412 ownGroup.updateClipRect(); 413 } 414 } 415 416 @Override prepareFadeIn()417 public void prepareFadeIn() { 418 super.prepareFadeIn(); 419 setVisible(true /* visible */, false /* force */); 420 } 421 resetTransformedView(View child)422 private void resetTransformedView(View child) { 423 TransformState ownState = TransformState.createFrom(child, mTransformInfo); 424 ownState.resetTransformedView(); 425 ownState.recycle(); 426 } 427 428 @Override reset()429 protected void reset() { 430 super.reset(); 431 mMessageContainer = null; 432 mMessagingLayout = null; 433 } 434 435 @Override recycle()436 public void recycle() { 437 super.recycle(); 438 mGroupMap.clear();; 439 sInstancePool.release(this); 440 } 441 } 442