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