|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 package org.mozilla.gecko.util; |
|
6 |
|
7 import org.mozilla.gecko.mozglue.NativeZip; |
|
8 |
|
9 import android.content.res.Resources; |
|
10 import android.graphics.Bitmap; |
|
11 import android.graphics.drawable.BitmapDrawable; |
|
12 import android.util.Log; |
|
13 import org.mozilla.gecko.mozglue.RobocopTarget; |
|
14 |
|
15 import java.io.BufferedReader; |
|
16 import java.io.IOException; |
|
17 import java.io.InputStream; |
|
18 import java.io.InputStreamReader; |
|
19 import java.net.URI; |
|
20 import java.net.URISyntaxException; |
|
21 import java.util.Stack; |
|
22 |
|
23 /* Reads out of a multiple level deep jar file such as |
|
24 * jar:jar:file:///data/app/org.mozilla.fennec.apk!/omni.ja!/chrome/chrome/content/branding/favicon32.png |
|
25 */ |
|
26 public final class GeckoJarReader { |
|
27 private static final String LOGTAG = "GeckoJarReader"; |
|
28 |
|
29 private GeckoJarReader() {} |
|
30 |
|
31 public static Bitmap getBitmap(Resources resources, String url) { |
|
32 BitmapDrawable drawable = getBitmapDrawable(resources, url); |
|
33 return (drawable != null) ? drawable.getBitmap() : null; |
|
34 } |
|
35 |
|
36 public static BitmapDrawable getBitmapDrawable(Resources resources, String url) { |
|
37 Stack<String> jarUrls = parseUrl(url); |
|
38 InputStream inputStream = null; |
|
39 BitmapDrawable bitmap = null; |
|
40 |
|
41 NativeZip zip = null; |
|
42 try { |
|
43 // Load the initial jar file as a zip |
|
44 zip = getZipFile(jarUrls.pop()); |
|
45 inputStream = getStream(zip, jarUrls, url); |
|
46 if (inputStream != null) { |
|
47 bitmap = new BitmapDrawable(resources, inputStream); |
|
48 } |
|
49 } catch (IOException ex) { |
|
50 Log.e(LOGTAG, "Exception ", ex); |
|
51 } catch (URISyntaxException ex) { |
|
52 Log.e(LOGTAG, "Exception ", ex); |
|
53 } finally { |
|
54 if (inputStream != null) { |
|
55 try { |
|
56 inputStream.close(); |
|
57 } catch(IOException ex) { |
|
58 Log.e(LOGTAG, "Error closing stream", ex); |
|
59 } |
|
60 } |
|
61 if (zip != null) { |
|
62 zip.close(); |
|
63 } |
|
64 } |
|
65 |
|
66 return bitmap; |
|
67 } |
|
68 |
|
69 public static String getText(String url) { |
|
70 Stack<String> jarUrls = parseUrl(url); |
|
71 |
|
72 NativeZip zip = null; |
|
73 BufferedReader reader = null; |
|
74 String text = null; |
|
75 try { |
|
76 zip = getZipFile(jarUrls.pop()); |
|
77 InputStream input = getStream(zip, jarUrls, url); |
|
78 if (input != null) { |
|
79 reader = new BufferedReader(new InputStreamReader(input)); |
|
80 text = reader.readLine(); |
|
81 } |
|
82 } catch (IOException ex) { |
|
83 Log.e(LOGTAG, "Exception ", ex); |
|
84 } catch (URISyntaxException ex) { |
|
85 Log.e(LOGTAG, "Exception ", ex); |
|
86 } finally { |
|
87 if (reader != null) { |
|
88 try { |
|
89 reader.close(); |
|
90 } catch(IOException ex) { |
|
91 Log.e(LOGTAG, "Error closing reader", ex); |
|
92 } |
|
93 } |
|
94 if (zip != null) { |
|
95 zip.close(); |
|
96 } |
|
97 } |
|
98 |
|
99 return text; |
|
100 } |
|
101 |
|
102 private static NativeZip getZipFile(String url) throws IOException, URISyntaxException { |
|
103 URI fileUrl = new URI(url); |
|
104 return new NativeZip(fileUrl.getPath()); |
|
105 } |
|
106 |
|
107 @RobocopTarget |
|
108 public static InputStream getStream(String url) { |
|
109 Stack<String> jarUrls = parseUrl(url); |
|
110 try { |
|
111 NativeZip zip = getZipFile(jarUrls.pop()); |
|
112 return getStream(zip, jarUrls, url); |
|
113 } catch (Exception ex) { |
|
114 // Some JNI code throws IllegalArgumentException on a bad file name; |
|
115 // swallow the error and return null. We could also see legitimate |
|
116 // IOExceptions here. |
|
117 return null; |
|
118 } |
|
119 } |
|
120 |
|
121 private static InputStream getStream(NativeZip zip, Stack<String> jarUrls, String origUrl) { |
|
122 InputStream inputStream = null; |
|
123 |
|
124 // loop through children jar files until we reach the innermost one |
|
125 while (!jarUrls.empty()) { |
|
126 String fileName = jarUrls.pop(); |
|
127 |
|
128 if (inputStream != null) { |
|
129 // intermediate NativeZips and InputStreams will be garbage collected. |
|
130 try { |
|
131 zip = new NativeZip(inputStream); |
|
132 } catch (IllegalArgumentException e) { |
|
133 String description = "!!! BUG 849589 !!! origUrl=" + origUrl; |
|
134 Log.e(LOGTAG, description, e); |
|
135 throw new IllegalArgumentException(description); |
|
136 } |
|
137 } |
|
138 |
|
139 inputStream = zip.getInputStream(fileName); |
|
140 if (inputStream == null) { |
|
141 Log.d(LOGTAG, "No Entry for " + fileName); |
|
142 return null; |
|
143 } |
|
144 } |
|
145 |
|
146 return inputStream; |
|
147 } |
|
148 |
|
149 /* Returns a stack of strings breaking the url up into pieces. Each piece |
|
150 * is assumed to point to a jar file except for the final one. Callers should |
|
151 * pass in the url to parse, and null for the parent parameter (used for recursion) |
|
152 * For example, jar:jar:file:///data/app/org.mozilla.fennec.apk!/omni.ja!/chrome/chrome/content/branding/favicon32.png |
|
153 * will return: |
|
154 * file:///data/app/org.mozilla.fennec.apk |
|
155 * omni.ja |
|
156 * chrome/chrome/content/branding/favicon32.png |
|
157 */ |
|
158 private static Stack<String> parseUrl(String url) { |
|
159 return parseUrl(url, null); |
|
160 } |
|
161 |
|
162 private static Stack<String> parseUrl(String url, Stack<String> results) { |
|
163 if (results == null) { |
|
164 results = new Stack<String>(); |
|
165 } |
|
166 |
|
167 if (url.startsWith("jar:")) { |
|
168 int jarEnd = url.lastIndexOf("!"); |
|
169 String subStr = url.substring(4, jarEnd); |
|
170 results.push(url.substring(jarEnd+2)); // remove the !/ characters |
|
171 return parseUrl(subStr, results); |
|
172 } else { |
|
173 results.push(url); |
|
174 return results; |
|
175 } |
|
176 } |
|
177 } |