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