OSDN Git Service

Merge "Pin compiled code of HOME app"
[android-x86/frameworks-base.git] / packages / SystemUI / src / com / android / systemui / statusbar / notification / NotificationTemplateViewWrapper.java
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
17 package com.android.systemui.statusbar.notification;
18
19 import android.app.PendingIntent;
20 import android.content.Context;
21 import android.content.res.ColorStateList;
22 import android.graphics.Color;
23 import android.graphics.PorterDuffColorFilter;
24 import android.graphics.Rect;
25 import android.graphics.drawable.Drawable;
26 import android.service.notification.StatusBarNotification;
27 import android.util.ArraySet;
28 import android.view.View;
29 import android.widget.Button;
30 import android.widget.ImageView;
31 import android.widget.ProgressBar;
32 import android.widget.TextView;
33
34 import com.android.internal.util.ContrastColorUtil;
35 import com.android.internal.widget.NotificationActionListLayout;
36 import com.android.systemui.Dependency;
37 import com.android.systemui.R;
38 import com.android.systemui.UiOffloadThread;
39 import com.android.systemui.statusbar.CrossFadeHelper;
40 import com.android.systemui.statusbar.ExpandableNotificationRow;
41 import com.android.systemui.statusbar.TransformableView;
42 import com.android.systemui.statusbar.ViewTransformationHelper;
43
44 /**
45  * Wraps a notification view inflated from a template.
46  */
47 public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapper {
48
49     protected ImageView mPicture;
50     private ProgressBar mProgressBar;
51     private TextView mTitle;
52     private TextView mText;
53     protected View mActionsContainer;
54     private ImageView mReplyAction;
55     private Rect mTmpRect = new Rect();
56
57     private int mContentHeight;
58     private int mMinHeightHint;
59     private NotificationActionListLayout mActions;
60     private ArraySet<PendingIntent> mCancelledPendingIntents = new ArraySet<>();
61     private UiOffloadThread mUiOffloadThread;
62     private View mRemoteInputHistory;
63
64     protected NotificationTemplateViewWrapper(Context ctx, View view,
65             ExpandableNotificationRow row) {
66         super(ctx, view, row);
67         mTransformationHelper.setCustomTransformation(
68                 new ViewTransformationHelper.CustomTransformation() {
69                     @Override
70                     public boolean transformTo(TransformState ownState,
71                             TransformableView notification, final float transformationAmount) {
72                         if (!(notification instanceof HybridNotificationView)) {
73                             return false;
74                         }
75                         TransformState otherState = notification.getCurrentState(
76                                 TRANSFORMING_VIEW_TITLE);
77                         final View text = ownState.getTransformedView();
78                         CrossFadeHelper.fadeOut(text, transformationAmount);
79                         if (otherState != null) {
80                             ownState.transformViewVerticalTo(otherState, this,
81                                     transformationAmount);
82                             otherState.recycle();
83                         }
84                         return true;
85                     }
86
87                     @Override
88                     public boolean customTransformTarget(TransformState ownState,
89                             TransformState otherState) {
90                         float endY = getTransformationY(ownState, otherState);
91                         ownState.setTransformationEndY(endY);
92                         return true;
93                     }
94
95                     @Override
96                     public boolean transformFrom(TransformState ownState,
97                             TransformableView notification, float transformationAmount) {
98                         if (!(notification instanceof HybridNotificationView)) {
99                             return false;
100                         }
101                         TransformState otherState = notification.getCurrentState(
102                                 TRANSFORMING_VIEW_TITLE);
103                         final View text = ownState.getTransformedView();
104                         CrossFadeHelper.fadeIn(text, transformationAmount);
105                         if (otherState != null) {
106                             ownState.transformViewVerticalFrom(otherState, this,
107                                     transformationAmount);
108                             otherState.recycle();
109                         }
110                         return true;
111                     }
112
113                     @Override
114                     public boolean initTransformation(TransformState ownState,
115                             TransformState otherState) {
116                         float startY = getTransformationY(ownState, otherState);
117                         ownState.setTransformationStartY(startY);
118                         return true;
119                     }
120
121                     private float getTransformationY(TransformState ownState,
122                             TransformState otherState) {
123                         int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
124                         int[] ownStablePosition = ownState.getLaidOutLocationOnScreen();
125                         return (otherStablePosition[1]
126                                 + otherState.getTransformedView().getHeight()
127                                 - ownStablePosition[1]) * 0.33f;
128                     }
129
130                 }, TRANSFORMING_VIEW_TEXT);
131     }
132
133     private void resolveTemplateViews(StatusBarNotification notification) {
134         mPicture = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon);
135         if (mPicture != null) {
136             mPicture.setTag(ImageTransformState.ICON_TAG,
137                     notification.getNotification().getLargeIcon());
138         }
139         mTitle = (TextView) mView.findViewById(com.android.internal.R.id.title);
140         mText = (TextView) mView.findViewById(com.android.internal.R.id.text);
141         final View progress = mView.findViewById(com.android.internal.R.id.progress);
142         if (progress instanceof ProgressBar) {
143             mProgressBar = (ProgressBar) progress;
144         } else {
145             // It's still a viewstub
146             mProgressBar = null;
147         }
148         mActionsContainer = mView.findViewById(com.android.internal.R.id.actions_container);
149         mActions = mView.findViewById(com.android.internal.R.id.actions);
150         mReplyAction = mView.findViewById(com.android.internal.R.id.reply_icon_action);
151         mRemoteInputHistory = mView.findViewById(
152                 com.android.internal.R.id.notification_material_reply_container);
153         updatePendingIntentCancellations();
154     }
155
156     private void updatePendingIntentCancellations() {
157         if (mActions != null) {
158             int numActions = mActions.getChildCount();
159             for (int i = 0; i < numActions; i++) {
160                 Button action = (Button) mActions.getChildAt(i);
161                 performOnPendingIntentCancellation(action, () -> {
162                     if (action.isEnabled()) {
163                         action.setEnabled(false);
164                         // The visual appearance doesn't look disabled enough yet, let's add the
165                         // alpha as well. Since Alpha doesn't play nicely right now with the
166                         // transformation, we rather blend it manually with the background color.
167                         ColorStateList textColors = action.getTextColors();
168                         int[] colors = textColors.getColors();
169                         int[] newColors = new int[colors.length];
170                         float disabledAlpha = mView.getResources().getFloat(
171                                 com.android.internal.R.dimen.notification_action_disabled_alpha);
172                         for (int j = 0; j < colors.length; j++) {
173                             int color = colors[j];
174                             color = blendColorWithBackground(color, disabledAlpha);
175                             newColors[j] = color;
176                         }
177                         ColorStateList newColorStateList = new ColorStateList(
178                                 textColors.getStates(), newColors);
179                         action.setTextColor(newColorStateList);
180                     }
181                 });
182             }
183         }
184         if (mReplyAction != null) {
185             // Let's reset the view on update, assuming the new pending intent isn't cancelled
186             // anymore. The color filter automatically resets when it's updated.
187             mReplyAction.setEnabled(true);
188             performOnPendingIntentCancellation(mReplyAction, () -> {
189                 if (mReplyAction != null && mReplyAction.isEnabled()) {
190                     mReplyAction.setEnabled(false);
191                     // The visual appearance doesn't look disabled enough yet, let's add the
192                     // alpha as well. Since Alpha doesn't play nicely right now with the
193                     // transformation, we rather blend it manually with the background color.
194                     Drawable drawable = mReplyAction.getDrawable().mutate();
195                     PorterDuffColorFilter colorFilter =
196                             (PorterDuffColorFilter) drawable.getColorFilter();
197                     float disabledAlpha = mView.getResources().getFloat(
198                             com.android.internal.R.dimen.notification_action_disabled_alpha);
199                     if (colorFilter != null) {
200                         int color = colorFilter.getColor();
201                         color = blendColorWithBackground(color, disabledAlpha);
202                         drawable.mutate().setColorFilter(color, colorFilter.getMode());
203                     } else {
204                         mReplyAction.setAlpha(disabledAlpha);
205                     }
206                 }
207             });
208         }
209     }
210
211     private int blendColorWithBackground(int color, float alpha) {
212         // alpha doesn't go well for color filters, so let's blend it manually
213         return ContrastColorUtil.compositeColors(Color.argb((int) (alpha * 255),
214                 Color.red(color), Color.green(color), Color.blue(color)), resolveBackgroundColor());
215     }
216
217     private void performOnPendingIntentCancellation(View view, Runnable cancellationRunnable) {
218         PendingIntent pendingIntent = (PendingIntent) view.getTag(
219                 com.android.internal.R.id.pending_intent_tag);
220         if (pendingIntent == null) {
221             return;
222         }
223         if (mCancelledPendingIntents.contains(pendingIntent)) {
224             cancellationRunnable.run();
225         } else {
226             PendingIntent.CancelListener listener = (PendingIntent intent) -> {
227                 mView.post(() -> {
228                     mCancelledPendingIntents.add(pendingIntent);
229                     cancellationRunnable.run();
230                 });
231             };
232             if (mUiOffloadThread == null) {
233                 mUiOffloadThread = Dependency.get(UiOffloadThread.class);
234             }
235             if (view.isAttachedToWindow()) {
236                 mUiOffloadThread.submit(() -> pendingIntent.registerCancelListener(listener));
237             }
238             view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
239                 @Override
240                 public void onViewAttachedToWindow(View v) {
241                     mUiOffloadThread.submit(() -> pendingIntent.registerCancelListener(listener));
242                 }
243
244                 @Override
245                 public void onViewDetachedFromWindow(View v) {
246                     mUiOffloadThread.submit(() -> pendingIntent.unregisterCancelListener(listener));
247                 }
248             });
249         }
250     }
251
252     @Override
253     public boolean disallowSingleClick(float x, float y) {
254         if (mReplyAction != null && mReplyAction.getVisibility() == View.VISIBLE) {
255             if (isOnView(mReplyAction, x, y) || isOnView(mPicture, x, y)) {
256                 return true;
257             }
258         }
259         return super.disallowSingleClick(x, y);
260     }
261
262     private boolean isOnView(View view, float x, float y) {
263         View searchView = (View) view.getParent();
264         while (searchView != null && !(searchView instanceof ExpandableNotificationRow)) {
265             searchView.getHitRect(mTmpRect);
266             x -= mTmpRect.left;
267             y -= mTmpRect.top;
268             searchView = (View) searchView.getParent();
269         }
270         view.getHitRect(mTmpRect);
271         return mTmpRect.contains((int) x,(int) y);
272     }
273
274     @Override
275     public void onContentUpdated(ExpandableNotificationRow row) {
276         // Reinspect the notification. Before the super call, because the super call also updates
277         // the transformation types and we need to have our values set by then.
278         resolveTemplateViews(row.getStatusBarNotification());
279         super.onContentUpdated(row);
280     }
281
282     @Override
283     protected void updateTransformedTypes() {
284         // This also clears the existing types
285         super.updateTransformedTypes();
286         if (mTitle != null) {
287             mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
288                     mTitle);
289         }
290         if (mText != null) {
291             mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT,
292                     mText);
293         }
294         if (mPicture != null) {
295             mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE,
296                     mPicture);
297         }
298         if (mProgressBar != null) {
299             mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS,
300                     mProgressBar);
301         }
302     }
303
304     @Override
305     public void setContentHeight(int contentHeight, int minHeightHint) {
306         super.setContentHeight(contentHeight, minHeightHint);
307
308         mContentHeight = contentHeight;
309         mMinHeightHint = minHeightHint;
310         updateActionOffset();
311     }
312
313     @Override
314     public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) {
315         if (super.shouldClipToRounding(topRounded, bottomRounded)) {
316             return true;
317         }
318         return bottomRounded && mActionsContainer != null
319                 && mActionsContainer.getVisibility() != View.GONE;
320     }
321
322     private void updateActionOffset() {
323         if (mActionsContainer != null) {
324             // We should never push the actions higher than they are in the headsup view.
325             int constrainedContentHeight = Math.max(mContentHeight, mMinHeightHint);
326
327             // We also need to compensate for any header translation, since we're always at the end.
328             mActionsContainer.setTranslationY(constrainedContentHeight - mView.getHeight()
329                     - getHeaderTranslation());
330         }
331     }
332
333     @Override
334     public int getExtraMeasureHeight() {
335         int extra = 0;
336         if (mActions != null) {
337             extra = mActions.getExtraMeasureHeight();
338         }
339         if (mRemoteInputHistory != null && mRemoteInputHistory.getVisibility() != View.GONE) {
340             extra += mRow.getContext().getResources().getDimensionPixelSize(
341                     R.dimen.remote_input_history_extra_height);
342         }
343         return extra + super.getExtraMeasureHeight();
344     }
345 }