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 17package com.android.systemui.statusbar.notification; 18 19import android.content.res.Resources; 20import android.util.Pools; 21import android.view.View; 22import android.view.ViewGroup; 23 24import com.android.internal.widget.MessagingGroup; 25import com.android.internal.widget.MessagingImageMessage; 26import com.android.internal.widget.MessagingLayout; 27import com.android.internal.widget.MessagingLinearLayout; 28import com.android.internal.widget.MessagingMessage; 29import com.android.internal.widget.MessagingPropertyAnimator; 30import com.android.systemui.Interpolators; 31 32import java.util.ArrayList; 33import java.util.HashMap; 34import java.util.List; 35 36/** 37 * A transform state of the action list 38*/ 39public 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 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 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 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 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 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 float transformationDistanceRemaining = 0; 101 for (int i = ownGroups.size() - 1; i >= 0; i--) { 102 MessagingGroup ownGroup = ownGroups.get(i); 103 MessagingGroup matchingGroup = pairs.get(ownGroup); 104 if (!isGone(ownGroup)) { 105 if (matchingGroup != null) { 106 transformGroups(ownGroup, matchingGroup, transformationAmount, to); 107 if (lastPairedGroup == null) { 108 lastPairedGroup = ownGroup; 109 if (to){ 110 float totalTranslation = ownGroup.getTop() - matchingGroup.getTop(); 111 transformationDistanceRemaining 112 = matchingGroup.getAvatar().getTranslationY(); 113 currentTranslation = transformationDistanceRemaining - totalTranslation; 114 } else { 115 float totalTranslation = matchingGroup.getTop() - ownGroup.getTop(); 116 currentTranslation = ownGroup.getAvatar().getTranslationY(); 117 transformationDistanceRemaining = currentTranslation - totalTranslation; 118 } 119 } 120 } else { 121 float groupTransformationAmount = transformationAmount; 122 if (lastPairedGroup != null) { 123 adaptGroupAppear(ownGroup, transformationAmount, currentTranslation, 124 to); 125 int distance = lastPairedGroup.getTop() - ownGroup.getTop(); 126 float transformationDistance = mTransformInfo.isAnimating() 127 ? distance 128 : ownGroup.getHeight() * 0.75f; 129 float translationProgress = transformationDistanceRemaining 130 - (distance - transformationDistance); 131 groupTransformationAmount = 132 translationProgress / transformationDistance; 133 groupTransformationAmount = Math.max(0.0f, Math.min(1.0f, 134 groupTransformationAmount)); 135 if (to) { 136 groupTransformationAmount = 1.0f - groupTransformationAmount; 137 } 138 } 139 if (to) { 140 disappear(ownGroup, groupTransformationAmount); 141 } else { 142 appear(ownGroup, groupTransformationAmount); 143 } 144 } 145 } 146 } 147 } 148 149 private void appear(MessagingGroup ownGroup, float transformationAmount) { 150 MessagingLinearLayout ownMessages = ownGroup.getMessageContainer(); 151 for (int j = 0; j < ownMessages.getChildCount(); j++) { 152 View child = ownMessages.getChildAt(j); 153 if (isGone(child)) { 154 continue; 155 } 156 appear(child, transformationAmount); 157 setClippingDeactivated(child, true); 158 } 159 appear(ownGroup.getAvatar(), transformationAmount); 160 appear(ownGroup.getSenderView(), transformationAmount); 161 appear(ownGroup.getIsolatedMessage(), transformationAmount); 162 setClippingDeactivated(ownGroup.getSenderView(), true); 163 setClippingDeactivated(ownGroup.getAvatar(), true); 164 } 165 166 private void adaptGroupAppear(MessagingGroup ownGroup, float transformationAmount, 167 float overallTranslation, boolean to) { 168 float relativeOffset; 169 if (to) { 170 relativeOffset = transformationAmount * mRelativeTranslationOffset; 171 } else { 172 relativeOffset = (1.0f - transformationAmount) * mRelativeTranslationOffset; 173 } 174 if (ownGroup.getSenderView().getVisibility() != View.GONE) { 175 relativeOffset *= 0.5f; 176 } 177 ownGroup.getMessageContainer().setTranslationY(relativeOffset); 178 ownGroup.setTranslationY(overallTranslation * 0.85f); 179 } 180 181 private void disappear(MessagingGroup ownGroup, float transformationAmount) { 182 MessagingLinearLayout ownMessages = ownGroup.getMessageContainer(); 183 for (int j = 0; j < ownMessages.getChildCount(); j++) { 184 View child = ownMessages.getChildAt(j); 185 if (isGone(child)) { 186 continue; 187 } 188 disappear(child, transformationAmount); 189 setClippingDeactivated(child, true); 190 } 191 disappear(ownGroup.getAvatar(), transformationAmount); 192 disappear(ownGroup.getSenderView(), transformationAmount); 193 disappear(ownGroup.getIsolatedMessage(), transformationAmount); 194 setClippingDeactivated(ownGroup.getSenderView(), true); 195 setClippingDeactivated(ownGroup.getAvatar(), true); 196 } 197 198 private void appear(View child, float transformationAmount) { 199 if (child == null || child.getVisibility() == View.GONE) { 200 return; 201 } 202 TransformState ownState = TransformState.createFrom(child, mTransformInfo); 203 ownState.appear(transformationAmount, null); 204 ownState.recycle(); 205 } 206 207 private void disappear(View child, float transformationAmount) { 208 if (child == null || child.getVisibility() == View.GONE) { 209 return; 210 } 211 TransformState ownState = TransformState.createFrom(child, mTransformInfo); 212 ownState.disappear(transformationAmount, null); 213 ownState.recycle(); 214 } 215 216 private ArrayList<MessagingGroup> filterHiddenGroups( 217 ArrayList<MessagingGroup> groups) { 218 ArrayList<MessagingGroup> result = new ArrayList<>(groups); 219 for (int i = 0; i < result.size(); i++) { 220 MessagingGroup messagingGroup = result.get(i); 221 if (isGone(messagingGroup)) { 222 result.remove(i); 223 i--; 224 } 225 } 226 return result; 227 } 228 229 private void transformGroups(MessagingGroup ownGroup, MessagingGroup otherGroup, 230 float transformationAmount, boolean to) { 231 boolean useLinearTransformation = 232 otherGroup.getIsolatedMessage() == null && !mTransformInfo.isAnimating(); 233 transformView(transformationAmount, to, ownGroup.getSenderView(), otherGroup.getSenderView(), 234 true /* sameAsAny */, useLinearTransformation); 235 transformView(transformationAmount, to, ownGroup.getAvatar(), otherGroup.getAvatar(), 236 true /* sameAsAny */, useLinearTransformation); 237 List<MessagingMessage> ownMessages = ownGroup.getMessages(); 238 List<MessagingMessage> otherMessages = otherGroup.getMessages(); 239 float previousTranslation = 0; 240 for (int i = 0; i < ownMessages.size(); i++) { 241 View child = ownMessages.get(ownMessages.size() - 1 - i).getView(); 242 if (isGone(child)) { 243 continue; 244 } 245 int otherIndex = otherMessages.size() - 1 - i; 246 View otherChild = null; 247 if (otherIndex >= 0) { 248 otherChild = otherMessages.get(otherIndex).getView(); 249 if (isGone(otherChild)) { 250 otherChild = null; 251 } 252 } 253 if (otherChild == null) { 254 float distanceToTop = child.getTop() + child.getHeight() + previousTranslation; 255 transformationAmount = distanceToTop / child.getHeight(); 256 transformationAmount = Math.max(0.0f, Math.min(1.0f, transformationAmount)); 257 if (to) { 258 transformationAmount = 1.0f - transformationAmount; 259 } 260 } 261 transformView(transformationAmount, to, child, otherChild, false, /* sameAsAny */ 262 useLinearTransformation); 263 if (transformationAmount == 0.0f 264 && otherGroup.getIsolatedMessage() == otherChild) { 265 ownGroup.setTransformingImages(true); 266 } 267 if (otherChild == null) { 268 child.setTranslationY(previousTranslation); 269 setClippingDeactivated(child, true); 270 } else if (to) { 271 float totalTranslation = child.getTop() + ownGroup.getTop() 272 - otherChild.getTop() - otherChild.getTop(); 273 previousTranslation = otherChild.getTranslationY() - totalTranslation; 274 } else { 275 previousTranslation = child.getTranslationY(); 276 } 277 } 278 ownGroup.updateClipRect(); 279 } 280 281 private void transformView(float transformationAmount, boolean to, View ownView, 282 View otherView, boolean sameAsAny, boolean useLinearTransformation) { 283 TransformState ownState = TransformState.createFrom(ownView, mTransformInfo); 284 if (useLinearTransformation) { 285 ownState.setDefaultInterpolator(Interpolators.LINEAR); 286 } 287 ownState.setIsSameAsAnyView(sameAsAny); 288 if (to) { 289 if (otherView != null) { 290 TransformState otherState = TransformState.createFrom(otherView, mTransformInfo); 291 ownState.transformViewTo(otherState, transformationAmount); 292 otherState.recycle(); 293 } else { 294 ownState.disappear(transformationAmount, null); 295 } 296 } else { 297 if (otherView != null) { 298 TransformState otherState = TransformState.createFrom(otherView, mTransformInfo); 299 ownState.transformViewFrom(otherState, transformationAmount); 300 otherState.recycle(); 301 } else { 302 ownState.appear(transformationAmount, null); 303 } 304 } 305 ownState.recycle(); 306 } 307 308 private HashMap<MessagingGroup, MessagingGroup> findPairs(ArrayList<MessagingGroup> ownGroups, 309 ArrayList<MessagingGroup> otherGroups) { 310 mGroupMap.clear(); 311 int lastMatch = Integer.MAX_VALUE; 312 for (int i = ownGroups.size() - 1; i >= 0; i--) { 313 MessagingGroup ownGroup = ownGroups.get(i); 314 MessagingGroup bestMatch = null; 315 int bestCompatibility = 0; 316 for (int j = Math.min(otherGroups.size(), lastMatch) - 1; j >= 0; j--) { 317 MessagingGroup otherGroup = otherGroups.get(j); 318 int compatibility = ownGroup.calculateGroupCompatibility(otherGroup); 319 if (compatibility > bestCompatibility) { 320 bestCompatibility = compatibility; 321 bestMatch = otherGroup; 322 lastMatch = j; 323 } 324 } 325 if (bestMatch != null) { 326 mGroupMap.put(ownGroup, bestMatch); 327 } 328 } 329 return mGroupMap; 330 } 331 332 private boolean isGone(View view) { 333 if (view.getVisibility() == View.GONE) { 334 return true; 335 } 336 final ViewGroup.LayoutParams lp = view.getLayoutParams(); 337 if (lp instanceof MessagingLinearLayout.LayoutParams 338 && ((MessagingLinearLayout.LayoutParams) lp).hide) { 339 return true; 340 } 341 return false; 342 } 343 344 @Override 345 public void setVisible(boolean visible, boolean force) { 346 super.setVisible(visible, force); 347 resetTransformedView(); 348 ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups(); 349 for (int i = 0; i < ownGroups.size(); i++) { 350 MessagingGroup ownGroup = ownGroups.get(i); 351 if (!isGone(ownGroup)) { 352 MessagingLinearLayout ownMessages = ownGroup.getMessageContainer(); 353 for (int j = 0; j < ownMessages.getChildCount(); j++) { 354 View child = ownMessages.getChildAt(j); 355 setVisible(child, visible, force); 356 } 357 setVisible(ownGroup.getAvatar(), visible, force); 358 setVisible(ownGroup.getSenderView(), visible, force); 359 MessagingImageMessage isolatedMessage = ownGroup.getIsolatedMessage(); 360 if (isolatedMessage != null) { 361 setVisible(isolatedMessage, visible, force); 362 } 363 } 364 } 365 } 366 367 private void setVisible(View child, boolean visible, boolean force) { 368 if (isGone(child) || MessagingPropertyAnimator.isAnimatingAlpha(child)) { 369 return; 370 } 371 TransformState ownState = TransformState.createFrom(child, mTransformInfo); 372 ownState.setVisible(visible, force); 373 ownState.recycle(); 374 } 375 376 @Override 377 protected void resetTransformedView() { 378 super.resetTransformedView(); 379 ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups(); 380 for (int i = 0; i < ownGroups.size(); i++) { 381 MessagingGroup ownGroup = ownGroups.get(i); 382 if (!isGone(ownGroup)) { 383 MessagingLinearLayout ownMessages = ownGroup.getMessageContainer(); 384 for (int j = 0; j < ownMessages.getChildCount(); j++) { 385 View child = ownMessages.getChildAt(j); 386 if (isGone(child)) { 387 continue; 388 } 389 resetTransformedView(child); 390 setClippingDeactivated(child, false); 391 } 392 resetTransformedView(ownGroup.getAvatar()); 393 resetTransformedView(ownGroup.getSenderView()); 394 MessagingImageMessage isolatedMessage = ownGroup.getIsolatedMessage(); 395 if (isolatedMessage != null) { 396 resetTransformedView(isolatedMessage); 397 } 398 setClippingDeactivated(ownGroup.getAvatar(), false); 399 setClippingDeactivated(ownGroup.getSenderView(), false); 400 ownGroup.setTranslationY(0); 401 ownGroup.getMessageContainer().setTranslationY(0); 402 } 403 ownGroup.setTransformingImages(false); 404 ownGroup.updateClipRect(); 405 } 406 } 407 408 @Override 409 public void prepareFadeIn() { 410 super.prepareFadeIn(); 411 setVisible(true /* visible */, false /* force */); 412 } 413 414 private void resetTransformedView(View child) { 415 TransformState ownState = TransformState.createFrom(child, mTransformInfo); 416 ownState.resetTransformedView(); 417 ownState.recycle(); 418 } 419 420 @Override 421 protected void reset() { 422 super.reset(); 423 mMessageContainer = null; 424 mMessagingLayout = null; 425 } 426 427 @Override 428 public void recycle() { 429 super.recycle(); 430 mGroupMap.clear();; 431 sInstancePool.release(this); 432 } 433} 434