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
17package android.support.v4.view;
18
19import android.content.res.ColorStateList;
20import android.graphics.PorterDuff;
21import android.graphics.Rect;
22import android.graphics.drawable.Drawable;
23import android.os.Build;
24import android.view.View;
25import android.view.ViewParent;
26import android.view.WindowInsets;
27
28class ViewCompatLollipop {
29
30    public interface OnApplyWindowInsetsListenerBridge {
31        Object onApplyWindowInsets(View v, Object insets);
32    }
33
34    private static ThreadLocal<Rect> sThreadLocalRect;
35
36    public static void setTransitionName(View view, String transitionName) {
37        view.setTransitionName(transitionName);
38    }
39
40    public static String getTransitionName(View view) {
41        return view.getTransitionName();
42    }
43
44    public static void requestApplyInsets(View view) {
45        view.requestApplyInsets();
46    }
47
48    public static void setElevation(View view, float elevation) {
49        view.setElevation(elevation);
50    }
51
52    public static float getElevation(View view) {
53        return view.getElevation();
54    }
55
56    public static void setTranslationZ(View view, float translationZ) {
57        view.setTranslationZ(translationZ);
58    }
59
60    public static float getTranslationZ(View view) {
61        return view.getTranslationZ();
62    }
63
64    public static void setOnApplyWindowInsetsListener(
65            View view, final OnApplyWindowInsetsListenerBridge bridge) {
66        if (bridge == null) {
67            view.setOnApplyWindowInsetsListener(null);
68        } else {
69            view.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
70                @Override
71                public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) {
72                    return (WindowInsets) bridge.onApplyWindowInsets(view, insets);
73                }
74            });
75        }
76    }
77
78    public static boolean isImportantForAccessibility(View view) {
79        return view.isImportantForAccessibility();
80    }
81
82    static ColorStateList getBackgroundTintList(View view) {
83        return view.getBackgroundTintList();
84    }
85
86    static void setBackgroundTintList(View view, ColorStateList tintList) {
87        view.setBackgroundTintList(tintList);
88
89        if (Build.VERSION.SDK_INT == 21) {
90            // Work around a bug in L that did not update the state of the background
91            // after applying the tint
92            Drawable background = view.getBackground();
93            boolean hasTint = (view.getBackgroundTintList() != null)
94                    && (view.getBackgroundTintMode() != null);
95            if ((background != null) && hasTint) {
96                if (background.isStateful()) {
97                    background.setState(view.getDrawableState());
98                }
99                view.setBackground(background);
100            }
101        }
102    }
103
104    static PorterDuff.Mode getBackgroundTintMode(View view) {
105        return view.getBackgroundTintMode();
106    }
107
108    static void setBackgroundTintMode(View view, PorterDuff.Mode mode) {
109        view.setBackgroundTintMode(mode);
110
111        if (Build.VERSION.SDK_INT == 21) {
112            // Work around a bug in L that did not update the state of the background
113            // after applying the tint
114            Drawable background = view.getBackground();
115            boolean hasTint = (view.getBackgroundTintList() != null)
116                    && (view.getBackgroundTintMode() != null);
117            if ((background != null) && hasTint) {
118                if (background.isStateful()) {
119                    background.setState(view.getDrawableState());
120                }
121                view.setBackground(background);
122            }
123        }
124    }
125
126    public static Object onApplyWindowInsets(View v, Object insets) {
127        WindowInsets unwrapped = (WindowInsets) insets;
128        WindowInsets result = v.onApplyWindowInsets(unwrapped);
129        if (result != unwrapped) {
130            insets = new WindowInsets(result);
131        }
132        return insets;
133    }
134
135    public static Object dispatchApplyWindowInsets(View v, Object insets) {
136        WindowInsets unwrapped = (WindowInsets) insets;
137        WindowInsets result = v.dispatchApplyWindowInsets(unwrapped);
138        if (result != unwrapped) {
139            insets = new WindowInsets(result);
140        }
141        return insets;
142    }
143
144    public static void setNestedScrollingEnabled(View view, boolean enabled) {
145        view.setNestedScrollingEnabled(enabled);
146    }
147
148    public static boolean isNestedScrollingEnabled(View view) {
149        return view.isNestedScrollingEnabled();
150    }
151
152    public static boolean startNestedScroll(View view, int axes) {
153        return view.startNestedScroll(axes);
154    }
155
156    public static void stopNestedScroll(View view) {
157        view.stopNestedScroll();
158    }
159
160    public static boolean hasNestedScrollingParent(View view) {
161        return view.hasNestedScrollingParent();
162    }
163
164    public static boolean dispatchNestedScroll(View view, int dxConsumed, int dyConsumed,
165            int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
166        return view.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
167                offsetInWindow);
168    }
169
170    public static boolean dispatchNestedPreScroll(View view, int dx, int dy, int[] consumed,
171            int[] offsetInWindow) {
172        return view.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
173    }
174
175    public static boolean dispatchNestedFling(View view, float velocityX, float velocityY,
176            boolean consumed) {
177        return view.dispatchNestedFling(velocityX, velocityY, consumed);
178    }
179
180    public static boolean dispatchNestedPreFling(View view, float velocityX, float velocityY) {
181        return view.dispatchNestedPreFling(velocityX, velocityY);
182    }
183
184    public static float getZ(View view) {
185        return view.getZ();
186    }
187
188    public static void setZ(View view, float z) {
189        view.setZ(z);
190    }
191
192    static void offsetTopAndBottom(final View view, final int offset) {
193        final Rect parentRect = getEmptyTempRect();
194        boolean needInvalidateWorkaround = false;
195
196        final ViewParent parent = view.getParent();
197        if (parent instanceof View) {
198            final View p = (View) parent;
199            parentRect.set(p.getLeft(), p.getTop(), p.getRight(), p.getBottom());
200            // If the view currently does not currently intersect the parent (and is therefore
201            // not displayed) we may need need to invalidate
202            needInvalidateWorkaround = !parentRect.intersects(view.getLeft(), view.getTop(),
203                    view.getRight(), view.getBottom());
204        }
205
206        // Now offset, invoking the API 11+ implementation (which contains it's own workarounds)
207        ViewCompatHC.offsetTopAndBottom(view, offset);
208
209        // The view has now been offset, so let's intersect the Rect and invalidate where
210        // the View is now displayed
211        if (needInvalidateWorkaround && parentRect.intersect(view.getLeft(), view.getTop(),
212                view.getRight(), view.getBottom())) {
213            ((View) parent).invalidate(parentRect);
214        }
215    }
216
217    static void offsetLeftAndRight(final View view, final int offset) {
218        final Rect parentRect = getEmptyTempRect();
219        boolean needInvalidateWorkaround = false;
220
221        final ViewParent parent = view.getParent();
222        if (parent instanceof View) {
223            final View p = (View) parent;
224            parentRect.set(p.getLeft(), p.getTop(), p.getRight(), p.getBottom());
225            // If the view currently does not currently intersect the parent (and is therefore
226            // not displayed) we may need need to invalidate
227            needInvalidateWorkaround = !parentRect.intersects(view.getLeft(), view.getTop(),
228                    view.getRight(), view.getBottom());
229        }
230
231        // Now offset, invoking the API 11+ implementation (which contains it's own workarounds)
232        ViewCompatHC.offsetLeftAndRight(view, offset);
233
234        // The view has now been offset, so let's intersect the Rect and invalidate where
235        // the View is now displayed
236        if (needInvalidateWorkaround && parentRect.intersect(view.getLeft(), view.getTop(),
237                view.getRight(), view.getBottom())) {
238            ((View) parent).invalidate(parentRect);
239        }
240    }
241
242    private static Rect getEmptyTempRect() {
243        if (sThreadLocalRect == null) {
244            sThreadLocalRect = new ThreadLocal<>();
245        }
246        Rect rect = sThreadLocalRect.get();
247        if (rect == null) {
248            rect = new Rect();
249            sThreadLocalRect.set(rect);
250        }
251        rect.setEmpty();
252        return rect;
253    }
254}
255