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.notification;
18
19import android.content.Context;
20import android.graphics.Color;
21import android.graphics.Rect;
22import android.service.notification.StatusBarNotification;
23import android.view.View;
24import android.widget.ImageView;
25import android.widget.ProgressBar;
26import android.widget.TextView;
27
28import com.android.systemui.statusbar.CrossFadeHelper;
29import com.android.systemui.statusbar.ExpandableNotificationRow;
30import com.android.systemui.statusbar.TransformableView;
31import com.android.systemui.statusbar.ViewTransformationHelper;
32
33/**
34 * Wraps a notification view inflated from a template.
35 */
36public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapper {
37
38    private static final int mDarkProgressTint = 0xffffffff;
39
40    protected ImageView mPicture;
41    private ProgressBar mProgressBar;
42    private TextView mTitle;
43    private TextView mText;
44    private View mActionsContainer;
45    private View mReplyAction;
46    private Rect mTmpRect = new Rect();
47
48    private int mContentHeight;
49    private int mMinHeightHint;
50
51    protected NotificationTemplateViewWrapper(Context ctx, View view,
52            ExpandableNotificationRow row) {
53        super(ctx, view, row);
54        mTransformationHelper.setCustomTransformation(
55                new ViewTransformationHelper.CustomTransformation() {
56                    @Override
57                    public boolean transformTo(TransformState ownState,
58                            TransformableView notification, final float transformationAmount) {
59                        if (!(notification instanceof HybridNotificationView)) {
60                            return false;
61                        }
62                        TransformState otherState = notification.getCurrentState(
63                                TRANSFORMING_VIEW_TITLE);
64                        final View text = ownState.getTransformedView();
65                        CrossFadeHelper.fadeOut(text, transformationAmount);
66                        if (otherState != null) {
67                            ownState.transformViewVerticalTo(otherState, this,
68                                    transformationAmount);
69                            otherState.recycle();
70                        }
71                        return true;
72                    }
73
74                    @Override
75                    public boolean customTransformTarget(TransformState ownState,
76                            TransformState otherState) {
77                        float endY = getTransformationY(ownState, otherState);
78                        ownState.setTransformationEndY(endY);
79                        return true;
80                    }
81
82                    @Override
83                    public boolean transformFrom(TransformState ownState,
84                            TransformableView notification, float transformationAmount) {
85                        if (!(notification instanceof HybridNotificationView)) {
86                            return false;
87                        }
88                        TransformState otherState = notification.getCurrentState(
89                                TRANSFORMING_VIEW_TITLE);
90                        final View text = ownState.getTransformedView();
91                        CrossFadeHelper.fadeIn(text, transformationAmount);
92                        if (otherState != null) {
93                            ownState.transformViewVerticalFrom(otherState, this,
94                                    transformationAmount);
95                            otherState.recycle();
96                        }
97                        return true;
98                    }
99
100                    @Override
101                    public boolean initTransformation(TransformState ownState,
102                            TransformState otherState) {
103                        float startY = getTransformationY(ownState, otherState);
104                        ownState.setTransformationStartY(startY);
105                        return true;
106                    }
107
108                    private float getTransformationY(TransformState ownState,
109                            TransformState otherState) {
110                        int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
111                        int[] ownStablePosition = ownState.getLaidOutLocationOnScreen();
112                        return (otherStablePosition[1]
113                                + otherState.getTransformedView().getHeight()
114                                - ownStablePosition[1]) * 0.33f;
115                    }
116
117                }, TRANSFORMING_VIEW_TEXT);
118    }
119
120    private void resolveTemplateViews(StatusBarNotification notification) {
121        mPicture = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon);
122        if (mPicture != null) {
123            mPicture.setTag(ImageTransformState.ICON_TAG,
124                    notification.getNotification().getLargeIcon());
125        }
126        mTitle = (TextView) mView.findViewById(com.android.internal.R.id.title);
127        mText = (TextView) mView.findViewById(com.android.internal.R.id.text);
128        final View progress = mView.findViewById(com.android.internal.R.id.progress);
129        if (progress instanceof ProgressBar) {
130            mProgressBar = (ProgressBar) progress;
131        } else {
132            // It's still a viewstub
133            mProgressBar = null;
134        }
135        mActionsContainer = mView.findViewById(com.android.internal.R.id.actions_container);
136        mReplyAction = mView.findViewById(com.android.internal.R.id.reply_icon_action);
137    }
138
139    @Override
140    public boolean disallowSingleClick(float x, float y) {
141        if (mReplyAction != null && mReplyAction.getVisibility() == View.VISIBLE) {
142            if (isOnView(mReplyAction, x, y) || isOnView(mPicture, x, y)) {
143                return true;
144            }
145        }
146        return super.disallowSingleClick(x, y);
147    }
148
149    private boolean isOnView(View view, float x, float y) {
150        View searchView = (View) view.getParent();
151        while (searchView != null && !(searchView instanceof ExpandableNotificationRow)) {
152            searchView.getHitRect(mTmpRect);
153            x -= mTmpRect.left;
154            y -= mTmpRect.top;
155            searchView = (View) searchView.getParent();
156        }
157        view.getHitRect(mTmpRect);
158        return mTmpRect.contains((int) x,(int) y);
159    }
160
161    @Override
162    public void onContentUpdated(ExpandableNotificationRow row) {
163        // Reinspect the notification. Before the super call, because the super call also updates
164        // the transformation types and we need to have our values set by then.
165        resolveTemplateViews(row.getStatusBarNotification());
166        super.onContentUpdated(row);
167    }
168
169    @Override
170    protected void updateInvertHelper() {
171        super.updateInvertHelper();
172        View mainColumn = mView.findViewById(com.android.internal.R.id.notification_main_column);
173        if (mainColumn != null) {
174            mInvertHelper.addTarget(mainColumn);
175        }
176    }
177
178    @Override
179    protected void updateTransformedTypes() {
180        // This also clears the existing types
181        super.updateTransformedTypes();
182        if (mTitle != null) {
183            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
184                    mTitle);
185        }
186        if (mText != null) {
187            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT,
188                    mText);
189        }
190        if (mPicture != null) {
191            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE,
192                    mPicture);
193        }
194        if (mProgressBar != null) {
195            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS,
196                    mProgressBar);
197        }
198    }
199
200    @Override
201    public void setDark(boolean dark, boolean fade, long delay) {
202        if (dark == mDark && mDarkInitialized) {
203            return;
204        }
205        super.setDark(dark, fade, delay);
206        setPictureDark(dark, fade, delay);
207        setProgressBarDark(dark, fade, delay);
208    }
209
210    private void setProgressBarDark(boolean dark, boolean fade, long delay) {
211        if (mProgressBar != null) {
212            if (fade) {
213                fadeProgressDark(mProgressBar, dark, delay);
214            } else {
215                updateProgressDark(mProgressBar, dark);
216            }
217        }
218    }
219
220    private void fadeProgressDark(final ProgressBar target, final boolean dark, long delay) {
221        getDozer().startIntensityAnimation(animation -> {
222            float t = (float) animation.getAnimatedValue();
223            updateProgressDark(target, t);
224        }, dark, delay, null /* listener */);
225    }
226
227    private void updateProgressDark(ProgressBar target, float intensity) {
228        int color = interpolateColor(mColor, mDarkProgressTint, intensity);
229        target.getIndeterminateDrawable().mutate().setTint(color);
230        target.getProgressDrawable().mutate().setTint(color);
231    }
232
233    private void updateProgressDark(ProgressBar target, boolean dark) {
234        updateProgressDark(target, dark ? 1f : 0f);
235    }
236
237    private void setPictureDark(boolean dark, boolean fade, long delay) {
238        if (mPicture != null) {
239            getDozer().setImageDark(mPicture, dark, fade, delay, true /* useGrayscale */);
240        }
241    }
242
243    private static int interpolateColor(int source, int target, float t) {
244        int aSource = Color.alpha(source);
245        int rSource = Color.red(source);
246        int gSource = Color.green(source);
247        int bSource = Color.blue(source);
248        int aTarget = Color.alpha(target);
249        int rTarget = Color.red(target);
250        int gTarget = Color.green(target);
251        int bTarget = Color.blue(target);
252        return Color.argb(
253                (int) (aSource * (1f - t) + aTarget * t),
254                (int) (rSource * (1f - t) + rTarget * t),
255                (int) (gSource * (1f - t) + gTarget * t),
256                (int) (bSource * (1f - t) + bTarget * t));
257    }
258
259    @Override
260    public void setContentHeight(int contentHeight, int minHeightHint) {
261        super.setContentHeight(contentHeight, minHeightHint);
262
263        mContentHeight = contentHeight;
264        mMinHeightHint = minHeightHint;
265        updateActionOffset();
266    }
267
268    private void updateActionOffset() {
269        if (mActionsContainer != null) {
270            // We should never push the actions higher than they are in the headsup view.
271            int constrainedContentHeight = Math.max(mContentHeight, mMinHeightHint);
272            mActionsContainer.setTranslationY(constrainedContentHeight - mView.getHeight());
273        }
274    }
275}
276