• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.systemui.statusbar;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ValueAnimator;
22 import android.content.Context;
23 import android.content.res.Resources;
24 import android.util.AttributeSet;
25 import android.view.View;
26 import android.widget.FrameLayout;
27 
28 import com.android.systemui.Interpolators;
29 import com.android.systemui.R;
30 
31 public class NotificationSettingsIconRow extends FrameLayout implements View.OnClickListener {
32 
33     private static final int GEAR_ALPHA_ANIM_DURATION = 200;
34 
35     public interface SettingsIconRowListener {
36         /**
37          * Called when the gear behind a notification is touched.
38          */
onGearTouched(ExpandableNotificationRow row, int x, int y)39         public void onGearTouched(ExpandableNotificationRow row, int x, int y);
40 
41         /**
42          * Called when a notification is slid back over the gear.
43          */
onSettingsIconRowReset(ExpandableNotificationRow row)44         public void onSettingsIconRowReset(ExpandableNotificationRow row);
45     }
46 
47     private ExpandableNotificationRow mParent;
48     private AlphaOptimizedImageView mGearIcon;
49     private float mHorizSpaceForGear;
50     private SettingsIconRowListener mListener;
51 
52     private ValueAnimator mFadeAnimator;
53     private boolean mSettingsFadedIn = false;
54     private boolean mAnimating = false;
55     private boolean mOnLeft = true;
56     private boolean mDismissing = false;
57     private boolean mSnapping = false;
58     private boolean mIconPlaced = false;
59 
60     private int[] mGearLocation = new int[2];
61     private int[] mParentLocation = new int[2];
62     private int mVertSpaceForGear;
63 
NotificationSettingsIconRow(Context context)64     public NotificationSettingsIconRow(Context context) {
65         this(context, null);
66     }
67 
NotificationSettingsIconRow(Context context, AttributeSet attrs)68     public NotificationSettingsIconRow(Context context, AttributeSet attrs) {
69         this(context, attrs, 0);
70     }
71 
NotificationSettingsIconRow(Context context, AttributeSet attrs, int defStyleAttr)72     public NotificationSettingsIconRow(Context context, AttributeSet attrs, int defStyleAttr) {
73         this(context, attrs, defStyleAttr, 0);
74     }
75 
NotificationSettingsIconRow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)76     public NotificationSettingsIconRow(Context context, AttributeSet attrs, int defStyleAttr,
77             int defStyleRes) {
78         super(context, attrs);
79     }
80 
81     @Override
onFinishInflate()82     protected void onFinishInflate() {
83         super.onFinishInflate();
84         mGearIcon = (AlphaOptimizedImageView) findViewById(R.id.gear_icon);
85         mGearIcon.setOnClickListener(this);
86         setOnClickListener(this);
87         mHorizSpaceForGear =
88                 getResources().getDimensionPixelOffset(R.dimen.notification_gear_width);
89         mVertSpaceForGear = getResources().getDimensionPixelOffset(R.dimen.notification_min_height);
90         resetState();
91     }
92 
resetState()93     public void resetState() {
94         setGearAlpha(0f);
95         mIconPlaced = false;
96         mSettingsFadedIn = false;
97         mAnimating = false;
98         mSnapping = false;
99         mDismissing = false;
100         setIconLocation(true /* on left */);
101         if (mListener != null) {
102             mListener.onSettingsIconRowReset(mParent);
103         }
104     }
105 
setGearListener(SettingsIconRowListener listener)106     public void setGearListener(SettingsIconRowListener listener) {
107         mListener = listener;
108     }
109 
setNotificationRowParent(ExpandableNotificationRow parent)110     public void setNotificationRowParent(ExpandableNotificationRow parent) {
111         mParent = parent;
112         setIconLocation(mOnLeft);
113     }
114 
setAppName(String appName)115     public void setAppName(String appName) {
116         Resources res = getResources();
117         String description = String.format(res.getString(R.string.notification_gear_accessibility),
118                 appName);
119         mGearIcon.setContentDescription(description);
120     }
121 
getNotificationParent()122     public ExpandableNotificationRow getNotificationParent() {
123         return mParent;
124     }
125 
setGearAlpha(float alpha)126     public void setGearAlpha(float alpha) {
127         if (alpha == 0) {
128             mSettingsFadedIn = false; // Can fade in again once it's gone.
129             setVisibility(View.INVISIBLE);
130         } else {
131             setVisibility(View.VISIBLE);
132         }
133         mGearIcon.setAlpha(alpha);
134     }
135 
136     /**
137      * Returns whether the icon is on the left side of the view or not.
138      */
isIconOnLeft()139     public boolean isIconOnLeft() {
140         return mOnLeft;
141     }
142 
143     /**
144      * Returns the horizontal space in pixels required to display the gear behind a notification.
145      */
getSpaceForGear()146     public float getSpaceForGear() {
147         return mHorizSpaceForGear;
148     }
149 
150     /**
151      * Indicates whether the gear is visible at 1 alpha. Does not indicate
152      * if entire view is visible.
153      */
isVisible()154     public boolean isVisible() {
155         return mGearIcon.getAlpha() > 0;
156     }
157 
cancelFadeAnimator()158     public void cancelFadeAnimator() {
159         if (mFadeAnimator != null) {
160             mFadeAnimator.cancel();
161         }
162     }
163 
updateSettingsIcons(final float transX, final float size)164     public void updateSettingsIcons(final float transX, final float size) {
165         if (mAnimating || !mSettingsFadedIn) {
166             // Don't adjust when animating, or if the gear hasn't been shown yet.
167             return;
168         }
169 
170         final float fadeThreshold = size * 0.3f;
171         final float absTrans = Math.abs(transX);
172         float desiredAlpha = 0;
173 
174         if (absTrans == 0) {
175             desiredAlpha = 0;
176         } else if (absTrans <= fadeThreshold) {
177             desiredAlpha = 1;
178         } else {
179             desiredAlpha = 1 - ((absTrans - fadeThreshold) / (size - fadeThreshold));
180         }
181         setGearAlpha(desiredAlpha);
182     }
183 
fadeInSettings(final boolean fromLeft, final float transX, final float notiThreshold)184     public void fadeInSettings(final boolean fromLeft, final float transX,
185             final float notiThreshold) {
186         if (mDismissing || mAnimating) {
187             return;
188         }
189         if (isIconLocationChange(transX)) {
190             setGearAlpha(0f);
191         }
192         setIconLocation(transX > 0 /* fromLeft */);
193         mFadeAnimator = ValueAnimator.ofFloat(mGearIcon.getAlpha(), 1);
194         mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
195             @Override
196             public void onAnimationUpdate(ValueAnimator animation) {
197                 final float absTrans = Math.abs(transX);
198 
199                 boolean pastGear = (fromLeft && transX <= notiThreshold)
200                         || (!fromLeft && absTrans <= notiThreshold);
201                 if (pastGear && !mSettingsFadedIn) {
202                     setGearAlpha((float) animation.getAnimatedValue());
203                 }
204             }
205         });
206         mFadeAnimator.addListener(new AnimatorListenerAdapter() {
207             @Override
208             public void onAnimationStart(Animator animation) {
209                 mAnimating = true;
210             }
211 
212             @Override
213             public void onAnimationCancel(Animator animation) {
214                 // TODO should animate back to 0f from current alpha
215                 mGearIcon.setAlpha(0f);
216             }
217 
218             @Override
219             public void onAnimationEnd(Animator animation) {
220                 mAnimating = false;
221                 mSettingsFadedIn = mGearIcon.getAlpha() == 1;
222             }
223         });
224         mFadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
225         mFadeAnimator.setDuration(GEAR_ALPHA_ANIM_DURATION);
226         mFadeAnimator.start();
227     }
228 
updateVerticalLocation()229     public void updateVerticalLocation() {
230         if (mParent == null) {
231             return;
232         }
233         int parentHeight = mParent.getCollapsedHeight();
234         if (parentHeight < mVertSpaceForGear) {
235             mGearIcon.setTranslationY((parentHeight / 2) - (mGearIcon.getHeight() / 2));
236         } else {
237             mGearIcon.setTranslationY((mVertSpaceForGear - mGearIcon.getHeight()) / 2);
238         }
239     }
240 
241     @Override
onRtlPropertiesChanged(int layoutDirection)242     public void onRtlPropertiesChanged(int layoutDirection) {
243         setIconLocation(mOnLeft);
244     }
245 
setIconLocation(boolean onLeft)246     public void setIconLocation(boolean onLeft) {
247         if ((mIconPlaced && onLeft == mOnLeft) || mSnapping || mParent == null
248                 || mGearIcon.getWidth() == 0) {
249             // Do nothing
250             return;
251         }
252         final boolean isRtl = mParent.isLayoutRtl();
253 
254         // TODO No need to cast to float here once b/28050538 is fixed.
255         final float left = (float) (isRtl ? -(mParent.getWidth() - mHorizSpaceForGear) : 0);
256         final float right = (float) (isRtl ? 0 : (mParent.getWidth() - mHorizSpaceForGear));
257         final float centerX = ((mHorizSpaceForGear - mGearIcon.getWidth()) / 2);
258         setTranslationX(onLeft ? left + centerX : right + centerX);
259         mOnLeft = onLeft;
260         mIconPlaced = true;
261     }
262 
isIconLocationChange(float translation)263     public boolean isIconLocationChange(float translation) {
264         boolean onLeft = translation > mGearIcon.getPaddingStart();
265         boolean onRight = translation < -mGearIcon.getPaddingStart();
266         if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) {
267             return true;
268         }
269         return false;
270     }
271 
272     public void setDismissing() {
273         mDismissing = true;
274     }
275 
276     public void setSnapping(boolean snapping) {
277         mSnapping = snapping;
278     }
279 
280     @Override
281     public void onClick(View v) {
282         if (v.getId() == R.id.gear_icon) {
283             if (mListener != null) {
284                 mGearIcon.getLocationOnScreen(mGearLocation);
285                 mParent.getLocationOnScreen(mParentLocation);
286 
287                 final int centerX = (int) (mHorizSpaceForGear / 2);
288                 final int centerY =
289                         (int) (mGearIcon.getTranslationY() * 2 + mGearIcon.getHeight())/ 2;
290                 final int x = mGearLocation[0] - mParentLocation[0] + centerX;
291                 final int y = mGearLocation[1] - mParentLocation[1] + centerY;
292                 mListener.onGearTouched(mParent, x, y);
293             }
294         } else {
295             // Do nothing when the background is touched.
296         }
297     }
298 }
299