1/*
2 * Copyright (C) 2014 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.stack;
18
19import android.util.Log;
20import android.view.View;
21import android.view.ViewGroup;
22
23import com.android.systemui.R;
24import com.android.systemui.statusbar.DismissView;
25import com.android.systemui.statusbar.EmptyShadeView;
26import com.android.systemui.statusbar.ExpandableNotificationRow;
27import com.android.systemui.statusbar.ExpandableView;
28
29import java.util.List;
30import java.util.WeakHashMap;
31
32/**
33 * A state of a {@link com.android.systemui.statusbar.stack.NotificationStackScrollLayout} which
34 * can be applied to a viewGroup.
35 */
36public class StackScrollState {
37
38    private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild";
39
40    private final ViewGroup mHostView;
41    private WeakHashMap<ExpandableView, StackViewState> mStateMap;
42    private final int mClearAllTopPadding;
43
44    public StackScrollState(ViewGroup hostView) {
45        mHostView = hostView;
46        mStateMap = new WeakHashMap<>();
47        mClearAllTopPadding = hostView.getContext().getResources().getDimensionPixelSize(
48                R.dimen.clear_all_padding_top);
49    }
50
51    public ViewGroup getHostView() {
52        return mHostView;
53    }
54
55    public void resetViewStates() {
56        int numChildren = mHostView.getChildCount();
57        for (int i = 0; i < numChildren; i++) {
58            ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
59            resetViewState(child);
60
61            // handling reset for child notifications
62            if (child instanceof ExpandableNotificationRow) {
63                ExpandableNotificationRow row = (ExpandableNotificationRow) child;
64                List<ExpandableNotificationRow> children =
65                        row.getNotificationChildren();
66                if (row.isSummaryWithChildren() && children != null) {
67                    for (ExpandableNotificationRow childRow : children) {
68                        resetViewState(childRow);
69                    }
70                }
71            }
72        }
73    }
74
75    private void resetViewState(ExpandableView view) {
76        StackViewState viewState = mStateMap.get(view);
77        if (viewState == null) {
78            viewState = new StackViewState();
79            mStateMap.put(view, viewState);
80        }
81        // initialize with the default values of the view
82        viewState.height = view.getIntrinsicHeight();
83        viewState.gone = view.getVisibility() == View.GONE;
84        viewState.alpha = 1f;
85        viewState.shadowAlpha = 1f;
86        viewState.notGoneIndex = -1;
87        viewState.hidden = false;
88    }
89
90    public StackViewState getViewStateForView(View requestedView) {
91        return mStateMap.get(requestedView);
92    }
93
94    public void removeViewStateForView(View child) {
95        mStateMap.remove(child);
96    }
97
98    /**
99     * Apply the properties saved in {@link #mStateMap} to the children of the {@link #mHostView}.
100     * The properties are only applied if they effectively changed.
101     */
102    public void apply() {
103        int numChildren = mHostView.getChildCount();
104        for (int i = 0; i < numChildren; i++) {
105            ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
106            StackViewState state = mStateMap.get(child);
107            if (!applyState(child, state)) {
108                continue;
109            }
110            if (child instanceof DismissView) {
111                DismissView dismissView = (DismissView) child;
112                boolean visible = state.clipTopAmount < mClearAllTopPadding;
113                dismissView.performVisibilityAnimation(visible && !dismissView.willBeGone());
114            } else if (child instanceof EmptyShadeView) {
115                EmptyShadeView emptyShadeView = (EmptyShadeView) child;
116                boolean visible = state.clipTopAmount <= 0;
117                emptyShadeView.performVisibilityAnimation(
118                        visible && !emptyShadeView.willBeGone());
119            }
120        }
121    }
122
123    /**
124     * Applies a  {@link StackViewState} to an  {@link ExpandableView}.
125     *
126     * @return whether the state was applied correctly
127     */
128    public boolean applyState(ExpandableView view, StackViewState state) {
129        if (state == null) {
130            Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " +
131                    "to the hostView");
132            return false;
133        }
134        if (state.gone) {
135            return false;
136        }
137        applyViewState(view, state);
138
139        int height = view.getActualHeight();
140        int newHeight = state.height;
141
142        // apply height
143        if (height != newHeight) {
144            view.setActualHeight(newHeight, false /* notifyListeners */);
145        }
146
147        float shadowAlpha = view.getShadowAlpha();
148        float newShadowAlpha = state.shadowAlpha;
149
150        // apply shadowAlpha
151        if (shadowAlpha != newShadowAlpha) {
152            view.setShadowAlpha(newShadowAlpha);
153        }
154
155        // apply dimming
156        view.setDimmed(state.dimmed, false /* animate */);
157
158        // apply hiding sensitive
159        view.setHideSensitive(
160                state.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
161
162        // apply speed bump state
163        view.setBelowSpeedBump(state.belowSpeedBump);
164
165        // apply dark
166        view.setDark(state.dark, false /* animate */, 0 /* delay */);
167
168        // apply clipping
169        float oldClipTopAmount = view.getClipTopAmount();
170        if (oldClipTopAmount != state.clipTopAmount) {
171            view.setClipTopAmount(state.clipTopAmount);
172        }
173        if (view instanceof ExpandableNotificationRow) {
174            ExpandableNotificationRow row = (ExpandableNotificationRow) view;
175            if (state.isBottomClipped) {
176                row.setClipToActualHeight(true);
177            }
178            row.applyChildrenState(this);
179        }
180        return true;
181    }
182
183    /**
184     * Applies a  {@link ViewState} to a normal view.
185     */
186    public void applyViewState(View view, ViewState state) {
187        float alpha = view.getAlpha();
188        float yTranslation = view.getTranslationY();
189        float xTranslation = view.getTranslationX();
190        float zTranslation = view.getTranslationZ();
191        float newAlpha = state.alpha;
192        float newYTranslation = state.yTranslation;
193        float newZTranslation = state.zTranslation;
194        boolean becomesInvisible = newAlpha == 0.0f || state.hidden;
195        if (alpha != newAlpha && xTranslation == 0) {
196            // apply layer type
197            boolean becomesFullyVisible = newAlpha == 1.0f;
198            boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible
199                    && view.hasOverlappingRendering();
200            int layerType = view.getLayerType();
201            int newLayerType = newLayerTypeIsHardware
202                    ? View.LAYER_TYPE_HARDWARE
203                    : View.LAYER_TYPE_NONE;
204            if (layerType != newLayerType) {
205                view.setLayerType(newLayerType, null);
206            }
207
208            // apply alpha
209            view.setAlpha(newAlpha);
210        }
211
212        // apply visibility
213        int oldVisibility = view.getVisibility();
214        int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
215        if (newVisibility != oldVisibility) {
216            if (!(view instanceof ExpandableView) || !((ExpandableView) view).willBeGone()) {
217                // We don't want views to change visibility when they are animating to GONE
218                view.setVisibility(newVisibility);
219            }
220        }
221
222        // apply yTranslation
223        if (yTranslation != newYTranslation) {
224            view.setTranslationY(newYTranslation);
225        }
226
227        // apply zTranslation
228        if (zTranslation != newZTranslation) {
229            view.setTranslationZ(newZTranslation);
230        }
231    }
232}
233