1 /*
2  * Copyright (C) 2022 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.layoutlib.bridge.intensive.setup;
18 
19 import com.android.ide.common.rendering.api.ILayoutPullParser;
20 import com.android.ide.common.rendering.api.ResourceNamespace;
21 
22 import org.kxml2.io.KXmlParser;
23 import org.xmlpull.v1.XmlPullParserException;
24 
25 import android.annotation.NonNull;
26 
27 import java.io.ByteArrayInputStream;
28 import java.io.File;
29 import java.io.FileInputStream;
30 import java.io.FileNotFoundException;
31 import java.io.IOError;
32 import java.io.InputStream;
33 import java.nio.charset.StandardCharsets;
34 import java.util.HashMap;
35 import java.util.Map;
36 
37 import static com.android.SdkConstants.ATTR_IGNORE;
38 import static com.android.SdkConstants.EXPANDABLE_LIST_VIEW;
39 import static com.android.SdkConstants.GRID_VIEW;
40 import static com.android.SdkConstants.LIST_VIEW;
41 import static com.android.SdkConstants.SPINNER;
42 import static com.android.SdkConstants.TOOLS_URI;
43 
44 public class LayoutPullParser extends KXmlParser implements ILayoutPullParser{
45 
46     private ResourceNamespace mLayoutNamespace = ResourceNamespace.RES_AUTO;
47 
48     @NonNull
createFromFile(@onNull File layoutFile)49     public static LayoutPullParser createFromFile(@NonNull File layoutFile)
50             throws FileNotFoundException {
51         return new LayoutPullParser(new FileInputStream(layoutFile));
52     }
53 
54     /**
55      * @param layoutPath Must start with '/' and be relative to test resources.
56      */
57     @NonNull
createFromPath(@onNull String layoutPath)58     public static LayoutPullParser createFromPath(@NonNull String layoutPath) {
59         if (layoutPath.startsWith("/")) {
60             layoutPath = layoutPath.substring(1);
61         }
62 
63         return new LayoutPullParser(LayoutPullParser.class.getClassLoader().getResourceAsStream
64                 (layoutPath));
65     }
66 
67     @NonNull
createFromString(@onNull String contents)68     public static LayoutPullParser createFromString(@NonNull String contents) {
69         return new LayoutPullParser(new ByteArrayInputStream(
70                 contents.getBytes(StandardCharsets.UTF_8)));
71     }
72 
LayoutPullParser(@onNull InputStream inputStream)73     private LayoutPullParser(@NonNull InputStream inputStream) {
74         try {
75             setFeature(FEATURE_PROCESS_NAMESPACES, true);
76             setInput(inputStream, null);
77         } catch (XmlPullParserException e) {
78             throw new IOError(e);
79         }
80     }
81 
82     @Override
getViewCookie()83     public Object getViewCookie() {
84         // TODO: Implement this properly.
85         String name = super.getName();
86         if (name == null) {
87             return null;
88         }
89 
90         // Store tools attributes if this looks like a layout we'll need adapter view
91         // bindings for in the LayoutlibCallback.
92         if (LIST_VIEW.equals(name) || EXPANDABLE_LIST_VIEW.equals(name) || GRID_VIEW.equals(name) || SPINNER.equals(name)) {
93             Map<String, String> map = null;
94             int count = getAttributeCount();
95             for (int i = 0; i < count; i++) {
96                 String namespace = getAttributeNamespace(i);
97                 if (namespace != null && namespace.equals(TOOLS_URI)) {
98                     String attribute = getAttributeName(i);
99                     if (attribute.equals(ATTR_IGNORE)) {
100                         continue;
101                     }
102                     if (map == null) {
103                         map = new HashMap<>(4);
104                     }
105                     map.put(attribute, getAttributeValue(i));
106                 }
107             }
108 
109             return map;
110         }
111 
112         return null;
113     }
114 
115     @Override
116     @NonNull
getLayoutNamespace()117     public ResourceNamespace getLayoutNamespace() {
118         return mLayoutNamespace;
119     }
120 
setLayoutNamespace(@onNull ResourceNamespace layoutNamespace)121     public void setLayoutNamespace(@NonNull ResourceNamespace layoutNamespace) {
122         mLayoutNamespace = layoutNamespace;
123     }
124 }
125