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
17package android.transition;
18
19import android.content.Context;
20import android.content.res.Resources;
21import android.content.res.TypedArray;
22import android.content.res.XmlResourceParser;
23import android.util.AttributeSet;
24import android.util.Xml;
25import android.view.InflateException;
26import android.view.ViewGroup;
27import android.view.animation.AnimationUtils;
28import org.xmlpull.v1.XmlPullParser;
29import org.xmlpull.v1.XmlPullParserException;
30
31import java.io.IOException;
32import java.util.ArrayList;
33
34/**
35 * This class inflates scenes and transitions from resource files.
36 *
37 * Information on XML resource descriptions for transitions can be found for
38 * {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet},
39 * {@link android.R.styleable#TransitionTarget}, {@link android.R.styleable#Fade},
40 * and {@link android.R.styleable#TransitionManager}.
41 */
42public class TransitionInflater {
43
44    private Context mContext;
45
46    private TransitionInflater(Context context) {
47        mContext = context;
48    }
49
50    /**
51     * Obtains the TransitionInflater from the given context.
52     */
53    public static TransitionInflater from(Context context) {
54        return new TransitionInflater(context);
55    }
56
57    /**
58     * Loads a {@link Transition} object from a resource
59     *
60     * @param resource The resource id of the transition to load
61     * @return The loaded Transition object
62     * @throws android.content.res.Resources.NotFoundException when the
63     * transition cannot be loaded
64     */
65    public Transition inflateTransition(int resource) {
66        XmlResourceParser parser =  mContext.getResources().getXml(resource);
67        try {
68            return createTransitionFromXml(parser, Xml.asAttributeSet(parser), null);
69        } catch (XmlPullParserException e) {
70            InflateException ex = new InflateException(e.getMessage());
71            ex.initCause(e);
72            throw ex;
73        } catch (IOException e) {
74            InflateException ex = new InflateException(
75                    parser.getPositionDescription()
76                            + ": " + e.getMessage());
77            ex.initCause(e);
78            throw ex;
79        } finally {
80            parser.close();
81        }
82    }
83
84    /**
85     * Loads a {@link TransitionManager} object from a resource
86     *
87     *
88     *
89     * @param resource The resource id of the transition manager to load
90     * @return The loaded TransitionManager object
91     * @throws android.content.res.Resources.NotFoundException when the
92     * transition manager cannot be loaded
93     */
94    public TransitionManager inflateTransitionManager(int resource, ViewGroup sceneRoot) {
95        XmlResourceParser parser =  mContext.getResources().getXml(resource);
96        try {
97            return createTransitionManagerFromXml(parser, Xml.asAttributeSet(parser), sceneRoot);
98        } catch (XmlPullParserException e) {
99            InflateException ex = new InflateException(e.getMessage());
100            ex.initCause(e);
101            throw ex;
102        } catch (IOException e) {
103            InflateException ex = new InflateException(
104                    parser.getPositionDescription()
105                            + ": " + e.getMessage());
106            ex.initCause(e);
107            throw ex;
108        } finally {
109            parser.close();
110        }
111    }
112
113    //
114    // Transition loading
115    //
116
117    private Transition createTransitionFromXml(XmlPullParser parser,
118            AttributeSet attrs, TransitionSet transitionSet)
119            throws XmlPullParserException, IOException {
120
121        Transition transition = null;
122
123        // Make sure we are on a start tag.
124        int type;
125        int depth = parser.getDepth();
126
127        while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
128                && type != XmlPullParser.END_DOCUMENT) {
129
130            boolean newTransition = false;
131
132            if (type != XmlPullParser.START_TAG) {
133                continue;
134            }
135
136            String  name = parser.getName();
137            if ("fade".equals(name)) {
138                TypedArray a = mContext.obtainStyledAttributes(attrs,
139                        com.android.internal.R.styleable.Fade);
140                int fadingMode = a.getInt(com.android.internal.R.styleable.Fade_fadingMode,
141                        Fade.IN | Fade.OUT);
142                transition = new Fade(fadingMode);
143                newTransition = true;
144            } else if ("changeBounds".equals(name)) {
145                transition = new ChangeBounds();
146                newTransition = true;
147            } else if ("slide".equals(name)) {
148                transition = new Slide();
149                newTransition = true;
150            } else if ("autoTransition".equals(name)) {
151                transition = new AutoTransition();
152                newTransition = true;
153            } else if ("recolor".equals(name)) {
154                transition = new Recolor();
155                newTransition = true;
156            } else if ("transitionSet".equals(name)) {
157                transition = new TransitionSet();
158                TypedArray a = mContext.obtainStyledAttributes(attrs,
159                        com.android.internal.R.styleable.TransitionSet);
160                int ordering = a.getInt(
161                        com.android.internal.R.styleable.TransitionSet_transitionOrdering,
162                        TransitionSet.ORDERING_TOGETHER);
163                ((TransitionSet) transition).setOrdering(ordering);
164                createTransitionFromXml(parser, attrs, ((TransitionSet) transition));
165                a.recycle();
166                newTransition = true;
167            } else if ("targets".equals(name)) {
168                if (parser.getDepth() - 1 > depth && transition != null) {
169                    // We're inside the child tag - add targets to the child
170                    getTargetIds(parser, attrs, transition);
171                } else if (parser.getDepth() - 1 == depth && transitionSet != null) {
172                    // add targets to the set
173                    getTargetIds(parser, attrs, transitionSet);
174                }
175            }
176            if (transition != null || "targets".equals(name)) {
177                if (newTransition) {
178                    loadTransition(transition, attrs);
179                    if (transitionSet != null) {
180                        transitionSet.addTransition(transition);
181                    }
182                }
183            } else {
184                throw new RuntimeException("Unknown scene name: " + parser.getName());
185            }
186        }
187
188        return transition;
189    }
190
191    private void getTargetIds(XmlPullParser parser,
192            AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException {
193
194        // Make sure we are on a start tag.
195        int type;
196        int depth = parser.getDepth();
197
198        ArrayList<Integer> targetIds = new ArrayList<Integer>();
199        while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
200                && type != XmlPullParser.END_DOCUMENT) {
201
202            if (type != XmlPullParser.START_TAG) {
203                continue;
204            }
205
206            String  name = parser.getName();
207            if (name.equals("target")) {
208                TypedArray a = mContext.obtainStyledAttributes(attrs,
209                        com.android.internal.R.styleable.TransitionTarget);
210                int id = a.getResourceId(
211                        com.android.internal.R.styleable.TransitionTarget_targetId, -1);
212                if (id >= 0) {
213                    targetIds.add(id);
214                }
215            } else {
216                throw new RuntimeException("Unknown scene name: " + parser.getName());
217            }
218        }
219        int numTargets = targetIds.size();
220        if (numTargets > 0) {
221            for (int i = 0; i < numTargets; ++i) {
222                transition.addTarget(targetIds.get(i));
223            }
224        }
225    }
226
227    private Transition loadTransition(Transition transition, AttributeSet attrs)
228            throws Resources.NotFoundException {
229
230        TypedArray a =
231                mContext.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Transition);
232        long duration = a.getInt(com.android.internal.R.styleable.Transition_duration, -1);
233        if (duration >= 0) {
234            transition.setDuration(duration);
235        }
236        long startDelay = a.getInt(com.android.internal.R.styleable.Transition_startDelay, -1);
237        if (startDelay > 0) {
238            transition.setStartDelay(startDelay);
239        }
240        final int resID =
241                a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0);
242        if (resID > 0) {
243            transition.setInterpolator(AnimationUtils.loadInterpolator(mContext, resID));
244        }
245        a.recycle();
246        return transition;
247    }
248
249    //
250    // TransitionManager loading
251    //
252
253    private TransitionManager createTransitionManagerFromXml(XmlPullParser parser,
254            AttributeSet attrs, ViewGroup sceneRoot) throws XmlPullParserException, IOException {
255
256        // Make sure we are on a start tag.
257        int type;
258        int depth = parser.getDepth();
259        TransitionManager transitionManager = null;
260
261        while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
262                && type != XmlPullParser.END_DOCUMENT) {
263
264            if (type != XmlPullParser.START_TAG) {
265                continue;
266            }
267
268            String  name = parser.getName();
269            if (name.equals("transitionManager")) {
270                transitionManager = new TransitionManager();
271            } else if (name.equals("transition") && (transitionManager != null)) {
272                loadTransition(attrs, sceneRoot, transitionManager);
273            } else {
274                throw new RuntimeException("Unknown scene name: " + parser.getName());
275            }
276        }
277        return transitionManager;
278    }
279
280    private void loadTransition(AttributeSet attrs, ViewGroup sceneRoot,
281            TransitionManager transitionManager) throws Resources.NotFoundException {
282
283        TypedArray a = mContext.obtainStyledAttributes(attrs,
284                com.android.internal.R.styleable.TransitionManager);
285        int transitionId = a.getResourceId(
286                com.android.internal.R.styleable.TransitionManager_transition, -1);
287        Scene fromScene = null, toScene = null;
288        int fromId = a.getResourceId(
289                com.android.internal.R.styleable.TransitionManager_fromScene, -1);
290        if (fromId >= 0) fromScene = Scene.getSceneForLayout(sceneRoot, fromId, mContext);
291        int toId = a.getResourceId(
292                com.android.internal.R.styleable.TransitionManager_toScene, -1);
293        if (toId >= 0) toScene = Scene.getSceneForLayout(sceneRoot, toId, mContext);
294        if (transitionId >= 0) {
295            Transition transition = inflateTransition(transitionId);
296            if (transition != null) {
297                if (fromScene != null) {
298                    if (toScene == null){
299                        throw new RuntimeException("No matching toScene for given fromScene " +
300                                "for transition ID " + transitionId);
301                    } else {
302                        transitionManager.setTransition(fromScene, toScene, transition);
303                    }
304                } else if (toId >= 0) {
305                    transitionManager.setTransition(toScene, transition);
306                }
307            }
308        }
309        a.recycle();
310    }
311}
312