1 /*
2  * Copyright (C) 2013 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 android.transition;
18 
19 import com.android.internal.R;
20 
21 import org.xmlpull.v1.XmlPullParser;
22 import org.xmlpull.v1.XmlPullParserException;
23 
24 import android.content.Context;
25 import android.content.res.Resources;
26 import android.content.res.TypedArray;
27 import android.content.res.XmlResourceParser;
28 import android.util.ArrayMap;
29 import android.util.AttributeSet;
30 import android.util.Xml;
31 import android.view.InflateException;
32 import android.view.ViewGroup;
33 
34 import java.io.IOException;
35 import java.lang.reflect.Constructor;
36 import java.lang.reflect.InvocationTargetException;
37 
38 /**
39  * This class inflates scenes and transitions from resource files.
40  *
41  * Information on XML resource descriptions for transitions can be found for
42  * {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet},
43  * {@link android.R.styleable#TransitionTarget}, {@link android.R.styleable#Fade},
44  * and {@link android.R.styleable#TransitionManager}.
45  */
46 public class TransitionInflater {
47 
48     private static final Class<?>[] sConstructorSignature = new Class[] {
49             Context.class, AttributeSet.class};
50     private final static ArrayMap<String, Constructor> sConstructors =
51             new ArrayMap<String, Constructor>();
52 
53     private Context mContext;
54 
TransitionInflater(Context context)55     private TransitionInflater(Context context) {
56         mContext = context;
57     }
58 
59     /**
60      * Obtains the TransitionInflater from the given context.
61      */
from(Context context)62     public static TransitionInflater from(Context context) {
63         return new TransitionInflater(context);
64     }
65 
66     /**
67      * Loads a {@link Transition} object from a resource
68      *
69      * @param resource The resource id of the transition to load
70      * @return The loaded Transition object
71      * @throws android.content.res.Resources.NotFoundException when the
72      * transition cannot be loaded
73      */
inflateTransition(int resource)74     public Transition inflateTransition(int resource) {
75         XmlResourceParser parser =  mContext.getResources().getXml(resource);
76         try {
77             return createTransitionFromXml(parser, Xml.asAttributeSet(parser), null);
78         } catch (XmlPullParserException e) {
79             InflateException ex = new InflateException(e.getMessage());
80             ex.initCause(e);
81             throw ex;
82         } catch (IOException e) {
83             InflateException ex = new InflateException(
84                     parser.getPositionDescription()
85                             + ": " + e.getMessage());
86             ex.initCause(e);
87             throw ex;
88         } finally {
89             parser.close();
90         }
91     }
92 
93     /**
94      * Loads a {@link TransitionManager} object from a resource
95      *
96      * @param resource The resource id of the transition manager to load
97      * @return The loaded TransitionManager object
98      * @throws android.content.res.Resources.NotFoundException when the
99      * transition manager cannot be loaded
100      */
inflateTransitionManager(int resource, ViewGroup sceneRoot)101     public TransitionManager inflateTransitionManager(int resource, ViewGroup sceneRoot) {
102         XmlResourceParser parser =  mContext.getResources().getXml(resource);
103         try {
104             return createTransitionManagerFromXml(parser, Xml.asAttributeSet(parser), sceneRoot);
105         } catch (XmlPullParserException e) {
106             InflateException ex = new InflateException(e.getMessage());
107             ex.initCause(e);
108             throw ex;
109         } catch (IOException e) {
110             InflateException ex = new InflateException(
111                     parser.getPositionDescription()
112                             + ": " + e.getMessage());
113             ex.initCause(e);
114             throw ex;
115         } finally {
116             parser.close();
117         }
118     }
119 
120     //
121     // Transition loading
122     //
createTransitionFromXml(XmlPullParser parser, AttributeSet attrs, Transition parent)123     private Transition createTransitionFromXml(XmlPullParser parser,
124             AttributeSet attrs, Transition parent)
125             throws XmlPullParserException, IOException {
126 
127         Transition transition = null;
128 
129         // Make sure we are on a start tag.
130         int type;
131         int depth = parser.getDepth();
132 
133         TransitionSet transitionSet = (parent instanceof TransitionSet)
134                 ? (TransitionSet) parent : null;
135 
136         while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
137                 && type != XmlPullParser.END_DOCUMENT) {
138 
139             if (type != XmlPullParser.START_TAG) {
140                 continue;
141             }
142 
143             String  name = parser.getName();
144             if ("fade".equals(name)) {
145                 transition = new Fade(mContext, attrs);
146             } else if ("changeBounds".equals(name)) {
147                 transition = new ChangeBounds(mContext, attrs);
148             } else if ("slide".equals(name)) {
149                 transition = new Slide(mContext, attrs);
150             } else if ("explode".equals(name)) {
151                 transition = new Explode(mContext, attrs);
152             } else if ("changeImageTransform".equals(name)) {
153                 transition = new ChangeImageTransform(mContext, attrs);
154             } else if ("changeTransform".equals(name)) {
155                 transition = new ChangeTransform(mContext, attrs);
156             } else if ("changeClipBounds".equals(name)) {
157                 transition = new ChangeClipBounds(mContext, attrs);
158             } else if ("autoTransition".equals(name)) {
159                 transition = new AutoTransition(mContext, attrs);
160             } else if ("recolor".equals(name)) {
161                 transition = new Recolor(mContext, attrs);
162             } else if ("changeScroll".equals(name)) {
163                 transition = new ChangeScroll(mContext, attrs);
164             } else if ("transitionSet".equals(name)) {
165                 transition = new TransitionSet(mContext, attrs);
166             } else if ("transition".equals(name)) {
167                 transition = (Transition) createCustom(attrs, Transition.class, "transition");
168             } else if ("targets".equals(name)) {
169                 getTargetIds(parser, attrs, parent);
170             } else if ("arcMotion".equals(name)) {
171                 parent.setPathMotion(new ArcMotion(mContext, attrs));
172             } else if ("pathMotion".equals(name)) {
173                 parent.setPathMotion((PathMotion)createCustom(attrs, PathMotion.class, "pathMotion"));
174             } else if ("patternPathMotion".equals(name)) {
175                 parent.setPathMotion(new PatternPathMotion(mContext, attrs));
176             } else {
177                 throw new RuntimeException("Unknown scene name: " + parser.getName());
178             }
179             if (transition != null) {
180                 if (!parser.isEmptyElementTag()) {
181                     createTransitionFromXml(parser, attrs, transition);
182                 }
183                 if (transitionSet != null) {
184                     transitionSet.addTransition(transition);
185                     transition = null;
186                 } else if (parent != null) {
187                     throw new InflateException("Could not add transition to another transition.");
188                 }
189             }
190         }
191 
192         return transition;
193     }
194 
createCustom(AttributeSet attrs, Class expectedType, String tag)195     private Object createCustom(AttributeSet attrs, Class expectedType, String tag) {
196         String className = attrs.getAttributeValue(null, "class");
197 
198         if (className == null) {
199             throw new InflateException(tag + " tag must have a 'class' attribute");
200         }
201 
202         try {
203             synchronized (sConstructors) {
204                 Constructor constructor = sConstructors.get(className);
205                 if (constructor == null) {
206                     Class c = mContext.getClassLoader().loadClass(className)
207                             .asSubclass(expectedType);
208                     if (c != null) {
209                         constructor = c.getConstructor(sConstructorSignature);
210                         sConstructors.put(className, constructor);
211                     }
212                 }
213 
214                 return constructor.newInstance(mContext, attrs);
215             }
216         } catch (InstantiationException e) {
217             throw new InflateException("Could not instantiate " + expectedType + " class " +
218                     className, e);
219         } catch (ClassNotFoundException e) {
220             throw new InflateException("Could not instantiate " + expectedType + " class " +
221                     className, e);
222         } catch (InvocationTargetException e) {
223             throw new InflateException("Could not instantiate " + expectedType + " class " +
224                     className, e);
225         } catch (NoSuchMethodException e) {
226             throw new InflateException("Could not instantiate " + expectedType + " class " +
227                     className, e);
228         } catch (IllegalAccessException e) {
229             throw new InflateException("Could not instantiate " + expectedType + " class " +
230                     className, e);
231         }
232     }
233 
getTargetIds(XmlPullParser parser, AttributeSet attrs, Transition transition)234     private void getTargetIds(XmlPullParser parser,
235             AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException {
236 
237         // Make sure we are on a start tag.
238         int type;
239         int depth = parser.getDepth();
240 
241         while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
242                 && type != XmlPullParser.END_DOCUMENT) {
243 
244             if (type != XmlPullParser.START_TAG) {
245                 continue;
246             }
247 
248             String  name = parser.getName();
249             if (name.equals("target")) {
250                 TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TransitionTarget);
251                 int id = a.getResourceId(R.styleable.TransitionTarget_targetId, 0);
252                 String transitionName;
253                 if (id != 0) {
254                     transition.addTarget(id);
255                 } else if ((id = a.getResourceId(R.styleable.TransitionTarget_excludeId, 0)) != 0) {
256                     transition.excludeTarget(id, true);
257                 } else if ((transitionName = a.getString(R.styleable.TransitionTarget_targetName))
258                         != null) {
259                     transition.addTarget(transitionName);
260                 } else if ((transitionName = a.getString(R.styleable.TransitionTarget_excludeName))
261                         != null) {
262                     transition.excludeTarget(transitionName, true);
263                 } else {
264                     String className = a.getString(R.styleable.TransitionTarget_excludeClass);
265                     try {
266                         if (className != null) {
267                             Class clazz = Class.forName(className);
268                             transition.excludeTarget(clazz, true);
269                         } else if ((className =
270                                 a.getString(R.styleable.TransitionTarget_targetClass)) != null) {
271                             Class clazz = Class.forName(className);
272                             transition.addTarget(clazz);
273                         }
274                     } catch (ClassNotFoundException e) {
275                         throw new RuntimeException("Could not create " + className, e);
276                     }
277                 }
278             } else {
279                 throw new RuntimeException("Unknown scene name: " + parser.getName());
280             }
281         }
282     }
283 
284     //
285     // TransitionManager loading
286     //
287 
createTransitionManagerFromXml(XmlPullParser parser, AttributeSet attrs, ViewGroup sceneRoot)288     private TransitionManager createTransitionManagerFromXml(XmlPullParser parser,
289             AttributeSet attrs, ViewGroup sceneRoot) throws XmlPullParserException, IOException {
290 
291         // Make sure we are on a start tag.
292         int type;
293         int depth = parser.getDepth();
294         TransitionManager transitionManager = null;
295 
296         while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
297                 && type != XmlPullParser.END_DOCUMENT) {
298 
299             if (type != XmlPullParser.START_TAG) {
300                 continue;
301             }
302 
303             String  name = parser.getName();
304             if (name.equals("transitionManager")) {
305                 transitionManager = new TransitionManager();
306             } else if (name.equals("transition") && (transitionManager != null)) {
307                 loadTransition(attrs, sceneRoot, transitionManager);
308             } else {
309                 throw new RuntimeException("Unknown scene name: " + parser.getName());
310             }
311         }
312         return transitionManager;
313     }
314 
loadTransition(AttributeSet attrs, ViewGroup sceneRoot, TransitionManager transitionManager)315     private void loadTransition(AttributeSet attrs, ViewGroup sceneRoot,
316             TransitionManager transitionManager) throws Resources.NotFoundException {
317 
318         TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TransitionManager);
319         int transitionId = a.getResourceId(R.styleable.TransitionManager_transition, -1);
320         int fromId = a.getResourceId(R.styleable.TransitionManager_fromScene, -1);
321         Scene fromScene = (fromId < 0) ? null: Scene.getSceneForLayout(sceneRoot, fromId, mContext);
322         int toId = a.getResourceId(R.styleable.TransitionManager_toScene, -1);
323         Scene toScene = (toId < 0) ? null : Scene.getSceneForLayout(sceneRoot, toId, mContext);
324 
325         if (transitionId >= 0) {
326             Transition transition = inflateTransition(transitionId);
327             if (transition != null) {
328                 if (toScene == null) {
329                     throw new RuntimeException("No toScene for transition ID " + transitionId);
330                 }
331                 if (fromScene == null) {
332                     transitionManager.setTransition(toScene, transition);
333                 } else {
334                     transitionManager.setTransition(fromScene, toScene, transition);
335                 }
336             }
337         }
338         a.recycle();
339     }
340 }
341