Skip to content

Commit 8ef0c30

Browse files
authored
feat(Android): Implement ionic-file and ionic-content urls (#242)
* feat(Android): Implement ionic-file and ionic-content urls closes #204, closes #183 * Change scheme and remove port
1 parent 486d412 commit 8ef0c30

File tree

5 files changed

+87
-159
lines changed

5 files changed

+87
-159
lines changed

plugin.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838

3939
<platform name="android">
4040
<config-file target="config.xml" parent="/*">
41-
<allow-navigation href="http://localhost:8080/*"/>
41+
<allow-navigation href="http://localhost/*"/>
4242
<preference name="webView" value="com.ionicframework.cordova.webview.IonicWebViewEngine"/>
4343
<feature name="IonicWebView">
4444
<param name="android-package" value="com.ionicframework.cordova.webview.IonicWebView"/>

src/android/com/ionicframework/cordova/webview/AndroidProtocolHandler.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,8 @@ public AndroidProtocolHandler(Context context) {
2626
this.context = context;
2727
}
2828

29-
public InputStream openAsset(String path, String assetPath) throws IOException {
30-
if (path.startsWith(assetPath + "/_file_")) {
31-
if (path.contains("content://")) {
32-
String contentPath = path.replace(assetPath + "/_file_/", "content://");
33-
return context.getContentResolver().openInputStream(Uri.parse(contentPath));
34-
} else {
35-
String filePath = path.replace(assetPath + "/_file_/", "");
36-
return new FileInputStream(new File(filePath));
37-
}
38-
} else {
39-
return context.getAssets().open(path, AssetManager.ACCESS_STREAMING);
40-
}
29+
public InputStream openAsset(String path) throws IOException {
30+
return context.getAssets().open(path, AssetManager.ACCESS_STREAMING);
4131
}
4232

4333
public InputStream openResource(Uri uri) {
@@ -82,6 +72,16 @@ public InputStream openFile(String filePath) throws IOException {
8272
return new FileInputStream(localFile);
8373
}
8474

75+
public InputStream openContentUrl(Uri uri) throws IOException {
76+
InputStream stream = null;
77+
try {
78+
stream = context.getContentResolver().openInputStream(Uri.parse(uri.toString().replace(WebViewLocalServer.ionicContentScheme + ":///", "content://")));
79+
} catch (SecurityException e) {
80+
Log.e(TAG, "Unable to open content URL: " + uri, e);
81+
}
82+
return stream;
83+
}
84+
8585
private static int getFieldId(Context context, String assetType, String assetName)
8686
throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
8787
Class<?> d = context.getClassLoader()

src/android/com/ionicframework/cordova/webview/IonicWebViewEngine.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@ public void init(CordovaWebView parentWebView, CordovaInterface cordova, final C
5959
ConfigXmlParser parser = new ConfigXmlParser();
6060
parser.parse(cordova.getActivity());
6161

62-
String port = preferences.getString("WKPort", "8080");
63-
CDV_LOCAL_SERVER = "http://localhost:" + port;
62+
String hostname = preferences.getString("Hostname", "localhost");
63+
CDV_LOCAL_SERVER = "http://" + hostname;
6464

65-
localServer = new WebViewLocalServer(cordova.getActivity(), "localhost:" + port, true, parser);
66-
WebViewLocalServer.AssetHostingDetails ahd = localServer.hostAssets("www");
65+
localServer = new WebViewLocalServer(cordova.getActivity(), hostname, true, parser);
66+
localServer.hostAssets("www");
6767

6868
webView.setWebViewClient(new ServerClient(this, parser));
6969

@@ -143,7 +143,9 @@ public void onPageStarted(WebView view, String url, Bitmap favicon) {
143143
public void onPageFinished(WebView view, String url) {
144144
super.onPageFinished(view, url);
145145
view.loadUrl("javascript:(function() { " +
146-
"window.WEBVIEW_SERVER_URL = '" + CDV_LOCAL_SERVER + "'" +
146+
"window.WEBVIEW_SERVER_URL = '" + CDV_LOCAL_SERVER + "';" +
147+
"window.WEBVIEW_FILE_PREFIX = '" + WebViewLocalServer.ionicFileScheme + "';" +
148+
"window.WEBVIEW_CONTENT_PREFIX = '" + WebViewLocalServer.ionicContentScheme + "';" +
147149
"})()");
148150
}
149151
}

src/android/com/ionicframework/cordova/webview/WebViewLocalServer.java

Lines changed: 61 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,16 @@
4848
public class WebViewLocalServer {
4949
private static String TAG = "WebViewAssetServer";
5050
private String basePath;
51-
/**
52-
* capacitorapp.net is reserved by the Ionic team for use in local capacitor apps.
53-
*/
54-
public final static String knownUnusedAuthority = "capacitorapp.net";
5551
private final static String httpScheme = "http";
5652
private final static String httpsScheme = "https";
53+
public final static String ionicFileScheme = "app-file";
54+
public final static String ionicContentScheme = "app-content";
5755

5856
private final UriMatcher uriMatcher;
5957
private final AndroidProtocolHandler protocolHandler;
6058
private final String authority;
6159
// Whether we're serving local files or proxying (for example, when doing livereload on a
6260
// non-local endpoint (will be false in that case)
63-
private final boolean isLocal;
6461
private boolean isAsset;
6562
// Whether to route all requests to paths without extensions back to `index.html`
6663
private final boolean html5mode;
@@ -169,18 +166,7 @@ public Uri getHttpsPrefix() {
169166
this.html5mode = html5mode;
170167
this.parser = parser;
171168
this.protocolHandler = new AndroidProtocolHandler(context.getApplicationContext());
172-
if (authority != null) {
173-
this.authority = authority;
174-
if (authority.startsWith("localhost")) {
175-
this.isLocal = true;
176-
} else {
177-
this.isLocal = false;
178-
}
179-
180-
} else {
181-
this.isLocal = true;
182-
this.authority = UUID.randomUUID().toString() + "" + knownUnusedAuthority;
183-
}
169+
this.authority = authority;
184170
}
185171

186172
private static Uri parseAndVerifyUrl(String url) {
@@ -234,24 +220,38 @@ public WebResourceResponse shouldInterceptRequest(Uri uri) {
234220
return null;
235221
}
236222

237-
if (this.isLocal) {
223+
if (isLocalFile(uri) || uri.getAuthority().equals(this.authority)) {
238224
Log.d("SERVER", "Handling local request: " + uri.toString());
239225
return handleLocalRequest(uri, handler);
240226
} else {
241227
return handleProxyRequest(uri, handler);
242228
}
243229
}
244230

231+
private boolean isLocalFile(Uri uri) {
232+
if (uri.getScheme().equals(ionicContentScheme) || uri.getScheme().equals(ionicFileScheme)) {
233+
return true;
234+
}
235+
return false;
236+
}
245237
private WebResourceResponse handleLocalRequest(Uri uri, PathHandler handler) {
246238
String path = uri.getPath();
239+
240+
if (isLocalFile(uri)) {
241+
InputStream responseStream = new LollipopLazyInputStream(handler, uri);
242+
String mimeType = getMimeType(path, responseStream);
243+
return createWebResourceResponse(mimeType, handler.getEncoding(),
244+
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), responseStream);
245+
}
246+
247247
if (path.equals("/") || (!uri.getLastPathSegment().contains(".") && html5mode)) {
248248
InputStream stream;
249249
String launchURL = parser.getLaunchUrl();
250250
String launchFile = launchURL.substring(launchURL.lastIndexOf("/") + 1, launchURL.length());
251251
try {
252252
String startPath = this.basePath + "/" + launchFile;
253253
if (isAsset) {
254-
stream = protocolHandler.openAsset(startPath, "");
254+
stream = protocolHandler.openAsset(startPath);
255255
} else {
256256
stream = protocolHandler.openFile(startPath);
257257
}
@@ -267,15 +267,10 @@ private WebResourceResponse handleLocalRequest(Uri uri, PathHandler handler) {
267267

268268
int periodIndex = path.lastIndexOf(".");
269269
if (periodIndex >= 0) {
270-
String ext = path.substring(path.lastIndexOf("."), path.length());
271-
272270
InputStream responseStream = new LollipopLazyInputStream(handler, uri);
273-
InputStream stream = responseStream;
274-
275-
String mimeType = getMimeType(path, stream);
276-
271+
String mimeType = getMimeType(path, responseStream);
277272
return createWebResourceResponse(mimeType, handler.getEncoding(),
278-
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), stream);
273+
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), responseStream);
279274
}
280275

281276
return null;
@@ -375,31 +370,12 @@ void register(Uri uri, PathHandler handler) {
375370
*
376371
* @param assetPath the local path in the application's asset folder which will be made
377372
* available by the server (for example "/www").
378-
* @return prefixes under which the assets are hosted.
379373
*/
380-
public AssetHostingDetails hostAssets(String assetPath) {
381-
return hostAssets(authority, assetPath, "", true, true);
374+
public void hostAssets(String assetPath) {
375+
hostAssets(authority, assetPath);
382376
}
383377

384378

385-
/**
386-
* Hosts the application's assets on an http(s):// URL. Assets from the local path
387-
* <code>assetPath/...</code> will be available under
388-
* <code>http(s)://{uuid}.androidplatform.net/{virtualAssetPath}/...</code>.
389-
*
390-
* @param assetPath the local path in the application's asset folder which will be made
391-
* available by the server (for example "/www").
392-
* @param virtualAssetPath the path on the local server under which the assets should be hosted.
393-
* @param enableHttp whether to enable hosting using the http scheme.
394-
* @param enableHttps whether to enable hosting using the https scheme.
395-
* @return prefixes under which the assets are hosted.
396-
*/
397-
public AssetHostingDetails hostAssets(final String assetPath, final String virtualAssetPath,
398-
boolean enableHttp, boolean enableHttps) {
399-
return hostAssets(authority, assetPath, virtualAssetPath, enableHttp,
400-
enableHttps);
401-
}
402-
403379
/**
404380
* Hosts the application's assets on an http(s):// URL. Assets from the local path
405381
* <code>assetPath/...</code> will be available under
@@ -408,39 +384,39 @@ public AssetHostingDetails hostAssets(final String assetPath, final String virtu
408384
* @param domain custom domain on which the assets should be hosted (for example "example.com").
409385
* @param assetPath the local path in the application's asset folder which will be made
410386
* available by the server (for example "/www").
411-
* @param virtualAssetPath the path on the local server under which the assets should be hosted.
412-
* @param enableHttp whether to enable hosting using the http scheme.
413-
* @param enableHttps whether to enable hosting using the https scheme.
414387
* @return prefixes under which the assets are hosted.
415388
*/
416-
public AssetHostingDetails hostAssets(final String domain,
417-
final String assetPath, final String virtualAssetPath,
418-
boolean enableHttp, boolean enableHttps) {
389+
public void hostAssets(final String domain,
390+
final String assetPath) {
419391
this.isAsset = true;
420392
this.basePath = assetPath;
421-
Uri.Builder uriBuilder = new Uri.Builder();
422-
uriBuilder.scheme(httpScheme);
423-
uriBuilder.authority(domain);
424-
uriBuilder.path(virtualAssetPath);
393+
394+
createHostingDetails();
395+
}
396+
397+
private void createHostingDetails() {
398+
final String assetPath = this.basePath;
425399

426400
if (assetPath.indexOf('*') != -1) {
427401
throw new IllegalArgumentException("assetPath cannot contain the '*' character.");
428402
}
429-
if (virtualAssetPath.indexOf('*') != -1) {
430-
throw new IllegalArgumentException(
431-
"virtualAssetPath cannot contain the '*' character.");
432-
}
433-
434-
Uri httpPrefix = null;
435-
Uri httpsPrefix = null;
436403

437404
PathHandler handler = new PathHandler() {
438405
@Override
439406
public InputStream handle(Uri url) {
440-
InputStream stream;
441-
String path = url.getPath().replaceFirst(virtualAssetPath, assetPath);
407+
InputStream stream = null;
408+
String path = url.getPath();
409+
if (!isAsset) {
410+
path = basePath + url.getPath();
411+
}
442412
try {
443-
stream = protocolHandler.openAsset(path, assetPath);
413+
if ((url.getScheme().equals(httpScheme) || url.getScheme().equals(httpsScheme)) && isAsset) {
414+
stream = protocolHandler.openAsset(assetPath + path);
415+
} else if (url.getScheme().equals(ionicFileScheme) || !isAsset) {
416+
stream = protocolHandler.openFile(path);
417+
} else if (url.getScheme().equals(ionicContentScheme)) {
418+
stream = protocolHandler.openContentUrl(url);
419+
}
444420
} catch (IOException e) {
445421
Log.e(TAG, "Unable to open asset URL: " + url);
446422
return null;
@@ -450,18 +426,22 @@ public InputStream handle(Uri url) {
450426
}
451427
};
452428

453-
if (enableHttp) {
454-
httpPrefix = uriBuilder.build();
455-
register(Uri.withAppendedPath(httpPrefix, "/"), handler);
456-
register(Uri.withAppendedPath(httpPrefix, "**"), handler);
457-
}
458-
if (enableHttps) {
459-
uriBuilder.scheme(httpsScheme);
460-
httpsPrefix = uriBuilder.build();
461-
register(Uri.withAppendedPath(httpsPrefix, "/"), handler);
462-
register(Uri.withAppendedPath(httpsPrefix, "**"), handler);
463-
}
464-
return new AssetHostingDetails(httpPrefix, httpsPrefix);
429+
registerUriForScheme(httpScheme, handler, authority);
430+
registerUriForScheme(httpsScheme, handler, authority);
431+
registerUriForScheme(ionicFileScheme, handler, "");
432+
registerUriForScheme(ionicContentScheme, handler, "");
433+
434+
}
435+
436+
private void registerUriForScheme(String scheme, PathHandler handler, String authority) {
437+
Uri.Builder uriBuilder = new Uri.Builder();
438+
uriBuilder.scheme(scheme);
439+
uriBuilder.authority(authority);
440+
uriBuilder.path("");
441+
Uri uriPrefix = uriBuilder.build();
442+
443+
register(Uri.withAppendedPath(uriPrefix, "/"), handler);
444+
register(Uri.withAppendedPath(uriPrefix, "**"), handler);
465445
}
466446

467447
/**
@@ -553,62 +533,11 @@ public InputStream handle(Uri url) {
553533
*
554534
* @param basePath the local path in the application's data folder which will be made
555535
* available by the server (for example "/www").
556-
* @return prefixes under which the assets are hosted.
557536
*/
558-
public AssetHostingDetails hostFiles(String basePath) {
559-
return hostFiles(basePath, true, true);
560-
}
561-
562-
public AssetHostingDetails hostFiles(final String basePath, boolean enableHttp,
563-
boolean enableHttps) {
537+
public void hostFiles(final String basePath) {
564538
this.isAsset = false;
565539
this.basePath = basePath;
566-
Uri.Builder uriBuilder = new Uri.Builder();
567-
uriBuilder.scheme(httpScheme);
568-
uriBuilder.authority(authority);
569-
uriBuilder.path("");
570-
571-
Uri httpPrefix = null;
572-
Uri httpsPrefix = null;
573-
574-
PathHandler handler = new PathHandler() {
575-
@Override
576-
public InputStream handle(Uri url) {
577-
InputStream stream;
578-
try {
579-
if (url.getPath().startsWith("/_file_/")) {
580-
stream = protocolHandler.openFile( url.getPath().replace("/_file_/", ""));
581-
} else {
582-
stream = protocolHandler.openFile(basePath + url.getPath());
583-
}
584-
} catch (IOException e) {
585-
Log.e(TAG, "Unable to open asset URL: " + url);
586-
return null;
587-
}
588-
589-
String mimeType = null;
590-
try {
591-
mimeType = URLConnection.guessContentTypeFromStream(stream);
592-
} catch (Exception ex) {
593-
Log.e(TAG, "Unable to get mime type" + url);
594-
}
595-
596-
return stream;
597-
}
598-
};
599-
600-
if (enableHttp) {
601-
httpPrefix = uriBuilder.build();
602-
register(Uri.withAppendedPath(httpPrefix, "/"), handler);
603-
register(Uri.withAppendedPath(httpPrefix, "**"), handler);
604-
}
605-
if (enableHttps) {
606-
uriBuilder.scheme(httpsScheme);
607-
httpsPrefix = uriBuilder.build();
608-
register(Uri.withAppendedPath(httpsPrefix, "/"), handler);
609-
register(Uri.withAppendedPath(httpsPrefix, "**"), handler);
610-
}
611-
return new AssetHostingDetails(httpPrefix, httpsPrefix);
540+
createHostingDetails();
612541
}
613542

614543
/**

src/www/util.js

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,14 @@ var WebView = {
55
if (!url) {
66
return url;
77
}
8-
if (!url.startsWith('file://')) {
9-
return url;
10-
}
11-
if (window.WEBVIEW_SERVER_URL.startsWith('ionic://')) {
12-
return url.replace('file', 'ionic-asset');
8+
if (url.startsWith('file://')) {
9+
return url.replace('file', window.WEBVIEW_FILE_PREFIX);
1310
}
14-
url = url.substr(7); // len("file://") == 7
15-
if (url.length === 0 || url[0] !== '/') { // ensure the new URL starts with /
16-
url = '/' + url;
11+
if (url.startsWith('content://')) {
12+
return url.replace('content://', window.WEBVIEW_CONTENT_PREFIX + ':///');
1713
}
18-
return window.WEBVIEW_SERVER_URL + '/_file_' + url;
14+
15+
return url;
1916
},
2017
setServerBasePath: function(path) {
2118
exec(null, null, 'IonicWebView', 'setServerBasePath', [path]);

0 commit comments

Comments
 (0)