• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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