1 /*
2  * Copyright (C) 2020 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.navigationbar.buttons;
18 
19 import static com.android.app.animation.Interpolators.LINEAR;
20 
21 import android.animation.Animator;
22 import android.animation.AnimatorListenerAdapter;
23 import android.animation.ValueAnimator;
24 import android.view.View;
25 import android.view.View.AccessibilityDelegate;
26 
27 import java.util.ArrayList;
28 
29 /**
30  * Dispatches common view calls to multiple views.  This is used to handle
31  * multiples of the same nav bar icon appearing.
32  */
33 public class ButtonDispatcher {
34     private static final int FADE_DURATION_IN = 150;
35     private static final int FADE_DURATION_OUT = 250;
36 
37     private final ArrayList<View> mViews = new ArrayList<>();
38 
39     private final int mId;
40 
41     private View.OnClickListener mClickListener;
42     private View.OnTouchListener mTouchListener;
43     private View.OnLongClickListener mLongClickListener;
44     private View.OnHoverListener mOnHoverListener;
45     private Boolean mLongClickable;
46     private Float mAlpha;
47     private Float mDarkIntensity;
48     private Integer mVisibility = View.VISIBLE;
49     private Boolean mDelayTouchFeedback;
50     private KeyButtonDrawable mImageDrawable;
51     private View mCurrentView;
52     private boolean mVertical;
53     private ValueAnimator mFadeAnimator;
54     private AccessibilityDelegate mAccessibilityDelegate;
55 
56     private final ValueAnimator.AnimatorUpdateListener mAlphaListener = animation ->
57             setAlpha(
58                     (float) animation.getAnimatedValue(),
59                     false /* animate */,
60                     false /* cancelAnimator */);
61 
62     private final AnimatorListenerAdapter mFadeListener = new AnimatorListenerAdapter() {
63         @Override
64         public void onAnimationEnd(Animator animation) {
65             mFadeAnimator = null;
66             setVisibility(getAlpha() == 1 ? View.VISIBLE : View.INVISIBLE);
67         }
68     };
69 
ButtonDispatcher(int id)70     public ButtonDispatcher(int id) {
71         mId = id;
72     }
73 
clear()74     public void clear() {
75         mViews.clear();
76     }
77 
addView(View view)78     public void addView(View view) {
79         mViews.add(view);
80         view.setOnClickListener(mClickListener);
81         view.setOnTouchListener(mTouchListener);
82         view.setOnLongClickListener(mLongClickListener);
83         view.setOnHoverListener(mOnHoverListener);
84         if (mLongClickable != null) {
85             view.setLongClickable(mLongClickable);
86         }
87         if (mAlpha != null) {
88             view.setAlpha(mAlpha);
89         }
90         if (mVisibility != null) {
91             view.setVisibility(mVisibility);
92         }
93         if (mAccessibilityDelegate != null) {
94             view.setAccessibilityDelegate(mAccessibilityDelegate);
95         }
96         if (view instanceof ButtonInterface) {
97             final ButtonInterface button = (ButtonInterface) view;
98             if (mDarkIntensity != null) {
99                 button.setDarkIntensity(mDarkIntensity);
100             }
101             if (mImageDrawable != null) {
102                 button.setImageDrawable(mImageDrawable);
103             }
104             if (mDelayTouchFeedback != null) {
105                 button.setDelayTouchFeedback(mDelayTouchFeedback);
106             }
107             button.setVertical(mVertical);
108         }
109     }
110 
getId()111     public int getId() {
112         return mId;
113     }
114 
getVisibility()115     public int getVisibility() {
116         return mVisibility != null ? mVisibility : View.VISIBLE;
117     }
118 
isVisible()119     public boolean isVisible() {
120         return getVisibility() == View.VISIBLE;
121     }
122 
getAlpha()123     public float getAlpha() {
124         return mAlpha != null ? mAlpha : 1;
125     }
126 
getImageDrawable()127     public KeyButtonDrawable getImageDrawable() {
128         return mImageDrawable;
129     }
130 
setImageDrawable(KeyButtonDrawable drawable)131     public void setImageDrawable(KeyButtonDrawable drawable) {
132         mImageDrawable = drawable;
133         final int N = mViews.size();
134         for (int i = 0; i < N; i++) {
135             if (mViews.get(i) instanceof ButtonInterface) {
136                 ((ButtonInterface) mViews.get(i)).setImageDrawable(mImageDrawable);
137             }
138         }
139         if (mImageDrawable != null) {
140             mImageDrawable.setCallback(mCurrentView);
141         }
142     }
143 
setVisibility(int visibility)144     public void setVisibility(int visibility) {
145         if (mVisibility == visibility) return;
146         if (mFadeAnimator != null) {
147             mFadeAnimator.cancel();
148         }
149 
150         mVisibility = visibility;
151         final int N = mViews.size();
152         for (int i = 0; i < N; i++) {
153             mViews.get(i).setVisibility(mVisibility);
154         }
155     }
156 
abortCurrentGesture()157     public void abortCurrentGesture() {
158         // This seems to be an instantaneous thing, so not going to persist it.
159         final int N = mViews.size();
160         for (int i = 0; i < N; i++) {
161             if (mViews.get(i) instanceof ButtonInterface) {
162                 ((ButtonInterface) mViews.get(i)).abortCurrentGesture();
163             }
164         }
165     }
166 
setAlpha(float alpha)167     public void setAlpha(float alpha) {
168         setAlpha(alpha, false /* animate */);
169     }
170 
setAlpha(float alpha, boolean animate)171     public void setAlpha(float alpha, boolean animate) {
172         setAlpha(alpha, animate, true /* cancelAnimator */);
173     }
174 
setAlpha(float alpha, boolean animate, long duration)175     public void setAlpha(float alpha, boolean animate, long duration) {
176         setAlpha(alpha, animate, duration, true /* cancelAnimator */);
177     }
178 
setAlpha(float alpha, boolean animate, boolean cancelAnimator)179     public void setAlpha(float alpha, boolean animate, boolean cancelAnimator) {
180         setAlpha(
181                 alpha,
182                 animate,
183                 (getAlpha() < alpha) ? FADE_DURATION_IN : FADE_DURATION_OUT,
184                 cancelAnimator);
185     }
186 
setAlpha(float alpha, boolean animate, long duration, boolean cancelAnimator)187     public void setAlpha(float alpha, boolean animate, long duration, boolean cancelAnimator) {
188         if (mFadeAnimator != null && (cancelAnimator || animate)) {
189             mFadeAnimator.cancel();
190         }
191         if (animate) {
192             setVisibility(View.VISIBLE);
193             mFadeAnimator = ValueAnimator.ofFloat(getAlpha(), alpha);
194             mFadeAnimator.setDuration(duration);
195             mFadeAnimator.setInterpolator(LINEAR);
196             mFadeAnimator.addListener(mFadeListener);
197             mFadeAnimator.addUpdateListener(mAlphaListener);
198             mFadeAnimator.start();
199         } else {
200             // Discretize the alpha updates to prevent too frequent updates when there is a long
201             // alpha animation
202             int prevAlpha = (int) (getAlpha() * 255);
203             int nextAlpha = (int) (alpha * 255);
204             if (prevAlpha != nextAlpha) {
205                 mAlpha = nextAlpha / 255f;
206                 final int N = mViews.size();
207                 for (int i = 0; i < N; i++) {
208                     mViews.get(i).setAlpha(mAlpha);
209                 }
210             }
211         }
212     }
213 
setDarkIntensity(float darkIntensity)214     public void setDarkIntensity(float darkIntensity) {
215         mDarkIntensity = darkIntensity;
216         final int N = mViews.size();
217         for (int i = 0; i < N; i++) {
218             if (mViews.get(i) instanceof ButtonInterface) {
219                 ((ButtonInterface) mViews.get(i)).setDarkIntensity(darkIntensity);
220             }
221         }
222     }
223 
setDelayTouchFeedback(boolean delay)224     public void setDelayTouchFeedback(boolean delay) {
225         mDelayTouchFeedback = delay;
226         final int N = mViews.size();
227         for (int i = 0; i < N; i++) {
228             if (mViews.get(i) instanceof ButtonInterface) {
229                 ((ButtonInterface) mViews.get(i)).setDelayTouchFeedback(delay);
230             }
231         }
232     }
233 
setOnClickListener(View.OnClickListener clickListener)234     public void setOnClickListener(View.OnClickListener clickListener) {
235         mClickListener = clickListener;
236         final int N = mViews.size();
237         for (int i = 0; i < N; i++) {
238             mViews.get(i).setOnClickListener(mClickListener);
239         }
240     }
241 
setOnTouchListener(View.OnTouchListener touchListener)242     public void setOnTouchListener(View.OnTouchListener touchListener) {
243         mTouchListener = touchListener;
244         final int N = mViews.size();
245         for (int i = 0; i < N; i++) {
246             mViews.get(i).setOnTouchListener(mTouchListener);
247         }
248     }
249 
setLongClickable(boolean isLongClickable)250     public void setLongClickable(boolean isLongClickable) {
251         mLongClickable = isLongClickable;
252         final int N = mViews.size();
253         for (int i = 0; i < N; i++) {
254             mViews.get(i).setLongClickable(mLongClickable);
255         }
256     }
257 
setOnLongClickListener(View.OnLongClickListener longClickListener)258     public void setOnLongClickListener(View.OnLongClickListener longClickListener) {
259         mLongClickListener = longClickListener;
260         final int N = mViews.size();
261         for (int i = 0; i < N; i++) {
262             mViews.get(i).setOnLongClickListener(mLongClickListener);
263         }
264     }
265 
setOnHoverListener(View.OnHoverListener hoverListener)266     public void setOnHoverListener(View.OnHoverListener hoverListener) {
267         mOnHoverListener = hoverListener;
268         final int N = mViews.size();
269         for (int i = 0; i < N; i++) {
270             mViews.get(i).setOnHoverListener(mOnHoverListener);
271         }
272     }
273 
setAccessibilityDelegate(AccessibilityDelegate delegate)274     public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
275         mAccessibilityDelegate = delegate;
276         final int N = mViews.size();
277         for (int i = 0; i < N; i++) {
278             mViews.get(i).setAccessibilityDelegate(delegate);
279         }
280     }
281 
setClickable(boolean clickable)282     public void setClickable(boolean clickable) {
283         abortCurrentGesture();
284         final int N = mViews.size();
285         for (int i = 0; i < N; i++) {
286             mViews.get(i).setClickable(clickable);
287         }
288     }
289 
setTranslation(int x, int y, int z)290     public void setTranslation(int x, int y, int z) {
291         final int N = mViews.size();
292         for (int i = 0; i < N; i++) {
293             final View view = mViews.get(i);
294             view.setTranslationX(x);
295             view.setTranslationY(y);
296             view.setTranslationZ(z);
297         }
298     }
299 
getViews()300     public ArrayList<View> getViews() {
301         return mViews;
302     }
303 
getCurrentView()304     public View getCurrentView() {
305         return mCurrentView;
306     }
307 
setCurrentView(View currentView)308     public void setCurrentView(View currentView) {
309         mCurrentView = currentView.findViewById(mId);
310         if (mImageDrawable != null) {
311             mImageDrawable.setCallback(mCurrentView);
312         }
313         if (mCurrentView != null) {
314             mCurrentView.setTranslationX(0);
315             mCurrentView.setTranslationY(0);
316             mCurrentView.setTranslationZ(0);
317         }
318     }
319 
setVertical(boolean vertical)320     public void setVertical(boolean vertical) {
321         mVertical = vertical;
322         final int N = mViews.size();
323         for (int i = 0; i < N; i++) {
324             final View view = mViews.get(i);
325             if (view instanceof ButtonInterface) {
326                 ((ButtonInterface) view).setVertical(vertical);
327             }
328         }
329     }
330 
331     /**
332      * Executes when button is detached from window.
333      */
onDestroy()334     public void onDestroy() {
335     }
336 }
337