1 /*
2  * Copyright (C) 2016 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.internal.widget;
18 
19 import android.annotation.Nullable;
20 import android.content.Context;
21 import android.util.AttributeSet;
22 import android.view.NotificationHeaderView;
23 import android.view.View;
24 import android.view.ViewGroup;
25 import android.widget.FrameLayout;
26 import android.widget.ImageView;
27 import android.widget.RemoteViews;
28 
29 import java.util.ArrayList;
30 
31 /**
32  * A TextView that can float around an image on the end.
33  *
34  * @hide
35  */
36 @RemoteViews.RemoteView
37 public class MediaNotificationView extends FrameLayout {
38 
39     private final int mNotificationContentMarginEnd;
40     private final int mNotificationContentImageMarginEnd;
41     private ImageView mRightIcon;
42     private View mActions;
43     private NotificationHeaderView mHeader;
44     private View mMainColumn;
45     private View mMediaContent;
46     private int mImagePushIn;
47     private ArrayList<VisibilityChangeListener> mListeners;
48 
MediaNotificationView(Context context)49     public MediaNotificationView(Context context) {
50         this(context, null);
51     }
52 
MediaNotificationView(Context context, @Nullable AttributeSet attrs)53     public MediaNotificationView(Context context, @Nullable AttributeSet attrs) {
54         this(context, attrs, 0);
55     }
56 
MediaNotificationView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)57     public MediaNotificationView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
58         this(context, attrs, defStyleAttr, 0);
59     }
60 
61     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)62     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
63         boolean hasIcon = mRightIcon.getVisibility() != GONE;
64         if (!hasIcon) {
65             resetHeaderIndention();
66         }
67         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
68         int mode = MeasureSpec.getMode(widthMeasureSpec);
69         boolean reMeasure = false;
70         mImagePushIn = 0;
71         if (hasIcon && mode != MeasureSpec.UNSPECIFIED) {
72             int size = MeasureSpec.getSize(widthMeasureSpec);
73             size = size - mActions.getMeasuredWidth();
74             ViewGroup.MarginLayoutParams layoutParams =
75                     (MarginLayoutParams) mRightIcon.getLayoutParams();
76             int imageEndMargin = layoutParams.getMarginEnd();
77             size -= imageEndMargin;
78             int fullHeight = mMediaContent.getMeasuredHeight();
79             if (size > fullHeight) {
80                 size = fullHeight;
81             } else if (size < fullHeight) {
82                 size = Math.max(0, size);
83                 mImagePushIn = fullHeight - size;
84             }
85             if (layoutParams.width != fullHeight || layoutParams.height != fullHeight) {
86                 layoutParams.width = fullHeight;
87                 layoutParams.height = fullHeight;
88                 mRightIcon.setLayoutParams(layoutParams);
89                 reMeasure = true;
90             }
91 
92             // lets ensure that the main column doesn't run into the image
93             ViewGroup.MarginLayoutParams params
94                     = (MarginLayoutParams) mMainColumn.getLayoutParams();
95             int marginEnd = size + imageEndMargin + mNotificationContentMarginEnd;
96             if (marginEnd != params.getMarginEnd()) {
97                 params.setMarginEnd(marginEnd);
98                 mMainColumn.setLayoutParams(params);
99                 reMeasure = true;
100             }
101             // margin for the entire header line
102             int headerMarginEnd = imageEndMargin;
103             // margin for the header text (not including the expand button and other icons)
104             int headerTextMarginEnd = size + imageEndMargin;
105             if (headerTextMarginEnd != mHeader.getHeaderTextMarginEnd()) {
106                 mHeader.setHeaderTextMarginEnd(headerTextMarginEnd);
107                 reMeasure = true;
108             }
109             params = (MarginLayoutParams) mHeader.getLayoutParams();
110             if (params.getMarginEnd() != headerMarginEnd) {
111                 params.setMarginEnd(headerMarginEnd);
112                 mHeader.setLayoutParams(params);
113                 reMeasure = true;
114             }
115             if (mHeader.getPaddingEnd() != mNotificationContentImageMarginEnd) {
116                 mHeader.setPaddingRelative(mHeader.getPaddingStart(),
117                         mHeader.getPaddingTop(),
118                         mNotificationContentImageMarginEnd,
119                         mHeader.getPaddingBottom());
120                 reMeasure = true;
121             }
122         }
123         if (reMeasure) {
124             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
125         }
126     }
127 
128     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)129     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
130         super.onLayout(changed, left, top, right, bottom);
131         if (mImagePushIn > 0) {
132             if (this.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
133                 mImagePushIn *= -1;
134             }
135             mRightIcon.layout(mRightIcon.getLeft() + mImagePushIn, mRightIcon.getTop(),
136                     mRightIcon.getRight()  + mImagePushIn, mRightIcon.getBottom());
137         }
138     }
139 
resetHeaderIndention()140     private void resetHeaderIndention() {
141         if (mHeader.getPaddingEnd() != mNotificationContentMarginEnd) {
142             mHeader.setPaddingRelative(mHeader.getPaddingStart(),
143                     mHeader.getPaddingTop(),
144                     mNotificationContentMarginEnd,
145                     mHeader.getPaddingBottom());
146         }
147         ViewGroup.MarginLayoutParams headerParams =
148                 (MarginLayoutParams) mHeader.getLayoutParams();
149         headerParams.setMarginEnd(0);
150         if (headerParams.getMarginEnd() != 0) {
151             headerParams.setMarginEnd(0);
152             mHeader.setLayoutParams(headerParams);
153         }
154     }
155 
MediaNotificationView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)156     public MediaNotificationView(Context context, AttributeSet attrs, int defStyleAttr,
157             int defStyleRes) {
158         super(context, attrs, defStyleAttr, defStyleRes);
159         mNotificationContentMarginEnd = context.getResources().getDimensionPixelSize(
160                 com.android.internal.R.dimen.notification_content_margin_end);
161         mNotificationContentImageMarginEnd = context.getResources().getDimensionPixelSize(
162                 com.android.internal.R.dimen.notification_content_image_margin_end);
163     }
164 
165     @Override
onFinishInflate()166     protected void onFinishInflate() {
167         super.onFinishInflate();
168         mRightIcon = findViewById(com.android.internal.R.id.right_icon);
169         mActions = findViewById(com.android.internal.R.id.media_actions);
170         mHeader = findViewById(com.android.internal.R.id.notification_header);
171         mMainColumn = findViewById(com.android.internal.R.id.notification_main_column);
172         mMediaContent = findViewById(com.android.internal.R.id.notification_media_content);
173     }
174 
175     @Override
onVisibilityAggregated(boolean isVisible)176     public void onVisibilityAggregated(boolean isVisible) {
177         super.onVisibilityAggregated(isVisible);
178         if (mListeners != null) {
179             for (int i = 0; i < mListeners.size(); i++) {
180                 mListeners.get(i).onAggregatedVisibilityChanged(isVisible);
181             }
182         }
183     }
184 
185     /**
186      * Add a listener to receive updates on the visibility of this view
187      *
188      * @param listener The listener to add.
189      */
addVisibilityListener(VisibilityChangeListener listener)190     public void addVisibilityListener(VisibilityChangeListener listener) {
191         if (mListeners == null) {
192             mListeners = new ArrayList<>();
193         }
194         if (!mListeners.contains(listener)) {
195             mListeners.add(listener);
196         }
197     }
198 
199     /**
200      * Remove the specified listener
201      *
202      * @param listener The listener to remove.
203      */
removeVisibilityListener(VisibilityChangeListener listener)204     public void removeVisibilityListener(VisibilityChangeListener listener) {
205         if (mListeners != null) {
206             mListeners.remove(listener);
207         }
208     }
209 
210     /**
211      * Interface for receiving updates when the view's visibility changes
212      */
213     public interface VisibilityChangeListener {
214         /**
215          * Method called when the visibility of this view has changed
216          * @param isVisible true if the view is now visible
217          */
onAggregatedVisibilityChanged(boolean isVisible)218         void onAggregatedVisibilityChanged(boolean isVisible);
219     }
220 }
221