1 /*
2  * Copyright (C) 2017 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 androidx.vectordrawable.graphics.drawable;
18 
19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
20 
21 import android.content.Context;
22 import android.content.res.Resources.NotFoundException;
23 import android.content.res.XmlResourceParser;
24 import android.os.Build;
25 import android.util.AttributeSet;
26 import android.util.Xml;
27 import android.view.animation.AccelerateDecelerateInterpolator;
28 import android.view.animation.AccelerateInterpolator;
29 import android.view.animation.AnimationUtils;
30 import android.view.animation.AnticipateInterpolator;
31 import android.view.animation.AnticipateOvershootInterpolator;
32 import android.view.animation.BounceInterpolator;
33 import android.view.animation.CycleInterpolator;
34 import android.view.animation.DecelerateInterpolator;
35 import android.view.animation.Interpolator;
36 import android.view.animation.LinearInterpolator;
37 import android.view.animation.OvershootInterpolator;
38 
39 import androidx.annotation.AnimRes;
40 import androidx.annotation.RestrictTo;
41 import androidx.core.util.ObjectsCompat;
42 import androidx.interpolator.view.animation.FastOutLinearInInterpolator;
43 import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
44 import androidx.interpolator.view.animation.LinearOutSlowInInterpolator;
45 
46 import org.jspecify.annotations.NonNull;
47 import org.xmlpull.v1.XmlPullParser;
48 import org.xmlpull.v1.XmlPullParserException;
49 
50 import java.io.IOException;
51 
52 /**
53  * Defines common utilities for working with animations.
54  */
55 @RestrictTo(LIBRARY_GROUP_PREFIX)
56 public class AnimationUtilsCompat {
57     /**
58      * Loads an {@link Interpolator} object from a resource
59      *
60      * @param context Application context used to access resources
61      * @param id      The resource id of the animation to load
62      * @return The animation object reference by the specified id
63      */
64     @SuppressWarnings("UnnecessaryInitCause") // requires API 24+
loadInterpolator(@onNull Context context, @AnimRes int id)65     public static @NonNull Interpolator loadInterpolator(@NonNull Context context, @AnimRes int id)
66             throws NotFoundException {
67         // From API 21, we added path Interpolator .
68         if (Build.VERSION.SDK_INT >= 21) {
69             Interpolator interp = AnimationUtils.loadInterpolator(context, id);
70             ObjectsCompat.requireNonNull(interp, "Failed to parse interpolator, no start tag "
71                     + "found");
72             return interp;
73         }
74 
75         XmlResourceParser parser = null;
76         try {
77             // Special treatment for the interpolator introduced at API 21.
78             if (id == AndroidResources.FAST_OUT_LINEAR_IN) {
79                 return new FastOutLinearInInterpolator();
80             } else if (id == AndroidResources.FAST_OUT_SLOW_IN) {
81                 return new FastOutSlowInInterpolator();
82             } else if (id == AndroidResources.LINEAR_OUT_SLOW_IN) {
83                 return new LinearOutSlowInInterpolator();
84             }
85             parser = context.getResources().getAnimation(id);
86             return createInterpolatorFromXml(context, parser);
87         } catch (XmlPullParserException ex) {
88             NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x"
89                     + Integer.toHexString(id));
90             rnf.initCause(ex);
91             throw rnf;
92         } catch (IOException ex) {
93             NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x"
94                     + Integer.toHexString(id));
95             rnf.initCause(ex);
96             throw rnf;
97         } finally {
98             if (parser != null) parser.close();
99         }
100 
101     }
102 
createInterpolatorFromXml(@onNull Context context, @NonNull XmlPullParser parser)103     private static @NonNull Interpolator createInterpolatorFromXml(@NonNull Context context,
104             @NonNull XmlPullParser parser) throws XmlPullParserException, IOException {
105 
106         Interpolator interpolator = null;
107 
108         // Make sure we are on a start tag.
109         int type;
110         int depth = parser.getDepth();
111 
112         while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
113                 && type != XmlPullParser.END_DOCUMENT) {
114 
115             if (type != XmlPullParser.START_TAG) {
116                 continue;
117             }
118 
119             AttributeSet attrs = Xml.asAttributeSet(parser);
120 
121             String name = parser.getName();
122 
123             switch (name) {
124                 case "linearInterpolator":
125                     interpolator = new LinearInterpolator();
126                     break;
127                 case "accelerateInterpolator":
128                     interpolator = new AccelerateInterpolator(context, attrs);
129                     break;
130                 case "decelerateInterpolator":
131                     interpolator = new DecelerateInterpolator(context, attrs);
132                     break;
133                 case "accelerateDecelerateInterpolator":
134                     interpolator = new AccelerateDecelerateInterpolator();
135                     break;
136                 case "cycleInterpolator":
137                     interpolator = new CycleInterpolator(context, attrs);
138                     break;
139                 case "anticipateInterpolator":
140                     interpolator = new AnticipateInterpolator(context, attrs);
141                     break;
142                 case "overshootInterpolator":
143                     interpolator = new OvershootInterpolator(context, attrs);
144                     break;
145                 case "anticipateOvershootInterpolator":
146                     interpolator = new AnticipateOvershootInterpolator(context, attrs);
147                     break;
148                 case "bounceInterpolator":
149                     interpolator = new BounceInterpolator();
150                     break;
151                 case "pathInterpolator":
152                     interpolator = new PathInterpolatorCompat(context, attrs, parser);
153                     break;
154                 default:
155                     throw new RuntimeException("Unknown interpolator name: " + parser.getName());
156             }
157         }
158 
159         if (interpolator == null) {
160             throw new RuntimeException("Failed to parse interpolator, no start tag found");
161         }
162 
163         return interpolator;
164     }
165 
AnimationUtilsCompat()166     private AnimationUtilsCompat() {
167     }
168 }
169