1/*
2 * Copyright (C) 2015 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.app.Notification;
20import android.content.Context;
21import android.graphics.ColorFilter;
22import android.graphics.PorterDuffColorFilter;
23import android.util.ArraySet;
24import android.view.NotificationHeaderView;
25import android.view.View;
26import android.view.ViewGroup;
27import android.view.animation.Interpolator;
28import android.view.animation.PathInterpolator;
29import android.widget.ImageView;
30import android.widget.TextView;
31
32import com.android.internal.widget.NotificationExpandButton;
33import com.android.systemui.Interpolators;
34import com.android.systemui.ViewInvertHelper;
35import com.android.systemui.statusbar.ExpandableNotificationRow;
36import com.android.systemui.statusbar.TransformableView;
37import com.android.systemui.statusbar.ViewTransformationHelper;
38import com.android.systemui.statusbar.phone.NotificationPanelView;
39
40import java.util.Stack;
41
42import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y;
43
44/**
45 * Wraps a notification header view.
46 */
47public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
48
49    private static final Interpolator LOW_PRIORITY_HEADER_CLOSE
50            = new PathInterpolator(0.4f, 0f, 0.7f, 1f);
51
52    protected final ViewInvertHelper mInvertHelper;
53    protected final ViewTransformationHelper mTransformationHelper;
54
55    protected int mColor;
56    private ImageView mIcon;
57
58    private NotificationExpandButton mExpandButton;
59    private NotificationHeaderView mNotificationHeader;
60    private TextView mHeaderText;
61    private ImageView mWorkProfileImage;
62    private boolean mIsLowPriority;
63    private boolean mTransformLowPriorityTitle;
64
65    protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
66        super(ctx, view, row);
67        mInvertHelper = new ViewInvertHelper(ctx, NotificationPanelView.DOZE_ANIMATION_DURATION);
68        mTransformationHelper = new ViewTransformationHelper();
69
70        // we want to avoid that the header clashes with the other text when transforming
71        // low-priority
72        mTransformationHelper.setCustomTransformation(
73                new CustomInterpolatorTransformation(TRANSFORMING_VIEW_TITLE) {
74
75                    @Override
76                    public Interpolator getCustomInterpolator(int interpolationType,
77                            boolean isFrom) {
78                        boolean isLowPriority = mView instanceof NotificationHeaderView;
79                        if (interpolationType == TRANSFORM_Y) {
80                            if (isLowPriority && !isFrom
81                                    || !isLowPriority && isFrom) {
82                                return Interpolators.LINEAR_OUT_SLOW_IN;
83                            } else {
84                                return LOW_PRIORITY_HEADER_CLOSE;
85                            }
86                        }
87                        return null;
88                    }
89
90                    @Override
91                    protected boolean hasCustomTransformation() {
92                        return mIsLowPriority && mTransformLowPriorityTitle;
93                    }
94                }, TRANSFORMING_VIEW_TITLE);
95        resolveHeaderViews();
96        updateInvertHelper();
97    }
98
99    @Override
100    protected NotificationDozeHelper createDozer(Context ctx) {
101        return new NotificationIconDozeHelper(ctx);
102    }
103
104    @Override
105    protected NotificationIconDozeHelper getDozer() {
106        return (NotificationIconDozeHelper) super.getDozer();
107    }
108
109    protected void resolveHeaderViews() {
110        mIcon = mView.findViewById(com.android.internal.R.id.icon);
111        mHeaderText = mView.findViewById(com.android.internal.R.id.header_text);
112        mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button);
113        mExpandButton.setLabeledBy(mRow);
114        mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge);
115        mColor = resolveColor(mExpandButton);
116        mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header);
117        getDozer().setColor(mColor);
118    }
119
120    private int resolveColor(ImageView icon) {
121        if (icon != null && icon.getDrawable() != null) {
122            ColorFilter filter = icon.getDrawable().getColorFilter();
123            if (filter instanceof PorterDuffColorFilter) {
124                return ((PorterDuffColorFilter) filter).getColor();
125            }
126        }
127        return 0;
128    }
129
130    @Override
131    public void onContentUpdated(ExpandableNotificationRow row) {
132        super.onContentUpdated(row);
133        mIsLowPriority = row.isLowPriority();
134        mTransformLowPriorityTitle = !row.isChildInGroup() && !row.isSummaryWithChildren();
135        ArraySet<View> previousViews = mTransformationHelper.getAllTransformingViews();
136
137        // Reinspect the notification.
138        resolveHeaderViews();
139        updateInvertHelper();
140        updateTransformedTypes();
141        addRemainingTransformTypes();
142        updateCropToPaddingForImageViews();
143        Notification notification = row.getStatusBarNotification().getNotification();
144        mIcon.setTag(ImageTransformState.ICON_TAG, notification.getSmallIcon());
145        // The work profile image is always the same lets just set the icon tag for it not to
146        // animate
147        mWorkProfileImage.setTag(ImageTransformState.ICON_TAG, notification.getSmallIcon());
148
149        // We need to reset all views that are no longer transforming in case a view was previously
150        // transformed, but now we decided to transform its container instead.
151        ArraySet<View> currentViews = mTransformationHelper.getAllTransformingViews();
152        for (int i = 0; i < previousViews.size(); i++) {
153            View view = previousViews.valueAt(i);
154            if (!currentViews.contains(view)) {
155                mTransformationHelper.resetTransformedView(view);
156            }
157        }
158    }
159
160    /**
161     * Adds the remaining TransformTypes to the TransformHelper. This is done to make sure that each
162     * child is faded automatically and doesn't have to be manually added.
163     * The keys used for the views are the ids.
164     */
165    private void addRemainingTransformTypes() {
166        mTransformationHelper.addRemainingTransformTypes(mView);
167    }
168
169    /**
170     * Since we are deactivating the clipping when transforming the ImageViews don't get clipped
171     * anymore during these transitions. We can avoid that by using
172     * {@link ImageView#setCropToPadding(boolean)} on all ImageViews.
173     */
174    private void updateCropToPaddingForImageViews() {
175        Stack<View> stack = new Stack<>();
176        stack.push(mView);
177        while (!stack.isEmpty()) {
178            View child = stack.pop();
179            if (child instanceof ImageView) {
180                ((ImageView) child).setCropToPadding(true);
181            } else if (child instanceof ViewGroup){
182                ViewGroup group = (ViewGroup) child;
183                for (int i = 0; i < group.getChildCount(); i++) {
184                    stack.push(group.getChildAt(i));
185                }
186            }
187        }
188    }
189
190    protected void updateInvertHelper() {
191        mInvertHelper.clearTargets();
192        for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
193            View child = mNotificationHeader.getChildAt(i);
194            if (child != mIcon) {
195                mInvertHelper.addTarget(child);
196            }
197        }
198    }
199
200    protected void updateTransformedTypes() {
201        mTransformationHelper.reset();
202        mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_ICON, mIcon);
203        if (mIsLowPriority) {
204            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
205                    mHeaderText);
206        }
207    }
208
209    @Override
210    public void setDark(boolean dark, boolean fade, long delay) {
211        if (dark == mDark && mDarkInitialized) {
212            return;
213        }
214        super.setDark(dark, fade, delay);
215        if (fade) {
216            mInvertHelper.fade(dark, delay);
217        } else {
218            mInvertHelper.update(dark);
219        }
220        if (mIcon != null && !mRow.isChildInGroup()) {
221            // We don't update the color for children views / their icon is invisible anyway.
222            // It also may lead to bugs where the icon isn't correctly greyed out.
223            boolean hadColorFilter = mNotificationHeader.getOriginalIconColor()
224                    != NotificationHeaderView.NO_COLOR;
225
226            getDozer().setImageDark(mIcon, dark, fade, delay, !hadColorFilter);
227        }
228    }
229
230    @Override
231    public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {
232        mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
233        mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
234    }
235
236    @Override
237    public NotificationHeaderView getNotificationHeader() {
238        return mNotificationHeader;
239    }
240
241    @Override
242    public TransformState getCurrentState(int fadingView) {
243        return mTransformationHelper.getCurrentState(fadingView);
244    }
245
246    @Override
247    public void transformTo(TransformableView notification, Runnable endRunnable) {
248        mTransformationHelper.transformTo(notification, endRunnable);
249    }
250
251    @Override
252    public void transformTo(TransformableView notification, float transformationAmount) {
253        mTransformationHelper.transformTo(notification, transformationAmount);
254    }
255
256    @Override
257    public void transformFrom(TransformableView notification) {
258        mTransformationHelper.transformFrom(notification);
259    }
260
261    @Override
262    public void transformFrom(TransformableView notification, float transformationAmount) {
263        mTransformationHelper.transformFrom(notification, transformationAmount);
264    }
265
266    @Override
267    public void setIsChildInGroup(boolean isChildInGroup) {
268        super.setIsChildInGroup(isChildInGroup);
269        mTransformLowPriorityTitle = !isChildInGroup;
270    }
271
272    @Override
273    public void setVisible(boolean visible) {
274        super.setVisible(visible);
275        mTransformationHelper.setVisible(visible);
276    }
277}
278