Initial Commit on git.saherelm.ir ...
@@ -0,0 +1,17 @@
|
|||||||
|
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
||||||
|
# For additional information regarding the format and rule options, please see:
|
||||||
|
# https://github.com/browserslist/browserslist#queries
|
||||||
|
|
||||||
|
# For the full list of supported browsers by the Angular framework, please see:
|
||||||
|
# https://angular.io/guide/browser-support
|
||||||
|
|
||||||
|
# You can see what browsers were selected by your queries by running:
|
||||||
|
# npx browserslist
|
||||||
|
|
||||||
|
last 1 Chrome version
|
||||||
|
last 1 Firefox version
|
||||||
|
last 2 Edge major versions
|
||||||
|
last 2 Safari major versions
|
||||||
|
last 2 iOS major versions
|
||||||
|
Firefox ESR
|
||||||
|
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Editor configuration, see https://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.ts]
|
||||||
|
quote_type = single
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
max_line_length = off
|
||||||
|
trim_trailing_whitespace = false
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# compiled output
|
||||||
|
/dist
|
||||||
|
/www
|
||||||
|
/tmp
|
||||||
|
/out-tsc
|
||||||
|
# Only exists if Bazel was run
|
||||||
|
/bazel-out
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# profiling files
|
||||||
|
chrome-profiler-events*.json
|
||||||
|
speed-measure-plugin*.json
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
/.idea
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# IDE - VSCode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.history/*
|
||||||
|
|
||||||
|
# misc
|
||||||
|
/.sass-cache
|
||||||
|
/connect.lock
|
||||||
|
/coverage
|
||||||
|
/libpeerconnection.log
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
testem.log
|
||||||
|
/typings
|
||||||
|
|
||||||
|
# System Files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Generated by xFramework
|
||||||
|
/pack
|
||||||
|
package-lock.json
|
||||||
|
/platforms/ios/www
|
||||||
|
/platforms/electron/www
|
||||||
|
/platforms/electron/node_modules
|
||||||
|
/platforms/android/app/src/main/assets/www
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# xFrameworkTest
|
||||||
|
|
||||||
|
a simple client web application based on xFramework which provides all informations about implementation of xFramework tools and components ...
|
||||||
|
|
||||||
|
## Maintainer
|
||||||
|
|
||||||
|
Hadi Khazaee asl
|
||||||
|
|
||||||
|
[https://www.saherelm.ir](https://www.saherelm.ir)
|
||||||
|
|
||||||
|
[hadi_khazaee_asl@yahoo.com](mailto:hadi_khazaee_asl@yahoo.com)
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"app": {
|
||||||
|
"projectType": "application",
|
||||||
|
"schematics": {
|
||||||
|
"@schematics/angular:application": {
|
||||||
|
"strict": true
|
||||||
|
},
|
||||||
|
"@schematics/angular:component": {
|
||||||
|
"style": "scss"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"prefix": "app",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular-devkit/build-angular:browser",
|
||||||
|
"options": {
|
||||||
|
"outputPath": "www",
|
||||||
|
"index": "src/index.html",
|
||||||
|
"main": "src/main.ts",
|
||||||
|
"polyfills": "src/polyfills.ts",
|
||||||
|
"tsConfig": "tsconfig.app.json",
|
||||||
|
"aot": true,
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"glob": "**/*",
|
||||||
|
"input": "src/assets",
|
||||||
|
"output": "assets"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"glob": "**/*.svg",
|
||||||
|
"input": "node_modules/x-framework-components/assets/icon/svg",
|
||||||
|
"output": "./svg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"glob": "**/*.svg",
|
||||||
|
"input": "node_modules/ionicons/dist/ionicons/svg",
|
||||||
|
"output": "./svg"
|
||||||
|
},
|
||||||
|
"src/manifest.webmanifest"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"src/theme/theme.scss"
|
||||||
|
],
|
||||||
|
"scripts": [],
|
||||||
|
"allowedCommonJsDependencies": [
|
||||||
|
"lodash"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/environments/environment.ts",
|
||||||
|
"with": "src/environments/environment.prod.ts"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"optimization": true,
|
||||||
|
"outputHashing": "all",
|
||||||
|
"sourceMap": false,
|
||||||
|
"extractCss": true,
|
||||||
|
"namedChunks": false,
|
||||||
|
"extractLicenses": true,
|
||||||
|
"vendorChunk": false,
|
||||||
|
"buildOptimizer": true,
|
||||||
|
"budgets": [
|
||||||
|
{
|
||||||
|
"type": "initial",
|
||||||
|
"maximumWarning": "2mb",
|
||||||
|
"maximumError": "15mb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "anyComponentStyle",
|
||||||
|
"maximumWarning": "6kb",
|
||||||
|
"maximumError": "10kb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"serviceWorker": true,
|
||||||
|
"ngswConfigPath": "ngsw-config.json"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"buildOptimizer": false,
|
||||||
|
"optimization": false,
|
||||||
|
"vendorChunk": true,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"sourceMap": true,
|
||||||
|
"namedChunks": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "development"
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
|
"options": {
|
||||||
|
"browserTarget": "app:build"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"browserTarget": "app:build:production"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"browserTarget": "app:build:development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "development"
|
||||||
|
},
|
||||||
|
"ionic-cordova-build": {
|
||||||
|
"builder": "@ionic/angular-toolkit:cordova-build",
|
||||||
|
"options": {
|
||||||
|
"browserTarget": "app:build"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"browserTarget": "app:build:production"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ionic-cordova-serve": {
|
||||||
|
"builder": "@ionic/angular-toolkit:cordova-serve",
|
||||||
|
"options": {
|
||||||
|
"cordovaBuildTarget": "app:ionic-cordova-build",
|
||||||
|
"devServerTarget": "app:serve"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"cordovaBuildTarget": "app:ionic-cordova-build:production",
|
||||||
|
"devServerTarget": "app:serve:production"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultProject": "app",
|
||||||
|
"cli": {
|
||||||
|
"analytics": false,
|
||||||
|
"defaultCollection": "@ionic/angular-toolkit"
|
||||||
|
},
|
||||||
|
"schematics": {
|
||||||
|
"@ionic/angular-toolkit:component": {
|
||||||
|
"styleext": "scss"
|
||||||
|
},
|
||||||
|
"@ionic/angular-toolkit:page": {
|
||||||
|
"styleext": "scss"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<widget id="ir.xFramework.xFrameworkTest" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
||||||
|
<name>xFrameworkTest</name>
|
||||||
|
<description>test all xFramework capabilities</description>
|
||||||
|
<author email="hadi_khazaee_asl@yahoo.com" href="https://x-framework.ir">Hadi Khazaee Asl</author>
|
||||||
|
<content src="index.html" />
|
||||||
|
<access origin="*" />
|
||||||
|
<allow-intent href="http://*/*" />
|
||||||
|
<allow-intent href="https://*/*" />
|
||||||
|
<allow-intent href="tel:*" />
|
||||||
|
<allow-intent href="sms:*" />
|
||||||
|
<allow-intent href="mailto:*" />
|
||||||
|
<allow-intent href="geo:*" />
|
||||||
|
<preference name="ScrollEnabled" value="false" />
|
||||||
|
<preference name="BackupWebStorage" value="none" />
|
||||||
|
<preference name="SplashMaintainAspectRatio" value="true" />
|
||||||
|
<preference name="FadeSplashScreenDuration" value="300" />
|
||||||
|
<preference name="SplashShowOnlyFirstTime" value="false" />
|
||||||
|
<preference name="SplashScreen" value="screen" />
|
||||||
|
<preference name="SplashScreenDelay" value="3000" />
|
||||||
|
<platform name="android">
|
||||||
|
<edit-config file="app/src/main/AndroidManifest.xml" mode="merge" target="/manifest/application" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<application android:networkSecurityConfig="@xml/network_security_config" />
|
||||||
|
</edit-config>
|
||||||
|
<resource-file src="resources/android/xml/network_security_config.xml" target="app/src/main/res/xml/network_security_config.xml" />
|
||||||
|
<allow-intent href="market:*" />
|
||||||
|
<icon density="ldpi" src="resources/android/icon/drawable-ldpi-icon.png" />
|
||||||
|
<icon density="mdpi" src="resources/android/icon/drawable-mdpi-icon.png" />
|
||||||
|
<icon density="hdpi" src="resources/android/icon/drawable-hdpi-icon.png" />
|
||||||
|
<icon density="xhdpi" src="resources/android/icon/drawable-xhdpi-icon.png" />
|
||||||
|
<icon density="xxhdpi" src="resources/android/icon/drawable-xxhdpi-icon.png" />
|
||||||
|
<icon density="xxxhdpi" src="resources/android/icon/drawable-xxxhdpi-icon.png" />
|
||||||
|
<splash density="land-ldpi" src="resources/android/splash/drawable-land-ldpi-screen.png" />
|
||||||
|
<splash density="land-mdpi" src="resources/android/splash/drawable-land-mdpi-screen.png" />
|
||||||
|
<splash density="land-hdpi" src="resources/android/splash/drawable-land-hdpi-screen.png" />
|
||||||
|
<splash density="land-xhdpi" src="resources/android/splash/drawable-land-xhdpi-screen.png" />
|
||||||
|
<splash density="land-xxhdpi" src="resources/android/splash/drawable-land-xxhdpi-screen.png" />
|
||||||
|
<splash density="land-xxxhdpi" src="resources/android/splash/drawable-land-xxxhdpi-screen.png" />
|
||||||
|
<splash density="port-ldpi" src="resources/android/splash/drawable-port-ldpi-screen.png" />
|
||||||
|
<splash density="port-mdpi" src="resources/android/splash/drawable-port-mdpi-screen.png" />
|
||||||
|
<splash density="port-hdpi" src="resources/android/splash/drawable-port-hdpi-screen.png" />
|
||||||
|
<splash density="port-xhdpi" src="resources/android/splash/drawable-port-xhdpi-screen.png" />
|
||||||
|
<splash density="port-xxhdpi" src="resources/android/splash/drawable-port-xxhdpi-screen.png" />
|
||||||
|
<splash density="port-xxxhdpi" src="resources/android/splash/drawable-port-xxxhdpi-screen.png" />
|
||||||
|
</platform>
|
||||||
|
<platform name="ios">
|
||||||
|
<allow-intent href="itms:*" />
|
||||||
|
<allow-intent href="itms-apps:*" />
|
||||||
|
<icon height="57" src="resources/ios/icon/icon.png" width="57" />
|
||||||
|
<icon height="114" src="resources/ios/icon/icon@2x.png" width="114" />
|
||||||
|
<icon height="29" src="resources/ios/icon/icon-small.png" width="29" />
|
||||||
|
<icon height="58" src="resources/ios/icon/icon-small@2x.png" width="58" />
|
||||||
|
<icon height="87" src="resources/ios/icon/icon-small@3x.png" width="87" />
|
||||||
|
<icon height="20" src="resources/ios/icon/icon-20.png" width="20" />
|
||||||
|
<icon height="40" src="resources/ios/icon/icon-20@2x.png" width="40" />
|
||||||
|
<icon height="60" src="resources/ios/icon/icon-20@3x.png" width="60" />
|
||||||
|
<icon height="48" src="resources/ios/icon/icon-24@2x.png" width="48" />
|
||||||
|
<icon height="55" src="resources/ios/icon/icon-27.5@2x.png" width="55" />
|
||||||
|
<icon height="29" src="resources/ios/icon/icon-29.png" width="29" />
|
||||||
|
<icon height="58" src="resources/ios/icon/icon-29@2x.png" width="58" />
|
||||||
|
<icon height="87" src="resources/ios/icon/icon-29@3x.png" width="87" />
|
||||||
|
<icon height="40" src="resources/ios/icon/icon-40.png" width="40" />
|
||||||
|
<icon height="80" src="resources/ios/icon/icon-40@2x.png" width="80" />
|
||||||
|
<icon height="120" src="resources/ios/icon/icon-40@3x.png" width="120" />
|
||||||
|
<icon height="88" src="resources/ios/icon/icon-44@2x.png" width="88" />
|
||||||
|
<icon height="50" src="resources/ios/icon/icon-50.png" width="50" />
|
||||||
|
<icon height="100" src="resources/ios/icon/icon-50@2x.png" width="100" />
|
||||||
|
<icon height="60" src="resources/ios/icon/icon-60.png" width="60" />
|
||||||
|
<icon height="120" src="resources/ios/icon/icon-60@2x.png" width="120" />
|
||||||
|
<icon height="180" src="resources/ios/icon/icon-60@3x.png" width="180" />
|
||||||
|
<icon height="72" src="resources/ios/icon/icon-72.png" width="72" />
|
||||||
|
<icon height="144" src="resources/ios/icon/icon-72@2x.png" width="144" />
|
||||||
|
<icon height="76" src="resources/ios/icon/icon-76.png" width="76" />
|
||||||
|
<icon height="152" src="resources/ios/icon/icon-76@2x.png" width="152" />
|
||||||
|
<icon height="167" src="resources/ios/icon/icon-83.5@2x.png" width="167" />
|
||||||
|
<icon height="172" src="resources/ios/icon/icon-86@2x.png" width="172" />
|
||||||
|
<icon height="196" src="resources/ios/icon/icon-98@2x.png" width="196" />
|
||||||
|
<icon height="1024" src="resources/ios/icon/icon-1024.png" width="1024" />
|
||||||
|
<splash height="480" src="resources/ios/splash/Default~iphone.png" width="320" />
|
||||||
|
<splash height="960" src="resources/ios/splash/Default@2x~iphone.png" width="640" />
|
||||||
|
<splash height="1024" src="resources/ios/splash/Default-Portrait~ipad.png" width="768" />
|
||||||
|
<splash height="768" src="resources/ios/splash/Default-Landscape~ipad.png" width="1024" />
|
||||||
|
<splash height="1125" src="resources/ios/splash/Default-Landscape-2436h.png" width="2436" />
|
||||||
|
<splash height="1242" src="resources/ios/splash/Default-Landscape-736h.png" width="2208" />
|
||||||
|
<splash height="2048" src="resources/ios/splash/Default-Portrait@2x~ipad.png" width="1536" />
|
||||||
|
<splash height="1536" src="resources/ios/splash/Default-Landscape@2x~ipad.png" width="2048" />
|
||||||
|
<splash height="2732" src="resources/ios/splash/Default-Portrait@~ipadpro.png" width="2048" />
|
||||||
|
<splash height="2048" src="resources/ios/splash/Default-Landscape@~ipadpro.png" width="2732" />
|
||||||
|
<splash height="1136" src="resources/ios/splash/Default-568h@2x~iphone.png" width="640" />
|
||||||
|
<splash height="1334" src="resources/ios/splash/Default-667h.png" width="750" />
|
||||||
|
<splash height="2208" src="resources/ios/splash/Default-736h.png" width="1242" />
|
||||||
|
<splash height="2436" src="resources/ios/splash/Default-2436h.png" width="1125" />
|
||||||
|
<splash height="2732" src="resources/ios/splash/Default@2x~universal~anyany.png" width="2732" />
|
||||||
|
<icon height="57" src="resources/ios/icon/icon.png" width="57" />
|
||||||
|
<icon height="114" src="resources/ios/icon/icon@2x.png" width="114" />
|
||||||
|
<icon height="20" src="resources/ios/icon/icon-20.png" width="20" />
|
||||||
|
<icon height="40" src="resources/ios/icon/icon-20@2x.png" width="40" />
|
||||||
|
<icon height="60" src="resources/ios/icon/icon-20@3x.png" width="60" />
|
||||||
|
<icon height="29" src="resources/ios/icon/icon-29.png" width="29" />
|
||||||
|
<icon height="58" src="resources/ios/icon/icon-29@2x.png" width="58" />
|
||||||
|
<icon height="87" src="resources/ios/icon/icon-29@3x.png" width="87" />
|
||||||
|
<icon height="48" src="resources/ios/icon/icon-24@2x.png" width="48" />
|
||||||
|
<icon height="55" src="resources/ios/icon/icon-27.5@2x.png" width="55" />
|
||||||
|
<icon height="88" src="resources/ios/icon/icon-44@2x.png" width="88" />
|
||||||
|
<icon height="172" src="resources/ios/icon/icon-86@2x.png" width="172" />
|
||||||
|
<icon height="196" src="resources/ios/icon/icon-98@2x.png" width="196" />
|
||||||
|
<icon height="216" src="resources/ios/icon/icon-108@2x.png" width="216" />
|
||||||
|
<icon height="40" src="resources/ios/icon/icon-40.png" width="40" />
|
||||||
|
<icon height="80" src="resources/ios/icon/icon-40@2x.png" width="80" />
|
||||||
|
<icon height="120" src="resources/ios/icon/icon-40@3x.png" width="120" />
|
||||||
|
<icon height="50" src="resources/ios/icon/icon-50.png" width="50" />
|
||||||
|
<icon height="100" src="resources/ios/icon/icon-50@2x.png" width="100" />
|
||||||
|
<icon height="60" src="resources/ios/icon/icon-60.png" width="60" />
|
||||||
|
<icon height="120" src="resources/ios/icon/icon-60@2x.png" width="120" />
|
||||||
|
<icon height="180" src="resources/ios/icon/icon-60@3x.png" width="180" />
|
||||||
|
<icon height="72" src="resources/ios/icon/icon-72.png" width="72" />
|
||||||
|
<icon height="144" src="resources/ios/icon/icon-72@2x.png" width="144" />
|
||||||
|
<icon height="76" src="resources/ios/icon/icon-76.png" width="76" />
|
||||||
|
<icon height="152" src="resources/ios/icon/icon-76@2x.png" width="152" />
|
||||||
|
<icon height="167" src="resources/ios/icon/icon-83.5@2x.png" width="167" />
|
||||||
|
<icon height="1024" src="resources/ios/icon/icon-1024.png" width="1024" />
|
||||||
|
<splash height="1136" src="resources/ios/splash/Default-568h@2x~iphone.png" width="640" />
|
||||||
|
<splash height="1334" src="resources/ios/splash/Default-667h.png" width="750" />
|
||||||
|
<splash height="2688" src="resources/ios/splash/Default-2688h~iphone.png" width="1242" />
|
||||||
|
<splash height="1242" src="resources/ios/splash/Default-Landscape-2688h~iphone.png" width="2688" />
|
||||||
|
<splash height="1792" src="resources/ios/splash/Default-1792h~iphone.png" width="828" />
|
||||||
|
<splash height="828" src="resources/ios/splash/Default-Landscape-1792h~iphone.png" width="1792" />
|
||||||
|
<splash height="2436" src="resources/ios/splash/Default-2436h.png" width="1125" />
|
||||||
|
<splash height="1125" src="resources/ios/splash/Default-Landscape-2436h.png" width="2436" />
|
||||||
|
<splash height="2208" src="resources/ios/splash/Default-736h.png" width="1242" />
|
||||||
|
<splash height="1242" src="resources/ios/splash/Default-Landscape-736h.png" width="2208" />
|
||||||
|
<splash height="1536" src="resources/ios/splash/Default-Landscape@2x~ipad.png" width="2048" />
|
||||||
|
<splash height="2048" src="resources/ios/splash/Default-Landscape@~ipadpro.png" width="2732" />
|
||||||
|
<splash height="768" src="resources/ios/splash/Default-Landscape~ipad.png" width="1024" />
|
||||||
|
<splash height="2048" src="resources/ios/splash/Default-Portrait@2x~ipad.png" width="1536" />
|
||||||
|
<splash height="2732" src="resources/ios/splash/Default-Portrait@~ipadpro.png" width="2048" />
|
||||||
|
<splash height="1024" src="resources/ios/splash/Default-Portrait~ipad.png" width="768" />
|
||||||
|
<splash height="960" src="resources/ios/splash/Default@2x~iphone.png" width="640" />
|
||||||
|
<splash height="480" src="resources/ios/splash/Default~iphone.png" width="320" />
|
||||||
|
<splash height="2732" src="resources/ios/splash/Default@2x~universal~anyany.png" width="2732" />
|
||||||
|
</platform>
|
||||||
|
<plugin name="cordova-plugin-whitelist" spec="1.3.3" />
|
||||||
|
<plugin name="cordova-plugin-statusbar" spec="2.4.2" />
|
||||||
|
<plugin name="cordova-plugin-device" spec="2.0.2" />
|
||||||
|
<plugin name="cordova-plugin-splashscreen" spec="5.0.2" />
|
||||||
|
<plugin name="cordova-plugin-ionic-webview" spec="^4.0.0" />
|
||||||
|
<plugin name="cordova-plugin-ionic-keyboard" spec="^2.0.5" />
|
||||||
|
</widget>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "x-framework-test",
|
||||||
|
"integrations": {
|
||||||
|
"cordova": {}
|
||||||
|
},
|
||||||
|
"type": "angular"
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// Karma configuration file, see link for more information
|
||||||
|
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||||
|
|
||||||
|
module.exports = function (config) {
|
||||||
|
config.set({
|
||||||
|
basePath: '',
|
||||||
|
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||||
|
plugins: [
|
||||||
|
require('karma-jasmine'),
|
||||||
|
require('karma-chrome-launcher'),
|
||||||
|
require('karma-jasmine-html-reporter'),
|
||||||
|
require('karma-coverage'),
|
||||||
|
require('@angular-devkit/build-angular/plugins/karma')
|
||||||
|
],
|
||||||
|
client: {
|
||||||
|
jasmine: {
|
||||||
|
// you can add configuration options for Jasmine here
|
||||||
|
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||||
|
// for example, you can disable the random execution with `random: false`
|
||||||
|
// or set a specific seed with `seed: 4321`
|
||||||
|
},
|
||||||
|
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||||
|
},
|
||||||
|
jasmineHtmlReporter: {
|
||||||
|
suppressAll: true // removes the duplicated traces
|
||||||
|
},
|
||||||
|
coverageReporter: {
|
||||||
|
dir: require('path').join(__dirname, './coverage/xFrameworkTest'),
|
||||||
|
subdir: '.',
|
||||||
|
reporters: [
|
||||||
|
{ type: 'html' },
|
||||||
|
{ type: 'text-summary' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
reporters: ['progress', 'kjhtml'],
|
||||||
|
port: 9876,
|
||||||
|
colors: true,
|
||||||
|
logLevel: config.LOG_INFO,
|
||||||
|
autoWatch: true,
|
||||||
|
browsers: ['Chrome'],
|
||||||
|
singleRun: false,
|
||||||
|
restartOnFileChange: true
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
|
||||||
|
"index": "/index.html",
|
||||||
|
"assetGroups": [
|
||||||
|
{
|
||||||
|
"name": "app",
|
||||||
|
"installMode": "prefetch",
|
||||||
|
"resources": {
|
||||||
|
"files": [
|
||||||
|
"/favicon.ico",
|
||||||
|
"/index.html",
|
||||||
|
"/manifest.webmanifest",
|
||||||
|
"/*.css",
|
||||||
|
"/*.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "assets",
|
||||||
|
"installMode": "lazy",
|
||||||
|
"updateMode": "prefetch",
|
||||||
|
"resources": {
|
||||||
|
"files": [
|
||||||
|
"/assets/**",
|
||||||
|
"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
{
|
||||||
|
"name": "x-framework-test",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "test all xFramework capabilities",
|
||||||
|
"homepage": "https://x-framework.ir",
|
||||||
|
"author": {
|
||||||
|
"name": "Hadi Khazaee Asl",
|
||||||
|
"email": "hadi_khazaee_asl@yahoo.com",
|
||||||
|
"url": "https://x-framework.ir"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"x-framework",
|
||||||
|
"saherelm"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"ng": "ng",
|
||||||
|
"runIos": "ionic cordova run ios",
|
||||||
|
"serveXFrameworkProject": "ng serve -o",
|
||||||
|
"build": "ng build --aot --base-href ./",
|
||||||
|
"start": "npm run serveXFrameworkProject",
|
||||||
|
"runAndroid": "ionic cordova run android",
|
||||||
|
"emulateIos": "ionic cordova emulate ios",
|
||||||
|
"resourcesIos": "ionic cordova resources ios",
|
||||||
|
"serveSSLXFrameworkProject": "ng serve -o --ssl",
|
||||||
|
"emulateAndroid": "ionic cordova emulate android",
|
||||||
|
"requirementsIos": "ionic cordova requirements ios",
|
||||||
|
"resourcesAndroid": "ionic cordova resources android",
|
||||||
|
"watch": "ng build --watch --configuration development",
|
||||||
|
"requirementsAndroid": "ionic cordova requirements android",
|
||||||
|
"runElectron": "cd platforms/electron && npm run --force runElectron",
|
||||||
|
"prepareElectron": "shx --silent cp -r ./www ./platforms/electron/www",
|
||||||
|
"buildProd": "ng build --configuration production --aot --base-href /",
|
||||||
|
"startElectron": "cd platforms/electron && npm run --force startElectron",
|
||||||
|
"runProdElectron": "cd platforms/electron && npm run --force runProdElectron",
|
||||||
|
"buildElectronWin32": "cd platforms/electron && npm run --force buildElectronWin32",
|
||||||
|
"buildElectronWin64": "cd platforms/electron && npm run --force buildElectronWin64",
|
||||||
|
"buildElectronMac64": "cd platforms/electron && npm run --force buildElectronMac64",
|
||||||
|
"serveInLabXFrameworkProject": "npm run --force prepareDependencies && ionic serve -l",
|
||||||
|
"buildElectronLinux64": "cd platforms/electron && npm run --force buildElectronLinux64",
|
||||||
|
"prepareAndServeXFrameworkProject": "npm run --force prepareDependencies && ng serve -o",
|
||||||
|
"prepareAndServeSSLXFrameworkProject": "npm run --force prepareDependencies && ng serve -o --ssl",
|
||||||
|
"prepareDependenciesFromRegistry": "npm i -D --registry https://npm.saherelm.ir x-framework-core x-framework-services x-framework-components",
|
||||||
|
"prepareDependencies": "npm i --force -D ../Documents/pack/x-framework-core-0.0.1.tgz ../Documents/pack/x-framework-services-0.0.1.tgz ../Documents/pack/x-framework-components-0.0.1.tgz"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/animations": "~12.1.3",
|
||||||
|
"@angular/common": "~12.1.3",
|
||||||
|
"@angular/compiler": "~12.1.3",
|
||||||
|
"@angular/core": "~12.1.3",
|
||||||
|
"@angular/forms": "~12.1.3",
|
||||||
|
"@angular/platform-browser": "~12.1.3",
|
||||||
|
"@angular/platform-browser-dynamic": "~12.1.3",
|
||||||
|
"@angular/router": "~12.1.3",
|
||||||
|
"@angular/service-worker": "~12.1.3",
|
||||||
|
"rxjs": "~7.2.0",
|
||||||
|
"tslib": "^2.3.0",
|
||||||
|
"zone.js": "~0.11.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular-devkit/build-angular": "~12.1.3",
|
||||||
|
"@angular/cdk": "^12.1.3",
|
||||||
|
"@angular/cli": "~12.1.3",
|
||||||
|
"@angular/compiler-cli": "~12.1.3",
|
||||||
|
"@angular/flex-layout": "^12.0.0-beta.34",
|
||||||
|
"@angular/material": "^12.1.3",
|
||||||
|
"@angular/material-moment-adapter": "^12.1.3",
|
||||||
|
"@compodoc/compodoc": "^1.1.14",
|
||||||
|
"@ionic/angular": "^5.6.12",
|
||||||
|
"@ionic/angular-toolkit": "^4.0.0",
|
||||||
|
"@ionic/lab": "3.2.10",
|
||||||
|
"@types/crypto-js": "^4.0.2",
|
||||||
|
"@types/dom-to-image": "^2.6.3",
|
||||||
|
"@types/howler": "^2.2.3",
|
||||||
|
"@types/jasmine": "~3.8.1",
|
||||||
|
"@types/node": "^16.4.1",
|
||||||
|
"@types/uuid": "^8.3.1",
|
||||||
|
"ace-builds": "^1.12.3",
|
||||||
|
"angular-gridster2": "^12.1.0",
|
||||||
|
"apexcharts": "^6.10.0",
|
||||||
|
"codelyzer": "^6.0.2",
|
||||||
|
"cordova-android": "^9.1.0",
|
||||||
|
"cordova-ios": "^6.2.0",
|
||||||
|
"cordova-plugin-device": "^2.0.2",
|
||||||
|
"cordova-plugin-ionic-keyboard": "^2.2.0",
|
||||||
|
"cordova-plugin-ionic-webview": "^4.2.1",
|
||||||
|
"cordova-plugin-splashscreen": "^5.0.2",
|
||||||
|
"cordova-plugin-statusbar": "^2.4.2",
|
||||||
|
"cordova-plugin-whitelist": "^1.3.3",
|
||||||
|
"cordova-res": "^0.15.3",
|
||||||
|
"crypto-js": "^4.1.1",
|
||||||
|
"dom-to-image": "^2.6.0",
|
||||||
|
"howler": "^2.2.3",
|
||||||
|
"ionicons": "^5.5.2",
|
||||||
|
"jalali-moment": "^3.3.10",
|
||||||
|
"jasmine-core": "~3.8.0",
|
||||||
|
"karma": "~6.3.4",
|
||||||
|
"karma-chrome-launcher": "~3.1.0",
|
||||||
|
"karma-coverage": "~2.0.3",
|
||||||
|
"karma-jasmine": "~4.0.1",
|
||||||
|
"karma-jasmine-html-reporter": "^1.7.0",
|
||||||
|
"libphonenumber-js": "^1.9.22",
|
||||||
|
"md5-typescript": "^1.0.5",
|
||||||
|
"moment": "^2.29.1",
|
||||||
|
"native-run": "^1.4.0",
|
||||||
|
"ng-packagr": "^12.1.2",
|
||||||
|
"ngx-device-detector": "^2.1.1",
|
||||||
|
"ngx-markdown": "^12.0.1",
|
||||||
|
"ol": "^6.6.1",
|
||||||
|
"shx": "^0.3.3",
|
||||||
|
"typescript": "~4.3.5",
|
||||||
|
"uuid": "^8.3.2",
|
||||||
|
"x-framework-components": "file:../Documents/pack/x-framework-components-0.0.1.tgz",
|
||||||
|
"x-framework-core": "file:../Documents/pack/x-framework-core-0.0.1.tgz",
|
||||||
|
"x-framework-services": "file:../Documents/pack/x-framework-services-0.0.1.tgz"
|
||||||
|
},
|
||||||
|
"cordova": {
|
||||||
|
"plugins": {
|
||||||
|
"cordova-plugin-whitelist": {},
|
||||||
|
"cordova-plugin-statusbar": {},
|
||||||
|
"cordova-plugin-device": {},
|
||||||
|
"cordova-plugin-splashscreen": {},
|
||||||
|
"cordova-plugin-ionic-webview": {},
|
||||||
|
"cordova-plugin-ionic-keyboard": {}
|
||||||
|
},
|
||||||
|
"platforms": [
|
||||||
|
"android",
|
||||||
|
"ios"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"allowScripts": {
|
||||||
|
"@angular/cli@12.1.4": true,
|
||||||
|
"@compodoc/compodoc@1.2.1": true,
|
||||||
|
"core-js@3.15.1": true,
|
||||||
|
"esbuild@0.13.8": true,
|
||||||
|
"sharp@0.29.3": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Non-project-specific build files:
|
||||||
|
build.xml
|
||||||
|
local.properties
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/gradle
|
||||||
|
# Ant builds
|
||||||
|
ant-build
|
||||||
|
ant-gen
|
||||||
|
# Eclipse builds
|
||||||
|
gen
|
||||||
|
out
|
||||||
|
# Gradle build artifacts
|
||||||
|
.gradle
|
||||||
|
.gradletasknamecache
|
||||||
|
/build
|
||||||
|
/CordovaLib/build
|
||||||
|
/app/build
|
||||||
|
gradle-app.setting
|
||||||
|
# Android Studio
|
||||||
|
.idea
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
-->
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="org.apache.cordova" android:versionName="1.0" android:versionCode="1">
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
/* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
ext {
|
||||||
|
apply from: 'cordova.gradle'
|
||||||
|
cdvCompileSdkVersion = privateHelpers.getProjectTarget()
|
||||||
|
cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools()
|
||||||
|
if (project.hasProperty('cdvMinSdkVersion') && cdvMinSdkVersion.isInteger()) {
|
||||||
|
cdvMinSdkVersion = cdvMinSdkVersion as int
|
||||||
|
println '[Cordova] cdvMinSdkVersion is overridden, try it at your own risk.'
|
||||||
|
} else {
|
||||||
|
cdvMinSdkVersion = 22; // current Cordova's default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
apply from: 'repositories.gradle'
|
||||||
|
repositories repos
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// The gradle plugin and the maven plugin have to be updated after each version of Android
|
||||||
|
// studio comes out
|
||||||
|
classpath 'com.android.tools.build:gradle:4.0.0'
|
||||||
|
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
|
||||||
|
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
apply from: 'repositories.gradle'
|
||||||
|
repositories repos
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'com.github.dcendents.android-maven'
|
||||||
|
apply plugin: 'com.jfrog.bintray'
|
||||||
|
|
||||||
|
group = 'org.apache.cordova'
|
||||||
|
version = '9.1.0'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion cdvCompileSdkVersion
|
||||||
|
buildToolsVersion cdvBuildToolsVersion
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
// For the Android Cordova Lib, we allow changing the minSdkVersion, but it is at the users own risk
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion cdvMinSdkVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
manifest.srcFile 'AndroidManifest.xml'
|
||||||
|
java.srcDirs = ['src']
|
||||||
|
resources.srcDirs = ['src']
|
||||||
|
aidl.srcDirs = ['src']
|
||||||
|
renderscript.srcDirs = ['src']
|
||||||
|
res.srcDirs = ['res']
|
||||||
|
assets.srcDirs = ['assets']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
exclude 'META-INF/LICENSE'
|
||||||
|
exclude 'META-INF/LICENSE.txt'
|
||||||
|
exclude 'META-INF/DEPENDENCIES'
|
||||||
|
exclude 'META-INF/NOTICE'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
install {
|
||||||
|
repositories.mavenInstaller {
|
||||||
|
pom {
|
||||||
|
project {
|
||||||
|
packaging 'aar'
|
||||||
|
name 'Cordova'
|
||||||
|
url 'https://cordova.apache.org'
|
||||||
|
licenses {
|
||||||
|
license {
|
||||||
|
name 'The Apache Software License, Version 2.0'
|
||||||
|
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id 'stevengill'
|
||||||
|
name 'Steve Gill'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scm {
|
||||||
|
connection 'scm:git:https://github.com/apache/cordova-android.git'
|
||||||
|
developerConnection 'scm:git:git@github.com:apache/cordova-android.git'
|
||||||
|
url 'https://github.com/apache/cordova-android'
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task sourcesJar(type: Jar) {
|
||||||
|
from android.sourceSets.main.java.srcDirs
|
||||||
|
classifier = 'sources'
|
||||||
|
}
|
||||||
|
|
||||||
|
artifacts {
|
||||||
|
archives sourcesJar
|
||||||
|
}
|
||||||
|
|
||||||
|
bintray {
|
||||||
|
user = System.getenv('BINTRAY_USER')
|
||||||
|
key = System.getenv('BINTRAY_KEY')
|
||||||
|
configurations = ['archives']
|
||||||
|
pkg {
|
||||||
|
repo = 'maven'
|
||||||
|
name = 'cordova-android'
|
||||||
|
userOrg = 'cordova'
|
||||||
|
licenses = ['Apache-2.0']
|
||||||
|
vcsUrl = 'https://github.com/apache/cordova-android'
|
||||||
|
websiteUrl = 'https://cordova.apache.org'
|
||||||
|
issueTrackerUrl = 'https://github.com/apache/cordova-android/issues'
|
||||||
|
publicDownloadNumbers = true
|
||||||
|
licenses = ['Apache-2.0']
|
||||||
|
labels = ['android', 'cordova', 'phonegap']
|
||||||
|
version {
|
||||||
|
name = '9.1.0'
|
||||||
|
released = new Date()
|
||||||
|
vcsTag = '9.1.0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
import groovy.swing.SwingBuilder
|
||||||
|
import com.g00fy2.versioncompare.Version
|
||||||
|
|
||||||
|
String doEnsureValueExists(filePath, props, key) {
|
||||||
|
if (props.get(key) == null) {
|
||||||
|
throw new GradleException(filePath + ': Missing key required "' + key + '"')
|
||||||
|
}
|
||||||
|
return props.get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
String doGetProjectTarget() {
|
||||||
|
def props = new Properties()
|
||||||
|
def propertiesFile = 'project.properties';
|
||||||
|
if(!(file(propertiesFile).exists())) {
|
||||||
|
propertiesFile = '../project.properties';
|
||||||
|
}
|
||||||
|
file(propertiesFile).withReader { reader ->
|
||||||
|
props.load(reader)
|
||||||
|
}
|
||||||
|
return doEnsureValueExists('project.properties', props, 'target')
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean isVersionValid(version) {
|
||||||
|
return !(new Version(version)).isEqual('0.0.0')
|
||||||
|
}
|
||||||
|
|
||||||
|
String doFindLatestInstalledBuildTools(String minBuildToolsVersionString) {
|
||||||
|
def buildToolsDirContents
|
||||||
|
try {
|
||||||
|
def buildToolsDir = new File(getAndroidSdkDir(), "build-tools")
|
||||||
|
buildToolsDirContents = buildToolsDir.list()
|
||||||
|
} catch (e) {
|
||||||
|
println "An exception occurred while trying to find the Android build tools."
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
|
||||||
|
def highestBuildToolsVersion = buildToolsDirContents
|
||||||
|
.collect { new Version(it) }
|
||||||
|
// Invalid inputs will be handled as 0.0.0
|
||||||
|
.findAll { it.isHigherThan('0.0.0') }
|
||||||
|
.max()
|
||||||
|
|
||||||
|
if (highestBuildToolsVersion == null) {
|
||||||
|
throw new RuntimeException("""
|
||||||
|
No installed build tools found. Install the Android build tools
|
||||||
|
version ${minBuildToolsVersionString} or higher.
|
||||||
|
""".replaceAll(/\s+/, ' ').trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (highestBuildToolsVersion.isLowerThan(minBuildToolsVersionString)) {
|
||||||
|
throw new RuntimeException("""
|
||||||
|
No usable Android build tools found. Highest installed version is
|
||||||
|
${highestBuildToolsVersion.getOriginalString()}; minimum version
|
||||||
|
required is ${minBuildToolsVersionString}.
|
||||||
|
""".replaceAll(/\s+/, ' ').trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
highestBuildToolsVersion.getOriginalString()
|
||||||
|
}
|
||||||
|
|
||||||
|
String getAndroidSdkDir() {
|
||||||
|
def rootDir = project.rootDir
|
||||||
|
def androidSdkDir = null
|
||||||
|
String envVar = System.getenv("ANDROID_SDK_ROOT")
|
||||||
|
if (envVar == null) {
|
||||||
|
envVar = System.getenv("ANDROID_HOME")
|
||||||
|
}
|
||||||
|
|
||||||
|
def localProperties = new File(rootDir, 'local.properties')
|
||||||
|
String systemProperty = System.getProperty("android.home")
|
||||||
|
if (envVar != null) {
|
||||||
|
androidSdkDir = envVar
|
||||||
|
} else if (localProperties.exists()) {
|
||||||
|
Properties properties = new Properties()
|
||||||
|
localProperties.withInputStream { instr ->
|
||||||
|
properties.load(instr)
|
||||||
|
}
|
||||||
|
def sdkDirProp = properties.getProperty('sdk.dir')
|
||||||
|
if (sdkDirProp != null) {
|
||||||
|
androidSdkDir = sdkDirProp
|
||||||
|
} else {
|
||||||
|
sdkDirProp = properties.getProperty('android.dir')
|
||||||
|
if (sdkDirProp != null) {
|
||||||
|
androidSdkDir = (new File(rootDir, sdkDirProp)).getAbsolutePath()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (androidSdkDir == null && systemProperty != null) {
|
||||||
|
androidSdkDir = systemProperty
|
||||||
|
}
|
||||||
|
if (androidSdkDir == null) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Unable to determine Android SDK directory.")
|
||||||
|
}
|
||||||
|
androidSdkDir
|
||||||
|
}
|
||||||
|
|
||||||
|
def doExtractIntFromManifest(name) {
|
||||||
|
def manifestFile = file(android.sourceSets.main.manifest.srcFile)
|
||||||
|
def pattern = Pattern.compile(name + "=\"(\\d+)\"")
|
||||||
|
def matcher = pattern.matcher(manifestFile.getText())
|
||||||
|
matcher.find()
|
||||||
|
return new BigInteger(matcher.group(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
def doExtractStringFromManifest(name) {
|
||||||
|
def manifestFile = file(android.sourceSets.main.manifest.srcFile)
|
||||||
|
def pattern = Pattern.compile(name + "=\"(\\S+)\"")
|
||||||
|
def matcher = pattern.matcher(manifestFile.getText())
|
||||||
|
matcher.find()
|
||||||
|
return matcher.group(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
def doPromptForPassword(msg) {
|
||||||
|
if (System.console() == null) {
|
||||||
|
def ret = null
|
||||||
|
new SwingBuilder().edt {
|
||||||
|
dialog(modal: true, title: 'Enter password', alwaysOnTop: true, resizable: false, locationRelativeTo: null, pack: true, show: true) {
|
||||||
|
vbox {
|
||||||
|
label(text: msg)
|
||||||
|
def input = passwordField()
|
||||||
|
button(defaultButton: true, text: 'OK', actionPerformed: {
|
||||||
|
ret = input.password;
|
||||||
|
dispose();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!ret) {
|
||||||
|
throw new GradleException('User canceled build')
|
||||||
|
}
|
||||||
|
return new String(ret)
|
||||||
|
} else {
|
||||||
|
return System.console().readPassword('\n' + msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def doGetConfigXml() {
|
||||||
|
def xml = file("src/main/res/xml/config.xml").getText()
|
||||||
|
// Disable namespace awareness since Cordova doesn't use them properly
|
||||||
|
return new XmlParser(false, false).parseText(xml)
|
||||||
|
}
|
||||||
|
|
||||||
|
def doGetConfigPreference(name, defaultValue) {
|
||||||
|
name = name.toLowerCase()
|
||||||
|
def root = doGetConfigXml()
|
||||||
|
|
||||||
|
def ret = defaultValue
|
||||||
|
root.preference.each { it ->
|
||||||
|
def attrName = it.attribute("name")
|
||||||
|
if (attrName && attrName.toLowerCase() == name) {
|
||||||
|
ret = it.attribute("value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Properties exported here are visible to all plugins.
|
||||||
|
ext {
|
||||||
|
// These helpers are shared, but are not guaranteed to be stable / unchanged.
|
||||||
|
privateHelpers = {}
|
||||||
|
privateHelpers.getProjectTarget = { doGetProjectTarget() }
|
||||||
|
privateHelpers.findLatestInstalledBuildTools = { doFindLatestInstalledBuildTools('19.1.0') }
|
||||||
|
privateHelpers.extractIntFromManifest = { name -> doExtractIntFromManifest(name) }
|
||||||
|
privateHelpers.extractStringFromManifest = { name -> doExtractStringFromManifest(name) }
|
||||||
|
privateHelpers.promptForPassword = { msg -> doPromptForPassword(msg) }
|
||||||
|
privateHelpers.ensureValueExists = { filePath, props, key -> doEnsureValueExists(filePath, props, key) }
|
||||||
|
|
||||||
|
// These helpers can be used by plugins / projects and will not change.
|
||||||
|
cdvHelpers = {}
|
||||||
|
// Returns a XmlParser for the config.xml. Added in 4.1.0.
|
||||||
|
cdvHelpers.getConfigXml = { doGetConfigXml() }
|
||||||
|
// Returns the value for the desired <preference>. Added in 4.1.0.
|
||||||
|
cdvHelpers.getConfigPreference = { name, defaultValue -> doGetConfigPreference(name, defaultValue) }
|
||||||
|
}
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.g00fy2:versioncompare:1.3.4@jar'
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# This file was originally created by the Android Tools, but is now
|
||||||
|
# used by cordova-android to manage the project configuration.
|
||||||
|
|
||||||
|
# Indicates whether an apk should be generated for each density.
|
||||||
|
split.density=false
|
||||||
|
|
||||||
|
# Project target.
|
||||||
|
target=android-29
|
||||||
|
apk-configurations=
|
||||||
|
renderscript.opt.level=O0
|
||||||
|
android.library=true
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
ext.repos = {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Class AuthenticationToken defines the userName and password to be used for authenticating a web resource
|
||||||
|
*/
|
||||||
|
public class AuthenticationToken {
|
||||||
|
private String userName;
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the user name.
|
||||||
|
*
|
||||||
|
* @return the user name
|
||||||
|
*/
|
||||||
|
public String getUserName() {
|
||||||
|
return userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the user name.
|
||||||
|
*
|
||||||
|
* @param userName
|
||||||
|
* the new user name
|
||||||
|
*/
|
||||||
|
public void setUserName(String userName) {
|
||||||
|
this.userName = userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the password.
|
||||||
|
*
|
||||||
|
* @return the password
|
||||||
|
*/
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the password.
|
||||||
|
*
|
||||||
|
* @param password
|
||||||
|
* the new password
|
||||||
|
*/
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is a utility class that allows us to get the BuildConfig variable, which is required
|
||||||
|
* for the use of different providers. This is not guaranteed to work, and it's better for this
|
||||||
|
* to be set in the build step in config.xml
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
|
||||||
|
public class BuildHelper {
|
||||||
|
|
||||||
|
|
||||||
|
private static String TAG="BuildHelper";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This needs to be implemented if you wish to use the Camera Plugin or other plugins
|
||||||
|
* that read the Build Configuration.
|
||||||
|
*
|
||||||
|
* Thanks to Phil@Medtronic and Graham Borland for finding the answer and posting it to
|
||||||
|
* StackOverflow. This is annoying as hell! However, this method does not work with
|
||||||
|
* ProGuard, and you should use the config.xml to define the application_id
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static Object getBuildConfigValue(Context ctx, String key)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class<?> clazz = Class.forName(ctx.getClass().getPackage().getName() + ".BuildConfig");
|
||||||
|
Field field = clazz.getField(key);
|
||||||
|
return field.get(null);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
LOG.d(TAG, "Unable to get the BuildConfig, is this built with ANT?");
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
LOG.d(TAG, key + " is not a valid field. Check your build.gradle");
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
LOG.d(TAG, "Illegal Access Exception: Let's print a stack trace.");
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
LOG.d(TAG, "Null Pointer Exception: Let's print a stack trace.");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
|
||||||
|
import org.apache.cordova.CordovaWebView;
|
||||||
|
import org.apache.cordova.PluginResult;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
public class CallbackContext {
|
||||||
|
private static final String LOG_TAG = "CordovaPlugin";
|
||||||
|
|
||||||
|
private String callbackId;
|
||||||
|
private CordovaWebView webView;
|
||||||
|
protected boolean finished;
|
||||||
|
private int changingThreads;
|
||||||
|
|
||||||
|
public CallbackContext(String callbackId, CordovaWebView webView) {
|
||||||
|
this.callbackId = callbackId;
|
||||||
|
this.webView = webView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFinished() {
|
||||||
|
return finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isChangingThreads() {
|
||||||
|
return changingThreads > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCallbackId() {
|
||||||
|
return callbackId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendPluginResult(PluginResult pluginResult) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (finished) {
|
||||||
|
LOG.w(LOG_TAG, "Attempted to send a second callback for ID: " + callbackId + "\nResult was: " + pluginResult.getMessage());
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
finished = !pluginResult.getKeepCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
webView.sendPluginResult(pluginResult, callbackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for success callbacks that just returns the Status.OK by default
|
||||||
|
*
|
||||||
|
* @param message The message to add to the success result.
|
||||||
|
*/
|
||||||
|
public void success(JSONObject message) {
|
||||||
|
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for success callbacks that just returns the Status.OK by default
|
||||||
|
*
|
||||||
|
* @param message The message to add to the success result.
|
||||||
|
*/
|
||||||
|
public void success(String message) {
|
||||||
|
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for success callbacks that just returns the Status.OK by default
|
||||||
|
*
|
||||||
|
* @param message The message to add to the success result.
|
||||||
|
*/
|
||||||
|
public void success(JSONArray message) {
|
||||||
|
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for success callbacks that just returns the Status.OK by default
|
||||||
|
*
|
||||||
|
* @param message The message to add to the success result.
|
||||||
|
*/
|
||||||
|
public void success(byte[] message) {
|
||||||
|
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for success callbacks that just returns the Status.OK by default
|
||||||
|
*
|
||||||
|
* @param message The message to add to the success result.
|
||||||
|
*/
|
||||||
|
public void success(int message) {
|
||||||
|
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for success callbacks that just returns the Status.OK by default
|
||||||
|
*/
|
||||||
|
public void success() {
|
||||||
|
sendPluginResult(new PluginResult(PluginResult.Status.OK));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for error callbacks that just returns the Status.ERROR by default
|
||||||
|
*
|
||||||
|
* @param message The message to add to the error result.
|
||||||
|
*/
|
||||||
|
public void error(JSONObject message) {
|
||||||
|
sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for error callbacks that just returns the Status.ERROR by default
|
||||||
|
*
|
||||||
|
* @param message The message to add to the error result.
|
||||||
|
*/
|
||||||
|
public void error(String message) {
|
||||||
|
sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for error callbacks that just returns the Status.ERROR by default
|
||||||
|
*
|
||||||
|
* @param message The message to add to the error result.
|
||||||
|
*/
|
||||||
|
public void error(int message) {
|
||||||
|
sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import android.util.Pair;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a collection that maps unique request codes to CordovaPlugins and Integers.
|
||||||
|
* Used to ensure that when plugins make requests for runtime permissions, those requests do not
|
||||||
|
* collide with requests from other plugins that use the same request code value.
|
||||||
|
*/
|
||||||
|
public class CallbackMap {
|
||||||
|
private int currentCallbackId = 0;
|
||||||
|
private SparseArray<Pair<CordovaPlugin, Integer>> callbacks;
|
||||||
|
|
||||||
|
public CallbackMap() {
|
||||||
|
this.callbacks = new SparseArray<Pair<CordovaPlugin, Integer>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a CordovaPlugin and request code and returns a new unique request code to use
|
||||||
|
* in a permission request.
|
||||||
|
*
|
||||||
|
* @param receiver The plugin that is making the request
|
||||||
|
* @param requestCode The original request code used by the plugin
|
||||||
|
* @return A unique request code that can be used to retrieve this callback
|
||||||
|
* with getAndRemoveCallback()
|
||||||
|
*/
|
||||||
|
public synchronized int registerCallback(CordovaPlugin receiver, int requestCode) {
|
||||||
|
int mappedId = this.currentCallbackId++;
|
||||||
|
callbacks.put(mappedId, new Pair<CordovaPlugin, Integer>(receiver, requestCode));
|
||||||
|
return mappedId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves and removes a callback stored in the map using the mapped request code
|
||||||
|
* obtained from registerCallback()
|
||||||
|
*
|
||||||
|
* @param mappedId The request code obtained from registerCallback()
|
||||||
|
* @return The CordovaPlugin and orignal request code that correspond to the
|
||||||
|
* given mappedCode
|
||||||
|
*/
|
||||||
|
public synchronized Pair<CordovaPlugin, Integer> getAndRemoveCallback(int mappedId) {
|
||||||
|
Pair<CordovaPlugin, Integer> callback = callbacks.get(mappedId);
|
||||||
|
callbacks.remove(mappedId);
|
||||||
|
return callback;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
|
||||||
|
@Deprecated // Use Whitelist, CordovaPrefences, etc. directly.
|
||||||
|
public class Config {
|
||||||
|
private static final String TAG = "Config";
|
||||||
|
|
||||||
|
static ConfigXmlParser parser;
|
||||||
|
|
||||||
|
private Config() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void init(Activity action) {
|
||||||
|
parser = new ConfigXmlParser();
|
||||||
|
parser.parse(action);
|
||||||
|
//TODO: Add feature to bring this back. Some preferences should be overridden by intents, but not all
|
||||||
|
parser.getPreferences().setPreferencesBundle(action.getIntent().getExtras());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intended to be used for testing only; creates an empty configuration.
|
||||||
|
public static void init() {
|
||||||
|
if (parser == null) {
|
||||||
|
parser = new ConfigXmlParser();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getStartUrl() {
|
||||||
|
if (parser == null) {
|
||||||
|
return "file:///android_asset/www/index.html";
|
||||||
|
}
|
||||||
|
return parser.getLaunchUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getErrorUrl() {
|
||||||
|
return parser.getPreferences().getString("errorurl", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<PluginEntry> getPluginEntries() {
|
||||||
|
return parser.getPluginEntries();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CordovaPreferences getPreferences() {
|
||||||
|
return parser.getPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isInitialized() {
|
||||||
|
return parser != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public class ConfigXmlParser {
|
||||||
|
private static String TAG = "ConfigXmlParser";
|
||||||
|
|
||||||
|
private String launchUrl = "file:///android_asset/www/index.html";
|
||||||
|
private CordovaPreferences prefs = new CordovaPreferences();
|
||||||
|
private ArrayList<PluginEntry> pluginEntries = new ArrayList<PluginEntry>(20);
|
||||||
|
|
||||||
|
public CordovaPreferences getPreferences() {
|
||||||
|
return prefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<PluginEntry> getPluginEntries() {
|
||||||
|
return pluginEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLaunchUrl() {
|
||||||
|
return launchUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void parse(Context action) {
|
||||||
|
// First checking the class namespace for config.xml
|
||||||
|
int id = action.getResources().getIdentifier("config", "xml", action.getClass().getPackage().getName());
|
||||||
|
if (id == 0) {
|
||||||
|
// If we couldn't find config.xml there, we'll look in the namespace from AndroidManifest.xml
|
||||||
|
id = action.getResources().getIdentifier("config", "xml", action.getPackageName());
|
||||||
|
if (id == 0) {
|
||||||
|
LOG.e(TAG, "res/xml/config.xml is missing!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parse(action.getResources().getXml(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean insideFeature = false;
|
||||||
|
String service = "", pluginClass = "", paramType = "";
|
||||||
|
boolean onload = false;
|
||||||
|
|
||||||
|
public void parse(XmlPullParser xml) {
|
||||||
|
int eventType = -1;
|
||||||
|
|
||||||
|
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||||
|
if (eventType == XmlPullParser.START_TAG) {
|
||||||
|
handleStartTag(xml);
|
||||||
|
}
|
||||||
|
else if (eventType == XmlPullParser.END_TAG)
|
||||||
|
{
|
||||||
|
handleEndTag(xml);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
eventType = xml.next();
|
||||||
|
} catch (XmlPullParserException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleStartTag(XmlPullParser xml) {
|
||||||
|
String strNode = xml.getName();
|
||||||
|
if (strNode.equals("feature")) {
|
||||||
|
//Check for supported feature sets aka. plugins (Accelerometer, Geolocation, etc)
|
||||||
|
//Set the bit for reading params
|
||||||
|
insideFeature = true;
|
||||||
|
service = xml.getAttributeValue(null, "name");
|
||||||
|
}
|
||||||
|
else if (insideFeature && strNode.equals("param")) {
|
||||||
|
paramType = xml.getAttributeValue(null, "name");
|
||||||
|
if (paramType.equals("service")) // check if it is using the older service param
|
||||||
|
service = xml.getAttributeValue(null, "value");
|
||||||
|
else if (paramType.equals("package") || paramType.equals("android-package"))
|
||||||
|
pluginClass = xml.getAttributeValue(null,"value");
|
||||||
|
else if (paramType.equals("onload"))
|
||||||
|
onload = "true".equals(xml.getAttributeValue(null, "value"));
|
||||||
|
}
|
||||||
|
else if (strNode.equals("preference")) {
|
||||||
|
String name = xml.getAttributeValue(null, "name").toLowerCase(Locale.ENGLISH);
|
||||||
|
String value = xml.getAttributeValue(null, "value");
|
||||||
|
prefs.set(name, value);
|
||||||
|
}
|
||||||
|
else if (strNode.equals("content")) {
|
||||||
|
String src = xml.getAttributeValue(null, "src");
|
||||||
|
if (src != null) {
|
||||||
|
setStartUrl(src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleEndTag(XmlPullParser xml) {
|
||||||
|
String strNode = xml.getName();
|
||||||
|
if (strNode.equals("feature")) {
|
||||||
|
pluginEntries.add(new PluginEntry(service, pluginClass, onload));
|
||||||
|
|
||||||
|
service = "";
|
||||||
|
pluginClass = "";
|
||||||
|
insideFeature = false;
|
||||||
|
onload = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setStartUrl(String src) {
|
||||||
|
Pattern schemeRegex = Pattern.compile("^[a-z-]+://");
|
||||||
|
Matcher matcher = schemeRegex.matcher(src);
|
||||||
|
if (matcher.find()) {
|
||||||
|
launchUrl = src;
|
||||||
|
} else {
|
||||||
|
if (src.charAt(0) == '/') {
|
||||||
|
src = src.substring(1);
|
||||||
|
}
|
||||||
|
launchUrl = "file:///android_asset/www/" + src;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,528 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.media.AudioManager;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.webkit.WebViewClient;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is the main Android activity that represents the Cordova
|
||||||
|
* application. It should be extended by the user to load the specific
|
||||||
|
* html file that contains the application.
|
||||||
|
*
|
||||||
|
* As an example:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* package org.apache.cordova.examples;
|
||||||
|
*
|
||||||
|
* import android.os.Bundle;
|
||||||
|
* import org.apache.cordova.*;
|
||||||
|
*
|
||||||
|
* public class Example extends CordovaActivity {
|
||||||
|
* @Override
|
||||||
|
* public void onCreate(Bundle savedInstanceState) {
|
||||||
|
* super.onCreate(savedInstanceState);
|
||||||
|
* super.init();
|
||||||
|
* // Load your application
|
||||||
|
* loadUrl(launchUrl);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* Cordova xml configuration: Cordova uses a configuration file at
|
||||||
|
* res/xml/config.xml to specify its settings. See "The config.xml File"
|
||||||
|
* guide in cordova-docs at http://cordova.apache.org/docs for the documentation
|
||||||
|
* for the configuration. The use of the set*Property() methods is
|
||||||
|
* deprecated in favor of the config.xml file.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class CordovaActivity extends Activity {
|
||||||
|
public static String TAG = "CordovaActivity";
|
||||||
|
|
||||||
|
// The webview for our app
|
||||||
|
protected CordovaWebView appView;
|
||||||
|
|
||||||
|
private static int ACTIVITY_STARTING = 0;
|
||||||
|
private static int ACTIVITY_RUNNING = 1;
|
||||||
|
private static int ACTIVITY_EXITING = 2;
|
||||||
|
|
||||||
|
// Keep app running when pause is received. (default = true)
|
||||||
|
// If true, then the JavaScript and native code continue to run in the background
|
||||||
|
// when another application (activity) is started.
|
||||||
|
protected boolean keepRunning = true;
|
||||||
|
|
||||||
|
// Flag to keep immersive mode if set to fullscreen
|
||||||
|
protected boolean immersiveMode;
|
||||||
|
|
||||||
|
// Read from config.xml:
|
||||||
|
protected CordovaPreferences preferences;
|
||||||
|
protected String launchUrl;
|
||||||
|
protected ArrayList<PluginEntry> pluginEntries;
|
||||||
|
protected CordovaInterfaceImpl cordovaInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the activity is first created.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
// need to activate preferences before super.onCreate to avoid "requestFeature() must be called before adding content" exception
|
||||||
|
loadConfig();
|
||||||
|
|
||||||
|
String logLevel = preferences.getString("loglevel", "ERROR");
|
||||||
|
LOG.setLogLevel(logLevel);
|
||||||
|
|
||||||
|
LOG.i(TAG, "Apache Cordova native platform version " + CordovaWebView.CORDOVA_VERSION + " is starting");
|
||||||
|
LOG.d(TAG, "CordovaActivity.onCreate()");
|
||||||
|
|
||||||
|
if (!preferences.getBoolean("ShowTitle", false)) {
|
||||||
|
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferences.getBoolean("SetFullscreen", false)) {
|
||||||
|
LOG.d(TAG, "The SetFullscreen configuration is deprecated in favor of Fullscreen, and will be removed in a future version.");
|
||||||
|
preferences.set("Fullscreen", true);
|
||||||
|
}
|
||||||
|
if (preferences.getBoolean("Fullscreen", false)) {
|
||||||
|
// NOTE: use the FullscreenNotImmersive configuration key to set the activity in a REAL full screen
|
||||||
|
// (as was the case in previous cordova versions)
|
||||||
|
if (!preferences.getBoolean("FullscreenNotImmersive", false)) {
|
||||||
|
immersiveMode = true;
|
||||||
|
// The splashscreen plugin needs the flags set before we're focused to prevent
|
||||||
|
// the nav and title bars from flashing in.
|
||||||
|
setImmersiveUiVisibility();
|
||||||
|
} else {
|
||||||
|
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||||
|
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN,
|
||||||
|
WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
cordovaInterface = makeCordovaInterface();
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
cordovaInterface.restoreInstanceState(savedInstanceState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void init() {
|
||||||
|
appView = makeWebView();
|
||||||
|
createViews();
|
||||||
|
if (!appView.isInitialized()) {
|
||||||
|
appView.init(cordovaInterface, pluginEntries, preferences);
|
||||||
|
}
|
||||||
|
cordovaInterface.onCordovaInit(appView.getPluginManager());
|
||||||
|
|
||||||
|
// Wire the hardware volume controls to control media if desired.
|
||||||
|
String volumePref = preferences.getString("DefaultVolumeStream", "");
|
||||||
|
if ("media".equals(volumePref.toLowerCase(Locale.ENGLISH))) {
|
||||||
|
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
protected void loadConfig() {
|
||||||
|
ConfigXmlParser parser = new ConfigXmlParser();
|
||||||
|
parser.parse(this);
|
||||||
|
preferences = parser.getPreferences();
|
||||||
|
preferences.setPreferencesBundle(getIntent().getExtras());
|
||||||
|
launchUrl = parser.getLaunchUrl();
|
||||||
|
pluginEntries = parser.getPluginEntries();
|
||||||
|
Config.parser = parser;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Suppressing warnings in AndroidStudio
|
||||||
|
@SuppressWarnings({"deprecation", "ResourceType"})
|
||||||
|
protected void createViews() {
|
||||||
|
//Why are we setting a constant as the ID? This should be investigated
|
||||||
|
appView.getView().setId(100);
|
||||||
|
appView.getView().setLayoutParams(new FrameLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
|
|
||||||
|
setContentView(appView.getView());
|
||||||
|
|
||||||
|
if (preferences.contains("BackgroundColor")) {
|
||||||
|
try {
|
||||||
|
int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK);
|
||||||
|
// Background of activity:
|
||||||
|
appView.getView().setBackgroundColor(backgroundColor);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e){
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
appView.getView().requestFocusFromTouch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct the default web view object.
|
||||||
|
* <p/>
|
||||||
|
* Override this to customize the webview that is used.
|
||||||
|
*/
|
||||||
|
protected CordovaWebView makeWebView() {
|
||||||
|
return new CordovaWebViewImpl(makeWebViewEngine());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CordovaWebViewEngine makeWebViewEngine() {
|
||||||
|
return CordovaWebViewImpl.createEngine(this, preferences);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CordovaInterfaceImpl makeCordovaInterface() {
|
||||||
|
return new CordovaInterfaceImpl(this) {
|
||||||
|
@Override
|
||||||
|
public Object onMessage(String id, Object data) {
|
||||||
|
// Plumb this to CordovaActivity.onMessage for backwards compatibility
|
||||||
|
return CordovaActivity.this.onMessage(id, data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the url into the webview.
|
||||||
|
*/
|
||||||
|
public void loadUrl(String url) {
|
||||||
|
if (appView == null) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If keepRunning
|
||||||
|
this.keepRunning = preferences.getBoolean("KeepRunning", true);
|
||||||
|
|
||||||
|
appView.loadUrlIntoView(url, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the system is about to start resuming a previous activity.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
LOG.d(TAG, "Paused the activity.");
|
||||||
|
|
||||||
|
if (this.appView != null) {
|
||||||
|
// CB-9382 If there is an activity that started for result and main activity is waiting for callback
|
||||||
|
// result, we shoudn't stop WebView Javascript timers, as activity for result might be using them
|
||||||
|
boolean keepRunning = this.keepRunning || this.cordovaInterface.activityResultCallback != null;
|
||||||
|
this.appView.handlePause(keepRunning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the activity receives a new intent
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void onNewIntent(Intent intent) {
|
||||||
|
super.onNewIntent(intent);
|
||||||
|
//Forward to plugins
|
||||||
|
if (this.appView != null)
|
||||||
|
this.appView.onNewIntent(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the activity will start interacting with the user.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
LOG.d(TAG, "Resumed the activity.");
|
||||||
|
|
||||||
|
if (this.appView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (! this.getWindow().getDecorView().hasFocus()) {
|
||||||
|
// Force window to have focus, so application always
|
||||||
|
// receive user input. Workaround for some devices (Samsung Galaxy Note 3 at least)
|
||||||
|
this.getWindow().getDecorView().requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.appView.handleResume(this.keepRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the activity is no longer visible to the user.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
LOG.d(TAG, "Stopped the activity.");
|
||||||
|
|
||||||
|
if (this.appView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.appView.handleStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the activity is becoming visible to the user.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
LOG.d(TAG, "Started the activity.");
|
||||||
|
|
||||||
|
if (this.appView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.appView.handleStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The final call you receive before your activity is destroyed.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
LOG.d(TAG, "CordovaActivity.onDestroy()");
|
||||||
|
super.onDestroy();
|
||||||
|
|
||||||
|
if (this.appView != null) {
|
||||||
|
appView.handleDestroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when view focus is changed
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onWindowFocusChanged(boolean hasFocus) {
|
||||||
|
super.onWindowFocusChanged(hasFocus);
|
||||||
|
if (hasFocus && immersiveMode) {
|
||||||
|
setImmersiveUiVisibility();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
protected void setImmersiveUiVisibility() {
|
||||||
|
final int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
|
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
|
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
|
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||||
|
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||||
|
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||||
|
|
||||||
|
getWindow().getDecorView().setSystemUiVisibility(uiOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
@Override
|
||||||
|
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
|
||||||
|
// Capture requestCode here so that it is captured in the setActivityResultCallback() case.
|
||||||
|
cordovaInterface.setActivityResultRequestCode(requestCode);
|
||||||
|
super.startActivityForResult(intent, requestCode, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an activity you launched exits, giving you the requestCode you started it with,
|
||||||
|
* the resultCode it returned, and any additional data from it.
|
||||||
|
*
|
||||||
|
* @param requestCode The request code originally supplied to startActivityForResult(),
|
||||||
|
* allowing you to identify who this result came from.
|
||||||
|
* @param resultCode The integer result code returned by the child activity through its setResult().
|
||||||
|
* @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||||
|
LOG.d(TAG, "Incoming Result. Request code = " + requestCode);
|
||||||
|
super.onActivityResult(requestCode, resultCode, intent);
|
||||||
|
cordovaInterface.onActivityResult(requestCode, resultCode, intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable).
|
||||||
|
* The errorCode parameter corresponds to one of the ERROR_* constants.
|
||||||
|
*
|
||||||
|
* @param errorCode The error code corresponding to an ERROR_* value.
|
||||||
|
* @param description A String describing the error.
|
||||||
|
* @param failingUrl The url that failed to load.
|
||||||
|
*/
|
||||||
|
public void onReceivedError(final int errorCode, final String description, final String failingUrl) {
|
||||||
|
final CordovaActivity me = this;
|
||||||
|
|
||||||
|
// If errorUrl specified, then load it
|
||||||
|
final String errorUrl = preferences.getString("errorUrl", null);
|
||||||
|
if ((errorUrl != null) && (!failingUrl.equals(errorUrl)) && (appView != null)) {
|
||||||
|
// Load URL on UI thread
|
||||||
|
me.runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
me.appView.showWebPage(errorUrl, false, true, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// If not, then display error dialog
|
||||||
|
else {
|
||||||
|
final boolean exit = !(errorCode == WebViewClient.ERROR_HOST_LOOKUP);
|
||||||
|
me.runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (exit) {
|
||||||
|
me.appView.getView().setVisibility(View.GONE);
|
||||||
|
me.displayError("Application Error", description + " (" + failingUrl + ")", "OK", exit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display an error dialog and optionally exit application.
|
||||||
|
*/
|
||||||
|
public void displayError(final String title, final String message, final String button, final boolean exit) {
|
||||||
|
final CordovaActivity me = this;
|
||||||
|
me.runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
AlertDialog.Builder dlg = new AlertDialog.Builder(me);
|
||||||
|
dlg.setMessage(message);
|
||||||
|
dlg.setTitle(title);
|
||||||
|
dlg.setCancelable(false);
|
||||||
|
dlg.setPositiveButton(button,
|
||||||
|
new AlertDialog.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
if (exit) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dlg.create();
|
||||||
|
dlg.show();
|
||||||
|
} catch (Exception e) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hook in Cordova for menu plugins
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
if (appView != null) {
|
||||||
|
appView.getPluginManager().postMessage("onCreateOptionsMenu", menu);
|
||||||
|
}
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
|
if (appView != null) {
|
||||||
|
appView.getPluginManager().postMessage("onPrepareOptionsMenu", menu);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (appView != null) {
|
||||||
|
appView.getPluginManager().postMessage("onOptionsItemSelected", item);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a message is sent to plugin.
|
||||||
|
*
|
||||||
|
* @param id The message id
|
||||||
|
* @param data The message data
|
||||||
|
* @return Object or null
|
||||||
|
*/
|
||||||
|
public Object onMessage(String id, Object data) {
|
||||||
|
if ("onReceivedError".equals(id)) {
|
||||||
|
JSONObject d = (JSONObject) data;
|
||||||
|
try {
|
||||||
|
this.onReceivedError(d.getInt("errorCode"), d.getString("description"), d.getString("url"));
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
} else if ("exit".equals(id)) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onSaveInstanceState(Bundle outState) {
|
||||||
|
cordovaInterface.onSaveInstanceState(outState);
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the system when the device configuration changes while your activity is running.
|
||||||
|
*
|
||||||
|
* @param newConfig The new device configuration
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onConfigurationChanged(Configuration newConfig) {
|
||||||
|
super.onConfigurationChanged(newConfig);
|
||||||
|
if (this.appView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PluginManager pm = this.appView.getPluginManager();
|
||||||
|
if (pm != null) {
|
||||||
|
pm.onConfigurationChanged(newConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the system when the user grants permissions
|
||||||
|
*
|
||||||
|
* @param requestCode
|
||||||
|
* @param permissions
|
||||||
|
* @param grantResults
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, String permissions[],
|
||||||
|
int[] grantResults) {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
cordovaInterface.onRequestPermissionResult(requestCode, permissions, grantResults);
|
||||||
|
}
|
||||||
|
catch (JSONException e)
|
||||||
|
{
|
||||||
|
LOG.d(TAG, "JSONException: Parameters fed into the method are not valid");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
|
public class CordovaArgs {
|
||||||
|
private JSONArray baseArgs;
|
||||||
|
|
||||||
|
public CordovaArgs(JSONArray args) {
|
||||||
|
this.baseArgs = args;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Pass through the basics to the base args.
|
||||||
|
public Object get(int index) throws JSONException {
|
||||||
|
return baseArgs.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getBoolean(int index) throws JSONException {
|
||||||
|
return baseArgs.getBoolean(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getDouble(int index) throws JSONException {
|
||||||
|
return baseArgs.getDouble(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInt(int index) throws JSONException {
|
||||||
|
return baseArgs.getInt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSONArray getJSONArray(int index) throws JSONException {
|
||||||
|
return baseArgs.getJSONArray(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSONObject getJSONObject(int index) throws JSONException {
|
||||||
|
return baseArgs.getJSONObject(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLong(int index) throws JSONException {
|
||||||
|
return baseArgs.getLong(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString(int index) throws JSONException {
|
||||||
|
return baseArgs.getString(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Object opt(int index) {
|
||||||
|
return baseArgs.opt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean optBoolean(int index) {
|
||||||
|
return baseArgs.optBoolean(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double optDouble(int index) {
|
||||||
|
return baseArgs.optDouble(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int optInt(int index) {
|
||||||
|
return baseArgs.optInt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSONArray optJSONArray(int index) {
|
||||||
|
return baseArgs.optJSONArray(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSONObject optJSONObject(int index) {
|
||||||
|
return baseArgs.optJSONObject(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long optLong(int index) {
|
||||||
|
return baseArgs.optLong(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String optString(int index) {
|
||||||
|
return baseArgs.optString(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNull(int index) {
|
||||||
|
return baseArgs.isNull(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The interesting custom helpers.
|
||||||
|
public byte[] getArrayBuffer(int index) throws JSONException {
|
||||||
|
String encoded = baseArgs.getString(index);
|
||||||
|
return Base64.decode(encoded, Base64.DEFAULT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains APIs that the JS can call. All functions in here should also have
|
||||||
|
* an equivalent entry in CordovaChromeClient.java, and be added to
|
||||||
|
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
|
||||||
|
*/
|
||||||
|
public class CordovaBridge {
|
||||||
|
private static final String LOG_TAG = "CordovaBridge";
|
||||||
|
private PluginManager pluginManager;
|
||||||
|
private NativeToJsMessageQueue jsMessageQueue;
|
||||||
|
private volatile int expectedBridgeSecret = -1; // written by UI thread, read by JS thread.
|
||||||
|
|
||||||
|
public CordovaBridge(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) {
|
||||||
|
this.pluginManager = pluginManager;
|
||||||
|
this.jsMessageQueue = jsMessageQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String jsExec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
|
||||||
|
if (!verifySecret("exec()", bridgeSecret)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// If the arguments weren't received, send a message back to JS. It will switch bridge modes and try again. See CB-2666.
|
||||||
|
// We send a message meant specifically for this case. It starts with "@" so no other message can be encoded into the same string.
|
||||||
|
if (arguments == null) {
|
||||||
|
return "@Null arguments.";
|
||||||
|
}
|
||||||
|
|
||||||
|
jsMessageQueue.setPaused(true);
|
||||||
|
try {
|
||||||
|
// Tell the resourceApi what thread the JS is running on.
|
||||||
|
CordovaResourceApi.jsThread = Thread.currentThread();
|
||||||
|
|
||||||
|
pluginManager.exec(service, action, callbackId, arguments);
|
||||||
|
String ret = null;
|
||||||
|
if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING) {
|
||||||
|
ret = jsMessageQueue.popAndEncode(false);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return "";
|
||||||
|
} finally {
|
||||||
|
jsMessageQueue.setPaused(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void jsSetNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
|
||||||
|
if (!verifySecret("setNativeToJsBridgeMode()", bridgeSecret)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
jsMessageQueue.setBridgeMode(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String jsRetrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
|
||||||
|
if (!verifySecret("retrieveJsMessages()", bridgeSecret)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return jsMessageQueue.popAndEncode(fromOnlineEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean verifySecret(String action, int bridgeSecret) throws IllegalAccessException {
|
||||||
|
if (!jsMessageQueue.isBridgeEnabled()) {
|
||||||
|
if (bridgeSecret == -1) {
|
||||||
|
LOG.d(LOG_TAG, action + " call made before bridge was enabled.");
|
||||||
|
} else {
|
||||||
|
LOG.d(LOG_TAG, "Ignoring " + action + " from previous page load.");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Bridge secret wrong and bridge not due to it being from the previous page.
|
||||||
|
if (expectedBridgeSecret < 0 || bridgeSecret != expectedBridgeSecret) {
|
||||||
|
LOG.e(LOG_TAG, "Bridge access attempt with wrong secret token, possibly from malicious code. Disabling exec() bridge!");
|
||||||
|
clearBridgeSecret();
|
||||||
|
throw new IllegalAccessException();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called on page transitions */
|
||||||
|
void clearBridgeSecret() {
|
||||||
|
expectedBridgeSecret = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSecretEstablished() {
|
||||||
|
return expectedBridgeSecret != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called by cordova.js to initialize the bridge. */
|
||||||
|
//On old Androids SecureRandom isn't really secure, this is the least of your problems if
|
||||||
|
//you're running Android 4.3 and below in 2017
|
||||||
|
int generateBridgeSecret() {
|
||||||
|
SecureRandom randGen = new SecureRandom();
|
||||||
|
expectedBridgeSecret = randGen.nextInt(Integer.MAX_VALUE);
|
||||||
|
return expectedBridgeSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
jsMessageQueue.reset();
|
||||||
|
clearBridgeSecret();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String promptOnJsPrompt(String origin, String message, String defaultValue) {
|
||||||
|
if (defaultValue != null && defaultValue.startsWith("gap:")) {
|
||||||
|
JSONArray array;
|
||||||
|
try {
|
||||||
|
array = new JSONArray(defaultValue.substring(4));
|
||||||
|
int bridgeSecret = array.getInt(0);
|
||||||
|
String service = array.getString(1);
|
||||||
|
String action = array.getString(2);
|
||||||
|
String callbackId = array.getString(3);
|
||||||
|
String r = jsExec(bridgeSecret, service, action, callbackId, message);
|
||||||
|
return r == null ? "" : r;
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
// Sets the native->JS bridge mode.
|
||||||
|
else if (defaultValue != null && defaultValue.startsWith("gap_bridge_mode:")) {
|
||||||
|
try {
|
||||||
|
int bridgeSecret = Integer.parseInt(defaultValue.substring(16));
|
||||||
|
jsSetNativeToJsBridgeMode(bridgeSecret, Integer.parseInt(message));
|
||||||
|
} catch (NumberFormatException e){
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
// Polling for JavaScript messages
|
||||||
|
else if (defaultValue != null && defaultValue.startsWith("gap_poll:")) {
|
||||||
|
int bridgeSecret = Integer.parseInt(defaultValue.substring(9));
|
||||||
|
try {
|
||||||
|
String r = jsRetrieveJsMessages(bridgeSecret, "1".equals(message));
|
||||||
|
return r == null ? "" : r;
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {
|
||||||
|
// Protect against random iframes being able to talk through the bridge.
|
||||||
|
// Trust only pages which the app would have been allowed to navigate to anyway.
|
||||||
|
if (pluginManager.shouldAllowBridgeAccess(origin)) {
|
||||||
|
// Enable the bridge
|
||||||
|
int bridgeMode = Integer.parseInt(defaultValue.substring(9));
|
||||||
|
jsMessageQueue.setBridgeMode(bridgeMode);
|
||||||
|
// Tell JS the bridge secret.
|
||||||
|
int secret = generateBridgeSecret();
|
||||||
|
return ""+secret;
|
||||||
|
} else {
|
||||||
|
LOG.e(LOG_TAG, "gap_init called from restricted origin: " + origin);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.webkit.ClientCertRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of the ICordovaClientCertRequest for Android WebView.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class CordovaClientCertRequest implements ICordovaClientCertRequest {
|
||||||
|
|
||||||
|
private final ClientCertRequest request;
|
||||||
|
|
||||||
|
public CordovaClientCertRequest(ClientCertRequest request) {
|
||||||
|
this.request = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel this request
|
||||||
|
*/
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public void cancel()
|
||||||
|
{
|
||||||
|
request.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the host name of the server requesting the certificate.
|
||||||
|
*/
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public String getHost()
|
||||||
|
{
|
||||||
|
return request.getHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the acceptable types of asymmetric keys (can be null).
|
||||||
|
*/
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public String[] getKeyTypes()
|
||||||
|
{
|
||||||
|
return request.getKeyTypes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the port number of the server requesting the certificate.
|
||||||
|
*/
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public int getPort()
|
||||||
|
{
|
||||||
|
return request.getPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the acceptable certificate issuers for the certificate matching the private key (can be null).
|
||||||
|
*/
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public Principal[] getPrincipals()
|
||||||
|
{
|
||||||
|
return request.getPrincipals();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ignore the request for now. Do not remember user's choice.
|
||||||
|
*/
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public void ignore()
|
||||||
|
{
|
||||||
|
request.ignore();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Proceed with the specified private key and client certificate chain. Remember the user's positive choice and use it for future requests.
|
||||||
|
*
|
||||||
|
* @param privateKey The privateKey
|
||||||
|
* @param chain The certificate chain
|
||||||
|
*/
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public void proceed(PrivateKey privateKey, X509Certificate[] chain)
|
||||||
|
{
|
||||||
|
request.proceed(privateKey, chain);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.widget.EditText;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class for WebViews to implement prompt(), alert(), confirm() dialogs.
|
||||||
|
*/
|
||||||
|
public class CordovaDialogsHelper {
|
||||||
|
private final Context context;
|
||||||
|
private AlertDialog lastHandledDialog;
|
||||||
|
|
||||||
|
public CordovaDialogsHelper(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showAlert(String message, final Result result) {
|
||||||
|
AlertDialog.Builder dlg = new AlertDialog.Builder(context);
|
||||||
|
dlg.setMessage(message);
|
||||||
|
dlg.setTitle("Alert");
|
||||||
|
//Don't let alerts break the back button
|
||||||
|
dlg.setCancelable(true);
|
||||||
|
dlg.setPositiveButton(android.R.string.ok,
|
||||||
|
new AlertDialog.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
result.gotResult(true, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dlg.setOnCancelListener(
|
||||||
|
new DialogInterface.OnCancelListener() {
|
||||||
|
public void onCancel(DialogInterface dialog) {
|
||||||
|
result.gotResult(false, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
|
||||||
|
//DO NOTHING
|
||||||
|
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_BACK)
|
||||||
|
{
|
||||||
|
result.gotResult(true, null);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
lastHandledDialog = dlg.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showConfirm(String message, final Result result) {
|
||||||
|
AlertDialog.Builder dlg = new AlertDialog.Builder(context);
|
||||||
|
dlg.setMessage(message);
|
||||||
|
dlg.setTitle("Confirm");
|
||||||
|
dlg.setCancelable(true);
|
||||||
|
dlg.setPositiveButton(android.R.string.ok,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
result.gotResult(true, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dlg.setNegativeButton(android.R.string.cancel,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
result.gotResult(false, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dlg.setOnCancelListener(
|
||||||
|
new DialogInterface.OnCancelListener() {
|
||||||
|
public void onCancel(DialogInterface dialog) {
|
||||||
|
result.gotResult(false, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
|
||||||
|
//DO NOTHING
|
||||||
|
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_BACK)
|
||||||
|
{
|
||||||
|
result.gotResult(false, null);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
lastHandledDialog = dlg.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell the client to display a prompt dialog to the user.
|
||||||
|
* If the client returns true, WebView will assume that the client will
|
||||||
|
* handle the prompt dialog and call the appropriate JsPromptResult method.
|
||||||
|
*
|
||||||
|
* Since we are hacking prompts for our own purposes, we should not be using them for
|
||||||
|
* this purpose, perhaps we should hack console.log to do this instead!
|
||||||
|
*/
|
||||||
|
public void showPrompt(String message, String defaultValue, final Result result) {
|
||||||
|
// Returning false would also show a dialog, but the default one shows the origin (ugly).
|
||||||
|
AlertDialog.Builder dlg = new AlertDialog.Builder(context);
|
||||||
|
dlg.setMessage(message);
|
||||||
|
final EditText input = new EditText(context);
|
||||||
|
if (defaultValue != null) {
|
||||||
|
input.setText(defaultValue);
|
||||||
|
}
|
||||||
|
dlg.setView(input);
|
||||||
|
dlg.setCancelable(false);
|
||||||
|
dlg.setPositiveButton(android.R.string.ok,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
String userText = input.getText().toString();
|
||||||
|
result.gotResult(true, userText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dlg.setNegativeButton(android.R.string.cancel,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
result.gotResult(false, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
lastHandledDialog = dlg.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void destroyLastDialog(){
|
||||||
|
if (lastHandledDialog != null){
|
||||||
|
lastHandledDialog.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Result {
|
||||||
|
public void gotResult(boolean success, String value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import android.webkit.HttpAuthHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies interface for HTTP auth handler object which is used to handle auth requests and
|
||||||
|
* specifying user credentials.
|
||||||
|
*/
|
||||||
|
public class CordovaHttpAuthHandler implements ICordovaHttpAuthHandler {
|
||||||
|
|
||||||
|
private final HttpAuthHandler handler;
|
||||||
|
|
||||||
|
public CordovaHttpAuthHandler(HttpAuthHandler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instructs the WebView to cancel the authentication request.
|
||||||
|
*/
|
||||||
|
public void cancel () {
|
||||||
|
this.handler.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instructs the WebView to proceed with the authentication with the given credentials.
|
||||||
|
*
|
||||||
|
* @param username
|
||||||
|
* @param password
|
||||||
|
*/
|
||||||
|
public void proceed (String username, String password) {
|
||||||
|
this.handler.proceed(username, password);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import org.apache.cordova.CordovaPlugin;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Activity interface that is implemented by CordovaActivity.
|
||||||
|
* It is used to isolate plugin development, and remove dependency on entire Cordova library.
|
||||||
|
*/
|
||||||
|
public interface CordovaInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launch an activity for which you would like a result when it finished. When this activity exits,
|
||||||
|
* your onActivityResult() method will be called.
|
||||||
|
*
|
||||||
|
* @param command The command object
|
||||||
|
* @param intent The intent to start
|
||||||
|
* @param requestCode The request code that is passed to callback to identify the activity
|
||||||
|
*/
|
||||||
|
abstract public void startActivityForResult(CordovaPlugin command, Intent intent, int requestCode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the plugin to be called when a sub-activity exits.
|
||||||
|
*
|
||||||
|
* @param plugin The plugin on which onActivityResult is to be called
|
||||||
|
*/
|
||||||
|
abstract public void setActivityResultCallback(CordovaPlugin plugin);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Android activity.
|
||||||
|
*
|
||||||
|
* If a custom engine lives outside of the Activity's lifecycle the return value may be null.
|
||||||
|
*
|
||||||
|
* @return the Activity
|
||||||
|
*/
|
||||||
|
public abstract Activity getActivity();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Android context.
|
||||||
|
*
|
||||||
|
* @return the Context
|
||||||
|
*/
|
||||||
|
public Context getContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a message is sent to plugin.
|
||||||
|
*
|
||||||
|
* @param id The message id
|
||||||
|
* @param data The message data
|
||||||
|
* @return Object or null
|
||||||
|
*/
|
||||||
|
public Object onMessage(String id, Object data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a shared thread pool that can be used for background tasks.
|
||||||
|
*/
|
||||||
|
public ExecutorService getThreadPool();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a permission request to the activity for one permission.
|
||||||
|
*/
|
||||||
|
public void requestPermission(CordovaPlugin plugin, int requestCode, String permission);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a permission request to the activity for a group of permissions
|
||||||
|
*/
|
||||||
|
public void requestPermissions(CordovaPlugin plugin, int requestCode, String [] permissions);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for a permission. Returns true if the permission is granted, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean hasPermission(String permission);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation of CordovaInterface.
|
||||||
|
*/
|
||||||
|
public class CordovaInterfaceImpl implements CordovaInterface {
|
||||||
|
private static final String TAG = "CordovaInterfaceImpl";
|
||||||
|
protected Activity activity;
|
||||||
|
protected ExecutorService threadPool;
|
||||||
|
protected PluginManager pluginManager;
|
||||||
|
|
||||||
|
protected ActivityResultHolder savedResult;
|
||||||
|
protected CallbackMap permissionResultCallbacks;
|
||||||
|
protected CordovaPlugin activityResultCallback;
|
||||||
|
protected String initCallbackService;
|
||||||
|
protected int activityResultRequestCode;
|
||||||
|
protected boolean activityWasDestroyed = false;
|
||||||
|
protected Bundle savedPluginState;
|
||||||
|
|
||||||
|
public CordovaInterfaceImpl(Activity activity) {
|
||||||
|
this(activity, Executors.newCachedThreadPool());
|
||||||
|
}
|
||||||
|
|
||||||
|
public CordovaInterfaceImpl(Activity activity, ExecutorService threadPool) {
|
||||||
|
this.activity = activity;
|
||||||
|
this.threadPool = threadPool;
|
||||||
|
this.permissionResultCallbacks = new CallbackMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startActivityForResult(CordovaPlugin command, Intent intent, int requestCode) {
|
||||||
|
setActivityResultCallback(command);
|
||||||
|
try {
|
||||||
|
activity.startActivityForResult(intent, requestCode);
|
||||||
|
} catch (RuntimeException e) { // E.g.: ActivityNotFoundException
|
||||||
|
activityResultCallback = null;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setActivityResultCallback(CordovaPlugin plugin) {
|
||||||
|
// Cancel any previously pending activity.
|
||||||
|
if (activityResultCallback != null) {
|
||||||
|
activityResultCallback.onActivityResult(activityResultRequestCode, Activity.RESULT_CANCELED, null);
|
||||||
|
}
|
||||||
|
activityResultCallback = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Activity getActivity() {
|
||||||
|
return activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Context getContext() {
|
||||||
|
return activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object onMessage(String id, Object data) {
|
||||||
|
if ("exit".equals(id)) {
|
||||||
|
activity.finish();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExecutorService getThreadPool() {
|
||||||
|
return threadPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches any pending onActivityResult callbacks and sends the resume event if the
|
||||||
|
* Activity was destroyed by the OS.
|
||||||
|
*/
|
||||||
|
public void onCordovaInit(PluginManager pluginManager) {
|
||||||
|
this.pluginManager = pluginManager;
|
||||||
|
if (savedResult != null) {
|
||||||
|
onActivityResult(savedResult.requestCode, savedResult.resultCode, savedResult.intent);
|
||||||
|
} else if(activityWasDestroyed) {
|
||||||
|
// If there was no Activity result, we still need to send out the resume event if the
|
||||||
|
// Activity was destroyed by the OS
|
||||||
|
activityWasDestroyed = false;
|
||||||
|
if(pluginManager != null)
|
||||||
|
{
|
||||||
|
CoreAndroid appPlugin = (CoreAndroid) pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
|
||||||
|
if(appPlugin != null) {
|
||||||
|
JSONObject obj = new JSONObject();
|
||||||
|
try {
|
||||||
|
obj.put("action", "resume");
|
||||||
|
} catch (JSONException e) {
|
||||||
|
LOG.e(TAG, "Failed to create event message", e);
|
||||||
|
}
|
||||||
|
appPlugin.sendResumeEvent(new PluginResult(PluginResult.Status.OK, obj));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Routes the result to the awaiting plugin. Returns false if no plugin was waiting.
|
||||||
|
*/
|
||||||
|
public boolean onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||||
|
CordovaPlugin callback = activityResultCallback;
|
||||||
|
if(callback == null && initCallbackService != null) {
|
||||||
|
// The application was restarted, but had defined an initial callback
|
||||||
|
// before being shut down.
|
||||||
|
savedResult = new ActivityResultHolder(requestCode, resultCode, intent);
|
||||||
|
if (pluginManager != null) {
|
||||||
|
callback = pluginManager.getPlugin(initCallbackService);
|
||||||
|
if(callback != null) {
|
||||||
|
callback.onRestoreStateForActivityResult(savedPluginState.getBundle(callback.getServiceName()),
|
||||||
|
new ResumeCallback(callback.getServiceName(), pluginManager));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
activityResultCallback = null;
|
||||||
|
|
||||||
|
if (callback != null) {
|
||||||
|
LOG.d(TAG, "Sending activity result to plugin");
|
||||||
|
initCallbackService = null;
|
||||||
|
savedResult = null;
|
||||||
|
callback.onActivityResult(requestCode, resultCode, intent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
LOG.w(TAG, "Got an activity result, but no plugin was registered to receive it" + (savedResult != null ? " yet!" : "."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this from your startActivityForResult() overload. This is required to catch the case
|
||||||
|
* where plugins use Activity.startActivityForResult() + CordovaInterface.setActivityResultCallback()
|
||||||
|
* rather than CordovaInterface.startActivityForResult().
|
||||||
|
*/
|
||||||
|
public void setActivityResultRequestCode(int requestCode) {
|
||||||
|
activityResultRequestCode = requestCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves parameters for startActivityForResult().
|
||||||
|
*/
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
if (activityResultCallback != null) {
|
||||||
|
String serviceName = activityResultCallback.getServiceName();
|
||||||
|
outState.putString("callbackService", serviceName);
|
||||||
|
}
|
||||||
|
if(pluginManager != null){
|
||||||
|
outState.putBundle("plugin", pluginManager.onSaveInstanceState());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this from onCreate() so that any saved startActivityForResult parameters will be restored.
|
||||||
|
*/
|
||||||
|
public void restoreInstanceState(Bundle savedInstanceState) {
|
||||||
|
initCallbackService = savedInstanceState.getString("callbackService");
|
||||||
|
savedPluginState = savedInstanceState.getBundle("plugin");
|
||||||
|
activityWasDestroyed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ActivityResultHolder {
|
||||||
|
private int requestCode;
|
||||||
|
private int resultCode;
|
||||||
|
private Intent intent;
|
||||||
|
|
||||||
|
public ActivityResultHolder(int requestCode, int resultCode, Intent intent) {
|
||||||
|
this.requestCode = requestCode;
|
||||||
|
this.resultCode = resultCode;
|
||||||
|
this.intent = intent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the system when the user grants permissions
|
||||||
|
*
|
||||||
|
* @param requestCode
|
||||||
|
* @param permissions
|
||||||
|
* @param grantResults
|
||||||
|
*/
|
||||||
|
public void onRequestPermissionResult(int requestCode, String[] permissions,
|
||||||
|
int[] grantResults) throws JSONException {
|
||||||
|
Pair<CordovaPlugin, Integer> callback = permissionResultCallbacks.getAndRemoveCallback(requestCode);
|
||||||
|
if(callback != null) {
|
||||||
|
callback.first.onRequestPermissionResult(callback.second, permissions, grantResults);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void requestPermission(CordovaPlugin plugin, int requestCode, String permission) {
|
||||||
|
String[] permissions = new String [1];
|
||||||
|
permissions[0] = permission;
|
||||||
|
requestPermissions(plugin, requestCode, permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public void requestPermissions(CordovaPlugin plugin, int requestCode, String [] permissions) {
|
||||||
|
int mappedRequestCode = permissionResultCallbacks.registerCallback(plugin, requestCode);
|
||||||
|
getActivity().requestPermissions(permissions, mappedRequestCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasPermission(String permission)
|
||||||
|
{
|
||||||
|
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
||||||
|
{
|
||||||
|
int result = activity.checkSelfPermission(permission);
|
||||||
|
return PackageManager.PERMISSION_GRANTED == result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,422 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import org.apache.cordova.CordovaArgs;
|
||||||
|
import org.apache.cordova.CordovaWebView;
|
||||||
|
import org.apache.cordova.CordovaInterface;
|
||||||
|
import org.apache.cordova.CallbackContext;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugins must extend this class and override one of the execute methods.
|
||||||
|
*/
|
||||||
|
public class CordovaPlugin {
|
||||||
|
public CordovaWebView webView;
|
||||||
|
public CordovaInterface cordova;
|
||||||
|
protected CordovaPreferences preferences;
|
||||||
|
private String serviceName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this after constructing to initialize the plugin.
|
||||||
|
* Final because we want to be able to change args without breaking plugins.
|
||||||
|
*/
|
||||||
|
public final void privateInitialize(String serviceName, CordovaInterface cordova, CordovaWebView webView, CordovaPreferences preferences) {
|
||||||
|
assert this.cordova == null;
|
||||||
|
this.serviceName = serviceName;
|
||||||
|
this.cordova = cordova;
|
||||||
|
this.webView = webView;
|
||||||
|
this.preferences = preferences;
|
||||||
|
initialize(cordova, webView);
|
||||||
|
pluginInitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after plugin construction and fields have been initialized.
|
||||||
|
* Prefer to use pluginInitialize instead since there is no value in
|
||||||
|
* having parameters on the initialize() function.
|
||||||
|
*/
|
||||||
|
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after plugin construction and fields have been initialized.
|
||||||
|
*/
|
||||||
|
protected void pluginInitialize() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the plugin's service name (what you'd use when calling pluginManger.getPlugin())
|
||||||
|
*/
|
||||||
|
public String getServiceName() {
|
||||||
|
return serviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the request.
|
||||||
|
*
|
||||||
|
* This method is called from the WebView thread. To do a non-trivial amount of work, use:
|
||||||
|
* cordova.getThreadPool().execute(runnable);
|
||||||
|
*
|
||||||
|
* To run on the UI thread, use:
|
||||||
|
* cordova.getActivity().runOnUiThread(runnable);
|
||||||
|
*
|
||||||
|
* @param action The action to execute.
|
||||||
|
* @param rawArgs The exec() arguments in JSON form.
|
||||||
|
* @param callbackContext The callback context used when calling back into JavaScript.
|
||||||
|
* @return Whether the action was valid.
|
||||||
|
*/
|
||||||
|
public boolean execute(String action, String rawArgs, CallbackContext callbackContext) throws JSONException {
|
||||||
|
JSONArray args = new JSONArray(rawArgs);
|
||||||
|
return execute(action, args, callbackContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the request.
|
||||||
|
*
|
||||||
|
* This method is called from the WebView thread. To do a non-trivial amount of work, use:
|
||||||
|
* cordova.getThreadPool().execute(runnable);
|
||||||
|
*
|
||||||
|
* To run on the UI thread, use:
|
||||||
|
* cordova.getActivity().runOnUiThread(runnable);
|
||||||
|
*
|
||||||
|
* @param action The action to execute.
|
||||||
|
* @param args The exec() arguments.
|
||||||
|
* @param callbackContext The callback context used when calling back into JavaScript.
|
||||||
|
* @return Whether the action was valid.
|
||||||
|
*/
|
||||||
|
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
|
||||||
|
CordovaArgs cordovaArgs = new CordovaArgs(args);
|
||||||
|
return execute(action, cordovaArgs, callbackContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the request.
|
||||||
|
*
|
||||||
|
* This method is called from the WebView thread. To do a non-trivial amount of work, use:
|
||||||
|
* cordova.getThreadPool().execute(runnable);
|
||||||
|
*
|
||||||
|
* To run on the UI thread, use:
|
||||||
|
* cordova.getActivity().runOnUiThread(runnable);
|
||||||
|
*
|
||||||
|
* @param action The action to execute.
|
||||||
|
* @param args The exec() arguments, wrapped with some Cordova helpers.
|
||||||
|
* @param callbackContext The callback context used when calling back into JavaScript.
|
||||||
|
* @return Whether the action was valid.
|
||||||
|
*/
|
||||||
|
public boolean execute(String action, CordovaArgs args, CallbackContext callbackContext) throws JSONException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the system is about to start resuming a previous activity.
|
||||||
|
*
|
||||||
|
* @param multitasking Flag indicating if multitasking is turned on for app
|
||||||
|
*/
|
||||||
|
public void onPause(boolean multitasking) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the activity will start interacting with the user.
|
||||||
|
*
|
||||||
|
* @param multitasking Flag indicating if multitasking is turned on for app
|
||||||
|
*/
|
||||||
|
public void onResume(boolean multitasking) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the activity is becoming visible to the user.
|
||||||
|
*/
|
||||||
|
public void onStart() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the activity is no longer visible to the user.
|
||||||
|
*/
|
||||||
|
public void onStop() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the activity receives a new intent.
|
||||||
|
*/
|
||||||
|
public void onNewIntent(Intent intent) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The final call you receive before your activity is destroyed.
|
||||||
|
*/
|
||||||
|
public void onDestroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the Activity is being destroyed (e.g. if a plugin calls out to an external
|
||||||
|
* Activity and the OS kills the CordovaActivity in the background). The plugin should save its
|
||||||
|
* state in this method only if it is awaiting the result of an external Activity and needs
|
||||||
|
* to preserve some information so as to handle that result; onRestoreStateForActivityResult()
|
||||||
|
* will only be called if the plugin is the recipient of an Activity result
|
||||||
|
*
|
||||||
|
* @return Bundle containing the state of the plugin or null if state does not need to be saved
|
||||||
|
*/
|
||||||
|
public Bundle onSaveInstanceState() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a plugin is the recipient of an Activity result after the CordovaActivity has
|
||||||
|
* been destroyed. The Bundle will be the same as the one the plugin returned in
|
||||||
|
* onSaveInstanceState()
|
||||||
|
*
|
||||||
|
* @param state Bundle containing the state of the plugin
|
||||||
|
* @param callbackContext Replacement Context to return the plugin result to
|
||||||
|
*/
|
||||||
|
public void onRestoreStateForActivityResult(Bundle state, CallbackContext callbackContext) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a message is sent to plugin.
|
||||||
|
*
|
||||||
|
* @param id The message id
|
||||||
|
* @param data The message data
|
||||||
|
* @return Object to stop propagation or null
|
||||||
|
*/
|
||||||
|
public Object onMessage(String id, Object data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an activity you launched exits, giving you the requestCode you started it with,
|
||||||
|
* the resultCode it returned, and any additional data from it.
|
||||||
|
*
|
||||||
|
* @param requestCode The request code originally supplied to startActivityForResult(),
|
||||||
|
* allowing you to identify who this result came from.
|
||||||
|
* @param resultCode The integer result code returned by the child activity through its setResult().
|
||||||
|
* @param intent An Intent, which can return result data to the caller (various data can be
|
||||||
|
* attached to Intent "extras").
|
||||||
|
*/
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for blocking the loading of external resources.
|
||||||
|
*
|
||||||
|
* This will be called when the WebView's shouldInterceptRequest wants to
|
||||||
|
* know whether to open a connection to an external resource. Return false
|
||||||
|
* to block the request: if any plugin returns false, Cordova will block
|
||||||
|
* the request. If all plugins return null, the default policy will be
|
||||||
|
* enforced. If at least one plugin returns true, and no plugins return
|
||||||
|
* false, then the request will proceed.
|
||||||
|
*
|
||||||
|
* Note that this only affects resource requests which are routed through
|
||||||
|
* WebViewClient.shouldInterceptRequest, such as XMLHttpRequest requests and
|
||||||
|
* img tag loads. WebSockets and media requests (such as <video> and <audio>
|
||||||
|
* tags) are not affected by this method. Use CSP headers to control access
|
||||||
|
* to such resources.
|
||||||
|
*/
|
||||||
|
public Boolean shouldAllowRequest(String url) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for blocking navigation by the Cordova WebView. This applies both to top-level and
|
||||||
|
* iframe navigations.
|
||||||
|
*
|
||||||
|
* This will be called when the WebView's needs to know whether to navigate
|
||||||
|
* to a new page. Return false to block the navigation: if any plugin
|
||||||
|
* returns false, Cordova will block the navigation. If all plugins return
|
||||||
|
* null, the default policy will be enforced. It at least one plugin returns
|
||||||
|
* true, and no plugins return false, then the navigation will proceed.
|
||||||
|
*/
|
||||||
|
public Boolean shouldAllowNavigation(String url) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for allowing page to call exec(). By default, this returns the result of
|
||||||
|
* shouldAllowNavigation(). It's generally unsafe to allow untrusted content to be loaded
|
||||||
|
* into a CordovaWebView, even within an iframe, so it's best not to touch this.
|
||||||
|
*/
|
||||||
|
public Boolean shouldAllowBridgeAccess(String url) {
|
||||||
|
return shouldAllowNavigation(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for blocking the launching of Intents by the Cordova application.
|
||||||
|
*
|
||||||
|
* This will be called when the WebView will not navigate to a page, but
|
||||||
|
* could launch an intent to handle the URL. Return false to block this: if
|
||||||
|
* any plugin returns false, Cordova will block the navigation. If all
|
||||||
|
* plugins return null, the default policy will be enforced. If at least one
|
||||||
|
* plugin returns true, and no plugins return false, then the URL will be
|
||||||
|
* opened.
|
||||||
|
*/
|
||||||
|
public Boolean shouldOpenExternalUrl(String url) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows plugins to handle a link being clicked. Return true here to cancel the navigation.
|
||||||
|
*
|
||||||
|
* @param url The URL that is trying to be loaded in the Cordova webview.
|
||||||
|
* @return Return true to prevent the URL from loading. Default is false.
|
||||||
|
*/
|
||||||
|
public boolean onOverrideUrlLoading(String url) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for redirecting requests. Applies to WebView requests as well as requests made by plugins.
|
||||||
|
* To handle the request directly, return a URI in the form:
|
||||||
|
*
|
||||||
|
* cdvplugin://pluginId/...
|
||||||
|
*
|
||||||
|
* And implement handleOpenForRead().
|
||||||
|
* To make this easier, use the toPluginUri() and fromPluginUri() helpers:
|
||||||
|
*
|
||||||
|
* public Uri remapUri(Uri uri) { return toPluginUri(uri); }
|
||||||
|
*
|
||||||
|
* public CordovaResourceApi.OpenForReadResult handleOpenForRead(Uri uri) throws IOException {
|
||||||
|
* Uri origUri = fromPluginUri(uri);
|
||||||
|
* ...
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
public Uri remapUri(Uri uri) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to handle CordovaResourceApi.openForRead() calls for a cdvplugin://pluginId/ URL.
|
||||||
|
* Should never return null.
|
||||||
|
* Added in cordova-android@4.0.0
|
||||||
|
*/
|
||||||
|
public CordovaResourceApi.OpenForReadResult handleOpenForRead(Uri uri) throws IOException {
|
||||||
|
throw new FileNotFoundException("Plugin can't handle uri: " + uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refer to remapUri()
|
||||||
|
* Added in cordova-android@4.0.0
|
||||||
|
*/
|
||||||
|
protected Uri toPluginUri(Uri origUri) {
|
||||||
|
return new Uri.Builder()
|
||||||
|
.scheme(CordovaResourceApi.PLUGIN_URI_SCHEME)
|
||||||
|
.authority(serviceName)
|
||||||
|
.appendQueryParameter("origUri", origUri.toString())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refer to remapUri()
|
||||||
|
* Added in cordova-android@4.0.0
|
||||||
|
*/
|
||||||
|
protected Uri fromPluginUri(Uri pluginUri) {
|
||||||
|
return Uri.parse(pluginUri.getQueryParameter("origUri"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the WebView does a top-level navigation or refreshes.
|
||||||
|
*
|
||||||
|
* Plugins should stop any long-running processes and clean up internal state.
|
||||||
|
*
|
||||||
|
* Does nothing by default.
|
||||||
|
*/
|
||||||
|
public void onReset() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the system received an HTTP authentication request. Plugin can use
|
||||||
|
* the supplied HttpAuthHandler to process this auth challenge.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback
|
||||||
|
* @param handler The HttpAuthHandler used to set the WebView's response
|
||||||
|
* @param host The host requiring authentication
|
||||||
|
* @param realm The realm for which authentication is required
|
||||||
|
*
|
||||||
|
* @return Returns True if plugin will resolve this auth challenge, otherwise False
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when he system received an SSL client certificate request. Plugin can use
|
||||||
|
* the supplied ClientCertRequest to process this certificate challenge.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback
|
||||||
|
* @param request The client certificate request
|
||||||
|
*
|
||||||
|
* @return Returns True if plugin will resolve this auth challenge, otherwise False
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the system when the device configuration changes while your activity is running.
|
||||||
|
*
|
||||||
|
* @param newConfig The new device configuration
|
||||||
|
*/
|
||||||
|
public void onConfigurationChanged(Configuration newConfig) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the Plugin Manager when we need to actually request permissions
|
||||||
|
*
|
||||||
|
* @param requestCode Passed to the activity to track the request
|
||||||
|
*
|
||||||
|
* @return Returns the permission that was stored in the plugin
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void requestPermissions(int requestCode) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called by the WebView implementation to check for geolocation permissions, can be used
|
||||||
|
* by other Java methods in the event that a plugin is using this as a dependency.
|
||||||
|
*
|
||||||
|
* @return Returns true if the plugin has all the permissions it needs to operate.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public boolean hasPermisssion() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the system when the user grants permissions
|
||||||
|
*
|
||||||
|
* @param requestCode
|
||||||
|
* @param permissions
|
||||||
|
* @param grantResults
|
||||||
|
*/
|
||||||
|
public void onRequestPermissionResult(int requestCode, String[] permissions,
|
||||||
|
int[] grantResults) throws JSONException {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.cordova.LOG;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
public class CordovaPreferences {
|
||||||
|
private HashMap<String, String> prefs = new HashMap<String, String>(20);
|
||||||
|
private Bundle preferencesBundleExtras;
|
||||||
|
|
||||||
|
public void setPreferencesBundle(Bundle extras) {
|
||||||
|
preferencesBundleExtras = extras;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(String name, String value) {
|
||||||
|
prefs.put(name.toLowerCase(Locale.ENGLISH), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(String name, boolean value) {
|
||||||
|
set(name, "" + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(String name, int value) {
|
||||||
|
set(name, "" + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(String name, double value) {
|
||||||
|
set(name, "" + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getAll() {
|
||||||
|
return prefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getBoolean(String name, boolean defaultValue) {
|
||||||
|
name = name.toLowerCase(Locale.ENGLISH);
|
||||||
|
String value = prefs.get(name);
|
||||||
|
if (value != null) {
|
||||||
|
return Boolean.parseBoolean(value);
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Added in 4.0.0
|
||||||
|
public boolean contains(String name) {
|
||||||
|
return getString(name, null) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInteger(String name, int defaultValue) {
|
||||||
|
name = name.toLowerCase(Locale.ENGLISH);
|
||||||
|
String value = prefs.get(name);
|
||||||
|
if (value != null) {
|
||||||
|
// Use Integer.decode() can't handle it if the highest bit is set.
|
||||||
|
return (int)(long)Long.decode(value);
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getDouble(String name, double defaultValue) {
|
||||||
|
name = name.toLowerCase(Locale.ENGLISH);
|
||||||
|
String value = prefs.get(name);
|
||||||
|
if (value != null) {
|
||||||
|
return Double.valueOf(value);
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString(String name, String defaultValue) {
|
||||||
|
name = name.toLowerCase(Locale.ENGLISH);
|
||||||
|
String value = prefs.get(name);
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,479 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.AssetFileDescriptor;
|
||||||
|
import android.content.res.AssetManager;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.Base64;
|
||||||
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What this class provides:
|
||||||
|
* 1. Helpers for reading & writing to URLs.
|
||||||
|
* - E.g. handles assets, resources, content providers, files, data URIs, http[s]
|
||||||
|
* - E.g. Can be used to query for mime-type & content length.
|
||||||
|
*
|
||||||
|
* 2. To allow plugins to redirect URLs (via remapUrl).
|
||||||
|
* - All plugins should call remapUrl() on URLs they receive from JS *before*
|
||||||
|
* passing the URL onto other utility functions in this class.
|
||||||
|
* - For an example usage of this, refer to the org.apache.cordova.file plugin.
|
||||||
|
*
|
||||||
|
* Future Work:
|
||||||
|
* - Consider using a Cursor to query content URLs for their size (like the file plugin does).
|
||||||
|
* - Allow plugins to remapUri to "cdv-plugin://plugin-name/foo", which CordovaResourceApi
|
||||||
|
* would then delegate to pluginManager.getPlugin(plugin-name).openForRead(url)
|
||||||
|
* - Currently, plugins *can* do this by remapping to a data: URL, but it's inefficient
|
||||||
|
* for large payloads.
|
||||||
|
*/
|
||||||
|
public class CordovaResourceApi {
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static final String LOG_TAG = "CordovaResourceApi";
|
||||||
|
|
||||||
|
public static final int URI_TYPE_FILE = 0;
|
||||||
|
public static final int URI_TYPE_ASSET = 1;
|
||||||
|
public static final int URI_TYPE_CONTENT = 2;
|
||||||
|
public static final int URI_TYPE_RESOURCE = 3;
|
||||||
|
public static final int URI_TYPE_DATA = 4;
|
||||||
|
public static final int URI_TYPE_HTTP = 5;
|
||||||
|
public static final int URI_TYPE_HTTPS = 6;
|
||||||
|
public static final int URI_TYPE_PLUGIN = 7;
|
||||||
|
public static final int URI_TYPE_UNKNOWN = -1;
|
||||||
|
|
||||||
|
public static final String PLUGIN_URI_SCHEME = "cdvplugin";
|
||||||
|
|
||||||
|
private static final String[] LOCAL_FILE_PROJECTION = { "_data" };
|
||||||
|
|
||||||
|
public static Thread jsThread;
|
||||||
|
|
||||||
|
private final AssetManager assetManager;
|
||||||
|
private final ContentResolver contentResolver;
|
||||||
|
private final PluginManager pluginManager;
|
||||||
|
private boolean threadCheckingEnabled = true;
|
||||||
|
|
||||||
|
|
||||||
|
public CordovaResourceApi(Context context, PluginManager pluginManager) {
|
||||||
|
this.contentResolver = context.getContentResolver();
|
||||||
|
this.assetManager = context.getAssets();
|
||||||
|
this.pluginManager = pluginManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThreadCheckingEnabled(boolean value) {
|
||||||
|
threadCheckingEnabled = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isThreadCheckingEnabled() {
|
||||||
|
return threadCheckingEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static int getUriType(Uri uri) {
|
||||||
|
assertNonRelative(uri);
|
||||||
|
String scheme = uri.getScheme();
|
||||||
|
if (ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(scheme)) {
|
||||||
|
return URI_TYPE_CONTENT;
|
||||||
|
}
|
||||||
|
if (ContentResolver.SCHEME_ANDROID_RESOURCE.equalsIgnoreCase(scheme)) {
|
||||||
|
return URI_TYPE_RESOURCE;
|
||||||
|
}
|
||||||
|
if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(scheme)) {
|
||||||
|
if (uri.getPath().startsWith("/android_asset/")) {
|
||||||
|
return URI_TYPE_ASSET;
|
||||||
|
}
|
||||||
|
return URI_TYPE_FILE;
|
||||||
|
}
|
||||||
|
if ("data".equalsIgnoreCase(scheme)) {
|
||||||
|
return URI_TYPE_DATA;
|
||||||
|
}
|
||||||
|
if ("http".equalsIgnoreCase(scheme)) {
|
||||||
|
return URI_TYPE_HTTP;
|
||||||
|
}
|
||||||
|
if ("https".equalsIgnoreCase(scheme)) {
|
||||||
|
return URI_TYPE_HTTPS;
|
||||||
|
}
|
||||||
|
if (PLUGIN_URI_SCHEME.equalsIgnoreCase(scheme)) {
|
||||||
|
return URI_TYPE_PLUGIN;
|
||||||
|
}
|
||||||
|
return URI_TYPE_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri remapUri(Uri uri) {
|
||||||
|
assertNonRelative(uri);
|
||||||
|
Uri pluginUri = pluginManager.remapUri(uri);
|
||||||
|
return pluginUri != null ? pluginUri : uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String remapPath(String path) {
|
||||||
|
return remapUri(Uri.fromFile(new File(path))).getPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a File that points to the resource, or null if the resource
|
||||||
|
* is not on the local filesystem.
|
||||||
|
*/
|
||||||
|
public File mapUriToFile(Uri uri) {
|
||||||
|
assertBackgroundThread();
|
||||||
|
switch (getUriType(uri)) {
|
||||||
|
case URI_TYPE_FILE:
|
||||||
|
return new File(uri.getPath());
|
||||||
|
case URI_TYPE_CONTENT: {
|
||||||
|
Cursor cursor = contentResolver.query(uri, LOCAL_FILE_PROJECTION, null, null, null);
|
||||||
|
if (cursor != null) {
|
||||||
|
try {
|
||||||
|
int columnIndex = cursor.getColumnIndex(LOCAL_FILE_PROJECTION[0]);
|
||||||
|
if (columnIndex != -1 && cursor.getCount() > 0) {
|
||||||
|
cursor.moveToFirst();
|
||||||
|
String realPath = cursor.getString(columnIndex);
|
||||||
|
if (realPath != null) {
|
||||||
|
return new File(realPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMimeType(Uri uri) {
|
||||||
|
switch (getUriType(uri)) {
|
||||||
|
case URI_TYPE_FILE:
|
||||||
|
case URI_TYPE_ASSET:
|
||||||
|
return getMimeTypeFromPath(uri.getPath());
|
||||||
|
case URI_TYPE_CONTENT:
|
||||||
|
case URI_TYPE_RESOURCE:
|
||||||
|
return contentResolver.getType(uri);
|
||||||
|
case URI_TYPE_DATA: {
|
||||||
|
return getDataUriMimeType(uri);
|
||||||
|
}
|
||||||
|
case URI_TYPE_HTTP:
|
||||||
|
case URI_TYPE_HTTPS: {
|
||||||
|
try {
|
||||||
|
HttpURLConnection conn = (HttpURLConnection)new URL(uri.toString()).openConnection();
|
||||||
|
conn.setDoInput(false);
|
||||||
|
conn.setRequestMethod("HEAD");
|
||||||
|
String mimeType = conn.getHeaderField("Content-Type");
|
||||||
|
if (mimeType != null) {
|
||||||
|
mimeType = mimeType.split(";")[0];
|
||||||
|
}
|
||||||
|
return mimeType;
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//This already exists
|
||||||
|
private String getMimeTypeFromPath(String path) {
|
||||||
|
String extension = path;
|
||||||
|
int lastDot = extension.lastIndexOf('.');
|
||||||
|
if (lastDot != -1) {
|
||||||
|
extension = extension.substring(lastDot + 1);
|
||||||
|
}
|
||||||
|
// Convert the URI string to lower case to ensure compatibility with MimeTypeMap (see CB-2185).
|
||||||
|
extension = extension.toLowerCase(Locale.getDefault());
|
||||||
|
if (extension.equals("3ga")) {
|
||||||
|
return "audio/3gpp";
|
||||||
|
} else if (extension.equals("js")) {
|
||||||
|
// Missing from the map :(.
|
||||||
|
return "text/javascript";
|
||||||
|
}
|
||||||
|
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a stream to the given URI, also providing the MIME type & length.
|
||||||
|
* @return Never returns null.
|
||||||
|
* @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
|
||||||
|
* resolved before being passed into this function.
|
||||||
|
* @throws Throws an IOException if the URI cannot be opened.
|
||||||
|
* @throws Throws an IllegalStateException if called on a foreground thread.
|
||||||
|
*/
|
||||||
|
public OpenForReadResult openForRead(Uri uri) throws IOException {
|
||||||
|
return openForRead(uri, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a stream to the given URI, also providing the MIME type & length.
|
||||||
|
* @return Never returns null.
|
||||||
|
* @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
|
||||||
|
* resolved before being passed into this function.
|
||||||
|
* @throws Throws an IOException if the URI cannot be opened.
|
||||||
|
* @throws Throws an IllegalStateException if called on a foreground thread and skipThreadCheck is false.
|
||||||
|
*/
|
||||||
|
public OpenForReadResult openForRead(Uri uri, boolean skipThreadCheck) throws IOException {
|
||||||
|
if (!skipThreadCheck) {
|
||||||
|
assertBackgroundThread();
|
||||||
|
}
|
||||||
|
switch (getUriType(uri)) {
|
||||||
|
case URI_TYPE_FILE: {
|
||||||
|
FileInputStream inputStream = new FileInputStream(uri.getPath());
|
||||||
|
String mimeType = getMimeTypeFromPath(uri.getPath());
|
||||||
|
long length = inputStream.getChannel().size();
|
||||||
|
return new OpenForReadResult(uri, inputStream, mimeType, length, null);
|
||||||
|
}
|
||||||
|
case URI_TYPE_ASSET: {
|
||||||
|
String assetPath = uri.getPath().substring(15);
|
||||||
|
AssetFileDescriptor assetFd = null;
|
||||||
|
InputStream inputStream;
|
||||||
|
long length = -1;
|
||||||
|
try {
|
||||||
|
assetFd = assetManager.openFd(assetPath);
|
||||||
|
inputStream = assetFd.createInputStream();
|
||||||
|
length = assetFd.getLength();
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
// Will occur if the file is compressed.
|
||||||
|
inputStream = assetManager.open(assetPath);
|
||||||
|
length = inputStream.available();
|
||||||
|
}
|
||||||
|
String mimeType = getMimeTypeFromPath(assetPath);
|
||||||
|
return new OpenForReadResult(uri, inputStream, mimeType, length, assetFd);
|
||||||
|
}
|
||||||
|
case URI_TYPE_CONTENT:
|
||||||
|
case URI_TYPE_RESOURCE: {
|
||||||
|
String mimeType = contentResolver.getType(uri);
|
||||||
|
AssetFileDescriptor assetFd = contentResolver.openAssetFileDescriptor(uri, "r");
|
||||||
|
InputStream inputStream = assetFd.createInputStream();
|
||||||
|
long length = assetFd.getLength();
|
||||||
|
return new OpenForReadResult(uri, inputStream, mimeType, length, assetFd);
|
||||||
|
}
|
||||||
|
case URI_TYPE_DATA: {
|
||||||
|
OpenForReadResult ret = readDataUri(uri);
|
||||||
|
if (ret == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
case URI_TYPE_HTTP:
|
||||||
|
case URI_TYPE_HTTPS: {
|
||||||
|
HttpURLConnection conn = (HttpURLConnection)new URL(uri.toString()).openConnection();
|
||||||
|
conn.setRequestProperty("Accept-Encoding", "gzip");
|
||||||
|
conn.setDoInput(true);
|
||||||
|
String mimeType = conn.getHeaderField("Content-Type");
|
||||||
|
if (mimeType != null) {
|
||||||
|
mimeType = mimeType.split(";")[0];
|
||||||
|
}
|
||||||
|
int length = conn.getContentLength();
|
||||||
|
InputStream inputStream;
|
||||||
|
if ("gzip".equals(conn.getContentEncoding())) {
|
||||||
|
inputStream = new GZIPInputStream(conn.getInputStream());
|
||||||
|
} else {
|
||||||
|
inputStream = conn.getInputStream();
|
||||||
|
}
|
||||||
|
return new OpenForReadResult(uri, inputStream, mimeType, length, null);
|
||||||
|
}
|
||||||
|
case URI_TYPE_PLUGIN: {
|
||||||
|
String pluginId = uri.getHost();
|
||||||
|
CordovaPlugin plugin = pluginManager.getPlugin(pluginId);
|
||||||
|
if (plugin == null) {
|
||||||
|
throw new FileNotFoundException("Invalid plugin ID in URI: " + uri);
|
||||||
|
}
|
||||||
|
return plugin.handleOpenForRead(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new FileNotFoundException("URI not supported by CordovaResourceApi: " + uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputStream openOutputStream(Uri uri) throws IOException {
|
||||||
|
return openOutputStream(uri, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a stream to the given URI.
|
||||||
|
* @return Never returns null.
|
||||||
|
* @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
|
||||||
|
* resolved before being passed into this function.
|
||||||
|
* @throws Throws an IOException if the URI cannot be opened.
|
||||||
|
*/
|
||||||
|
public OutputStream openOutputStream(Uri uri, boolean append) throws IOException {
|
||||||
|
assertBackgroundThread();
|
||||||
|
switch (getUriType(uri)) {
|
||||||
|
case URI_TYPE_FILE: {
|
||||||
|
File localFile = new File(uri.getPath());
|
||||||
|
File parent = localFile.getParentFile();
|
||||||
|
if (parent != null) {
|
||||||
|
parent.mkdirs();
|
||||||
|
}
|
||||||
|
return new FileOutputStream(localFile, append);
|
||||||
|
}
|
||||||
|
case URI_TYPE_CONTENT:
|
||||||
|
case URI_TYPE_RESOURCE: {
|
||||||
|
AssetFileDescriptor assetFd = contentResolver.openAssetFileDescriptor(uri, append ? "wa" : "w");
|
||||||
|
return assetFd.createOutputStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new FileNotFoundException("URI not supported by CordovaResourceApi: " + uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpURLConnection createHttpConnection(Uri uri) throws IOException {
|
||||||
|
assertBackgroundThread();
|
||||||
|
return (HttpURLConnection)new URL(uri.toString()).openConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies the input to the output in the most efficient manner possible.
|
||||||
|
// Closes both streams.
|
||||||
|
public void copyResource(OpenForReadResult input, OutputStream outputStream) throws IOException {
|
||||||
|
assertBackgroundThread();
|
||||||
|
try {
|
||||||
|
InputStream inputStream = input.inputStream;
|
||||||
|
if (inputStream instanceof FileInputStream && outputStream instanceof FileOutputStream) {
|
||||||
|
FileChannel inChannel = ((FileInputStream)input.inputStream).getChannel();
|
||||||
|
FileChannel outChannel = ((FileOutputStream)outputStream).getChannel();
|
||||||
|
long offset = 0;
|
||||||
|
long length = input.length;
|
||||||
|
if (input.assetFd != null) {
|
||||||
|
offset = input.assetFd.getStartOffset();
|
||||||
|
}
|
||||||
|
// transferFrom()'s 2nd arg is a relative position. Need to set the absolute
|
||||||
|
// position first.
|
||||||
|
inChannel.position(offset);
|
||||||
|
outChannel.transferFrom(inChannel, 0, length);
|
||||||
|
} else {
|
||||||
|
final int BUFFER_SIZE = 8192;
|
||||||
|
byte[] buffer = new byte[BUFFER_SIZE];
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
|
||||||
|
|
||||||
|
if (bytesRead <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
outputStream.write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
input.inputStream.close();
|
||||||
|
if (outputStream != null) {
|
||||||
|
outputStream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copyResource(Uri sourceUri, OutputStream outputStream) throws IOException {
|
||||||
|
copyResource(openForRead(sourceUri), outputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Added in 3.5.0.
|
||||||
|
public void copyResource(Uri sourceUri, Uri dstUri) throws IOException {
|
||||||
|
copyResource(openForRead(sourceUri), openOutputStream(dstUri));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertBackgroundThread() {
|
||||||
|
if (threadCheckingEnabled) {
|
||||||
|
Thread curThread = Thread.currentThread();
|
||||||
|
if (curThread == Looper.getMainLooper().getThread()) {
|
||||||
|
throw new IllegalStateException("Do not perform IO operations on the UI thread. Use CordovaInterface.getThreadPool() instead.");
|
||||||
|
}
|
||||||
|
if (curThread == jsThread) {
|
||||||
|
throw new IllegalStateException("Tried to perform an IO operation on the WebCore thread. Use CordovaInterface.getThreadPool() instead.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getDataUriMimeType(Uri uri) {
|
||||||
|
String uriAsString = uri.getSchemeSpecificPart();
|
||||||
|
int commaPos = uriAsString.indexOf(',');
|
||||||
|
if (commaPos == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String[] mimeParts = uriAsString.substring(0, commaPos).split(";");
|
||||||
|
if (mimeParts.length > 0) {
|
||||||
|
return mimeParts[0];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private OpenForReadResult readDataUri(Uri uri) {
|
||||||
|
String uriAsString = uri.getSchemeSpecificPart();
|
||||||
|
int commaPos = uriAsString.indexOf(',');
|
||||||
|
if (commaPos == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String[] mimeParts = uriAsString.substring(0, commaPos).split(";");
|
||||||
|
String contentType = null;
|
||||||
|
boolean base64 = false;
|
||||||
|
if (mimeParts.length > 0) {
|
||||||
|
contentType = mimeParts[0];
|
||||||
|
}
|
||||||
|
for (int i = 1; i < mimeParts.length; ++i) {
|
||||||
|
if ("base64".equalsIgnoreCase(mimeParts[i])) {
|
||||||
|
base64 = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String dataPartAsString = uriAsString.substring(commaPos + 1);
|
||||||
|
byte[] data;
|
||||||
|
if (base64) {
|
||||||
|
data = Base64.decode(dataPartAsString, Base64.DEFAULT);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
data = dataPartAsString.getBytes("UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
data = dataPartAsString.getBytes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(data);
|
||||||
|
return new OpenForReadResult(uri, inputStream, contentType, data.length, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertNonRelative(Uri uri) {
|
||||||
|
if (!uri.isAbsolute()) {
|
||||||
|
throw new IllegalArgumentException("Relative URIs are not supported.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class OpenForReadResult {
|
||||||
|
public final Uri uri;
|
||||||
|
public final InputStream inputStream;
|
||||||
|
public final String mimeType;
|
||||||
|
public final long length;
|
||||||
|
public final AssetFileDescriptor assetFd;
|
||||||
|
|
||||||
|
public OpenForReadResult(Uri uri, InputStream inputStream, String mimeType, long length, AssetFileDescriptor assetFd) {
|
||||||
|
this.uri = uri;
|
||||||
|
this.inputStream = inputStream;
|
||||||
|
this.mimeType = mimeType;
|
||||||
|
this.length = length;
|
||||||
|
this.assetFd = assetFd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.webkit.WebChromeClient.CustomViewCallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main interface for interacting with a Cordova webview - implemented by CordovaWebViewImpl.
|
||||||
|
* This is an interface so that it can be easily mocked in tests.
|
||||||
|
* Methods may be added to this interface without a major version bump, as plugins & embedders
|
||||||
|
* are not expected to implement it.
|
||||||
|
*/
|
||||||
|
public interface CordovaWebView {
|
||||||
|
public static final String CORDOVA_VERSION = "9.1.0";
|
||||||
|
|
||||||
|
void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences);
|
||||||
|
|
||||||
|
boolean isInitialized();
|
||||||
|
|
||||||
|
View getView();
|
||||||
|
|
||||||
|
void loadUrlIntoView(String url, boolean recreatePlugins);
|
||||||
|
|
||||||
|
void stopLoading();
|
||||||
|
|
||||||
|
boolean canGoBack();
|
||||||
|
|
||||||
|
void clearCache();
|
||||||
|
|
||||||
|
/** Use parameter-less overload */
|
||||||
|
@Deprecated
|
||||||
|
void clearCache(boolean b);
|
||||||
|
|
||||||
|
void clearHistory();
|
||||||
|
|
||||||
|
boolean backHistory();
|
||||||
|
|
||||||
|
void handlePause(boolean keepRunning);
|
||||||
|
|
||||||
|
void onNewIntent(Intent intent);
|
||||||
|
|
||||||
|
void handleResume(boolean keepRunning);
|
||||||
|
|
||||||
|
void handleStart();
|
||||||
|
|
||||||
|
void handleStop();
|
||||||
|
|
||||||
|
void handleDestroy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send JavaScript statement back to JavaScript.
|
||||||
|
*
|
||||||
|
* Deprecated (https://issues.apache.org/jira/browse/CB-6851)
|
||||||
|
* Instead of executing snippets of JS, you should use the exec bridge
|
||||||
|
* to create a Java->JS communication channel.
|
||||||
|
* To do this:
|
||||||
|
* 1. Within plugin.xml (to have your JS run before deviceready):
|
||||||
|
* <js-module><runs/></js-module>
|
||||||
|
* 2. Within your .js (call exec on start-up):
|
||||||
|
* require('cordova/channel').onCordovaReady.subscribe(function() {
|
||||||
|
* require('cordova/exec')(win, null, 'Plugin', 'method', []);
|
||||||
|
* function win(message) {
|
||||||
|
* ... process message from java here ...
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* 3. Within your .java:
|
||||||
|
* PluginResult dataResult = new PluginResult(PluginResult.Status.OK, CODE);
|
||||||
|
* dataResult.setKeepCallback(true);
|
||||||
|
* savedCallbackContext.sendPluginResult(dataResult);
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
void sendJavascript(String statememt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the specified URL in the Cordova webview or a new browser instance.
|
||||||
|
*
|
||||||
|
* NOTE: If openExternal is false, only whitelisted URLs can be loaded.
|
||||||
|
*
|
||||||
|
* @param url The url to load.
|
||||||
|
* @param openExternal Load url in browser instead of Cordova webview.
|
||||||
|
* @param clearHistory Clear the history stack, so new page becomes top of history
|
||||||
|
* @param params Parameters for new app
|
||||||
|
*/
|
||||||
|
void showWebPage(String url, boolean openExternal, boolean clearHistory, Map<String, Object> params);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deprecated in 4.0.0. Use your own View-toggling logic.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
boolean isCustomViewShowing();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deprecated in 4.0.0. Use your own View-toggling logic.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
void showCustomView(View view, CustomViewCallback callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deprecated in 4.0.0. Use your own View-toggling logic.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
void hideCustomView();
|
||||||
|
|
||||||
|
CordovaResourceApi getResourceApi();
|
||||||
|
|
||||||
|
void setButtonPlumbedToJs(int keyCode, boolean override);
|
||||||
|
boolean isButtonPlumbedToJs(int keyCode);
|
||||||
|
|
||||||
|
void sendPluginResult(PluginResult cr, String callbackId);
|
||||||
|
|
||||||
|
PluginManager getPluginManager();
|
||||||
|
CordovaWebViewEngine getEngine();
|
||||||
|
CordovaPreferences getPreferences();
|
||||||
|
ICordovaCookieManager getCookieManager();
|
||||||
|
|
||||||
|
String getUrl();
|
||||||
|
|
||||||
|
// TODO: Work on deleting these by removing refs from plugins.
|
||||||
|
Context getContext();
|
||||||
|
void loadUrl(String url);
|
||||||
|
Object postMessage(String id, Object data);
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.webkit.ValueCallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for all Cordova engines.
|
||||||
|
* No methods will be added to this class (in order to be compatible with existing engines).
|
||||||
|
* Instead, we will create a new interface: e.g. CordovaWebViewEngineV2
|
||||||
|
*/
|
||||||
|
public interface CordovaWebViewEngine {
|
||||||
|
void init(CordovaWebView parentWebView, CordovaInterface cordova, Client client,
|
||||||
|
CordovaResourceApi resourceApi, PluginManager pluginManager,
|
||||||
|
NativeToJsMessageQueue nativeToJsMessageQueue);
|
||||||
|
|
||||||
|
CordovaWebView getCordovaWebView();
|
||||||
|
ICordovaCookieManager getCookieManager();
|
||||||
|
View getView();
|
||||||
|
|
||||||
|
void loadUrl(String url, boolean clearNavigationStack);
|
||||||
|
|
||||||
|
void stopLoading();
|
||||||
|
|
||||||
|
/** Return the currently loaded URL */
|
||||||
|
String getUrl();
|
||||||
|
|
||||||
|
void clearCache();
|
||||||
|
|
||||||
|
/** After calling clearHistory(), canGoBack() should be false. */
|
||||||
|
void clearHistory();
|
||||||
|
|
||||||
|
boolean canGoBack();
|
||||||
|
|
||||||
|
/** Returns whether a navigation occurred */
|
||||||
|
boolean goBack();
|
||||||
|
|
||||||
|
/** Pauses / resumes the WebView's event loop. */
|
||||||
|
void setPaused(boolean value);
|
||||||
|
|
||||||
|
/** Clean up all resources associated with the WebView. */
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
/** Add the evaulate Javascript method **/
|
||||||
|
void evaluateJavascript(String js, ValueCallback<String> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to retrieve the associated CordovaWebView given a View without knowing the type of Engine.
|
||||||
|
* E.g. ((CordovaWebView.EngineView)activity.findViewById(android.R.id.webView)).getCordovaWebView();
|
||||||
|
*/
|
||||||
|
public interface EngineView {
|
||||||
|
CordovaWebView getCordovaWebView();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains methods that an engine uses to communicate with the parent CordovaWebView.
|
||||||
|
* Methods may be added in future cordova versions, but never removed.
|
||||||
|
*/
|
||||||
|
public interface Client {
|
||||||
|
Boolean onDispatchKeyEvent(KeyEvent event);
|
||||||
|
void clearLoadTimeoutTimer();
|
||||||
|
void onPageStarted(String newUrl);
|
||||||
|
void onReceivedError(int errorCode, String description, String failingUrl);
|
||||||
|
void onPageFinishedLoading(String url);
|
||||||
|
boolean onNavigationAttempt(String url);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,668 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.webkit.WebChromeClient;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
import org.apache.cordova.engine.SystemWebViewEngine;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main class for interacting with a Cordova webview. Manages plugins, events, and a CordovaWebViewEngine.
|
||||||
|
* Class uses two-phase initialization. You must call init() before calling any other methods.
|
||||||
|
*/
|
||||||
|
public class CordovaWebViewImpl implements CordovaWebView {
|
||||||
|
|
||||||
|
public static final String TAG = "CordovaWebViewImpl";
|
||||||
|
|
||||||
|
private PluginManager pluginManager;
|
||||||
|
|
||||||
|
protected final CordovaWebViewEngine engine;
|
||||||
|
private CordovaInterface cordova;
|
||||||
|
|
||||||
|
// Flag to track that a loadUrl timeout occurred
|
||||||
|
private int loadUrlTimeout = 0;
|
||||||
|
|
||||||
|
private CordovaResourceApi resourceApi;
|
||||||
|
private CordovaPreferences preferences;
|
||||||
|
private CoreAndroid appPlugin;
|
||||||
|
private NativeToJsMessageQueue nativeToJsMessageQueue;
|
||||||
|
private EngineClient engineClient = new EngineClient();
|
||||||
|
private boolean hasPausedEver;
|
||||||
|
|
||||||
|
// The URL passed to loadUrl(), not necessarily the URL of the current page.
|
||||||
|
String loadedUrl;
|
||||||
|
|
||||||
|
/** custom view created by the browser (a video player for example) */
|
||||||
|
private View mCustomView;
|
||||||
|
private WebChromeClient.CustomViewCallback mCustomViewCallback;
|
||||||
|
|
||||||
|
private Set<Integer> boundKeyCodes = new HashSet<Integer>();
|
||||||
|
|
||||||
|
public static CordovaWebViewEngine createEngine(Context context, CordovaPreferences preferences) {
|
||||||
|
String className = preferences.getString("webview", SystemWebViewEngine.class.getCanonicalName());
|
||||||
|
try {
|
||||||
|
Class<?> webViewClass = Class.forName(className);
|
||||||
|
Constructor<?> constructor = webViewClass.getConstructor(Context.class, CordovaPreferences.class);
|
||||||
|
return (CordovaWebViewEngine) constructor.newInstance(context, preferences);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to create webview. ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CordovaWebViewImpl(CordovaWebViewEngine cordovaWebViewEngine) {
|
||||||
|
this.engine = cordovaWebViewEngine;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience method for when creating programmatically (not from Config.xml).
|
||||||
|
public void init(CordovaInterface cordova) {
|
||||||
|
init(cordova, new ArrayList<PluginEntry>(), new CordovaPreferences());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("Assert")
|
||||||
|
@Override
|
||||||
|
public void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences) {
|
||||||
|
if (this.cordova != null) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
this.cordova = cordova;
|
||||||
|
this.preferences = preferences;
|
||||||
|
pluginManager = new PluginManager(this, this.cordova, pluginEntries);
|
||||||
|
resourceApi = new CordovaResourceApi(engine.getView().getContext(), pluginManager);
|
||||||
|
nativeToJsMessageQueue = new NativeToJsMessageQueue();
|
||||||
|
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.NoOpBridgeMode());
|
||||||
|
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.LoadUrlBridgeMode(engine, cordova));
|
||||||
|
|
||||||
|
if (preferences.getBoolean("DisallowOverscroll", false)) {
|
||||||
|
engine.getView().setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||||
|
}
|
||||||
|
engine.init(this, cordova, engineClient, resourceApi, pluginManager, nativeToJsMessageQueue);
|
||||||
|
// This isn't enforced by the compiler, so assert here.
|
||||||
|
assert engine.getView() instanceof CordovaWebViewEngine.EngineView;
|
||||||
|
|
||||||
|
pluginManager.addService(CoreAndroid.PLUGIN_NAME, "org.apache.cordova.CoreAndroid");
|
||||||
|
pluginManager.init();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInitialized() {
|
||||||
|
return cordova != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadUrlIntoView(final String url, boolean recreatePlugins) {
|
||||||
|
LOG.d(TAG, ">>> loadUrl(" + url + ")");
|
||||||
|
if (url.equals("about:blank") || url.startsWith("javascript:")) {
|
||||||
|
engine.loadUrl(url, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
recreatePlugins = recreatePlugins || (loadedUrl == null);
|
||||||
|
|
||||||
|
if (recreatePlugins) {
|
||||||
|
// Don't re-initialize on first load.
|
||||||
|
if (loadedUrl != null) {
|
||||||
|
appPlugin = null;
|
||||||
|
pluginManager.init();
|
||||||
|
}
|
||||||
|
loadedUrl = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a timeout timer for loadUrl
|
||||||
|
final int currentLoadUrlTimeout = loadUrlTimeout;
|
||||||
|
final int loadUrlTimeoutValue = preferences.getInteger("LoadUrlTimeoutValue", 20000);
|
||||||
|
|
||||||
|
// Timeout error method
|
||||||
|
final Runnable loadError = new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
stopLoading();
|
||||||
|
LOG.e(TAG, "CordovaWebView: TIMEOUT ERROR!");
|
||||||
|
|
||||||
|
// Handle other errors by passing them to the webview in JS
|
||||||
|
JSONObject data = new JSONObject();
|
||||||
|
try {
|
||||||
|
data.put("errorCode", -6);
|
||||||
|
data.put("description", "The connection to the server was unsuccessful.");
|
||||||
|
data.put("url", url);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
// Will never happen.
|
||||||
|
}
|
||||||
|
pluginManager.postMessage("onReceivedError", data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Timeout timer method
|
||||||
|
final Runnable timeoutCheck = new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
synchronized (this) {
|
||||||
|
wait(loadUrlTimeoutValue);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If timeout, then stop loading and handle error (if activity still exists)
|
||||||
|
if (loadUrlTimeout == currentLoadUrlTimeout && cordova.getActivity() != null) {
|
||||||
|
cordova.getActivity().runOnUiThread(loadError);
|
||||||
|
} else if (cordova.getActivity() == null) {
|
||||||
|
LOG.d(TAG, "Cordova activity does not exist.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (cordova.getActivity() != null) {
|
||||||
|
final boolean _recreatePlugins = recreatePlugins;
|
||||||
|
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (loadUrlTimeoutValue > 0) {
|
||||||
|
cordova.getThreadPool().execute(timeoutCheck);
|
||||||
|
}
|
||||||
|
engine.loadUrl(url, _recreatePlugins);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
LOG.d(TAG, "Cordova activity does not exist.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadUrl(String url) {
|
||||||
|
loadUrlIntoView(url, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showWebPage(String url, boolean openExternal, boolean clearHistory, Map<String, Object> params) {
|
||||||
|
LOG.d(TAG, "showWebPage(%s, %b, %b, HashMap)", url, openExternal, clearHistory);
|
||||||
|
|
||||||
|
// If clearing history
|
||||||
|
if (clearHistory) {
|
||||||
|
engine.clearHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If loading into our webview
|
||||||
|
if (!openExternal) {
|
||||||
|
// Make sure url is in whitelist
|
||||||
|
if (pluginManager.shouldAllowNavigation(url)) {
|
||||||
|
// TODO: What about params?
|
||||||
|
// Load new URL
|
||||||
|
loadUrlIntoView(url, true);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
LOG.w(TAG, "showWebPage: Refusing to load URL into webview since it is not in the <allow-navigation> whitelist. URL=" + url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!pluginManager.shouldOpenExternalUrl(url)) {
|
||||||
|
LOG.w(TAG, "showWebPage: Refusing to send intent for URL since it is not in the <allow-intent> whitelist. URL=" + url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = null;
|
||||||
|
try {
|
||||||
|
if (url.startsWith("intent://")) {
|
||||||
|
intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
|
||||||
|
} else {
|
||||||
|
intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
// To send an intent without CATEGORY_BROWSER, a custom plugin should be used.
|
||||||
|
intent.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||||
|
Uri uri = Uri.parse(url);
|
||||||
|
// Omitting the MIME type for file: URLs causes "No Activity found to handle Intent".
|
||||||
|
// Adding the MIME type to http: URLs causes them to not be handled by the downloader.
|
||||||
|
if ("file".equals(uri.getScheme())) {
|
||||||
|
intent.setDataAndType(uri, resourceApi.getMimeType(uri));
|
||||||
|
} else {
|
||||||
|
intent.setData(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cordova.getActivity() != null) {
|
||||||
|
cordova.getActivity().startActivity(intent);
|
||||||
|
} else {
|
||||||
|
LOG.d(TAG, "Cordova activity does not exist.");
|
||||||
|
}
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
LOG.e(TAG, "Error parsing url " + url, e);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
if (url.startsWith("intent://") && intent != null && intent.getStringExtra("browser_fallback_url") != null) {
|
||||||
|
showWebPage(intent.getStringExtra("browser_fallback_url"), openExternal, clearHistory, params);
|
||||||
|
} else {
|
||||||
|
LOG.e(TAG, "Error loading url " + url, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class WrapperView extends FrameLayout {
|
||||||
|
|
||||||
|
private final CordovaWebViewEngine engine;
|
||||||
|
|
||||||
|
public WrapperView(Context context, CordovaWebViewEngine engine) {
|
||||||
|
super(context);
|
||||||
|
this.engine = engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||||
|
boolean ret = engine.getView().dispatchKeyEvent(event);
|
||||||
|
if (!ret) {
|
||||||
|
// If the engine didn't handle the event, handle it normally.
|
||||||
|
ret = super.dispatchKeyEvent(event);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
|
||||||
|
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
|
||||||
|
LOG.d(TAG, "showing Custom View");
|
||||||
|
// if a view already exists then immediately terminate the new one
|
||||||
|
if (mCustomView != null) {
|
||||||
|
callback.onCustomViewHidden();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WrapperView wrapperView = new WrapperView(getContext(), engine);
|
||||||
|
wrapperView.addView(view);
|
||||||
|
|
||||||
|
// Store the view and its callback for later (to kill it properly)
|
||||||
|
mCustomView = wrapperView;
|
||||||
|
mCustomViewCallback = callback;
|
||||||
|
|
||||||
|
// Add the custom view to its container.
|
||||||
|
ViewGroup parent = (ViewGroup) engine.getView().getParent();
|
||||||
|
parent.addView(wrapperView, new FrameLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
Gravity.CENTER));
|
||||||
|
|
||||||
|
// Hide the content view.
|
||||||
|
engine.getView().setVisibility(View.GONE);
|
||||||
|
|
||||||
|
// Finally show the custom view container.
|
||||||
|
parent.setVisibility(View.VISIBLE);
|
||||||
|
parent.bringToFront();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public void hideCustomView() {
|
||||||
|
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
|
||||||
|
if (mCustomView == null) return;
|
||||||
|
LOG.d(TAG, "Hiding Custom View");
|
||||||
|
|
||||||
|
// Hide the custom view.
|
||||||
|
mCustomView.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
// Remove the custom view from its container.
|
||||||
|
ViewGroup parent = (ViewGroup) engine.getView().getParent();
|
||||||
|
parent.removeView(mCustomView);
|
||||||
|
mCustomView = null;
|
||||||
|
mCustomViewCallback.onCustomViewHidden();
|
||||||
|
|
||||||
|
// Show the content view.
|
||||||
|
engine.getView().setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public boolean isCustomViewShowing() {
|
||||||
|
return mCustomView != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public void sendJavascript(String statement) {
|
||||||
|
nativeToJsMessageQueue.addJavaScript(statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendPluginResult(PluginResult cr, String callbackId) {
|
||||||
|
nativeToJsMessageQueue.addPluginResult(cr, callbackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PluginManager getPluginManager() {
|
||||||
|
return pluginManager;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public CordovaPreferences getPreferences() {
|
||||||
|
return preferences;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public ICordovaCookieManager getCookieManager() {
|
||||||
|
return engine.getCookieManager();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public CordovaResourceApi getResourceApi() {
|
||||||
|
return resourceApi;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public CordovaWebViewEngine getEngine() {
|
||||||
|
return engine;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public View getView() {
|
||||||
|
return engine.getView();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Context getContext() {
|
||||||
|
return engine.getView().getContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendJavascriptEvent(String event) {
|
||||||
|
if (appPlugin == null) {
|
||||||
|
appPlugin = (CoreAndroid)pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appPlugin == null) {
|
||||||
|
LOG.w(TAG, "Unable to fire event without existing plugin");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
appPlugin.fireJavascriptEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setButtonPlumbedToJs(int keyCode, boolean override) {
|
||||||
|
switch (keyCode) {
|
||||||
|
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
||||||
|
case KeyEvent.KEYCODE_VOLUME_UP:
|
||||||
|
case KeyEvent.KEYCODE_BACK:
|
||||||
|
case KeyEvent.KEYCODE_MENU:
|
||||||
|
// TODO: Why are search and menu buttons handled separately?
|
||||||
|
if (override) {
|
||||||
|
boundKeyCodes.add(keyCode);
|
||||||
|
} else {
|
||||||
|
boundKeyCodes.remove(keyCode);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unsupported keycode: " + keyCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isButtonPlumbedToJs(int keyCode) {
|
||||||
|
return boundKeyCodes.contains(keyCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object postMessage(String id, Object data) {
|
||||||
|
return pluginManager.postMessage(id, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Engine method proxies:
|
||||||
|
@Override
|
||||||
|
public String getUrl() {
|
||||||
|
return engine.getUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopLoading() {
|
||||||
|
// Clear timeout flag
|
||||||
|
loadUrlTimeout++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canGoBack() {
|
||||||
|
return engine.canGoBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearCache() {
|
||||||
|
engine.clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public void clearCache(boolean b) {
|
||||||
|
engine.clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearHistory() {
|
||||||
|
engine.clearHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean backHistory() {
|
||||||
|
return engine.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
/////// LifeCycle methods ///////
|
||||||
|
@Override
|
||||||
|
public void onNewIntent(Intent intent) {
|
||||||
|
if (this.pluginManager != null) {
|
||||||
|
this.pluginManager.onNewIntent(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void handlePause(boolean keepRunning) {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hasPausedEver = true;
|
||||||
|
pluginManager.onPause(keepRunning);
|
||||||
|
sendJavascriptEvent("pause");
|
||||||
|
|
||||||
|
// If app doesn't want to run in background
|
||||||
|
if (!keepRunning) {
|
||||||
|
// Pause JavaScript timers. This affects all webviews within the app!
|
||||||
|
engine.setPaused(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void handleResume(boolean keepRunning) {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resume JavaScript timers. This affects all webviews within the app!
|
||||||
|
engine.setPaused(false);
|
||||||
|
this.pluginManager.onResume(keepRunning);
|
||||||
|
|
||||||
|
// In order to match the behavior of the other platforms, we only send onResume after an
|
||||||
|
// onPause has occurred. The resume event might still be sent if the Activity was killed
|
||||||
|
// while waiting for the result of an external Activity once the result is obtained
|
||||||
|
if (hasPausedEver) {
|
||||||
|
sendJavascriptEvent("resume");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void handleStart() {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pluginManager.onStart();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void handleStop() {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pluginManager.onStop();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void handleDestroy() {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Cancel pending timeout timer.
|
||||||
|
loadUrlTimeout++;
|
||||||
|
|
||||||
|
// Forward to plugins
|
||||||
|
this.pluginManager.onDestroy();
|
||||||
|
|
||||||
|
// TODO: about:blank is a bit special (and the default URL for new frames)
|
||||||
|
// We should use a blank data: url instead so it's more obvious
|
||||||
|
this.loadUrl("about:blank");
|
||||||
|
|
||||||
|
// TODO: Should not destroy webview until after about:blank is done loading.
|
||||||
|
engine.destroy();
|
||||||
|
hideCustomView();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class EngineClient implements CordovaWebViewEngine.Client {
|
||||||
|
@Override
|
||||||
|
public void clearLoadTimeoutTimer() {
|
||||||
|
loadUrlTimeout++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageStarted(String newUrl) {
|
||||||
|
LOG.d(TAG, "onPageDidNavigate(" + newUrl + ")");
|
||||||
|
boundKeyCodes.clear();
|
||||||
|
pluginManager.onReset();
|
||||||
|
pluginManager.postMessage("onPageStarted", newUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceivedError(int errorCode, String description, String failingUrl) {
|
||||||
|
clearLoadTimeoutTimer();
|
||||||
|
JSONObject data = new JSONObject();
|
||||||
|
try {
|
||||||
|
data.put("errorCode", errorCode);
|
||||||
|
data.put("description", description);
|
||||||
|
data.put("url", failingUrl);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
pluginManager.postMessage("onReceivedError", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageFinishedLoading(String url) {
|
||||||
|
LOG.d(TAG, "onPageFinished(" + url + ")");
|
||||||
|
|
||||||
|
clearLoadTimeoutTimer();
|
||||||
|
|
||||||
|
// Broadcast message that page has loaded
|
||||||
|
pluginManager.postMessage("onPageFinished", url);
|
||||||
|
|
||||||
|
// Make app visible after 2 sec in case there was a JS error and Cordova JS never initialized correctly
|
||||||
|
if (engine.getView().getVisibility() != View.VISIBLE) {
|
||||||
|
Thread t = new Thread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
Thread.sleep(2000);
|
||||||
|
if (cordova.getActivity() != null) {
|
||||||
|
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
pluginManager.postMessage("spinner", "stop");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
LOG.d(TAG, "Cordova activity does not exist.");
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown if blank loaded
|
||||||
|
if (url.equals("about:blank")) {
|
||||||
|
pluginManager.postMessage("exit", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean onDispatchKeyEvent(KeyEvent event) {
|
||||||
|
int keyCode = event.getKeyCode();
|
||||||
|
boolean isBackButton = keyCode == KeyEvent.KEYCODE_BACK;
|
||||||
|
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||||
|
if (isBackButton && mCustomView != null) {
|
||||||
|
return true;
|
||||||
|
} else if (boundKeyCodes.contains(keyCode)) {
|
||||||
|
return true;
|
||||||
|
} else if (isBackButton) {
|
||||||
|
return engine.canGoBack();
|
||||||
|
}
|
||||||
|
} else if (event.getAction() == KeyEvent.ACTION_UP) {
|
||||||
|
if (isBackButton && mCustomView != null) {
|
||||||
|
hideCustomView();
|
||||||
|
return true;
|
||||||
|
} else if (boundKeyCodes.contains(keyCode)) {
|
||||||
|
String eventName = null;
|
||||||
|
switch (keyCode) {
|
||||||
|
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
||||||
|
eventName = "volumedownbutton";
|
||||||
|
break;
|
||||||
|
case KeyEvent.KEYCODE_VOLUME_UP:
|
||||||
|
eventName = "volumeupbutton";
|
||||||
|
break;
|
||||||
|
case KeyEvent.KEYCODE_SEARCH:
|
||||||
|
eventName = "searchbutton";
|
||||||
|
break;
|
||||||
|
case KeyEvent.KEYCODE_MENU:
|
||||||
|
eventName = "menubutton";
|
||||||
|
break;
|
||||||
|
case KeyEvent.KEYCODE_BACK:
|
||||||
|
eventName = "backbutton";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (eventName != null) {
|
||||||
|
sendJavascriptEvent(eventName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (isBackButton) {
|
||||||
|
return engine.goBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onNavigationAttempt(String url) {
|
||||||
|
// Give plugins the chance to handle the url
|
||||||
|
if (pluginManager.onOverrideUrlLoading(url)) {
|
||||||
|
return true;
|
||||||
|
} else if (pluginManager.shouldAllowNavigation(url)) {
|
||||||
|
return false;
|
||||||
|
} else if (pluginManager.shouldOpenExternalUrl(url)) {
|
||||||
|
showWebPage(url, true, false, null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
LOG.w(TAG, "Blocked (possibly sub-frame) navigation to non-allowed URL: " + url);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,410 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.telephony.TelephonyManager;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class exposes methods in Cordova that can be called from JavaScript.
|
||||||
|
*/
|
||||||
|
public class CoreAndroid extends CordovaPlugin {
|
||||||
|
|
||||||
|
public static final String PLUGIN_NAME = "CoreAndroid";
|
||||||
|
protected static final String TAG = "CordovaApp";
|
||||||
|
private BroadcastReceiver telephonyReceiver;
|
||||||
|
private CallbackContext messageChannel;
|
||||||
|
private PluginResult pendingResume;
|
||||||
|
private PluginResult pendingPause;
|
||||||
|
private final Object messageChannelLock = new Object();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an event to be fired on the Javascript side.
|
||||||
|
*
|
||||||
|
* @param action The name of the event to be fired
|
||||||
|
*/
|
||||||
|
public void fireJavascriptEvent(String action) {
|
||||||
|
sendEventMessage(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the context of the Command. This can then be used to do things like
|
||||||
|
* get file paths associated with the Activity.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void pluginInitialize() {
|
||||||
|
this.initTelephonyReceiver();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the request and returns PluginResult.
|
||||||
|
*
|
||||||
|
* @param action The action to execute.
|
||||||
|
* @param args JSONArry of arguments for the plugin.
|
||||||
|
* @param callbackContext The callback context from which we were invoked.
|
||||||
|
* @return A PluginResult object with a status and message.
|
||||||
|
*/
|
||||||
|
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
|
||||||
|
PluginResult.Status status = PluginResult.Status.OK;
|
||||||
|
String result = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (action.equals("clearCache")) {
|
||||||
|
this.clearCache();
|
||||||
|
}
|
||||||
|
else if (action.equals("show")) {
|
||||||
|
// This gets called from JavaScript onCordovaReady to show the webview.
|
||||||
|
// I recommend we change the name of the Message as spinner/stop is not
|
||||||
|
// indicative of what this actually does (shows the webview).
|
||||||
|
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
webView.getPluginManager().postMessage("spinner", "stop");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (action.equals("loadUrl")) {
|
||||||
|
this.loadUrl(args.getString(0), args.optJSONObject(1));
|
||||||
|
}
|
||||||
|
else if (action.equals("cancelLoadUrl")) {
|
||||||
|
//this.cancelLoadUrl();
|
||||||
|
}
|
||||||
|
else if (action.equals("clearHistory")) {
|
||||||
|
this.clearHistory();
|
||||||
|
}
|
||||||
|
else if (action.equals("backHistory")) {
|
||||||
|
this.backHistory();
|
||||||
|
}
|
||||||
|
else if (action.equals("overrideButton")) {
|
||||||
|
this.overrideButton(args.getString(0), args.getBoolean(1));
|
||||||
|
}
|
||||||
|
else if (action.equals("overrideBackbutton")) {
|
||||||
|
this.overrideBackbutton(args.getBoolean(0));
|
||||||
|
}
|
||||||
|
else if (action.equals("exitApp")) {
|
||||||
|
this.exitApp();
|
||||||
|
}
|
||||||
|
else if (action.equals("messageChannel")) {
|
||||||
|
synchronized(messageChannelLock) {
|
||||||
|
messageChannel = callbackContext;
|
||||||
|
if (pendingPause != null) {
|
||||||
|
sendEventMessage(pendingPause);
|
||||||
|
pendingPause = null;
|
||||||
|
}
|
||||||
|
if (pendingResume != null) {
|
||||||
|
sendEventMessage(pendingResume);
|
||||||
|
pendingResume = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
callbackContext.sendPluginResult(new PluginResult(status, result));
|
||||||
|
return true;
|
||||||
|
} catch (JSONException e) {
|
||||||
|
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// LOCAL METHODS
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the resource cache.
|
||||||
|
*/
|
||||||
|
public void clearCache() {
|
||||||
|
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
webView.clearCache();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the url into the webview.
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
* @param props Properties that can be passed in to the Cordova activity (i.e. loadingDialog, wait, ...)
|
||||||
|
* @throws JSONException
|
||||||
|
*/
|
||||||
|
public void loadUrl(String url, JSONObject props) throws JSONException {
|
||||||
|
LOG.d("App", "App.loadUrl("+url+","+props+")");
|
||||||
|
int wait = 0;
|
||||||
|
boolean openExternal = false;
|
||||||
|
boolean clearHistory = false;
|
||||||
|
|
||||||
|
// If there are properties, then set them on the Activity
|
||||||
|
HashMap<String, Object> params = new HashMap<String, Object>();
|
||||||
|
if (props != null) {
|
||||||
|
JSONArray keys = props.names();
|
||||||
|
for (int i = 0; i < keys.length(); i++) {
|
||||||
|
String key = keys.getString(i);
|
||||||
|
if (key.equals("wait")) {
|
||||||
|
wait = props.getInt(key);
|
||||||
|
}
|
||||||
|
else if (key.equalsIgnoreCase("openexternal")) {
|
||||||
|
openExternal = props.getBoolean(key);
|
||||||
|
}
|
||||||
|
else if (key.equalsIgnoreCase("clearhistory")) {
|
||||||
|
clearHistory = props.getBoolean(key);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Object value = props.get(key);
|
||||||
|
if (value == null) {
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (value.getClass().equals(String.class)) {
|
||||||
|
params.put(key, (String)value);
|
||||||
|
}
|
||||||
|
else if (value.getClass().equals(Boolean.class)) {
|
||||||
|
params.put(key, (Boolean)value);
|
||||||
|
}
|
||||||
|
else if (value.getClass().equals(Integer.class)) {
|
||||||
|
params.put(key, (Integer)value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If wait property, then delay loading
|
||||||
|
|
||||||
|
if (wait > 0) {
|
||||||
|
try {
|
||||||
|
synchronized(this) {
|
||||||
|
this.wait(wait);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.webView.showWebPage(url, openExternal, clearHistory, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear page history for the app.
|
||||||
|
*/
|
||||||
|
public void clearHistory() {
|
||||||
|
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
webView.clearHistory();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to previous page displayed.
|
||||||
|
* This is the same as pressing the backbutton on Android device.
|
||||||
|
*/
|
||||||
|
public void backHistory() {
|
||||||
|
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
webView.backHistory();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override the default behavior of the Android back button.
|
||||||
|
* If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired.
|
||||||
|
*
|
||||||
|
* @param override T=override, F=cancel override
|
||||||
|
*/
|
||||||
|
public void overrideBackbutton(boolean override) {
|
||||||
|
LOG.i("App", "WARNING: Back Button Default Behavior will be overridden. The backbutton event will be fired!");
|
||||||
|
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_BACK, override);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override the default behavior of the Android volume buttons.
|
||||||
|
* If overridden, when the volume button is pressed, the "volume[up|down]button" JavaScript event will be fired.
|
||||||
|
*
|
||||||
|
* @param button volumeup, volumedown
|
||||||
|
* @param override T=override, F=cancel override
|
||||||
|
*/
|
||||||
|
public void overrideButton(String button, boolean override) {
|
||||||
|
LOG.i("App", "WARNING: Volume Button Default Behavior will be overridden. The volume event will be fired!");
|
||||||
|
if (button.equals("volumeup")) {
|
||||||
|
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_UP, override);
|
||||||
|
}
|
||||||
|
else if (button.equals("volumedown")) {
|
||||||
|
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_DOWN, override);
|
||||||
|
}
|
||||||
|
else if (button.equals("menubutton")) {
|
||||||
|
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_MENU, override);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether the Android back button is overridden by the user.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public boolean isBackbuttonOverridden() {
|
||||||
|
return webView.isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exit the Android application.
|
||||||
|
*/
|
||||||
|
public void exitApp() {
|
||||||
|
this.webView.getPluginManager().postMessage("exit", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen for telephony events: RINGING, OFFHOOK and IDLE
|
||||||
|
* Send these events to all plugins using
|
||||||
|
* CordovaActivity.onMessage("telephone", "ringing" | "offhook" | "idle")
|
||||||
|
*/
|
||||||
|
private void initTelephonyReceiver() {
|
||||||
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
|
intentFilter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
|
||||||
|
//final CordovaInterface mycordova = this.cordova;
|
||||||
|
this.telephonyReceiver = new BroadcastReceiver() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
|
||||||
|
// If state has changed
|
||||||
|
if ((intent != null) && intent.getAction().equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
|
||||||
|
if (intent.hasExtra(TelephonyManager.EXTRA_STATE)) {
|
||||||
|
String extraData = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
|
||||||
|
if (extraData.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
|
||||||
|
LOG.i(TAG, "Telephone RINGING");
|
||||||
|
webView.getPluginManager().postMessage("telephone", "ringing");
|
||||||
|
}
|
||||||
|
else if (extraData.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) {
|
||||||
|
LOG.i(TAG, "Telephone OFFHOOK");
|
||||||
|
webView.getPluginManager().postMessage("telephone", "offhook");
|
||||||
|
}
|
||||||
|
else if (extraData.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
|
||||||
|
LOG.i(TAG, "Telephone IDLE");
|
||||||
|
webView.getPluginManager().postMessage("telephone", "idle");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register the receiver
|
||||||
|
webView.getContext().registerReceiver(this.telephonyReceiver, intentFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendEventMessage(String action) {
|
||||||
|
JSONObject obj = new JSONObject();
|
||||||
|
try {
|
||||||
|
obj.put("action", action);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
LOG.e(TAG, "Failed to create event message", e);
|
||||||
|
}
|
||||||
|
PluginResult result = new PluginResult(PluginResult.Status.OK, obj);
|
||||||
|
|
||||||
|
if (messageChannel == null) {
|
||||||
|
LOG.i(TAG, "Request to send event before messageChannel initialised: " + action);
|
||||||
|
if ("pause".equals(action)) {
|
||||||
|
pendingPause = result;
|
||||||
|
} else if ("resume".equals(action)) {
|
||||||
|
// When starting normally onPause then onResume is called
|
||||||
|
pendingPause = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendEventMessage(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendEventMessage(PluginResult payload) {
|
||||||
|
payload.setKeepCallback(true);
|
||||||
|
if (messageChannel != null) {
|
||||||
|
messageChannel.sendPluginResult(payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Unregister the receiver
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public void onDestroy()
|
||||||
|
{
|
||||||
|
webView.getContext().unregisterReceiver(this.telephonyReceiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to send the resume event in the case that the Activity is destroyed by the OS
|
||||||
|
*
|
||||||
|
* @param resumeEvent PluginResult containing the payload for the resume event to be fired
|
||||||
|
*/
|
||||||
|
public void sendResumeEvent(PluginResult resumeEvent) {
|
||||||
|
// This operation must be synchronized because plugin results that trigger resume
|
||||||
|
// events can be processed asynchronously
|
||||||
|
synchronized(messageChannelLock) {
|
||||||
|
if (messageChannel != null) {
|
||||||
|
sendEventMessage(resumeEvent);
|
||||||
|
} else {
|
||||||
|
// Might get called before the page loads, so we need to store it until the
|
||||||
|
// messageChannel gets created
|
||||||
|
this.pendingResume = resumeEvent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This needs to be implemented if you wish to use the Camera Plugin or other plugins
|
||||||
|
* that read the Build Configuration.
|
||||||
|
*
|
||||||
|
* Thanks to Phil@Medtronic and Graham Borland for finding the answer and posting it to
|
||||||
|
* StackOverflow. This is annoying as hell!
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static Object getBuildConfigValue(Context ctx, String key)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class<?> clazz = Class.forName(ctx.getClass().getPackage().getName() + ".BuildConfig");
|
||||||
|
Field field = clazz.getField(key);
|
||||||
|
return field.get(null);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
LOG.d(TAG, "Unable to get the BuildConfig, is this built with ANT?");
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
LOG.d(TAG, key + " is not a valid field. Check your build.gradle");
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
LOG.d(TAG, "Illegal Access Exception: Let's print a stack trace.");
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
LOG.d(TAG, "Null Pointer Exception: Let's print a stack trace.");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Any exposed Javascript API MUST implement these three things!
|
||||||
|
*/
|
||||||
|
public interface ExposedJsApi {
|
||||||
|
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException;
|
||||||
|
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException;
|
||||||
|
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies interface for handling certificate requests.
|
||||||
|
*/
|
||||||
|
public interface ICordovaClientCertRequest {
|
||||||
|
/**
|
||||||
|
* Cancel this request
|
||||||
|
*/
|
||||||
|
public void cancel();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the host name of the server requesting the certificate.
|
||||||
|
*/
|
||||||
|
public String getHost();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the acceptable types of asymmetric keys (can be null).
|
||||||
|
*/
|
||||||
|
public String[] getKeyTypes();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the port number of the server requesting the certificate.
|
||||||
|
*/
|
||||||
|
public int getPort();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the acceptable certificate issuers for the certificate matching the private key (can be null).
|
||||||
|
*/
|
||||||
|
public Principal[] getPrincipals();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ignore the request for now. Do not remember user's choice.
|
||||||
|
*/
|
||||||
|
public void ignore();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Proceed with the specified private key and client certificate chain. Remember the user's positive choice and use it for future requests.
|
||||||
|
*
|
||||||
|
* @param privateKey The privateKey
|
||||||
|
* @param chain The certificate chain
|
||||||
|
*/
|
||||||
|
public void proceed(PrivateKey privateKey, X509Certificate[] chain);
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
public interface ICordovaCookieManager {
|
||||||
|
|
||||||
|
public void setCookiesEnabled(boolean accept);
|
||||||
|
|
||||||
|
public void setCookie(final String url, final String value);
|
||||||
|
|
||||||
|
public String getCookie(final String url);
|
||||||
|
|
||||||
|
public void clearCookies();
|
||||||
|
|
||||||
|
public void flush();
|
||||||
|
};
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies interface for HTTP auth handler object which is used to handle auth requests and
|
||||||
|
* specifying user credentials.
|
||||||
|
*/
|
||||||
|
public interface ICordovaHttpAuthHandler {
|
||||||
|
/**
|
||||||
|
* Instructs the WebView to cancel the authentication request.
|
||||||
|
*/
|
||||||
|
public void cancel ();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instructs the WebView to proceed with the authentication with the given credentials.
|
||||||
|
*
|
||||||
|
* @param username The user name
|
||||||
|
* @param password The password
|
||||||
|
*/
|
||||||
|
public void proceed (String username, String password);
|
||||||
|
}
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log to Android logging system.
|
||||||
|
*
|
||||||
|
* Log message can be a string or a printf formatted string with arguments.
|
||||||
|
* See http://developer.android.com/reference/java/util/Formatter.html
|
||||||
|
*/
|
||||||
|
public class LOG {
|
||||||
|
|
||||||
|
public static final int VERBOSE = Log.VERBOSE;
|
||||||
|
public static final int DEBUG = Log.DEBUG;
|
||||||
|
public static final int INFO = Log.INFO;
|
||||||
|
public static final int WARN = Log.WARN;
|
||||||
|
public static final int ERROR = Log.ERROR;
|
||||||
|
|
||||||
|
// Current log level
|
||||||
|
public static int LOGLEVEL = Log.ERROR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current log level.
|
||||||
|
*
|
||||||
|
* @param logLevel
|
||||||
|
*/
|
||||||
|
public static void setLogLevel(int logLevel) {
|
||||||
|
LOGLEVEL = logLevel;
|
||||||
|
Log.i("CordovaLog", "Changing log level to " + logLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current log level.
|
||||||
|
*
|
||||||
|
* @param logLevel
|
||||||
|
*/
|
||||||
|
public static void setLogLevel(String logLevel) {
|
||||||
|
if ("VERBOSE".equals(logLevel)) LOGLEVEL = VERBOSE;
|
||||||
|
else if ("DEBUG".equals(logLevel)) LOGLEVEL = DEBUG;
|
||||||
|
else if ("INFO".equals(logLevel)) LOGLEVEL = INFO;
|
||||||
|
else if ("WARN".equals(logLevel)) LOGLEVEL = WARN;
|
||||||
|
else if ("ERROR".equals(logLevel)) LOGLEVEL = ERROR;
|
||||||
|
Log.i("CordovaLog", "Changing log level to " + logLevel + "(" + LOGLEVEL + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if log level will be logged
|
||||||
|
*
|
||||||
|
* @param logLevel
|
||||||
|
* @return true if the parameter passed in is greater than or equal to the current log level
|
||||||
|
*/
|
||||||
|
public static boolean isLoggable(int logLevel) {
|
||||||
|
return (logLevel >= LOGLEVEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verbose log message.
|
||||||
|
*
|
||||||
|
* @param tag
|
||||||
|
* @param s
|
||||||
|
*/
|
||||||
|
public static void v(String tag, String s) {
|
||||||
|
if (LOG.VERBOSE >= LOGLEVEL) Log.v(tag, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debug log message.
|
||||||
|
*
|
||||||
|
* @param tag
|
||||||
|
* @param s
|
||||||
|
*/
|
||||||
|
public static void d(String tag, String s) {
|
||||||
|
if (LOG.DEBUG >= LOGLEVEL) Log.d(tag, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Info log message.
|
||||||
|
*
|
||||||
|
* @param tag
|
||||||
|
* @param s
|
||||||
|
*/
|
||||||
|
public static void i(String tag, String s) {
|
||||||
|
if (LOG.INFO >= LOGLEVEL) Log.i(tag, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Warning log message.
|
||||||
|
*
|
||||||
|
* @param tag
|
||||||
|
* @param s
|
||||||
|
*/
|
||||||
|
public static void w(String tag, String s) {
|
||||||
|
if (LOG.WARN >= LOGLEVEL) Log.w(tag, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error log message.
|
||||||
|
*
|
||||||
|
* @param tag
|
||||||
|
* @param s
|
||||||
|
*/
|
||||||
|
public static void e(String tag, String s) {
|
||||||
|
if (LOG.ERROR >= LOGLEVEL) Log.e(tag, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verbose log message.
|
||||||
|
*
|
||||||
|
* @param tag
|
||||||
|
* @param s
|
||||||
|
* @param e
|
||||||
|
*/
|
||||||
|
public static void v(String tag, String s, Throwable e) {
|
||||||
|
if (LOG.VERBOSE >= LOGLEVEL) Log.v(tag, s, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debug log message.
|
||||||
|
*
|
||||||
|
* @param tag
|
||||||
|
* @param s
|
||||||
|
* @param e
|
||||||
|
*/
|
||||||
|
public static void d(String tag, String s, Throwable e) {
|
||||||
|
if (LOG.DEBUG >= LOGLEVEL) Log.d(tag, s, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Info log message.
|
||||||
|
*
|
||||||
|
* @param tag
|
||||||
|
* @param s
|
||||||
|
* @param e
|
||||||
|
*/
|
||||||
|
public static void i(String tag, String s, Throwable e) {
|
||||||
|
if (LOG.INFO >= LOGLEVEL) Log.i(tag, s, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Warning log message.
|
||||||
|
*
|
||||||
|
* @param tag
|
||||||
|
* @param e
|
||||||
|
*/
|
||||||
|
public static void w(String tag, Throwable e) {
|
||||||
|
if (LOG.WARN >= LOGLEVEL) Log.w(tag, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Warning log message.
|
||||||
|
*
|
||||||
|
* @param tag
|
||||||
|
* @param s
|
||||||
|
* @param e
|
||||||
|
*/
|
||||||
|
public static void w(String tag, String s, Throwable e) {
|
||||||
|
if (LOG.WARN >= LOGLEVEL) Log.w(tag, s, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error log message.
|
||||||
|
*
|
||||||
|
* @param tag
|
||||||
|
* @param s
|
||||||
|
* @param e
|
||||||
|
*/
|
||||||
|
public static void e(String tag, String s, Throwable e) {
|
||||||
|
if (LOG.ERROR >= LOGLEVEL) Log.e(tag, s, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verbose log message with printf formatting.
|
||||||
|
*
|
||||||
|
* @param tag
|
||||||
|
* @param s
|
||||||
|
* @param args
|
||||||
|
*/
|
||||||
|
public static void v(String tag, String s, Object... args) {
|
||||||
|
if (LOG.VERBOSE >= LOGLEVEL) Log.v(tag, String.format(s, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debug log message with printf formatting.
|
||||||
|
*
|
||||||
|
* @param tag
|
||||||
|
* @param s
|
||||||
|
* @param args
|
||||||
|
*/
|
||||||
|
public static void d(String tag, String s, Object... args) {
|
||||||
|
if (LOG.DEBUG >= LOGLEVEL) Log.d(tag, String.format(s, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Info log message with printf formatting.
|
||||||
|
*
|
||||||
|
* @param tag
|
||||||
|
* @param s
|
||||||
|
* @param args
|
||||||
|
*/
|
||||||
|
public static void i(String tag, String s, Object... args) {
|
||||||
|
if (LOG.INFO >= LOGLEVEL) Log.i(tag, String.format(s, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Warning log message with printf formatting.
|
||||||
|
*
|
||||||
|
* @param tag
|
||||||
|
* @param s
|
||||||
|
* @param args
|
||||||
|
*/
|
||||||
|
public static void w(String tag, String s, Object... args) {
|
||||||
|
if (LOG.WARN >= LOGLEVEL) Log.w(tag, String.format(s, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error log message with printf formatting.
|
||||||
|
*
|
||||||
|
* @param tag
|
||||||
|
* @param s
|
||||||
|
* @param args
|
||||||
|
*/
|
||||||
|
public static void e(String tag, String s, Object... args) {
|
||||||
|
if (LOG.ERROR >= LOGLEVEL) Log.e(tag, String.format(s, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,552 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the list of messages to be sent to the WebView.
|
||||||
|
*/
|
||||||
|
public class NativeToJsMessageQueue {
|
||||||
|
private static final String LOG_TAG = "JsMessageQueue";
|
||||||
|
|
||||||
|
// Set this to true to force plugin results to be encoding as
|
||||||
|
// JS instead of the custom format (useful for benchmarking).
|
||||||
|
// Doesn't work for multipart messages.
|
||||||
|
private static final boolean FORCE_ENCODE_USING_EVAL = false;
|
||||||
|
|
||||||
|
// Disable sending back native->JS messages during an exec() when the active
|
||||||
|
// exec() is asynchronous. Set this to true when running bridge benchmarks.
|
||||||
|
static final boolean DISABLE_EXEC_CHAINING = false;
|
||||||
|
|
||||||
|
// A hopefully reasonable upper limit of how much combined payload data
|
||||||
|
// to send to the JavaScript in one shot.
|
||||||
|
// This currently only chops up on message boundaries.
|
||||||
|
// It may be useful to split and reassemble response messages someday.
|
||||||
|
private static int COMBINED_RESPONSE_CUTOFF = 16 * 1024 * 1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When true, the active listener is not fired upon enqueue. When set to false,
|
||||||
|
* the active listener will be fired if the queue is non-empty.
|
||||||
|
*/
|
||||||
|
private boolean paused;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of JavaScript statements to be sent to JavaScript.
|
||||||
|
*/
|
||||||
|
private final LinkedList<JsMessage> queue = new LinkedList<JsMessage>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The array of listeners that can be used to send messages to JS.
|
||||||
|
*/
|
||||||
|
private ArrayList<BridgeMode> bridgeModes = new ArrayList<BridgeMode>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When null, the bridge is disabled. This occurs during page transitions.
|
||||||
|
* When disabled, all callbacks are dropped since they are assumed to be
|
||||||
|
* relevant to the previous page.
|
||||||
|
*/
|
||||||
|
private BridgeMode activeBridgeMode;
|
||||||
|
|
||||||
|
public void addBridgeMode(BridgeMode bridgeMode) {
|
||||||
|
bridgeModes.add(bridgeMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBridgeEnabled() {
|
||||||
|
return activeBridgeMode != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return queue.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the bridge mode.
|
||||||
|
*/
|
||||||
|
public void setBridgeMode(int value) {
|
||||||
|
if (value < -1 || value >= bridgeModes.size()) {
|
||||||
|
LOG.d(LOG_TAG, "Invalid NativeToJsBridgeMode: " + value);
|
||||||
|
} else {
|
||||||
|
BridgeMode newMode = value < 0 ? null : bridgeModes.get(value);
|
||||||
|
if (newMode != activeBridgeMode) {
|
||||||
|
LOG.d(LOG_TAG, "Set native->JS mode to " + (newMode == null ? "null" : newMode.getClass().getSimpleName()));
|
||||||
|
synchronized (this) {
|
||||||
|
activeBridgeMode = newMode;
|
||||||
|
if (newMode != null) {
|
||||||
|
newMode.reset();
|
||||||
|
if (!paused && !queue.isEmpty()) {
|
||||||
|
newMode.onNativeToJsMessageAvailable(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all messages and resets to the default bridge mode.
|
||||||
|
*/
|
||||||
|
public void reset() {
|
||||||
|
synchronized (this) {
|
||||||
|
queue.clear();
|
||||||
|
setBridgeMode(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int calculatePackedMessageLength(JsMessage message) {
|
||||||
|
int messageLen = message.calculateEncodedLength();
|
||||||
|
String messageLenStr = String.valueOf(messageLen);
|
||||||
|
return messageLenStr.length() + messageLen + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void packMessage(JsMessage message, StringBuilder sb) {
|
||||||
|
int len = message.calculateEncodedLength();
|
||||||
|
sb.append(len)
|
||||||
|
.append(' ');
|
||||||
|
message.encodeAsMessage(sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combines and returns queued messages combined into a single string.
|
||||||
|
*
|
||||||
|
* Combines as many messages as possible, without exceeding
|
||||||
|
* COMBINED_RESPONSE_CUTOFF in case of multiple response messages.
|
||||||
|
*
|
||||||
|
* Returns null if the queue is empty.
|
||||||
|
*/
|
||||||
|
public String popAndEncode(boolean fromOnlineEvent) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (activeBridgeMode == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
activeBridgeMode.notifyOfFlush(this, fromOnlineEvent);
|
||||||
|
if (queue.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int totalPayloadLen = 0;
|
||||||
|
int numMessagesToSend = 0;
|
||||||
|
for (JsMessage message : queue) {
|
||||||
|
int messageSize = calculatePackedMessageLength(message);
|
||||||
|
if (numMessagesToSend > 0 &&
|
||||||
|
COMBINED_RESPONSE_CUTOFF > 0 &&
|
||||||
|
totalPayloadLen + messageSize > COMBINED_RESPONSE_CUTOFF
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
totalPayloadLen += messageSize;
|
||||||
|
numMessagesToSend += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder(totalPayloadLen);
|
||||||
|
for (int i = 0; i < numMessagesToSend; ++i) {
|
||||||
|
JsMessage message = queue.removeFirst();
|
||||||
|
packMessage(message, sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!queue.isEmpty()) {
|
||||||
|
// Attach a char to indicate that there are more messages pending.
|
||||||
|
sb.append('*');
|
||||||
|
}
|
||||||
|
String ret = sb.toString();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as popAndEncode(), except encodes in a form that can be executed as JS.
|
||||||
|
*/
|
||||||
|
public String popAndEncodeAsJs() {
|
||||||
|
synchronized (this) {
|
||||||
|
int length = queue.size();
|
||||||
|
if (length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int totalPayloadLen = 0;
|
||||||
|
int numMessagesToSend = 0;
|
||||||
|
for (JsMessage message : queue) {
|
||||||
|
int messageSize = message.calculateEncodedLength() + 50; // overestimate.
|
||||||
|
if (numMessagesToSend > 0 &&
|
||||||
|
COMBINED_RESPONSE_CUTOFF > 0 &&
|
||||||
|
totalPayloadLen + messageSize > COMBINED_RESPONSE_CUTOFF
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
totalPayloadLen += messageSize;
|
||||||
|
numMessagesToSend += 1;
|
||||||
|
}
|
||||||
|
boolean willSendAllMessages = numMessagesToSend == queue.size();
|
||||||
|
StringBuilder sb = new StringBuilder(totalPayloadLen + (willSendAllMessages ? 0 : 100));
|
||||||
|
// Wrap each statement in a try/finally so that if one throws it does
|
||||||
|
// not affect the next.
|
||||||
|
for (int i = 0; i < numMessagesToSend; ++i) {
|
||||||
|
JsMessage message = queue.removeFirst();
|
||||||
|
if (willSendAllMessages && (i + 1 == numMessagesToSend)) {
|
||||||
|
message.encodeAsJsMessage(sb);
|
||||||
|
} else {
|
||||||
|
sb.append("try{");
|
||||||
|
message.encodeAsJsMessage(sb);
|
||||||
|
sb.append("}finally{");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!willSendAllMessages) {
|
||||||
|
sb.append("window.setTimeout(function(){cordova.require('cordova/plugin/android/polling').pollOnce();},0);");
|
||||||
|
}
|
||||||
|
for (int i = willSendAllMessages ? 1 : 0; i < numMessagesToSend; ++i) {
|
||||||
|
sb.append('}');
|
||||||
|
}
|
||||||
|
String ret = sb.toString();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a JavaScript statement to the list.
|
||||||
|
*/
|
||||||
|
public void addJavaScript(String statement) {
|
||||||
|
enqueueMessage(new JsMessage(statement));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a JavaScript statement to the list.
|
||||||
|
*/
|
||||||
|
public void addPluginResult(PluginResult result, String callbackId) {
|
||||||
|
if (callbackId == null) {
|
||||||
|
LOG.e(LOG_TAG, "Got plugin result with no callbackId", new Throwable());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Don't send anything if there is no result and there is no need to
|
||||||
|
// clear the callbacks.
|
||||||
|
boolean noResult = result.getStatus() == PluginResult.Status.NO_RESULT.ordinal();
|
||||||
|
boolean keepCallback = result.getKeepCallback();
|
||||||
|
if (noResult && keepCallback) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JsMessage message = new JsMessage(result, callbackId);
|
||||||
|
if (FORCE_ENCODE_USING_EVAL) {
|
||||||
|
StringBuilder sb = new StringBuilder(message.calculateEncodedLength() + 50);
|
||||||
|
message.encodeAsJsMessage(sb);
|
||||||
|
message = new JsMessage(sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
enqueueMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enqueueMessage(JsMessage message) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (activeBridgeMode == null) {
|
||||||
|
LOG.d(LOG_TAG, "Dropping Native->JS message due to disabled bridge");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
queue.add(message);
|
||||||
|
if (!paused) {
|
||||||
|
activeBridgeMode.onNativeToJsMessageAvailable(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPaused(boolean value) {
|
||||||
|
if (paused && value) {
|
||||||
|
// This should never happen. If a use-case for it comes up, we should
|
||||||
|
// change pause to be a counter.
|
||||||
|
LOG.e(LOG_TAG, "nested call to setPaused detected.", new Throwable());
|
||||||
|
}
|
||||||
|
paused = value;
|
||||||
|
if (!value) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (!queue.isEmpty() && activeBridgeMode != null) {
|
||||||
|
activeBridgeMode.onNativeToJsMessageAvailable(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static abstract class BridgeMode {
|
||||||
|
public abstract void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue);
|
||||||
|
public void notifyOfFlush(NativeToJsMessageQueue queue, boolean fromOnlineEvent) {}
|
||||||
|
public void reset() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Uses JS polls for messages on a timer.. */
|
||||||
|
public static class NoOpBridgeMode extends BridgeMode {
|
||||||
|
@Override public void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Uses webView.loadUrl("javascript:") to execute messages. */
|
||||||
|
public static class LoadUrlBridgeMode extends BridgeMode {
|
||||||
|
private final CordovaWebViewEngine engine;
|
||||||
|
private final CordovaInterface cordova;
|
||||||
|
|
||||||
|
public LoadUrlBridgeMode(CordovaWebViewEngine engine, CordovaInterface cordova) {
|
||||||
|
this.engine = engine;
|
||||||
|
this.cordova = cordova;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
|
||||||
|
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
String js = queue.popAndEncodeAsJs();
|
||||||
|
if (js != null) {
|
||||||
|
engine.loadUrl("javascript:" + js, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Uses online/offline events to tell the JS when to poll for messages. */
|
||||||
|
public static class OnlineEventsBridgeMode extends BridgeMode {
|
||||||
|
private final OnlineEventsBridgeModeDelegate delegate;
|
||||||
|
private boolean online;
|
||||||
|
private boolean ignoreNextFlush;
|
||||||
|
|
||||||
|
public interface OnlineEventsBridgeModeDelegate {
|
||||||
|
void setNetworkAvailable(boolean value);
|
||||||
|
void runOnUiThread(Runnable r);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OnlineEventsBridgeMode(OnlineEventsBridgeModeDelegate delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
delegate.runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
online = false;
|
||||||
|
// If the following call triggers a notifyOfFlush, then ignore it.
|
||||||
|
ignoreNextFlush = true;
|
||||||
|
delegate.setNetworkAvailable(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
|
||||||
|
delegate.runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (!queue.isEmpty()) {
|
||||||
|
ignoreNextFlush = false;
|
||||||
|
delegate.setNetworkAvailable(online);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Track when online/offline events are fired so that we don't fire excess events.
|
||||||
|
@Override
|
||||||
|
public void notifyOfFlush(final NativeToJsMessageQueue queue, boolean fromOnlineEvent) {
|
||||||
|
if (fromOnlineEvent && !ignoreNextFlush) {
|
||||||
|
online = !online;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Uses webView.evaluateJavascript to execute messages. */
|
||||||
|
public static class EvalBridgeMode extends BridgeMode {
|
||||||
|
private final CordovaWebViewEngine engine;
|
||||||
|
private final CordovaInterface cordova;
|
||||||
|
|
||||||
|
public EvalBridgeMode(CordovaWebViewEngine engine, CordovaInterface cordova) {
|
||||||
|
this.engine = engine;
|
||||||
|
this.cordova = cordova;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
|
||||||
|
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
String js = queue.popAndEncodeAsJs();
|
||||||
|
if (js != null) {
|
||||||
|
engine.evaluateJavascript(js, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static class JsMessage {
|
||||||
|
final String jsPayloadOrCallbackId;
|
||||||
|
final PluginResult pluginResult;
|
||||||
|
JsMessage(String js) {
|
||||||
|
if (js == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
jsPayloadOrCallbackId = js;
|
||||||
|
pluginResult = null;
|
||||||
|
}
|
||||||
|
JsMessage(PluginResult pluginResult, String callbackId) {
|
||||||
|
if (callbackId == null || pluginResult == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
jsPayloadOrCallbackId = callbackId;
|
||||||
|
this.pluginResult = pluginResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int calculateEncodedLengthHelper(PluginResult pluginResult) {
|
||||||
|
switch (pluginResult.getMessageType()) {
|
||||||
|
case PluginResult.MESSAGE_TYPE_BOOLEAN: // f or t
|
||||||
|
case PluginResult.MESSAGE_TYPE_NULL: // N
|
||||||
|
return 1;
|
||||||
|
case PluginResult.MESSAGE_TYPE_NUMBER: // n
|
||||||
|
return 1 + pluginResult.getMessage().length();
|
||||||
|
case PluginResult.MESSAGE_TYPE_STRING: // s
|
||||||
|
return 1 + pluginResult.getStrMessage().length();
|
||||||
|
case PluginResult.MESSAGE_TYPE_BINARYSTRING:
|
||||||
|
return 1 + pluginResult.getMessage().length();
|
||||||
|
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
|
||||||
|
return 1 + pluginResult.getMessage().length();
|
||||||
|
case PluginResult.MESSAGE_TYPE_MULTIPART:
|
||||||
|
int ret = 1;
|
||||||
|
for (int i = 0; i < pluginResult.getMultipartMessagesSize(); i++) {
|
||||||
|
int length = calculateEncodedLengthHelper(pluginResult.getMultipartMessage(i));
|
||||||
|
int argLength = String.valueOf(length).length();
|
||||||
|
ret += argLength + 1 + length;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
case PluginResult.MESSAGE_TYPE_JSON:
|
||||||
|
default:
|
||||||
|
return pluginResult.getMessage().length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int calculateEncodedLength() {
|
||||||
|
if (pluginResult == null) {
|
||||||
|
return jsPayloadOrCallbackId.length() + 1;
|
||||||
|
}
|
||||||
|
int statusLen = String.valueOf(pluginResult.getStatus()).length();
|
||||||
|
int ret = 2 + statusLen + 1 + jsPayloadOrCallbackId.length() + 1;
|
||||||
|
return ret + calculateEncodedLengthHelper(pluginResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void encodeAsMessageHelper(StringBuilder sb, PluginResult pluginResult) {
|
||||||
|
switch (pluginResult.getMessageType()) {
|
||||||
|
case PluginResult.MESSAGE_TYPE_BOOLEAN:
|
||||||
|
sb.append(pluginResult.getMessage().charAt(0)); // t or f.
|
||||||
|
break;
|
||||||
|
case PluginResult.MESSAGE_TYPE_NULL: // N
|
||||||
|
sb.append('N');
|
||||||
|
break;
|
||||||
|
case PluginResult.MESSAGE_TYPE_NUMBER: // n
|
||||||
|
sb.append('n')
|
||||||
|
.append(pluginResult.getMessage());
|
||||||
|
break;
|
||||||
|
case PluginResult.MESSAGE_TYPE_STRING: // s
|
||||||
|
sb.append('s');
|
||||||
|
sb.append(pluginResult.getStrMessage());
|
||||||
|
break;
|
||||||
|
case PluginResult.MESSAGE_TYPE_BINARYSTRING: // S
|
||||||
|
sb.append('S');
|
||||||
|
sb.append(pluginResult.getMessage());
|
||||||
|
break;
|
||||||
|
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER: // A
|
||||||
|
sb.append('A');
|
||||||
|
sb.append(pluginResult.getMessage());
|
||||||
|
break;
|
||||||
|
case PluginResult.MESSAGE_TYPE_MULTIPART:
|
||||||
|
sb.append('M');
|
||||||
|
for (int i = 0; i < pluginResult.getMultipartMessagesSize(); i++) {
|
||||||
|
PluginResult multipartMessage = pluginResult.getMultipartMessage(i);
|
||||||
|
sb.append(String.valueOf(calculateEncodedLengthHelper(multipartMessage)));
|
||||||
|
sb.append(' ');
|
||||||
|
encodeAsMessageHelper(sb, multipartMessage);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PluginResult.MESSAGE_TYPE_JSON:
|
||||||
|
default:
|
||||||
|
sb.append(pluginResult.getMessage()); // [ or {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void encodeAsMessage(StringBuilder sb) {
|
||||||
|
if (pluginResult == null) {
|
||||||
|
sb.append('J')
|
||||||
|
.append(jsPayloadOrCallbackId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int status = pluginResult.getStatus();
|
||||||
|
boolean noResult = status == PluginResult.Status.NO_RESULT.ordinal();
|
||||||
|
boolean resultOk = status == PluginResult.Status.OK.ordinal();
|
||||||
|
boolean keepCallback = pluginResult.getKeepCallback();
|
||||||
|
|
||||||
|
sb.append((noResult || resultOk) ? 'S' : 'F')
|
||||||
|
.append(keepCallback ? '1' : '0')
|
||||||
|
.append(status)
|
||||||
|
.append(' ')
|
||||||
|
.append(jsPayloadOrCallbackId)
|
||||||
|
.append(' ');
|
||||||
|
|
||||||
|
encodeAsMessageHelper(sb, pluginResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
void buildJsMessage(StringBuilder sb) {
|
||||||
|
switch (pluginResult.getMessageType()) {
|
||||||
|
case PluginResult.MESSAGE_TYPE_MULTIPART:
|
||||||
|
int size = pluginResult.getMultipartMessagesSize();
|
||||||
|
for (int i=0; i<size; i++) {
|
||||||
|
PluginResult subresult = pluginResult.getMultipartMessage(i);
|
||||||
|
JsMessage submessage = new JsMessage(subresult, jsPayloadOrCallbackId);
|
||||||
|
submessage.buildJsMessage(sb);
|
||||||
|
if (i < (size-1)) {
|
||||||
|
sb.append(",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PluginResult.MESSAGE_TYPE_BINARYSTRING:
|
||||||
|
sb.append("atob('")
|
||||||
|
.append(pluginResult.getMessage())
|
||||||
|
.append("')");
|
||||||
|
break;
|
||||||
|
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
|
||||||
|
sb.append("cordova.require('cordova/base64').toArrayBuffer('")
|
||||||
|
.append(pluginResult.getMessage())
|
||||||
|
.append("')");
|
||||||
|
break;
|
||||||
|
case PluginResult.MESSAGE_TYPE_NULL:
|
||||||
|
sb.append("null");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sb.append(pluginResult.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void encodeAsJsMessage(StringBuilder sb) {
|
||||||
|
if (pluginResult == null) {
|
||||||
|
sb.append(jsPayloadOrCallbackId);
|
||||||
|
} else {
|
||||||
|
int status = pluginResult.getStatus();
|
||||||
|
boolean success = (status == PluginResult.Status.OK.ordinal()) || (status == PluginResult.Status.NO_RESULT.ordinal());
|
||||||
|
sb.append("cordova.callbackFromNative('")
|
||||||
|
.append(jsPayloadOrCallbackId)
|
||||||
|
.append("',")
|
||||||
|
.append(success)
|
||||||
|
.append(",")
|
||||||
|
.append(status)
|
||||||
|
.append(",[");
|
||||||
|
buildJsMessage(sb);
|
||||||
|
sb.append("],")
|
||||||
|
.append(pluginResult.getKeepCallback())
|
||||||
|
.append(");");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides reflective methods for permission requesting and checking so that plugins
|
||||||
|
* written for cordova-android 5.0.0+ can still compile with earlier cordova-android versions.
|
||||||
|
*/
|
||||||
|
public class PermissionHelper {
|
||||||
|
private static final String LOG_TAG = "CordovaPermissionHelper";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests a "dangerous" permission for the application at runtime. This is a helper method
|
||||||
|
* alternative to cordovaInterface.requestPermission() that does not require the project to be
|
||||||
|
* built with cordova-android 5.0.0+
|
||||||
|
*
|
||||||
|
* @param plugin The plugin the permission is being requested for
|
||||||
|
* @param requestCode A requestCode to be passed to the plugin's onRequestPermissionResult()
|
||||||
|
* along with the result of the permission request
|
||||||
|
* @param permission The permission to be requested
|
||||||
|
*/
|
||||||
|
public static void requestPermission(CordovaPlugin plugin, int requestCode, String permission) {
|
||||||
|
PermissionHelper.requestPermissions(plugin, requestCode, new String[] {permission});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests "dangerous" permissions for the application at runtime. This is a helper method
|
||||||
|
* alternative to cordovaInterface.requestPermissions() that does not require the project to be
|
||||||
|
* built with cordova-android 5.0.0+
|
||||||
|
*
|
||||||
|
* @param plugin The plugin the permissions are being requested for
|
||||||
|
* @param requestCode A requestCode to be passed to the plugin's onRequestPermissionResult()
|
||||||
|
* along with the result of the permissions request
|
||||||
|
* @param permissions The permissions to be requested
|
||||||
|
*/
|
||||||
|
public static void requestPermissions(CordovaPlugin plugin, int requestCode, String[] permissions) {
|
||||||
|
plugin.cordova.requestPermissions(plugin, requestCode, permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks at runtime to see if the application has been granted a permission. This is a helper
|
||||||
|
* method alternative to cordovaInterface.hasPermission() that does not require the project to
|
||||||
|
* be built with cordova-android 5.0.0+
|
||||||
|
*
|
||||||
|
* @param plugin The plugin the permission is being checked against
|
||||||
|
* @param permission The permission to be checked
|
||||||
|
*
|
||||||
|
* @return True if the permission has already been granted and false otherwise
|
||||||
|
*/
|
||||||
|
public static boolean hasPermission(CordovaPlugin plugin, String permission) {
|
||||||
|
return plugin.cordova.hasPermission(permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void deliverPermissionResult(CordovaPlugin plugin, int requestCode, String[] permissions) {
|
||||||
|
// Generate the request results
|
||||||
|
int[] requestResults = new int[permissions.length];
|
||||||
|
Arrays.fill(requestResults, PackageManager.PERMISSION_GRANTED);
|
||||||
|
|
||||||
|
try {
|
||||||
|
plugin.onRequestPermissionResult(requestCode, permissions, requestResults);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
LOG.e(LOG_TAG, "JSONException when delivering permissions results", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import org.apache.cordova.CordovaPlugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents a service entry object.
|
||||||
|
*/
|
||||||
|
public final class PluginEntry {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the service that this plugin implements
|
||||||
|
*/
|
||||||
|
public final String service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The plugin class name that implements the service.
|
||||||
|
*/
|
||||||
|
public final String pluginClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pre-instantiated plugin to use for this entry.
|
||||||
|
*/
|
||||||
|
public final CordovaPlugin plugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag that indicates the plugin object should be created when PluginManager is initialized.
|
||||||
|
*/
|
||||||
|
public final boolean onload;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs with a CordovaPlugin already instantiated.
|
||||||
|
*/
|
||||||
|
public PluginEntry(String service, CordovaPlugin plugin) {
|
||||||
|
this(service, plugin.getClass().getName(), true, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param service The name of the service
|
||||||
|
* @param pluginClass The plugin class name
|
||||||
|
* @param onload Create plugin object when HTML page is loaded
|
||||||
|
*/
|
||||||
|
public PluginEntry(String service, String pluginClass, boolean onload) {
|
||||||
|
this(service, pluginClass, onload, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PluginEntry(String service, String pluginClass, boolean onload, CordovaPlugin plugin) {
|
||||||
|
this.service = service;
|
||||||
|
this.pluginClass = pluginClass;
|
||||||
|
this.onload = onload;
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,580 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Debug;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PluginManager is exposed to JavaScript in the Cordova WebView.
|
||||||
|
*
|
||||||
|
* Calling native plugin code can be done by calling PluginManager.exec(...)
|
||||||
|
* from JavaScript.
|
||||||
|
*/
|
||||||
|
public class PluginManager {
|
||||||
|
private static String TAG = "PluginManager";
|
||||||
|
private static final int SLOW_EXEC_WARNING_THRESHOLD = Debug.isDebuggerConnected() ? 60 : 16;
|
||||||
|
|
||||||
|
// List of service entries
|
||||||
|
private final Map<String, CordovaPlugin> pluginMap = Collections.synchronizedMap(new LinkedHashMap<String, CordovaPlugin>());
|
||||||
|
private final Map<String, PluginEntry> entryMap = Collections.synchronizedMap(new LinkedHashMap<String, PluginEntry>());
|
||||||
|
|
||||||
|
private final CordovaInterface ctx;
|
||||||
|
private final CordovaWebView app;
|
||||||
|
private boolean isInitialized;
|
||||||
|
|
||||||
|
private CordovaPlugin permissionRequester;
|
||||||
|
|
||||||
|
public PluginManager(CordovaWebView cordovaWebView, CordovaInterface cordova, Collection<PluginEntry> pluginEntries) {
|
||||||
|
this.ctx = cordova;
|
||||||
|
this.app = cordovaWebView;
|
||||||
|
setPluginEntries(pluginEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<PluginEntry> getPluginEntries() {
|
||||||
|
return entryMap.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPluginEntries(Collection<PluginEntry> pluginEntries) {
|
||||||
|
if (isInitialized) {
|
||||||
|
this.onPause(false);
|
||||||
|
this.onDestroy();
|
||||||
|
pluginMap.clear();
|
||||||
|
entryMap.clear();
|
||||||
|
}
|
||||||
|
for (PluginEntry entry : pluginEntries) {
|
||||||
|
addService(entry);
|
||||||
|
}
|
||||||
|
if (isInitialized) {
|
||||||
|
startupPlugins();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init when loading a new HTML page into webview.
|
||||||
|
*/
|
||||||
|
public void init() {
|
||||||
|
LOG.d(TAG, "init()");
|
||||||
|
isInitialized = true;
|
||||||
|
this.onPause(false);
|
||||||
|
this.onDestroy();
|
||||||
|
pluginMap.clear();
|
||||||
|
this.startupPlugins();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create plugins objects that have onload set.
|
||||||
|
*/
|
||||||
|
private void startupPlugins() {
|
||||||
|
synchronized (entryMap) {
|
||||||
|
for (PluginEntry entry : entryMap.values()) {
|
||||||
|
// Add a null entry to for each non-startup plugin to avoid ConcurrentModificationException
|
||||||
|
// When iterating plugins.
|
||||||
|
if (entry.onload) {
|
||||||
|
getPlugin(entry.service);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LOG.d(TAG, "startupPlugins: put - " + entry.service);
|
||||||
|
pluginMap.put(entry.service, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives a request for execution and fulfills it by finding the appropriate
|
||||||
|
* Java class and calling it's execute method.
|
||||||
|
*
|
||||||
|
* PluginManager.exec can be used either synchronously or async. In either case, a JSON encoded
|
||||||
|
* string is returned that will indicate if any errors have occurred when trying to find
|
||||||
|
* or execute the class denoted by the clazz argument.
|
||||||
|
*
|
||||||
|
* @param service String containing the service to run
|
||||||
|
* @param action String containing the action that the class is supposed to perform. This is
|
||||||
|
* passed to the plugin execute method and it is up to the plugin developer
|
||||||
|
* how to deal with it.
|
||||||
|
* @param callbackId String containing the id of the callback that is execute in JavaScript if
|
||||||
|
* this is an async plugin call.
|
||||||
|
* @param rawArgs An Array literal string containing any arguments needed in the
|
||||||
|
* plugin execute method.
|
||||||
|
*/
|
||||||
|
public void exec(final String service, final String action, final String callbackId, final String rawArgs) {
|
||||||
|
CordovaPlugin plugin = getPlugin(service);
|
||||||
|
if (plugin == null) {
|
||||||
|
LOG.d(TAG, "exec() call to unknown plugin: " + service);
|
||||||
|
PluginResult cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION);
|
||||||
|
app.sendPluginResult(cr, callbackId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CallbackContext callbackContext = new CallbackContext(callbackId, app);
|
||||||
|
try {
|
||||||
|
long pluginStartTime = System.currentTimeMillis();
|
||||||
|
boolean wasValidAction = plugin.execute(action, rawArgs, callbackContext);
|
||||||
|
long duration = System.currentTimeMillis() - pluginStartTime;
|
||||||
|
|
||||||
|
if (duration > SLOW_EXEC_WARNING_THRESHOLD) {
|
||||||
|
LOG.w(TAG, "THREAD WARNING: exec() call to " + service + "." + action + " blocked the main thread for " + duration + "ms. Plugin should use CordovaInterface.getThreadPool().");
|
||||||
|
}
|
||||||
|
if (!wasValidAction) {
|
||||||
|
PluginResult cr = new PluginResult(PluginResult.Status.INVALID_ACTION);
|
||||||
|
callbackContext.sendPluginResult(cr);
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
PluginResult cr = new PluginResult(PluginResult.Status.JSON_EXCEPTION);
|
||||||
|
callbackContext.sendPluginResult(cr);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.e(TAG, "Uncaught exception from plugin", e);
|
||||||
|
callbackContext.error(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the plugin object that implements the service.
|
||||||
|
* If the plugin object does not already exist, then create it.
|
||||||
|
* If the service doesn't exist, then return null.
|
||||||
|
*
|
||||||
|
* @param service The name of the service.
|
||||||
|
* @return CordovaPlugin or null
|
||||||
|
*/
|
||||||
|
public CordovaPlugin getPlugin(String service) {
|
||||||
|
CordovaPlugin ret = pluginMap.get(service);
|
||||||
|
if (ret == null) {
|
||||||
|
PluginEntry pe = entryMap.get(service);
|
||||||
|
if (pe == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (pe.plugin != null) {
|
||||||
|
ret = pe.plugin;
|
||||||
|
} else {
|
||||||
|
ret = instantiatePlugin(pe.pluginClass);
|
||||||
|
}
|
||||||
|
ret.privateInitialize(service, ctx, app, app.getPreferences());
|
||||||
|
LOG.d(TAG, "getPlugin - put: " + service);
|
||||||
|
pluginMap.put(service, ret);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a plugin class that implements a service to the service entry table.
|
||||||
|
* This does not create the plugin object instance.
|
||||||
|
*
|
||||||
|
* @param service The service name
|
||||||
|
* @param className The plugin class name
|
||||||
|
*/
|
||||||
|
public void addService(String service, String className) {
|
||||||
|
PluginEntry entry = new PluginEntry(service, className, false);
|
||||||
|
this.addService(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a plugin class that implements a service to the service entry table.
|
||||||
|
* This does not create the plugin object instance.
|
||||||
|
*
|
||||||
|
* @param entry The plugin entry
|
||||||
|
*/
|
||||||
|
public void addService(PluginEntry entry) {
|
||||||
|
this.entryMap.put(entry.service, entry);
|
||||||
|
if (entry.plugin != null) {
|
||||||
|
entry.plugin.privateInitialize(entry.service, ctx, app, app.getPreferences());
|
||||||
|
LOG.d(TAG, "addService: put - " + entry.service);
|
||||||
|
pluginMap.put(entry.service, entry.plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the system is about to start resuming a previous activity.
|
||||||
|
*
|
||||||
|
* @param multitasking Flag indicating if multitasking is turned on for app
|
||||||
|
*/
|
||||||
|
public void onPause(boolean multitasking) {
|
||||||
|
synchronized (this.pluginMap) {
|
||||||
|
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||||
|
if (plugin != null) {
|
||||||
|
plugin.onPause(multitasking);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the system received an HTTP authentication request. Plugins can use
|
||||||
|
* the supplied HttpAuthHandler to process this auth challenge.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback
|
||||||
|
* @param handler The HttpAuthHandler used to set the WebView's response
|
||||||
|
* @param host The host requiring authentication
|
||||||
|
* @param realm The realm for which authentication is required
|
||||||
|
*
|
||||||
|
* @return Returns True if there is a plugin which will resolve this auth challenge, otherwise False
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
|
||||||
|
synchronized (this.pluginMap) {
|
||||||
|
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||||
|
if (plugin != null && plugin.onReceivedHttpAuthRequest(app, handler, host, realm)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when he system received an SSL client certificate request. Plugin can use
|
||||||
|
* the supplied ClientCertRequest to process this certificate challenge.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback
|
||||||
|
* @param request The client certificate request
|
||||||
|
*
|
||||||
|
* @return Returns True if plugin will resolve this auth challenge, otherwise False
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
|
||||||
|
synchronized (this.pluginMap) {
|
||||||
|
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||||
|
if (plugin != null && plugin.onReceivedClientCertRequest(app, request)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the activity will start interacting with the user.
|
||||||
|
*
|
||||||
|
* @param multitasking Flag indicating if multitasking is turned on for app
|
||||||
|
*/
|
||||||
|
public void onResume(boolean multitasking) {
|
||||||
|
synchronized (this.pluginMap) {
|
||||||
|
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||||
|
if (plugin != null) {
|
||||||
|
plugin.onResume(multitasking);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the activity is becoming visible to the user.
|
||||||
|
*/
|
||||||
|
public void onStart() {
|
||||||
|
synchronized (this.pluginMap) {
|
||||||
|
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||||
|
if (plugin != null) {
|
||||||
|
plugin.onStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the activity is no longer visible to the user.
|
||||||
|
*/
|
||||||
|
public void onStop() {
|
||||||
|
synchronized (this.pluginMap) {
|
||||||
|
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||||
|
if (plugin != null) {
|
||||||
|
plugin.onStop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The final call you receive before your activity is destroyed.
|
||||||
|
*/
|
||||||
|
public void onDestroy() {
|
||||||
|
synchronized (this.pluginMap) {
|
||||||
|
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||||
|
if (plugin != null) {
|
||||||
|
plugin.onDestroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message to all plugins.
|
||||||
|
*
|
||||||
|
* @param id The message id
|
||||||
|
* @param data The message data
|
||||||
|
* @return Object to stop propagation or null
|
||||||
|
*/
|
||||||
|
public Object postMessage(String id, Object data) {
|
||||||
|
LOG.d(TAG, "postMessage: " + id);
|
||||||
|
synchronized (this.pluginMap) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
this.pluginMap.forEach((s, plugin) -> {
|
||||||
|
if (plugin != null) {
|
||||||
|
plugin.onMessage(id, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||||
|
if (plugin != null) {
|
||||||
|
Object obj = plugin.onMessage(id, data);
|
||||||
|
if (obj != null) {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ctx.onMessage(id, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the activity receives a new intent.
|
||||||
|
*/
|
||||||
|
public void onNewIntent(Intent intent) {
|
||||||
|
synchronized (this.pluginMap) {
|
||||||
|
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||||
|
if (plugin != null) {
|
||||||
|
plugin.onNewIntent(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the webview is going to request an external resource.
|
||||||
|
*
|
||||||
|
* This delegates to the installed plugins, and returns true/false for the
|
||||||
|
* first plugin to provide a non-null result. If no plugins respond, then
|
||||||
|
* the default policy is applied.
|
||||||
|
*
|
||||||
|
* @param url The URL that is being requested.
|
||||||
|
* @return Returns true to allow the resource to load,
|
||||||
|
* false to block the resource.
|
||||||
|
*/
|
||||||
|
public boolean shouldAllowRequest(String url) {
|
||||||
|
synchronized (this.entryMap) {
|
||||||
|
for (PluginEntry entry : this.entryMap.values()) {
|
||||||
|
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||||
|
if (plugin != null) {
|
||||||
|
Boolean result = plugin.shouldAllowRequest(url);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default policy:
|
||||||
|
if (url.startsWith("blob:") || url.startsWith("data:") || url.startsWith("about:blank")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// TalkBack requires this, so allow it by default.
|
||||||
|
if (url.startsWith("https://ssl.gstatic.com/accessibility/javascript/android/")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (url.startsWith("file://")) {
|
||||||
|
//This directory on WebKit/Blink based webviews contains SQLite databases!
|
||||||
|
//DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING!
|
||||||
|
return !url.contains("/app_webview/");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the webview is going to change the URL of the loaded content.
|
||||||
|
*
|
||||||
|
* This delegates to the installed plugins, and returns true/false for the
|
||||||
|
* first plugin to provide a non-null result. If no plugins respond, then
|
||||||
|
* the default policy is applied.
|
||||||
|
*
|
||||||
|
* @param url The URL that is being requested.
|
||||||
|
* @return Returns true to allow the navigation,
|
||||||
|
* false to block the navigation.
|
||||||
|
*/
|
||||||
|
public boolean shouldAllowNavigation(String url) {
|
||||||
|
synchronized (this.entryMap) {
|
||||||
|
for (PluginEntry entry : this.entryMap.values()) {
|
||||||
|
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||||
|
if (plugin != null) {
|
||||||
|
Boolean result = plugin.shouldAllowNavigation(url);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default policy:
|
||||||
|
return url.startsWith("file://") || url.startsWith("about:blank");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the webview is requesting the exec() bridge be enabled.
|
||||||
|
*/
|
||||||
|
public boolean shouldAllowBridgeAccess(String url) {
|
||||||
|
synchronized (this.entryMap) {
|
||||||
|
for (PluginEntry entry : this.entryMap.values()) {
|
||||||
|
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||||
|
if (plugin != null) {
|
||||||
|
Boolean result = plugin.shouldAllowBridgeAccess(url);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default policy:
|
||||||
|
return url.startsWith("file://");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the webview is going not going to navigate, but may launch
|
||||||
|
* an Intent for an URL.
|
||||||
|
*
|
||||||
|
* This delegates to the installed plugins, and returns true/false for the
|
||||||
|
* first plugin to provide a non-null result. If no plugins respond, then
|
||||||
|
* the default policy is applied.
|
||||||
|
*
|
||||||
|
* @param url The URL that is being requested.
|
||||||
|
* @return Returns true to allow the URL to launch an intent,
|
||||||
|
* false to block the intent.
|
||||||
|
*/
|
||||||
|
public Boolean shouldOpenExternalUrl(String url) {
|
||||||
|
synchronized (this.entryMap) {
|
||||||
|
for (PluginEntry entry : this.entryMap.values()) {
|
||||||
|
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||||
|
if (plugin != null) {
|
||||||
|
Boolean result = plugin.shouldOpenExternalUrl(url);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Default policy:
|
||||||
|
// External URLs are not allowed
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the URL of the webview changes.
|
||||||
|
*
|
||||||
|
* @param url The URL that is being changed to.
|
||||||
|
* @return Return false to allow the URL to load, return true to prevent the URL from loading.
|
||||||
|
*/
|
||||||
|
public boolean onOverrideUrlLoading(String url) {
|
||||||
|
synchronized (this.entryMap) {
|
||||||
|
for (PluginEntry entry : this.entryMap.values()) {
|
||||||
|
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||||
|
if (plugin != null && plugin.onOverrideUrlLoading(url)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the app navigates or refreshes.
|
||||||
|
*/
|
||||||
|
public void onReset() {
|
||||||
|
synchronized (this.pluginMap) {
|
||||||
|
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||||
|
if (plugin != null) {
|
||||||
|
plugin.onReset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri remapUri(Uri uri) {
|
||||||
|
synchronized (this.pluginMap) {
|
||||||
|
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||||
|
if (plugin != null) {
|
||||||
|
Uri ret = plugin.remapUri(uri);
|
||||||
|
if (ret != null) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a plugin based on class name.
|
||||||
|
*/
|
||||||
|
private CordovaPlugin instantiatePlugin(String className) {
|
||||||
|
CordovaPlugin ret = null;
|
||||||
|
try {
|
||||||
|
Class<?> c = null;
|
||||||
|
if ((className != null) && !("".equals(className))) {
|
||||||
|
c = Class.forName(className);
|
||||||
|
}
|
||||||
|
if (c != null & CordovaPlugin.class.isAssignableFrom(c)) {
|
||||||
|
ret = (CordovaPlugin) c.newInstance();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.out.println("Error adding plugin " + className + ".");
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the system when the device configuration changes while your activity is running.
|
||||||
|
*
|
||||||
|
* @param newConfig The new device configuration
|
||||||
|
*/
|
||||||
|
public void onConfigurationChanged(Configuration newConfig) {
|
||||||
|
synchronized (this.pluginMap) {
|
||||||
|
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||||
|
if (plugin != null) {
|
||||||
|
plugin.onConfigurationChanged(newConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bundle onSaveInstanceState() {
|
||||||
|
Bundle state = new Bundle();
|
||||||
|
synchronized (this.pluginMap) {
|
||||||
|
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||||
|
if (plugin != null) {
|
||||||
|
Bundle pluginState = plugin.onSaveInstanceState();
|
||||||
|
if (pluginState != null) {
|
||||||
|
state.putBundle(plugin.getServiceName(), pluginState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
|
public class PluginResult {
|
||||||
|
private final int status;
|
||||||
|
private final int messageType;
|
||||||
|
private boolean keepCallback = false;
|
||||||
|
private String strMessage;
|
||||||
|
private String encodedMessage;
|
||||||
|
private List<PluginResult> multipartMessages;
|
||||||
|
|
||||||
|
public PluginResult(Status status) {
|
||||||
|
this(status, PluginResult.StatusMessages[status.ordinal()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginResult(Status status, String message) {
|
||||||
|
this.status = status.ordinal();
|
||||||
|
this.messageType = message == null ? MESSAGE_TYPE_NULL : MESSAGE_TYPE_STRING;
|
||||||
|
this.strMessage = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginResult(Status status, JSONArray message) {
|
||||||
|
this.status = status.ordinal();
|
||||||
|
this.messageType = MESSAGE_TYPE_JSON;
|
||||||
|
encodedMessage = message.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginResult(Status status, JSONObject message) {
|
||||||
|
this.status = status.ordinal();
|
||||||
|
this.messageType = MESSAGE_TYPE_JSON;
|
||||||
|
encodedMessage = message.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginResult(Status status, int i) {
|
||||||
|
this.status = status.ordinal();
|
||||||
|
this.messageType = MESSAGE_TYPE_NUMBER;
|
||||||
|
this.encodedMessage = ""+i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginResult(Status status, float f) {
|
||||||
|
this.status = status.ordinal();
|
||||||
|
this.messageType = MESSAGE_TYPE_NUMBER;
|
||||||
|
this.encodedMessage = ""+f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginResult(Status status, boolean b) {
|
||||||
|
this.status = status.ordinal();
|
||||||
|
this.messageType = MESSAGE_TYPE_BOOLEAN;
|
||||||
|
this.encodedMessage = Boolean.toString(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginResult(Status status, byte[] data) {
|
||||||
|
this(status, data, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginResult(Status status, byte[] data, boolean binaryString) {
|
||||||
|
this.status = status.ordinal();
|
||||||
|
this.messageType = binaryString ? MESSAGE_TYPE_BINARYSTRING : MESSAGE_TYPE_ARRAYBUFFER;
|
||||||
|
this.encodedMessage = Base64.encodeToString(data, Base64.NO_WRAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The keepCallback and status of multipartMessages are ignored.
|
||||||
|
public PluginResult(Status status, List<PluginResult> multipartMessages) {
|
||||||
|
this.status = status.ordinal();
|
||||||
|
this.messageType = MESSAGE_TYPE_MULTIPART;
|
||||||
|
this.multipartMessages = multipartMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKeepCallback(boolean b) {
|
||||||
|
this.keepCallback = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMessageType() {
|
||||||
|
return messageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
if (encodedMessage == null) {
|
||||||
|
encodedMessage = JSONObject.quote(strMessage);
|
||||||
|
}
|
||||||
|
return encodedMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMultipartMessagesSize() {
|
||||||
|
return multipartMessages.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginResult getMultipartMessage(int index) {
|
||||||
|
return multipartMessages.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If messageType == MESSAGE_TYPE_STRING, then returns the message string.
|
||||||
|
* Otherwise, returns null.
|
||||||
|
*/
|
||||||
|
public String getStrMessage() {
|
||||||
|
return strMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getKeepCallback() {
|
||||||
|
return this.keepCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated // Use sendPluginResult instead of sendJavascript.
|
||||||
|
public String getJSONString() {
|
||||||
|
return "{\"status\":" + this.status + ",\"message\":" + this.getMessage() + ",\"keepCallback\":" + this.keepCallback + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated // Use sendPluginResult instead of sendJavascript.
|
||||||
|
public String toCallbackString(String callbackId) {
|
||||||
|
// If no result to be sent and keeping callback, then no need to sent back to JavaScript
|
||||||
|
if ((status == PluginResult.Status.NO_RESULT.ordinal()) && keepCallback) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the success (OK, NO_RESULT & !KEEP_CALLBACK)
|
||||||
|
if ((status == PluginResult.Status.OK.ordinal()) || (status == PluginResult.Status.NO_RESULT.ordinal())) {
|
||||||
|
return toSuccessCallbackString(callbackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toErrorCallbackString(callbackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated // Use sendPluginResult instead of sendJavascript.
|
||||||
|
public String toSuccessCallbackString(String callbackId) {
|
||||||
|
return "cordova.callbackSuccess('"+callbackId+"',"+this.getJSONString()+");";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated // Use sendPluginResult instead of sendJavascript.
|
||||||
|
public String toErrorCallbackString(String callbackId) {
|
||||||
|
return "cordova.callbackError('"+callbackId+"', " + this.getJSONString()+ ");";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final int MESSAGE_TYPE_STRING = 1;
|
||||||
|
public static final int MESSAGE_TYPE_JSON = 2;
|
||||||
|
public static final int MESSAGE_TYPE_NUMBER = 3;
|
||||||
|
public static final int MESSAGE_TYPE_BOOLEAN = 4;
|
||||||
|
public static final int MESSAGE_TYPE_NULL = 5;
|
||||||
|
public static final int MESSAGE_TYPE_ARRAYBUFFER = 6;
|
||||||
|
// Use BINARYSTRING when your string may contain null characters.
|
||||||
|
// This is required to work around a bug in the platform :(.
|
||||||
|
public static final int MESSAGE_TYPE_BINARYSTRING = 7;
|
||||||
|
public static final int MESSAGE_TYPE_MULTIPART = 8;
|
||||||
|
|
||||||
|
public static String[] StatusMessages = new String[] {
|
||||||
|
"No result",
|
||||||
|
"OK",
|
||||||
|
"Class not found",
|
||||||
|
"Illegal access",
|
||||||
|
"Instantiation error",
|
||||||
|
"Malformed url",
|
||||||
|
"IO error",
|
||||||
|
"Invalid action",
|
||||||
|
"JSON error",
|
||||||
|
"Error"
|
||||||
|
};
|
||||||
|
|
||||||
|
public enum Status {
|
||||||
|
NO_RESULT,
|
||||||
|
OK,
|
||||||
|
CLASS_NOT_FOUND_EXCEPTION,
|
||||||
|
ILLEGAL_ACCESS_EXCEPTION,
|
||||||
|
INSTANTIATION_EXCEPTION,
|
||||||
|
MALFORMED_URL_EXCEPTION,
|
||||||
|
IO_EXCEPTION,
|
||||||
|
INVALID_ACTION,
|
||||||
|
JSON_EXCEPTION,
|
||||||
|
ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ResumeCallback extends CallbackContext {
|
||||||
|
private final String TAG = "CordovaResumeCallback";
|
||||||
|
private String serviceName;
|
||||||
|
private PluginManager pluginManager;
|
||||||
|
|
||||||
|
public ResumeCallback(String serviceName, PluginManager pluginManager) {
|
||||||
|
super("resumecallback", null);
|
||||||
|
this.serviceName = serviceName;
|
||||||
|
this.pluginManager = pluginManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendPluginResult(PluginResult pluginResult) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (finished) {
|
||||||
|
LOG.w(TAG, serviceName + " attempted to send a second callback to ResumeCallback\nResult was: " + pluginResult.getMessage());
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject event = new JSONObject();
|
||||||
|
JSONObject pluginResultObject = new JSONObject();
|
||||||
|
|
||||||
|
try {
|
||||||
|
pluginResultObject.put("pluginServiceName", this.serviceName);
|
||||||
|
pluginResultObject.put("pluginStatus", PluginResult.StatusMessages[pluginResult.getStatus()]);
|
||||||
|
|
||||||
|
event.put("action", "resume");
|
||||||
|
event.put("pendingResult", pluginResultObject);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
LOG.e(TAG, "Unable to create resume object for Activity Result");
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginResult eventResult = new PluginResult(PluginResult.Status.OK, event);
|
||||||
|
|
||||||
|
// We send a list of results to the js so that we don't have to decode
|
||||||
|
// the PluginResult passed to this CallbackContext into JSON twice.
|
||||||
|
// The results are combined into an event payload before the event is
|
||||||
|
// fired on the js side of things (see platform.js)
|
||||||
|
List<PluginResult> result = new ArrayList<PluginResult>();
|
||||||
|
result.add(eventResult);
|
||||||
|
result.add(pluginResult);
|
||||||
|
|
||||||
|
CoreAndroid appPlugin = (CoreAndroid) pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
|
||||||
|
appPlugin.sendResumeEvent(new PluginResult(PluginResult.Status.OK, result));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.apache.cordova.LOG;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
public class Whitelist {
|
||||||
|
private static class URLPattern {
|
||||||
|
public Pattern scheme;
|
||||||
|
public Pattern host;
|
||||||
|
public Integer port;
|
||||||
|
public Pattern path;
|
||||||
|
|
||||||
|
private String regexFromPattern(String pattern, boolean allowWildcards) {
|
||||||
|
final String toReplace = "\\.[]{}()^$?+|";
|
||||||
|
StringBuilder regex = new StringBuilder();
|
||||||
|
for (int i=0; i < pattern.length(); i++) {
|
||||||
|
char c = pattern.charAt(i);
|
||||||
|
if (c == '*' && allowWildcards) {
|
||||||
|
regex.append(".");
|
||||||
|
} else if (toReplace.indexOf(c) > -1) {
|
||||||
|
regex.append('\\');
|
||||||
|
}
|
||||||
|
regex.append(c);
|
||||||
|
}
|
||||||
|
return regex.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public URLPattern(String scheme, String host, String port, String path) throws MalformedURLException {
|
||||||
|
try {
|
||||||
|
if (scheme == null || "*".equals(scheme)) {
|
||||||
|
this.scheme = null;
|
||||||
|
} else {
|
||||||
|
this.scheme = Pattern.compile(regexFromPattern(scheme, false), Pattern.CASE_INSENSITIVE);
|
||||||
|
}
|
||||||
|
if ("*".equals(host)) {
|
||||||
|
this.host = null;
|
||||||
|
} else if (host.startsWith("*.")) {
|
||||||
|
this.host = Pattern.compile("([a-z0-9.-]*\\.)?" + regexFromPattern(host.substring(2), false), Pattern.CASE_INSENSITIVE);
|
||||||
|
} else {
|
||||||
|
this.host = Pattern.compile(regexFromPattern(host, false), Pattern.CASE_INSENSITIVE);
|
||||||
|
}
|
||||||
|
if (port == null || "*".equals(port)) {
|
||||||
|
this.port = null;
|
||||||
|
} else {
|
||||||
|
this.port = Integer.parseInt(port,10);
|
||||||
|
}
|
||||||
|
if (path == null || "/*".equals(path)) {
|
||||||
|
this.path = null;
|
||||||
|
} else {
|
||||||
|
this.path = Pattern.compile(regexFromPattern(path, true));
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new MalformedURLException("Port must be a number");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matches(Uri uri) {
|
||||||
|
try {
|
||||||
|
return ((scheme == null || scheme.matcher(uri.getScheme()).matches()) &&
|
||||||
|
(host == null || host.matcher(uri.getHost()).matches()) &&
|
||||||
|
(port == null || port.equals(uri.getPort())) &&
|
||||||
|
(path == null || path.matcher(uri.getPath()).matches()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.d(TAG, e.toString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<URLPattern> whiteList;
|
||||||
|
|
||||||
|
public static final String TAG = "Whitelist";
|
||||||
|
|
||||||
|
public Whitelist() {
|
||||||
|
this.whiteList = new ArrayList<URLPattern>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Match patterns (from http://developer.chrome.com/extensions/match_patterns.html)
|
||||||
|
*
|
||||||
|
* <url-pattern> := <scheme>://<host><path>
|
||||||
|
* <scheme> := '*' | 'http' | 'https' | 'file' | 'ftp' | 'chrome-extension'
|
||||||
|
* <host> := '*' | '*.' <any char except '/' and '*'>+
|
||||||
|
* <path> := '/' <any chars>
|
||||||
|
*
|
||||||
|
* We extend this to explicitly allow a port attached to the host, and we allow
|
||||||
|
* the scheme to be omitted for backwards compatibility. (Also host is not required
|
||||||
|
* to begin with a "*" or "*.".)
|
||||||
|
*/
|
||||||
|
public void addWhiteListEntry(String origin, boolean subdomains) {
|
||||||
|
if (whiteList != null) {
|
||||||
|
try {
|
||||||
|
// Unlimited access to network resources
|
||||||
|
if (origin.compareTo("*") == 0) {
|
||||||
|
LOG.d(TAG, "Unlimited access to network resources");
|
||||||
|
whiteList = null;
|
||||||
|
}
|
||||||
|
else { // specific access
|
||||||
|
Pattern parts = Pattern.compile("^((\\*|[A-Za-z-]+):(//)?)?(\\*|((\\*\\.)?[^*/:]+))?(:(\\d+))?(/.*)?");
|
||||||
|
Matcher m = parts.matcher(origin);
|
||||||
|
if (m.matches()) {
|
||||||
|
String scheme = m.group(2);
|
||||||
|
String host = m.group(4);
|
||||||
|
// Special case for two urls which are allowed to have empty hosts
|
||||||
|
if (("file".equals(scheme) || "content".equals(scheme)) && host == null) host = "*";
|
||||||
|
String port = m.group(8);
|
||||||
|
String path = m.group(9);
|
||||||
|
if (scheme == null) {
|
||||||
|
// XXX making it stupid friendly for people who forget to include protocol/SSL
|
||||||
|
whiteList.add(new URLPattern("http", host, port, path));
|
||||||
|
whiteList.add(new URLPattern("https", host, port, path));
|
||||||
|
} else {
|
||||||
|
whiteList.add(new URLPattern(scheme, host, port, path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.d(TAG, "Failed to add origin %s", origin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if URL is in approved list of URLs to load.
|
||||||
|
*
|
||||||
|
* @param uri
|
||||||
|
* @return true if wide open or whitelisted
|
||||||
|
*/
|
||||||
|
public boolean isUrlWhiteListed(String uri) {
|
||||||
|
// If there is no whitelist, then it's wide open
|
||||||
|
if (whiteList == null) return true;
|
||||||
|
|
||||||
|
Uri parsedUri = Uri.parse(uri);
|
||||||
|
// Look for match in white list
|
||||||
|
Iterator<URLPattern> pit = whiteList.iterator();
|
||||||
|
while (pit.hasNext()) {
|
||||||
|
URLPattern p = pit.next();
|
||||||
|
if (p.matches(parsedUri)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cordova.engine;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.webkit.CookieManager;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
|
||||||
|
import org.apache.cordova.ICordovaCookieManager;
|
||||||
|
|
||||||
|
class SystemCookieManager implements ICordovaCookieManager {
|
||||||
|
|
||||||
|
protected final WebView webView;
|
||||||
|
private final CookieManager cookieManager;
|
||||||
|
|
||||||
|
public SystemCookieManager(WebView webview) {
|
||||||
|
webView = webview;
|
||||||
|
cookieManager = CookieManager.getInstance();
|
||||||
|
|
||||||
|
cookieManager.setAcceptFileSchemeCookies(true);
|
||||||
|
cookieManager.setAcceptThirdPartyCookies(webView, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCookiesEnabled(boolean accept) {
|
||||||
|
cookieManager.setAcceptCookie(accept);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCookie(final String url, final String value) {
|
||||||
|
cookieManager.setCookie(url, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCookie(final String url) {
|
||||||
|
return cookieManager.getCookie(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public void clearCookies() {
|
||||||
|
cookieManager.removeAllCookies(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void flush() {
|
||||||
|
cookieManager.flush();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova.engine;
|
||||||
|
|
||||||
|
import android.webkit.JavascriptInterface;
|
||||||
|
|
||||||
|
import org.apache.cordova.CordovaBridge;
|
||||||
|
import org.apache.cordova.ExposedJsApi;
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains APIs that the JS can call. All functions in here should also have
|
||||||
|
* an equivalent entry in CordovaChromeClient.java, and be added to
|
||||||
|
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
|
||||||
|
*/
|
||||||
|
class SystemExposedJsApi implements ExposedJsApi {
|
||||||
|
private final CordovaBridge bridge;
|
||||||
|
|
||||||
|
SystemExposedJsApi(CordovaBridge bridge) {
|
||||||
|
this.bridge = bridge;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
|
||||||
|
return bridge.jsExec(bridgeSecret, service, action, callbackId, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
|
||||||
|
bridge.jsSetNativeToJsBridgeMode(bridgeSecret, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
|
||||||
|
return bridge.jsRetrieveJsMessages(bridgeSecret, fromOnlineEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,270 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova.engine;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup.LayoutParams;
|
||||||
|
import android.webkit.ConsoleMessage;
|
||||||
|
import android.webkit.GeolocationPermissions.Callback;
|
||||||
|
import android.webkit.JsPromptResult;
|
||||||
|
import android.webkit.JsResult;
|
||||||
|
import android.webkit.ValueCallback;
|
||||||
|
import android.webkit.WebChromeClient;
|
||||||
|
import android.webkit.WebStorage;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.PermissionRequest;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
|
||||||
|
import org.apache.cordova.CordovaDialogsHelper;
|
||||||
|
import org.apache.cordova.CordovaPlugin;
|
||||||
|
import org.apache.cordova.LOG;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is the WebChromeClient that implements callbacks for our web view.
|
||||||
|
* The kind of callbacks that happen here are on the chrome outside the document,
|
||||||
|
* such as onCreateWindow(), onConsoleMessage(), onProgressChanged(), etc. Related
|
||||||
|
* to but different than CordovaWebViewClient.
|
||||||
|
*/
|
||||||
|
public class SystemWebChromeClient extends WebChromeClient {
|
||||||
|
|
||||||
|
private static final int FILECHOOSER_RESULTCODE = 5173;
|
||||||
|
private static final String LOG_TAG = "SystemWebChromeClient";
|
||||||
|
private long MAX_QUOTA = 100 * 1024 * 1024;
|
||||||
|
protected final SystemWebViewEngine parentEngine;
|
||||||
|
|
||||||
|
// the video progress view
|
||||||
|
private View mVideoProgressView;
|
||||||
|
|
||||||
|
private CordovaDialogsHelper dialogsHelper;
|
||||||
|
private Context appContext;
|
||||||
|
|
||||||
|
private WebChromeClient.CustomViewCallback mCustomViewCallback;
|
||||||
|
private View mCustomView;
|
||||||
|
|
||||||
|
public SystemWebChromeClient(SystemWebViewEngine parentEngine) {
|
||||||
|
this.parentEngine = parentEngine;
|
||||||
|
appContext = parentEngine.webView.getContext();
|
||||||
|
dialogsHelper = new CordovaDialogsHelper(appContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell the client to display a javascript alert dialog.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
|
||||||
|
dialogsHelper.showAlert(message, new CordovaDialogsHelper.Result() {
|
||||||
|
@Override public void gotResult(boolean success, String value) {
|
||||||
|
if (success) {
|
||||||
|
result.confirm();
|
||||||
|
} else {
|
||||||
|
result.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell the client to display a confirm dialog to the user.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
|
||||||
|
dialogsHelper.showConfirm(message, new CordovaDialogsHelper.Result() {
|
||||||
|
@Override
|
||||||
|
public void gotResult(boolean success, String value) {
|
||||||
|
if (success) {
|
||||||
|
result.confirm();
|
||||||
|
} else {
|
||||||
|
result.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell the client to display a prompt dialog to the user.
|
||||||
|
* If the client returns true, WebView will assume that the client will
|
||||||
|
* handle the prompt dialog and call the appropriate JsPromptResult method.
|
||||||
|
*
|
||||||
|
* Since we are hacking prompts for our own purposes, we should not be using them for
|
||||||
|
* this purpose, perhaps we should hack console.log to do this instead!
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, final JsPromptResult result) {
|
||||||
|
// Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
|
||||||
|
String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message, defaultValue);
|
||||||
|
if (handledRet != null) {
|
||||||
|
result.confirm(handledRet);
|
||||||
|
} else {
|
||||||
|
dialogsHelper.showPrompt(message, defaultValue, new CordovaDialogsHelper.Result() {
|
||||||
|
@Override
|
||||||
|
public void gotResult(boolean success, String value) {
|
||||||
|
if (success) {
|
||||||
|
result.confirm(value);
|
||||||
|
} else {
|
||||||
|
result.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle database quota exceeded notification.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize,
|
||||||
|
long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater)
|
||||||
|
{
|
||||||
|
LOG.d(LOG_TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
|
||||||
|
quotaUpdater.updateQuota(MAX_QUOTA);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
/**
|
||||||
|
* Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin.
|
||||||
|
*
|
||||||
|
* This also checks for the Geolocation Plugin and requests permission from the application to use Geolocation.
|
||||||
|
*
|
||||||
|
* @param origin
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
|
||||||
|
super.onGeolocationPermissionsShowPrompt(origin, callback);
|
||||||
|
callback.invoke(origin, true, false);
|
||||||
|
//Get the plugin, it should be loaded
|
||||||
|
CordovaPlugin geolocation = parentEngine.pluginManager.getPlugin("Geolocation");
|
||||||
|
if(geolocation != null && !geolocation.hasPermisssion())
|
||||||
|
{
|
||||||
|
geolocation.requestPermissions(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API level 7 is required for this, see if we could lower this using something else
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
|
||||||
|
parentEngine.getCordovaWebView().showCustomView(view, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public void onHideCustomView() {
|
||||||
|
parentEngine.getCordovaWebView().hideCustomView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
/**
|
||||||
|
* Ask the host application for a custom progress view to show while
|
||||||
|
* a <video> is loading.
|
||||||
|
* @return View The progress view.
|
||||||
|
*/
|
||||||
|
public View getVideoLoadingProgressView() {
|
||||||
|
if (mVideoProgressView == null) {
|
||||||
|
// Create a new Loading view programmatically.
|
||||||
|
|
||||||
|
// create the linear layout
|
||||||
|
LinearLayout layout = new LinearLayout(parentEngine.getView().getContext());
|
||||||
|
layout.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||||
|
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
|
||||||
|
layout.setLayoutParams(layoutParams);
|
||||||
|
// the proress bar
|
||||||
|
ProgressBar bar = new ProgressBar(parentEngine.getView().getContext());
|
||||||
|
LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||||
|
barLayoutParams.gravity = Gravity.CENTER;
|
||||||
|
bar.setLayoutParams(barLayoutParams);
|
||||||
|
layout.addView(bar);
|
||||||
|
|
||||||
|
mVideoProgressView = layout;
|
||||||
|
}
|
||||||
|
return mVideoProgressView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback, final WebChromeClient.FileChooserParams fileChooserParams) {
|
||||||
|
// Check if multiple-select is specified
|
||||||
|
Boolean selectMultiple = false;
|
||||||
|
if (fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE) {
|
||||||
|
selectMultiple = true;
|
||||||
|
}
|
||||||
|
Intent intent = fileChooserParams.createIntent();
|
||||||
|
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, selectMultiple);
|
||||||
|
|
||||||
|
// Uses Intent.EXTRA_MIME_TYPES to pass multiple mime types.
|
||||||
|
String[] acceptTypes = fileChooserParams.getAcceptTypes();
|
||||||
|
if (acceptTypes.length > 1) {
|
||||||
|
intent.setType("*/*"); // Accept all, filter mime types by Intent.EXTRA_MIME_TYPES.
|
||||||
|
intent.putExtra(Intent.EXTRA_MIME_TYPES, acceptTypes);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||||
|
Uri[] result = null;
|
||||||
|
if (resultCode == Activity.RESULT_OK && intent != null) {
|
||||||
|
if (intent.getClipData() != null) {
|
||||||
|
// handle multiple-selected files
|
||||||
|
final int numSelectedFiles = intent.getClipData().getItemCount();
|
||||||
|
result = new Uri[numSelectedFiles];
|
||||||
|
for (int i = 0; i < numSelectedFiles; i++) {
|
||||||
|
result[i] = intent.getClipData().getItemAt(i).getUri();
|
||||||
|
LOG.d(LOG_TAG, "Receive file chooser URL: " + result[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (intent.getData() != null) {
|
||||||
|
// handle single-selected file
|
||||||
|
result = WebChromeClient.FileChooserParams.parseResult(resultCode, intent);
|
||||||
|
LOG.d(LOG_TAG, "Receive file chooser URL: " + result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filePathsCallback.onReceiveValue(result);
|
||||||
|
}
|
||||||
|
}, intent, FILECHOOSER_RESULTCODE);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
LOG.w("No activity found to handle file chooser intent.", e);
|
||||||
|
filePathsCallback.onReceiveValue(null);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPermissionRequest(final PermissionRequest request) {
|
||||||
|
LOG.d(LOG_TAG, "onPermissionRequest: " + Arrays.toString(request.getResources()));
|
||||||
|
request.grant(request.getResources());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void destroyLastDialog(){
|
||||||
|
dialogsHelper.destroyLastDialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cordova.engine;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.webkit.WebChromeClient;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.WebViewClient;
|
||||||
|
|
||||||
|
import org.apache.cordova.CordovaInterface;
|
||||||
|
import org.apache.cordova.CordovaWebView;
|
||||||
|
import org.apache.cordova.CordovaWebViewEngine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom WebView subclass that enables us to capture events needed for Cordova.
|
||||||
|
*/
|
||||||
|
public class SystemWebView extends WebView implements CordovaWebViewEngine.EngineView {
|
||||||
|
private SystemWebViewClient viewClient;
|
||||||
|
SystemWebChromeClient chromeClient;
|
||||||
|
private SystemWebViewEngine parentEngine;
|
||||||
|
private CordovaInterface cordova;
|
||||||
|
|
||||||
|
public SystemWebView(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SystemWebView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Package visibility to enforce that only SystemWebViewEngine should call this method.
|
||||||
|
void init(SystemWebViewEngine parentEngine, CordovaInterface cordova) {
|
||||||
|
this.cordova = cordova;
|
||||||
|
this.parentEngine = parentEngine;
|
||||||
|
if (this.viewClient == null) {
|
||||||
|
setWebViewClient(new SystemWebViewClient(parentEngine));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.chromeClient == null) {
|
||||||
|
setWebChromeClient(new SystemWebChromeClient(parentEngine));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CordovaWebView getCordovaWebView() {
|
||||||
|
return parentEngine != null ? parentEngine.getCordovaWebView() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWebViewClient(WebViewClient client) {
|
||||||
|
viewClient = (SystemWebViewClient)client;
|
||||||
|
super.setWebViewClient(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWebChromeClient(WebChromeClient client) {
|
||||||
|
chromeClient = (SystemWebChromeClient)client;
|
||||||
|
super.setWebChromeClient(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||||
|
Boolean ret = parentEngine.client.onDispatchKeyEvent(event);
|
||||||
|
if (ret != null) {
|
||||||
|
return ret.booleanValue();
|
||||||
|
}
|
||||||
|
return super.dispatchKeyEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,369 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova.engine;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.net.http.SslError;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.webkit.ClientCertRequest;
|
||||||
|
import android.webkit.HttpAuthHandler;
|
||||||
|
import android.webkit.SslErrorHandler;
|
||||||
|
import android.webkit.WebResourceResponse;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.WebViewClient;
|
||||||
|
|
||||||
|
import org.apache.cordova.AuthenticationToken;
|
||||||
|
import org.apache.cordova.CordovaClientCertRequest;
|
||||||
|
import org.apache.cordova.CordovaHttpAuthHandler;
|
||||||
|
import org.apache.cordova.CordovaResourceApi;
|
||||||
|
import org.apache.cordova.LOG;
|
||||||
|
import org.apache.cordova.PluginManager;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is the WebViewClient that implements callbacks for our web view.
|
||||||
|
* The kind of callbacks that happen here are regarding the rendering of the
|
||||||
|
* document instead of the chrome surrounding it, such as onPageStarted(),
|
||||||
|
* shouldOverrideUrlLoading(), etc. Related to but different than
|
||||||
|
* CordovaChromeClient.
|
||||||
|
*/
|
||||||
|
public class SystemWebViewClient extends WebViewClient {
|
||||||
|
|
||||||
|
private static final String TAG = "SystemWebViewClient";
|
||||||
|
protected final SystemWebViewEngine parentEngine;
|
||||||
|
private boolean doClearHistory = false;
|
||||||
|
boolean isCurrentlyLoading;
|
||||||
|
|
||||||
|
/** The authorization tokens. */
|
||||||
|
private Hashtable<String, AuthenticationToken> authenticationTokens = new Hashtable<String, AuthenticationToken>();
|
||||||
|
|
||||||
|
public SystemWebViewClient(SystemWebViewEngine parentEngine) {
|
||||||
|
this.parentEngine = parentEngine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give the host application a chance to take over the control when a new url
|
||||||
|
* is about to be loaded in the current WebView.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback.
|
||||||
|
* @param url The url to be loaded.
|
||||||
|
* @return true to override, false for default behavior
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||||
|
return parentEngine.client.onNavigationAttempt(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On received http auth request.
|
||||||
|
* The method reacts on all registered authentication tokens. There is one and only one authentication token for any host + realm combination
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
|
||||||
|
|
||||||
|
// Get the authentication token (if specified)
|
||||||
|
AuthenticationToken token = this.getAuthenticationToken(host, realm);
|
||||||
|
if (token != null) {
|
||||||
|
handler.proceed(token.getUserName(), token.getPassword());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there is some plugin which can resolve this auth challenge
|
||||||
|
PluginManager pluginManager = this.parentEngine.pluginManager;
|
||||||
|
if (pluginManager != null && pluginManager.onReceivedHttpAuthRequest(null, new CordovaHttpAuthHandler(handler), host, realm)) {
|
||||||
|
parentEngine.client.clearLoadTimeoutTimer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// By default handle 401 like we'd normally do!
|
||||||
|
super.onReceivedHttpAuthRequest(view, handler, host, realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On received client cert request.
|
||||||
|
* The method forwards the request to any running plugins before using the default implementation.
|
||||||
|
*
|
||||||
|
* @param view
|
||||||
|
* @param request
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onReceivedClientCertRequest (WebView view, ClientCertRequest request)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Check if there is some plugin which can resolve this certificate request
|
||||||
|
PluginManager pluginManager = this.parentEngine.pluginManager;
|
||||||
|
if (pluginManager != null && pluginManager.onReceivedClientCertRequest(null, new CordovaClientCertRequest(request))) {
|
||||||
|
parentEngine.client.clearLoadTimeoutTimer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// By default pass to WebViewClient
|
||||||
|
super.onReceivedClientCertRequest(view, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the host application that a page has started loading.
|
||||||
|
* This method is called once for each main frame load so a page with iframes or framesets will call onPageStarted
|
||||||
|
* one time for the main frame. This also means that onPageStarted will not be called when the contents of an
|
||||||
|
* embedded frame changes, i.e. clicking a link whose target is an iframe.
|
||||||
|
*
|
||||||
|
* @param view The webview initiating the callback.
|
||||||
|
* @param url The url of the page.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||||
|
super.onPageStarted(view, url, favicon);
|
||||||
|
isCurrentlyLoading = true;
|
||||||
|
// Flush stale messages & reset plugins.
|
||||||
|
parentEngine.bridge.reset();
|
||||||
|
parentEngine.client.onPageStarted(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the host application that a page has finished loading.
|
||||||
|
* This method is called only for main frame. When onPageFinished() is called, the rendering picture may not be updated yet.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param view The webview initiating the callback.
|
||||||
|
* @param url The url of the page.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onPageFinished(WebView view, String url) {
|
||||||
|
super.onPageFinished(view, url);
|
||||||
|
// Ignore excessive calls, if url is not about:blank (CB-8317).
|
||||||
|
if (!isCurrentlyLoading && !url.startsWith("about:")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isCurrentlyLoading = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Because of a timing issue we need to clear this history in onPageFinished as well as
|
||||||
|
* onPageStarted. However we only want to do this if the doClearHistory boolean is set to
|
||||||
|
* true. You see when you load a url with a # in it which is common in jQuery applications
|
||||||
|
* onPageStared is not called. Clearing the history at that point would break jQuery apps.
|
||||||
|
*/
|
||||||
|
if (this.doClearHistory) {
|
||||||
|
view.clearHistory();
|
||||||
|
this.doClearHistory = false;
|
||||||
|
}
|
||||||
|
parentEngine.client.onPageFinishedLoading(url);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable).
|
||||||
|
* The errorCode parameter corresponds to one of the ERROR_* constants.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback.
|
||||||
|
* @param errorCode The error code corresponding to an ERROR_* value.
|
||||||
|
* @param description A String describing the error.
|
||||||
|
* @param failingUrl The url that failed to load.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
|
||||||
|
// Ignore error due to stopLoading().
|
||||||
|
if (!isCurrentlyLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
|
||||||
|
|
||||||
|
// If this is a "Protocol Not Supported" error, then revert to the previous
|
||||||
|
// page. If there was no previous page, then punt. The application's config
|
||||||
|
// is likely incorrect (start page set to sms: or something like that)
|
||||||
|
if (errorCode == WebViewClient.ERROR_UNSUPPORTED_SCHEME) {
|
||||||
|
parentEngine.client.clearLoadTimeoutTimer();
|
||||||
|
|
||||||
|
if (view.canGoBack()) {
|
||||||
|
view.goBack();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
super.onReceivedError(view, errorCode, description, failingUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parentEngine.client.onReceivedError(errorCode, description, failingUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the host application that an SSL error occurred while loading a resource.
|
||||||
|
* The host application must call either handler.cancel() or handler.proceed().
|
||||||
|
* Note that the decision may be retained for use in response to future SSL errors.
|
||||||
|
* The default behavior is to cancel the load.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback.
|
||||||
|
* @param handler An SslErrorHandler object that will handle the user's response.
|
||||||
|
* @param error The SSL error object.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
|
||||||
|
|
||||||
|
final String packageName = parentEngine.cordova.getActivity().getPackageName();
|
||||||
|
final PackageManager pm = parentEngine.cordova.getActivity().getPackageManager();
|
||||||
|
|
||||||
|
ApplicationInfo appInfo;
|
||||||
|
try {
|
||||||
|
appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
|
||||||
|
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
|
||||||
|
// debug = true
|
||||||
|
handler.proceed();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// debug = false
|
||||||
|
super.onReceivedSslError(view, handler, error);
|
||||||
|
}
|
||||||
|
} catch (NameNotFoundException e) {
|
||||||
|
// When it doubt, lock it out!
|
||||||
|
super.onReceivedSslError(view, handler, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the authentication token.
|
||||||
|
*
|
||||||
|
* @param authenticationToken
|
||||||
|
* @param host
|
||||||
|
* @param realm
|
||||||
|
*/
|
||||||
|
public void setAuthenticationToken(AuthenticationToken authenticationToken, String host, String realm) {
|
||||||
|
if (host == null) {
|
||||||
|
host = "";
|
||||||
|
}
|
||||||
|
if (realm == null) {
|
||||||
|
realm = "";
|
||||||
|
}
|
||||||
|
this.authenticationTokens.put(host.concat(realm), authenticationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the authentication token.
|
||||||
|
*
|
||||||
|
* @param host
|
||||||
|
* @param realm
|
||||||
|
*
|
||||||
|
* @return the authentication token or null if did not exist
|
||||||
|
*/
|
||||||
|
public AuthenticationToken removeAuthenticationToken(String host, String realm) {
|
||||||
|
return this.authenticationTokens.remove(host.concat(realm));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the authentication token.
|
||||||
|
*
|
||||||
|
* In order it tries:
|
||||||
|
* 1- host + realm
|
||||||
|
* 2- host
|
||||||
|
* 3- realm
|
||||||
|
* 4- no host, no realm
|
||||||
|
*
|
||||||
|
* @param host
|
||||||
|
* @param realm
|
||||||
|
*
|
||||||
|
* @return the authentication token
|
||||||
|
*/
|
||||||
|
public AuthenticationToken getAuthenticationToken(String host, String realm) {
|
||||||
|
AuthenticationToken token = null;
|
||||||
|
token = this.authenticationTokens.get(host.concat(realm));
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
// try with just the host
|
||||||
|
token = this.authenticationTokens.get(host);
|
||||||
|
|
||||||
|
// Try the realm
|
||||||
|
if (token == null) {
|
||||||
|
token = this.authenticationTokens.get(realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no host found, just query for default
|
||||||
|
if (token == null) {
|
||||||
|
token = this.authenticationTokens.get("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all authentication tokens.
|
||||||
|
*/
|
||||||
|
public void clearAuthenticationTokens() {
|
||||||
|
this.authenticationTokens.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
|
||||||
|
try {
|
||||||
|
// Check the against the whitelist and lock out access to the WebView directory
|
||||||
|
// Changing this will cause problems for your application
|
||||||
|
if (!parentEngine.pluginManager.shouldAllowRequest(url)) {
|
||||||
|
LOG.w(TAG, "URL blocked by whitelist: " + url);
|
||||||
|
// Results in a 404.
|
||||||
|
return new WebResourceResponse("text/plain", "UTF-8", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
CordovaResourceApi resourceApi = parentEngine.resourceApi;
|
||||||
|
Uri origUri = Uri.parse(url);
|
||||||
|
// Allow plugins to intercept WebView requests.
|
||||||
|
Uri remappedUri = resourceApi.remapUri(origUri);
|
||||||
|
|
||||||
|
if (!origUri.equals(remappedUri) || needsSpecialsInAssetUrlFix(origUri) || needsContentUrlFix(origUri)) {
|
||||||
|
CordovaResourceApi.OpenForReadResult result = resourceApi.openForRead(remappedUri, true);
|
||||||
|
return new WebResourceResponse(result.mimeType, "UTF-8", result.inputStream);
|
||||||
|
}
|
||||||
|
// If we don't need to special-case the request, let the browser load it.
|
||||||
|
return null;
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (!(e instanceof FileNotFoundException)) {
|
||||||
|
LOG.e(TAG, "Error occurred while loading a file (returning a 404).", e);
|
||||||
|
}
|
||||||
|
// Results in a 404.
|
||||||
|
return new WebResourceResponse("text/plain", "UTF-8", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean needsContentUrlFix(Uri uri) {
|
||||||
|
return "content".equals(uri.getScheme());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean needsSpecialsInAssetUrlFix(Uri uri) {
|
||||||
|
if (CordovaResourceApi.getUriType(uri) != CordovaResourceApi.URI_TYPE_ASSET) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (uri.getQuery() != null || uri.getFragment() != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!uri.toString().contains("%")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,328 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cordova.engine;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.view.View;
|
||||||
|
import android.webkit.ValueCallback;
|
||||||
|
import android.webkit.WebSettings;
|
||||||
|
import android.webkit.WebSettings.LayoutAlgorithm;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
|
||||||
|
import org.apache.cordova.CordovaBridge;
|
||||||
|
import org.apache.cordova.CordovaInterface;
|
||||||
|
import org.apache.cordova.CordovaPreferences;
|
||||||
|
import org.apache.cordova.CordovaResourceApi;
|
||||||
|
import org.apache.cordova.CordovaWebView;
|
||||||
|
import org.apache.cordova.CordovaWebViewEngine;
|
||||||
|
import org.apache.cordova.ICordovaCookieManager;
|
||||||
|
import org.apache.cordova.LOG;
|
||||||
|
import org.apache.cordova.NativeToJsMessageQueue;
|
||||||
|
import org.apache.cordova.PluginManager;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Glue class between CordovaWebView (main Cordova logic) and SystemWebView (the actual View).
|
||||||
|
* We make the Engine separate from the actual View so that:
|
||||||
|
* A) We don't need to worry about WebView methods clashing with CordovaWebViewEngine methods
|
||||||
|
* (e.g.: goBack() is void for WebView, and boolean for CordovaWebViewEngine)
|
||||||
|
* B) Separating the actual View from the Engine makes API surfaces smaller.
|
||||||
|
* Class uses two-phase initialization. However, CordovaWebView is responsible for calling .init().
|
||||||
|
*/
|
||||||
|
public class SystemWebViewEngine implements CordovaWebViewEngine {
|
||||||
|
public static final String TAG = "SystemWebViewEngine";
|
||||||
|
|
||||||
|
protected final SystemWebView webView;
|
||||||
|
protected final SystemCookieManager cookieManager;
|
||||||
|
protected CordovaPreferences preferences;
|
||||||
|
protected CordovaBridge bridge;
|
||||||
|
protected CordovaWebViewEngine.Client client;
|
||||||
|
protected CordovaWebView parentWebView;
|
||||||
|
protected CordovaInterface cordova;
|
||||||
|
protected PluginManager pluginManager;
|
||||||
|
protected CordovaResourceApi resourceApi;
|
||||||
|
protected NativeToJsMessageQueue nativeToJsMessageQueue;
|
||||||
|
private BroadcastReceiver receiver;
|
||||||
|
|
||||||
|
/** Used when created via reflection. */
|
||||||
|
public SystemWebViewEngine(Context context, CordovaPreferences preferences) {
|
||||||
|
this(new SystemWebView(context), preferences);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SystemWebViewEngine(SystemWebView webView) {
|
||||||
|
this(webView, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SystemWebViewEngine(SystemWebView webView, CordovaPreferences preferences) {
|
||||||
|
this.preferences = preferences;
|
||||||
|
this.webView = webView;
|
||||||
|
cookieManager = new SystemCookieManager(webView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(CordovaWebView parentWebView, CordovaInterface cordova, CordovaWebViewEngine.Client client,
|
||||||
|
CordovaResourceApi resourceApi, PluginManager pluginManager,
|
||||||
|
NativeToJsMessageQueue nativeToJsMessageQueue) {
|
||||||
|
if (this.cordova != null) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
// Needed when prefs are not passed by the constructor
|
||||||
|
if (preferences == null) {
|
||||||
|
preferences = parentWebView.getPreferences();
|
||||||
|
}
|
||||||
|
this.parentWebView = parentWebView;
|
||||||
|
this.cordova = cordova;
|
||||||
|
this.client = client;
|
||||||
|
this.resourceApi = resourceApi;
|
||||||
|
this.pluginManager = pluginManager;
|
||||||
|
this.nativeToJsMessageQueue = nativeToJsMessageQueue;
|
||||||
|
webView.init(this, cordova);
|
||||||
|
|
||||||
|
initWebViewSettings();
|
||||||
|
|
||||||
|
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode.OnlineEventsBridgeModeDelegate() {
|
||||||
|
@Override
|
||||||
|
public void setNetworkAvailable(boolean value) {
|
||||||
|
//sometimes this can be called after calling webview.destroy() on destroy()
|
||||||
|
//thus resulting in a NullPointerException
|
||||||
|
if(webView!=null) {
|
||||||
|
webView.setNetworkAvailable(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void runOnUiThread(Runnable r) {
|
||||||
|
SystemWebViewEngine.this.cordova.getActivity().runOnUiThread(r);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.EvalBridgeMode(this, cordova));
|
||||||
|
bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue);
|
||||||
|
exposeJsInterface(webView, bridge);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CordovaWebView getCordovaWebView() {
|
||||||
|
return parentWebView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ICordovaCookieManager getCookieManager() {
|
||||||
|
return cookieManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView() {
|
||||||
|
return webView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint({"NewApi", "SetJavaScriptEnabled"})
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private void initWebViewSettings() {
|
||||||
|
webView.setInitialScale(0);
|
||||||
|
webView.setVerticalScrollBarEnabled(false);
|
||||||
|
// Enable JavaScript
|
||||||
|
final WebSettings settings = webView.getSettings();
|
||||||
|
settings.setJavaScriptEnabled(true);
|
||||||
|
settings.setJavaScriptCanOpenWindowsAutomatically(true);
|
||||||
|
settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.android.com/reference/android/webkit/WebSettings#setAllowFileAccess(boolean)
|
||||||
|
*
|
||||||
|
* SDK >= 30 has recently set this value to false by default.
|
||||||
|
* It is recommended to turn off this settings To prevent possible security issues targeting Build.VERSION_CODES.Q and earlier.
|
||||||
|
* For existing functionality, this setting is set to true. In a future release, this should be defaulted to false.
|
||||||
|
*/
|
||||||
|
settings.setAllowFileAccess(true);
|
||||||
|
|
||||||
|
String manufacturer = android.os.Build.MANUFACTURER;
|
||||||
|
LOG.d(TAG, "CordovaWebView is running on device made by: " + manufacturer);
|
||||||
|
|
||||||
|
//We don't save any form data in the application
|
||||||
|
settings.setSaveFormData(false);
|
||||||
|
settings.setSavePassword(false);
|
||||||
|
|
||||||
|
// Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist
|
||||||
|
// while we do this
|
||||||
|
settings.setAllowUniversalAccessFromFileURLs(true);
|
||||||
|
settings.setMediaPlaybackRequiresUserGesture(false);
|
||||||
|
|
||||||
|
// Enable database
|
||||||
|
// We keep this disabled because we use or shim to get around DOM_EXCEPTION_ERROR_16
|
||||||
|
String databasePath = webView.getContext().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
|
||||||
|
settings.setDatabaseEnabled(true);
|
||||||
|
settings.setDatabasePath(databasePath);
|
||||||
|
|
||||||
|
|
||||||
|
//Determine whether we're in debug or release mode, and turn on Debugging!
|
||||||
|
ApplicationInfo appInfo = webView.getContext().getApplicationContext().getApplicationInfo();
|
||||||
|
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
|
||||||
|
enableRemoteDebugging();
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.setGeolocationDatabasePath(databasePath);
|
||||||
|
|
||||||
|
// Enable DOM storage
|
||||||
|
settings.setDomStorageEnabled(true);
|
||||||
|
|
||||||
|
// Enable built-in geolocation
|
||||||
|
settings.setGeolocationEnabled(true);
|
||||||
|
|
||||||
|
// Enable AppCache
|
||||||
|
// Fix for CB-2282
|
||||||
|
settings.setAppCacheMaxSize(5 * 1048576);
|
||||||
|
settings.setAppCachePath(databasePath);
|
||||||
|
settings.setAppCacheEnabled(true);
|
||||||
|
|
||||||
|
// Fix for CB-1405
|
||||||
|
// Google issue 4641
|
||||||
|
String defaultUserAgent = settings.getUserAgentString();
|
||||||
|
|
||||||
|
// Fix for CB-3360
|
||||||
|
String overrideUserAgent = preferences.getString("OverrideUserAgent", null);
|
||||||
|
if (overrideUserAgent != null) {
|
||||||
|
settings.setUserAgentString(overrideUserAgent);
|
||||||
|
} else {
|
||||||
|
String appendUserAgent = preferences.getString("AppendUserAgent", null);
|
||||||
|
if (appendUserAgent != null) {
|
||||||
|
settings.setUserAgentString(defaultUserAgent + " " + appendUserAgent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// End CB-3360
|
||||||
|
|
||||||
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
|
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
|
||||||
|
if (this.receiver == null) {
|
||||||
|
this.receiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
settings.getUserAgentString();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
webView.getContext().registerReceiver(this.receiver, intentFilter);
|
||||||
|
}
|
||||||
|
// end CB-1405
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enableRemoteDebugging() {
|
||||||
|
try {
|
||||||
|
WebView.setWebContentsDebuggingEnabled(true);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LOG.d(TAG, "You have one job! To turn on Remote Web Debugging! YOU HAVE FAILED! ");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Yeah, we know. It'd be great if lint was just a little smarter.
|
||||||
|
@SuppressLint("AddJavascriptInterface")
|
||||||
|
private static void exposeJsInterface(WebView webView, CordovaBridge bridge) {
|
||||||
|
SystemExposedJsApi exposedJsApi = new SystemExposedJsApi(bridge);
|
||||||
|
webView.addJavascriptInterface(exposedJsApi, "_cordovaNative");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the url into the webview.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void loadUrl(final String url, boolean clearNavigationStack) {
|
||||||
|
webView.loadUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUrl() {
|
||||||
|
return webView.getUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopLoading() {
|
||||||
|
webView.stopLoading();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearCache() {
|
||||||
|
webView.clearCache(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearHistory() {
|
||||||
|
webView.clearHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canGoBack() {
|
||||||
|
return webView.canGoBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to previous page in history. (We manage our own history)
|
||||||
|
*
|
||||||
|
* @return true if we went back, false if we are already at top
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean goBack() {
|
||||||
|
// Check webview first to see if there is a history
|
||||||
|
// This is needed to support curPage#diffLink, since they are added to parentEngine's history, but not our history url array (JQMobile behavior)
|
||||||
|
if (webView.canGoBack()) {
|
||||||
|
webView.goBack();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPaused(boolean value) {
|
||||||
|
if (value) {
|
||||||
|
webView.onPause();
|
||||||
|
webView.pauseTimers();
|
||||||
|
} else {
|
||||||
|
webView.onResume();
|
||||||
|
webView.resumeTimers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
webView.chromeClient.destroyLastDialog();
|
||||||
|
webView.destroy();
|
||||||
|
// unregister the receiver
|
||||||
|
if (receiver != null) {
|
||||||
|
try {
|
||||||
|
webView.getContext().unregisterReceiver(receiver);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void evaluateJavascript(String js, ValueCallback<String> callback) {
|
||||||
|
webView.evaluateJavascript(js, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
{
|
||||||
|
"prepare_queue": {
|
||||||
|
"installed": [],
|
||||||
|
"uninstalled": []
|
||||||
|
},
|
||||||
|
"config_munge": {
|
||||||
|
"files": {
|
||||||
|
"app/src/main/AndroidManifest.xml": {
|
||||||
|
"parents": {
|
||||||
|
"/manifest/application": [
|
||||||
|
{
|
||||||
|
"xml": "<application android:networkSecurityConfig=\"@xml/network_security_config\" />",
|
||||||
|
"count": 1,
|
||||||
|
"mode": "merge",
|
||||||
|
"id": "config.xml",
|
||||||
|
"oldAttrib": {
|
||||||
|
"android:hardwareAccelerated": "true",
|
||||||
|
"android:icon": "@mipmap/ic_launcher",
|
||||||
|
"android:label": "@string/app_name",
|
||||||
|
"android:networkSecurityConfig": "@xml/network_security_config",
|
||||||
|
"android:supportsRtl": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"res/xml/config.xml": {
|
||||||
|
"parents": {
|
||||||
|
"/*": [
|
||||||
|
{
|
||||||
|
"xml": "<feature name=\"Device\"><param name=\"android-package\" value=\"org.apache.cordova.device.Device\" /></feature>",
|
||||||
|
"count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"xml": "<feature name=\"SplashScreen\"><param name=\"android-package\" value=\"org.apache.cordova.splashscreen.SplashScreen\" /><param name=\"onload\" value=\"true\" /></feature>",
|
||||||
|
"count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"xml": "<feature name=\"StatusBar\"><param name=\"android-package\" value=\"org.apache.cordova.statusbar.StatusBar\" /><param name=\"onload\" value=\"true\" /></feature>",
|
||||||
|
"count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"xml": "<feature name=\"Whitelist\"><param name=\"android-package\" value=\"org.apache.cordova.whitelist.WhitelistPlugin\" /><param name=\"onload\" value=\"true\" /></feature>",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config.xml": {
|
||||||
|
"parents": {
|
||||||
|
"/*": [
|
||||||
|
{
|
||||||
|
"xml": "<feature name=\"CDVIonicKeyboard\"><param name=\"android-package\" onload=\"true\" value=\"io.ionic.keyboard.CDVIonicKeyboard\" /></feature>",
|
||||||
|
"count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"xml": "<allow-navigation href=\"http://localhost/*\" />",
|
||||||
|
"count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"xml": "<allow-navigation href=\"https://localhost/*\" />",
|
||||||
|
"count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"xml": "<allow-navigation href=\"ionic://*\" />",
|
||||||
|
"count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"xml": "<preference name=\"webView\" value=\"com.ionicframework.cordova.webview.IonicWebViewEngine\" />",
|
||||||
|
"count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"xml": "<feature name=\"IonicWebView\"><param name=\"android-package\" value=\"com.ionicframework.cordova.webview.IonicWebView\" /></feature>",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"installed_plugins": {
|
||||||
|
"cordova-plugin-device": {
|
||||||
|
"PACKAGE_NAME": "ir.xFramework.xFrameworkTest"
|
||||||
|
},
|
||||||
|
"cordova-plugin-ionic-keyboard": {
|
||||||
|
"PACKAGE_NAME": "ir.xFramework.xFrameworkTest"
|
||||||
|
},
|
||||||
|
"cordova-plugin-ionic-webview": {
|
||||||
|
"ANDROID_SUPPORT_ANNOTATIONS_VERSION": "27.+",
|
||||||
|
"PACKAGE_NAME": "ir.xFramework.xFrameworkTest"
|
||||||
|
},
|
||||||
|
"cordova-plugin-splashscreen": {
|
||||||
|
"PACKAGE_NAME": "ir.xFramework.xFrameworkTest"
|
||||||
|
},
|
||||||
|
"cordova-plugin-statusbar": {
|
||||||
|
"PACKAGE_NAME": "ir.xFramework.xFrameworkTest"
|
||||||
|
},
|
||||||
|
"cordova-plugin-whitelist": {
|
||||||
|
"PACKAGE_NAME": "ir.xFramework.xFrameworkTest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependent_plugins": {},
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"id": "cordova-plugin-device.device",
|
||||||
|
"file": "plugins/cordova-plugin-device/www/device.js",
|
||||||
|
"pluginId": "cordova-plugin-device",
|
||||||
|
"clobbers": [
|
||||||
|
"device"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cordova-plugin-ionic-keyboard.keyboard",
|
||||||
|
"file": "plugins/cordova-plugin-ionic-keyboard/www/android/keyboard.js",
|
||||||
|
"pluginId": "cordova-plugin-ionic-keyboard",
|
||||||
|
"clobbers": [
|
||||||
|
"window.Keyboard"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cordova-plugin-ionic-webview.IonicWebView",
|
||||||
|
"file": "plugins/cordova-plugin-ionic-webview/src/www/util.js",
|
||||||
|
"pluginId": "cordova-plugin-ionic-webview",
|
||||||
|
"clobbers": [
|
||||||
|
"Ionic.WebView"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cordova-plugin-splashscreen.SplashScreen",
|
||||||
|
"file": "plugins/cordova-plugin-splashscreen/www/splashscreen.js",
|
||||||
|
"pluginId": "cordova-plugin-splashscreen",
|
||||||
|
"clobbers": [
|
||||||
|
"navigator.splashscreen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cordova-plugin-statusbar.statusbar",
|
||||||
|
"file": "plugins/cordova-plugin-statusbar/www/statusbar.js",
|
||||||
|
"pluginId": "cordova-plugin-statusbar",
|
||||||
|
"clobbers": [
|
||||||
|
"window.StatusBar"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"plugin_metadata": {
|
||||||
|
"cordova-plugin-device": "2.0.2",
|
||||||
|
"cordova-plugin-ionic-keyboard": "2.2.0",
|
||||||
|
"cordova-plugin-ionic-webview": "4.2.1",
|
||||||
|
"cordova-plugin-splashscreen": "5.0.2",
|
||||||
|
"cordova-plugin-statusbar": "2.4.2",
|
||||||
|
"cordova-plugin-whitelist": "1.3.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,415 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
|
if (cdvHelpers.getConfigPreference('GradlePluginKotlinEnabled', 'false').toBoolean()) {
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlin-android-extensions'
|
||||||
|
}
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
apply from: '../CordovaLib/cordova.gradle'
|
||||||
|
|
||||||
|
if(cdvHelpers.getConfigPreference('GradlePluginKotlinEnabled', 'false').toBoolean()) {
|
||||||
|
String defaultGradlePluginKotlinVersion = kotlin_version
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the user's defined Kotlin Version from config.xml.
|
||||||
|
* If the version is not set or invalid, it will default to the ${defaultGradlePluginKotlinVersion}
|
||||||
|
*/
|
||||||
|
String gradlePluginKotlinVersion = cdvHelpers.getConfigPreference('GradlePluginKotlinVersion', defaultGradlePluginKotlinVersion)
|
||||||
|
if(!cdvHelpers.isVersionValid(gradlePluginKotlinVersion)) {
|
||||||
|
println("The defined Kotlin version (${gradlePluginKotlinVersion}) does not appear to be a valid version. Falling back to version: ${defaultGradlePluginKotlinVersion}.")
|
||||||
|
gradlePluginKotlinVersion = defaultGradlePluginKotlinVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change the version to be used.
|
||||||
|
ext.kotlin_version = gradlePluginKotlinVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: 'repositories.gradle'
|
||||||
|
repositories repos
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
apply from: '../CordovaLib/cordova.gradle'
|
||||||
|
|
||||||
|
classpath 'com.android.tools.build:gradle:4.0.0'
|
||||||
|
|
||||||
|
if (cdvHelpers.getConfigPreference('GradlePluginKotlinEnabled', 'false').toBoolean()) {
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cdvHelpers.getConfigPreference('GradlePluginGoogleServicesEnabled', 'false').toBoolean()) {
|
||||||
|
String defaultGradlePluginGoogleServicesVersion = '4.2.0'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the user's defined Google Services Plugin Version from config.xml.
|
||||||
|
* If the version is not set or invalid, it will default to the ${defaultGradlePluginGoogleServicesVersion}
|
||||||
|
*/
|
||||||
|
String gradlePluginGoogleServicesVersion = cdvHelpers.getConfigPreference('GradlePluginGoogleServicesVersion', defaultGradlePluginGoogleServicesVersion)
|
||||||
|
if(!cdvHelpers.isVersionValid(gradlePluginGoogleServicesVersion)) {
|
||||||
|
println("The defined Google Services plugin version (${gradlePluginGoogleServicesVersion}) does not appear to be a valid version. Falling back to version: ${defaultGradlePluginGoogleServicesVersion}.")
|
||||||
|
gradlePluginGoogleServicesVersion = defaultGradlePluginGoogleServicesVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the Google Services classpath and set it.
|
||||||
|
String gradlePluginGoogleServicesClassPath = "com.google.gms:google-services:${gradlePluginGoogleServicesVersion}"
|
||||||
|
println "Adding classpath: ${gradlePluginGoogleServicesClassPath}"
|
||||||
|
classpath gradlePluginGoogleServicesClassPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow plugins to declare Maven dependencies via build-extras.gradle.
|
||||||
|
allprojects {
|
||||||
|
apply from: 'repositories.gradle'
|
||||||
|
repositories repos
|
||||||
|
}
|
||||||
|
|
||||||
|
task wrapper(type: Wrapper) {
|
||||||
|
gradleVersion = '6.5'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration properties. Set these via environment variables, build-extras.gradle, or gradle.properties.
|
||||||
|
// Refer to: http://www.gradle.org/docs/current/userguide/tutorial_this_and_that.html
|
||||||
|
ext {
|
||||||
|
apply from: '../CordovaLib/cordova.gradle'
|
||||||
|
|
||||||
|
// The value for android.compileSdkVersion.
|
||||||
|
if (!project.hasProperty('cdvCompileSdkVersion')) {
|
||||||
|
cdvCompileSdkVersion = null;
|
||||||
|
}
|
||||||
|
// The value for android.buildToolsVersion.
|
||||||
|
if (!project.hasProperty('cdvBuildToolsVersion')) {
|
||||||
|
cdvBuildToolsVersion = null;
|
||||||
|
}
|
||||||
|
// Sets the versionCode to the given value.
|
||||||
|
if (!project.hasProperty('cdvVersionCode')) {
|
||||||
|
cdvVersionCode = null
|
||||||
|
}
|
||||||
|
// Sets the minSdkVersion to the given value.
|
||||||
|
if (!project.hasProperty('cdvMinSdkVersion')) {
|
||||||
|
cdvMinSdkVersion = null
|
||||||
|
}
|
||||||
|
// Sets the maxSdkVersion to the given value.
|
||||||
|
if (!project.hasProperty('cdvMaxSdkVersion')) {
|
||||||
|
cdvMaxSdkVersion = null
|
||||||
|
}
|
||||||
|
// The value for android.targetSdkVersion.
|
||||||
|
if (!project.hasProperty('cdvTargetSdkVersion')) {
|
||||||
|
cdvTargetSdkVersion = null;
|
||||||
|
}
|
||||||
|
// Whether to build architecture-specific APKs.
|
||||||
|
if (!project.hasProperty('cdvBuildMultipleApks')) {
|
||||||
|
cdvBuildMultipleApks = null
|
||||||
|
}
|
||||||
|
// Whether to append a 0 "abi digit" to versionCode when only a single APK is build
|
||||||
|
if (!project.hasProperty('cdvVersionCodeForceAbiDigit')) {
|
||||||
|
cdvVersionCodeForceAbiDigit = null
|
||||||
|
}
|
||||||
|
// .properties files to use for release signing.
|
||||||
|
if (!project.hasProperty('cdvReleaseSigningPropertiesFile')) {
|
||||||
|
cdvReleaseSigningPropertiesFile = null
|
||||||
|
}
|
||||||
|
// .properties files to use for debug signing.
|
||||||
|
if (!project.hasProperty('cdvDebugSigningPropertiesFile')) {
|
||||||
|
cdvDebugSigningPropertiesFile = null
|
||||||
|
}
|
||||||
|
// Set by build.js script.
|
||||||
|
if (!project.hasProperty('cdvBuildArch')) {
|
||||||
|
cdvBuildArch = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plugin gradle extensions can append to this to have code run at the end.
|
||||||
|
cdvPluginPostBuildExtras = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// PLUGIN GRADLE EXTENSIONS START
|
||||||
|
// PLUGIN GRADLE EXTENSIONS END
|
||||||
|
|
||||||
|
def hasBuildExtras1 = file('build-extras.gradle').exists()
|
||||||
|
if (hasBuildExtras1) {
|
||||||
|
apply from: 'build-extras.gradle'
|
||||||
|
}
|
||||||
|
|
||||||
|
def hasBuildExtras2 = file('../build-extras.gradle').exists()
|
||||||
|
if (hasBuildExtras2) {
|
||||||
|
apply from: '../build-extras.gradle'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set property defaults after extension .gradle files.
|
||||||
|
ext.cdvCompileSdkVersion = cdvCompileSdkVersion == null ? (
|
||||||
|
defaultCompileSdkVersion == null
|
||||||
|
? privateHelpers.getProjectTarget()
|
||||||
|
: defaultCompileSdkVersion
|
||||||
|
) : Integer.parseInt('' + cdvCompileSdkVersion);
|
||||||
|
|
||||||
|
if (ext.cdvBuildToolsVersion == null) {
|
||||||
|
ext.cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools()
|
||||||
|
//ext.cdvBuildToolsVersion = project.ext.defaultBuildToolsVersion
|
||||||
|
}
|
||||||
|
if (ext.cdvDebugSigningPropertiesFile == null && file('../debug-signing.properties').exists()) {
|
||||||
|
ext.cdvDebugSigningPropertiesFile = '../debug-signing.properties'
|
||||||
|
}
|
||||||
|
if (ext.cdvReleaseSigningPropertiesFile == null && file('../release-signing.properties').exists()) {
|
||||||
|
ext.cdvReleaseSigningPropertiesFile = '../release-signing.properties'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cast to appropriate types.
|
||||||
|
ext.cdvBuildMultipleApks = cdvBuildMultipleApks == null ? false : cdvBuildMultipleApks.toBoolean();
|
||||||
|
ext.cdvVersionCodeForceAbiDigit = cdvVersionCodeForceAbiDigit == null ? false : cdvVersionCodeForceAbiDigit.toBoolean();
|
||||||
|
|
||||||
|
// minSdkVersion, maxSdkVersion and targetSdkVersion
|
||||||
|
ext.cdvMinSdkVersion = cdvMinSdkVersion == null ? defaultMinSdkVersion : Integer.parseInt('' + cdvMinSdkVersion)
|
||||||
|
if (cdvMaxSdkVersion != null) {
|
||||||
|
ext.cdvMaxSdkVersion = Integer.parseInt('' + cdvMaxSdkVersion)
|
||||||
|
}
|
||||||
|
ext.cdvTargetSdkVersion = cdvTargetSdkVersion == null ? defaultTargetSdkVersion : Integer.parseInt('' + cdvTargetSdkVersion)
|
||||||
|
|
||||||
|
ext.cdvVersionCode = cdvVersionCode == null ? null : Integer.parseInt('' + cdvVersionCode)
|
||||||
|
|
||||||
|
def computeBuildTargetName(debugBuild) {
|
||||||
|
def ret = 'assemble'
|
||||||
|
if (cdvBuildMultipleApks && cdvBuildArch) {
|
||||||
|
def arch = cdvBuildArch == 'arm' ? 'armv7' : cdvBuildArch
|
||||||
|
ret += '' + arch.toUpperCase().charAt(0) + arch.substring(1);
|
||||||
|
}
|
||||||
|
return ret + (debugBuild ? 'Debug' : 'Release')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make cdvBuild a task that depends on the debug/arch-sepecific task.
|
||||||
|
task cdvBuildDebug
|
||||||
|
cdvBuildDebug.dependsOn {
|
||||||
|
return computeBuildTargetName(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
task cdvBuildRelease
|
||||||
|
cdvBuildRelease.dependsOn {
|
||||||
|
return computeBuildTargetName(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
task cdvPrintProps {
|
||||||
|
doLast {
|
||||||
|
println('cdvCompileSdkVersion=' + cdvCompileSdkVersion)
|
||||||
|
println('cdvBuildToolsVersion=' + cdvBuildToolsVersion)
|
||||||
|
println('cdvVersionCode=' + cdvVersionCode)
|
||||||
|
println('cdvVersionCodeForceAbiDigit=' + cdvVersionCodeForceAbiDigit)
|
||||||
|
println('cdvMinSdkVersion=' + cdvMinSdkVersion)
|
||||||
|
println('cdvMaxSdkVersion=' + cdvMaxSdkVersion)
|
||||||
|
println('cdvTargetSdkVersion=' + cdvTargetSdkVersion)
|
||||||
|
println('cdvBuildMultipleApks=' + cdvBuildMultipleApks)
|
||||||
|
println('cdvReleaseSigningPropertiesFile=' + cdvReleaseSigningPropertiesFile)
|
||||||
|
println('cdvDebugSigningPropertiesFile=' + cdvDebugSigningPropertiesFile)
|
||||||
|
println('cdvBuildArch=' + cdvBuildArch)
|
||||||
|
println('computedVersionCode=' + android.defaultConfig.versionCode)
|
||||||
|
android.productFlavors.each { flavor ->
|
||||||
|
println('computed' + flavor.name.capitalize() + 'VersionCode=' + flavor.versionCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
defaultConfig {
|
||||||
|
versionCode cdvVersionCode ?: new BigInteger("" + privateHelpers.extractIntFromManifest("versionCode"))
|
||||||
|
applicationId privateHelpers.extractStringFromManifest("package")
|
||||||
|
|
||||||
|
if (cdvMinSdkVersion != null) {
|
||||||
|
minSdkVersion cdvMinSdkVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cdvMaxSdkVersion != null) {
|
||||||
|
maxSdkVersion cdvMaxSdkVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cdvTargetSdkVersion != null) {
|
||||||
|
targetSdkVersion cdvTargetSdkVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lintOptions {
|
||||||
|
abortOnError false;
|
||||||
|
}
|
||||||
|
|
||||||
|
compileSdkVersion cdvCompileSdkVersion
|
||||||
|
buildToolsVersion cdvBuildToolsVersion
|
||||||
|
|
||||||
|
// This code exists for Crosswalk and other Native APIs.
|
||||||
|
// By default, we multiply the existing version code in the
|
||||||
|
// Android Manifest by 10 and add a number for each architecture.
|
||||||
|
// If you are not using Crosswalk or SQLite, you can
|
||||||
|
// ignore this chunk of code, and your version codes will be respected.
|
||||||
|
|
||||||
|
if (Boolean.valueOf(cdvBuildMultipleApks)) {
|
||||||
|
flavorDimensions "default"
|
||||||
|
|
||||||
|
productFlavors {
|
||||||
|
armeabi {
|
||||||
|
versionCode defaultConfig.versionCode*10 + 1
|
||||||
|
ndk {
|
||||||
|
abiFilters = ["armeabi"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
armv7 {
|
||||||
|
versionCode defaultConfig.versionCode*10 + 2
|
||||||
|
ndk {
|
||||||
|
abiFilters = ["armeabi-v7a"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arm64 {
|
||||||
|
versionCode defaultConfig.versionCode*10 + 3
|
||||||
|
ndk {
|
||||||
|
abiFilters = ["arm64-v8a"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x86 {
|
||||||
|
versionCode defaultConfig.versionCode*10 + 4
|
||||||
|
ndk {
|
||||||
|
abiFilters = ["x86"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x86_64 {
|
||||||
|
versionCode defaultConfig.versionCode*10 + 5
|
||||||
|
ndk {
|
||||||
|
abiFilters = ["x86_64"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (Boolean.valueOf(cdvVersionCodeForceAbiDigit)) {
|
||||||
|
// This provides compatibility to the default logic for versionCode before cordova-android 5.2.0
|
||||||
|
defaultConfig {
|
||||||
|
versionCode defaultConfig.versionCode*10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cdvReleaseSigningPropertiesFile) {
|
||||||
|
signingConfigs {
|
||||||
|
release {
|
||||||
|
// These must be set or Gradle will complain (even if they are overridden).
|
||||||
|
keyAlias = ""
|
||||||
|
keyPassword = "__unset" // And these must be set to non-empty in order to have the signing step added to the task graph.
|
||||||
|
storeFile = null
|
||||||
|
storePassword = "__unset"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
signingConfig signingConfigs.release
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addSigningProps(cdvReleaseSigningPropertiesFile, signingConfigs.release)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cdvDebugSigningPropertiesFile) {
|
||||||
|
addSigningProps(cdvDebugSigningPropertiesFile, signingConfigs.debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* WARNING: Cordova Lib and platform scripts do management inside of this code here,
|
||||||
|
* if you are adding the dependencies manually, do so outside the comments, otherwise
|
||||||
|
* the Cordova tools will overwrite them
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(dir: 'libs', include: '*.jar')
|
||||||
|
|
||||||
|
if (cdvHelpers.getConfigPreference('GradlePluginKotlinEnabled', 'false').toBoolean()) {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
// SUB-PROJECT DEPENDENCIES START
|
||||||
|
implementation(project(path: ":CordovaLib"))
|
||||||
|
implementation "com.android.support:support-annotations:27.+"
|
||||||
|
// SUB-PROJECT DEPENDENCIES END
|
||||||
|
}
|
||||||
|
|
||||||
|
def promptForReleaseKeyPassword() {
|
||||||
|
if (!cdvReleaseSigningPropertiesFile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ('__unset'.equals(android.signingConfigs.release.storePassword)) {
|
||||||
|
android.signingConfigs.release.storePassword = privateHelpers.promptForPassword('Enter key store password: ')
|
||||||
|
}
|
||||||
|
if ('__unset'.equals(android.signingConfigs.release.keyPassword)) {
|
||||||
|
android.signingConfigs.release.keyPassword = privateHelpers.promptForPassword('Enter key password: ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gradle.taskGraph.whenReady { taskGraph ->
|
||||||
|
taskGraph.getAllTasks().each() { task ->
|
||||||
|
if(['validateReleaseSigning', 'validateSigningRelease', 'validateSigningArmv7Release', 'validateSigningX76Release'].contains(task.name)) {
|
||||||
|
promptForReleaseKeyPassword()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def addSigningProps(propsFilePath, signingConfig) {
|
||||||
|
def propsFile = file(propsFilePath)
|
||||||
|
def props = new Properties()
|
||||||
|
propsFile.withReader { reader ->
|
||||||
|
props.load(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
def storeFile = new File(props.get('key.store') ?: privateHelpers.ensureValueExists(propsFilePath, props, 'storeFile'))
|
||||||
|
if (!storeFile.isAbsolute()) {
|
||||||
|
storeFile = RelativePath.parse(true, storeFile.toString()).getFile(propsFile.getParentFile())
|
||||||
|
}
|
||||||
|
if (!storeFile.exists()) {
|
||||||
|
throw new FileNotFoundException('Keystore file does not exist: ' + storeFile.getAbsolutePath())
|
||||||
|
}
|
||||||
|
signingConfig.keyAlias = props.get('key.alias') ?: privateHelpers.ensureValueExists(propsFilePath, props, 'keyAlias')
|
||||||
|
signingConfig.keyPassword = props.get('keyPassword', props.get('key.alias.password', signingConfig.keyPassword))
|
||||||
|
signingConfig.storeFile = storeFile
|
||||||
|
signingConfig.storePassword = props.get('storePassword', props.get('key.store.password', signingConfig.storePassword))
|
||||||
|
def storeType = props.get('storeType', props.get('key.store.type', ''))
|
||||||
|
if (!storeType) {
|
||||||
|
def filename = storeFile.getName().toLowerCase();
|
||||||
|
if (filename.endsWith('.p12') || filename.endsWith('.pfx')) {
|
||||||
|
storeType = 'pkcs12'
|
||||||
|
} else {
|
||||||
|
storeType = signingConfig.storeType // "jks"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
signingConfig.storeType = storeType
|
||||||
|
}
|
||||||
|
|
||||||
|
for (def func : cdvPluginPostBuildExtras) {
|
||||||
|
func()
|
||||||
|
}
|
||||||
|
|
||||||
|
// This can be defined within build-extras.gradle as:
|
||||||
|
// ext.postBuildExtras = { ... code here ... }
|
||||||
|
if (hasProperty('postBuildExtras')) {
|
||||||
|
postBuildExtras()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cdvHelpers.getConfigPreference('GradlePluginGoogleServicesEnabled', 'false').toBoolean()) {
|
||||||
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
ext.repos = {
|
||||||
|
mavenCentral()
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<manifest android:hardwareAccelerated="true" android:versionCode="10000" android:versionName="1.0.0" package="ir.xFramework.xFrameworkTest" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:resizeable="true" android:smallScreens="true" android:xlargeScreens="true" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<application android:hardwareAccelerated="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:networkSecurityConfig="@xml/network_security_config" android:supportsRtl="true">
|
||||||
|
<activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode" android:label="@string/activity_name" android:launchMode="singleTop" android:name="MainActivity" android:theme="@android:style/Theme.DeviceDefault.NoActionBar" android:windowSoftInputMode="adjustResize">
|
||||||
|
<intent-filter android:label="@string/launcher_name">
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package com.ionicframework.cordova.webview;
|
||||||
|
|
||||||
|
// Copyright 2012 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.AssetManager;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
public class AndroidProtocolHandler {
|
||||||
|
private static final String TAG = "AndroidProtocolHandler";
|
||||||
|
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
public AndroidProtocolHandler(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream openAsset(String path) throws IOException {
|
||||||
|
return context.getAssets().open(path, AssetManager.ACCESS_STREAMING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream openResource(Uri uri) {
|
||||||
|
assert uri.getPath() != null;
|
||||||
|
// The path must be of the form ".../asset_type/asset_name.ext".
|
||||||
|
List<String> pathSegments = uri.getPathSegments();
|
||||||
|
String assetType = pathSegments.get(pathSegments.size() - 2);
|
||||||
|
String assetName = pathSegments.get(pathSegments.size() - 1);
|
||||||
|
|
||||||
|
// Drop the file extension.
|
||||||
|
assetName = assetName.split("\\.")[0];
|
||||||
|
try {
|
||||||
|
// Use the application context for resolving the resource package name so that we do
|
||||||
|
// not use the browser's own resources. Note that if 'context' here belongs to the
|
||||||
|
// test suite, it does not have a separate application context. In that case we use
|
||||||
|
// the original context object directly.
|
||||||
|
if (context.getApplicationContext() != null) {
|
||||||
|
context = context.getApplicationContext();
|
||||||
|
}
|
||||||
|
int fieldId = getFieldId(context, assetType, assetName);
|
||||||
|
int valueType = getValueType(context, fieldId);
|
||||||
|
if (valueType == TypedValue.TYPE_STRING) {
|
||||||
|
return context.getResources().openRawResource(fieldId);
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Asset not of type string: " + uri);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
Log.e(TAG, "Unable to open resource URL: " + uri, e);
|
||||||
|
return null;
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
Log.e(TAG, "Unable to open resource URL: " + uri, e);
|
||||||
|
return null;
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
Log.e(TAG, "Unable to open resource URL: " + uri, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream openFile(String filePath) throws IOException {
|
||||||
|
String realPath = filePath.replace(WebViewLocalServer.fileStart, "");
|
||||||
|
File localFile = new File(realPath);
|
||||||
|
return new FileInputStream(localFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream openContentUrl(Uri uri) throws IOException {
|
||||||
|
Integer port = uri.getPort();
|
||||||
|
String realPath;
|
||||||
|
if (port == -1) {
|
||||||
|
realPath = uri.toString().replace(uri.getScheme() + "://" + uri.getHost() + WebViewLocalServer.contentStart, "content:/");
|
||||||
|
} else {
|
||||||
|
realPath = uri.toString().replace(uri.getScheme() + "://" + uri.getHost() + ":" + port + WebViewLocalServer.contentStart, "content:/");
|
||||||
|
}
|
||||||
|
InputStream stream = null;
|
||||||
|
try {
|
||||||
|
stream = context.getContentResolver().openInputStream(Uri.parse(realPath));
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
Log.e(TAG, "Unable to open content URL: " + uri, e);
|
||||||
|
}
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getFieldId(Context context, String assetType, String assetName)
|
||||||
|
throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
|
||||||
|
Class<?> d = context.getClassLoader()
|
||||||
|
.loadClass(context.getPackageName() + ".R$" + assetType);
|
||||||
|
java.lang.reflect.Field field = d.getField(assetName);
|
||||||
|
int id = field.getInt(null);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getValueType(Context context, int fieldId) {
|
||||||
|
TypedValue value = new TypedValue();
|
||||||
|
context.getResources().getValue(fieldId, value, true);
|
||||||
|
return value.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.ionicframework.cordova.webview;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import org.apache.cordova.CallbackContext;
|
||||||
|
import org.apache.cordova.CordovaPlugin;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
public class IonicWebView extends CordovaPlugin {
|
||||||
|
|
||||||
|
public static final String WEBVIEW_PREFS_NAME = "WebViewSettings";
|
||||||
|
public static final String CDV_SERVER_PATH = "serverBasePath";
|
||||||
|
|
||||||
|
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
|
||||||
|
|
||||||
|
if (action.equals("setServerBasePath")) {
|
||||||
|
final String path = args.getString(0);
|
||||||
|
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
((IonicWebViewEngine)webView.getEngine()).setServerBasePath(path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} else if (action.equals("getServerBasePath")) {
|
||||||
|
callbackContext.success(((IonicWebViewEngine)webView.getEngine()).getServerBasePath());
|
||||||
|
return true;
|
||||||
|
} else if (action.equals("persistServerBasePath")) {
|
||||||
|
String path = ((IonicWebViewEngine)webView.getEngine()).getServerBasePath();
|
||||||
|
SharedPreferences prefs = cordova.getActivity().getApplicationContext().getSharedPreferences(WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putString(CDV_SERVER_PATH, path);
|
||||||
|
editor.apply();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
package com.ionicframework.cordova.webview;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.support.annotation.RequiresApi;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.webkit.ServiceWorkerController;
|
||||||
|
import android.webkit.ServiceWorkerClient;
|
||||||
|
import android.webkit.WebResourceRequest;
|
||||||
|
import android.webkit.WebResourceResponse;
|
||||||
|
import android.webkit.WebSettings;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import org.apache.cordova.ConfigXmlParser;
|
||||||
|
import org.apache.cordova.CordovaInterface;
|
||||||
|
import org.apache.cordova.CordovaPreferences;
|
||||||
|
import org.apache.cordova.CordovaResourceApi;
|
||||||
|
import org.apache.cordova.CordovaWebView;
|
||||||
|
import org.apache.cordova.CordovaWebViewEngine;
|
||||||
|
import org.apache.cordova.NativeToJsMessageQueue;
|
||||||
|
import org.apache.cordova.PluginManager;
|
||||||
|
import org.apache.cordova.engine.SystemWebViewClient;
|
||||||
|
import org.apache.cordova.engine.SystemWebViewEngine;
|
||||||
|
import org.apache.cordova.engine.SystemWebView;
|
||||||
|
|
||||||
|
public class IonicWebViewEngine extends SystemWebViewEngine {
|
||||||
|
public static final String TAG = "IonicWebViewEngine";
|
||||||
|
|
||||||
|
private WebViewLocalServer localServer;
|
||||||
|
private String CDV_LOCAL_SERVER;
|
||||||
|
private String scheme;
|
||||||
|
private static final String LAST_BINARY_VERSION_CODE = "lastBinaryVersionCode";
|
||||||
|
private static final String LAST_BINARY_VERSION_NAME = "lastBinaryVersionName";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used when created via reflection.
|
||||||
|
*/
|
||||||
|
public IonicWebViewEngine(Context context, CordovaPreferences preferences) {
|
||||||
|
super(new SystemWebView(context), preferences);
|
||||||
|
Log.d(TAG, "Ionic Web View Engine Starting Right Up 1...");
|
||||||
|
}
|
||||||
|
|
||||||
|
public IonicWebViewEngine(SystemWebView webView) {
|
||||||
|
super(webView, null);
|
||||||
|
Log.d(TAG, "Ionic Web View Engine Starting Right Up 2...");
|
||||||
|
}
|
||||||
|
|
||||||
|
public IonicWebViewEngine(SystemWebView webView, CordovaPreferences preferences) {
|
||||||
|
super(webView, preferences);
|
||||||
|
Log.d(TAG, "Ionic Web View Engine Starting Right Up 3...");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(CordovaWebView parentWebView, CordovaInterface cordova, final CordovaWebViewEngine.Client client,
|
||||||
|
CordovaResourceApi resourceApi, PluginManager pluginManager,
|
||||||
|
NativeToJsMessageQueue nativeToJsMessageQueue) {
|
||||||
|
ConfigXmlParser parser = new ConfigXmlParser();
|
||||||
|
parser.parse(cordova.getActivity());
|
||||||
|
|
||||||
|
String hostname = preferences.getString("Hostname", "localhost");
|
||||||
|
scheme = preferences.getString("Scheme", "http");
|
||||||
|
CDV_LOCAL_SERVER = scheme + "://" + hostname;
|
||||||
|
|
||||||
|
localServer = new WebViewLocalServer(cordova.getActivity(), hostname, true, parser, scheme);
|
||||||
|
localServer.hostAssets("www");
|
||||||
|
|
||||||
|
webView.setWebViewClient(new ServerClient(this, parser));
|
||||||
|
|
||||||
|
super.init(parentWebView, cordova, client, resourceApi, pluginManager, nativeToJsMessageQueue);
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
final WebSettings settings = webView.getSettings();
|
||||||
|
int mode = preferences.getInteger("MixedContentMode", 0);
|
||||||
|
settings.setMixedContentMode(mode);
|
||||||
|
}
|
||||||
|
SharedPreferences prefs = cordova.getActivity().getApplicationContext().getSharedPreferences(IonicWebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE);
|
||||||
|
String path = prefs.getString(IonicWebView.CDV_SERVER_PATH, null);
|
||||||
|
if (!isDeployDisabled() && !isNewBinary() && path != null && !path.isEmpty()) {
|
||||||
|
setServerBasePath(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean setAsServiceWorkerClient = preferences.getBoolean("ResolveServiceWorkerRequests", false);
|
||||||
|
ServiceWorkerController controller = null;
|
||||||
|
|
||||||
|
if (setAsServiceWorkerClient && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
||||||
|
controller = ServiceWorkerController.getInstance();
|
||||||
|
controller.setServiceWorkerClient(new ServiceWorkerClient(){
|
||||||
|
@Override
|
||||||
|
public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
|
||||||
|
return localServer.shouldInterceptRequest(request.getUrl(), request);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNewBinary() {
|
||||||
|
String versionCode = "";
|
||||||
|
String versionName = "";
|
||||||
|
SharedPreferences prefs = cordova.getActivity().getApplicationContext().getSharedPreferences(IonicWebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE);
|
||||||
|
String lastVersionCode = prefs.getString(LAST_BINARY_VERSION_CODE, null);
|
||||||
|
String lastVersionName = prefs.getString(LAST_BINARY_VERSION_NAME, null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
PackageInfo pInfo = this.cordova.getActivity().getPackageManager().getPackageInfo(this.cordova.getActivity().getPackageName(), 0);
|
||||||
|
versionCode = Integer.toString(pInfo.versionCode);
|
||||||
|
versionName = pInfo.versionName;
|
||||||
|
} catch(Exception ex) {
|
||||||
|
Log.e(TAG, "Unable to get package info", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!versionCode.equals(lastVersionCode) || !versionName.equals(lastVersionName)) {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putString(LAST_BINARY_VERSION_CODE, versionCode);
|
||||||
|
editor.putString(LAST_BINARY_VERSION_NAME, versionName);
|
||||||
|
editor.putString(IonicWebView.CDV_SERVER_PATH, "");
|
||||||
|
editor.apply();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDeployDisabled() {
|
||||||
|
return preferences.getBoolean("DisableDeploy", false);
|
||||||
|
}
|
||||||
|
private class ServerClient extends SystemWebViewClient {
|
||||||
|
private ConfigXmlParser parser;
|
||||||
|
|
||||||
|
public ServerClient(SystemWebViewEngine parentEngine, ConfigXmlParser parser) {
|
||||||
|
super(parentEngine);
|
||||||
|
this.parser = parser;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
@Override
|
||||||
|
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
|
||||||
|
return localServer.shouldInterceptRequest(request.getUrl(), request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||||
|
@Override
|
||||||
|
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
|
||||||
|
return localServer.shouldInterceptRequest(Uri.parse(url), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||||
|
super.onPageStarted(view, url, favicon);
|
||||||
|
String launchUrl = parser.getLaunchUrl();
|
||||||
|
if (!launchUrl.contains(WebViewLocalServer.httpsScheme) && !launchUrl.contains(WebViewLocalServer.httpScheme) && url.equals(launchUrl)) {
|
||||||
|
view.stopLoading();
|
||||||
|
// When using a custom scheme the app won't load if server start url doesn't end in /
|
||||||
|
String startUrl = CDV_LOCAL_SERVER;
|
||||||
|
if (!scheme.equalsIgnoreCase(WebViewLocalServer.httpsScheme) && !scheme.equalsIgnoreCase(WebViewLocalServer.httpScheme)) {
|
||||||
|
startUrl += "/";
|
||||||
|
}
|
||||||
|
view.loadUrl(startUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageFinished(WebView view, String url) {
|
||||||
|
super.onPageFinished(view, url);
|
||||||
|
view.loadUrl("javascript:(function() { " +
|
||||||
|
"window.WEBVIEW_SERVER_URL = '" + CDV_LOCAL_SERVER + "';" +
|
||||||
|
"})()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServerBasePath(String path) {
|
||||||
|
localServer.hostFiles(path);
|
||||||
|
webView.loadUrl(CDV_LOCAL_SERVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServerBasePath() {
|
||||||
|
return this.localServer.getBasePath();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
package com.ionicframework.cordova.webview;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2006 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
//package com.google.webviewlocalserver.third_party.android;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class UriMatcher {
|
||||||
|
/**
|
||||||
|
* Creates the root node of the URI tree.
|
||||||
|
*
|
||||||
|
* @param code the code to match for the root URI
|
||||||
|
*/
|
||||||
|
public UriMatcher(Object code) {
|
||||||
|
mCode = code;
|
||||||
|
mWhich = -1;
|
||||||
|
mChildren = new ArrayList<UriMatcher>();
|
||||||
|
mText = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UriMatcher() {
|
||||||
|
mCode = null;
|
||||||
|
mWhich = -1;
|
||||||
|
mChildren = new ArrayList<UriMatcher>();
|
||||||
|
mText = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a URI to match, and the code to return when this URI is
|
||||||
|
* matched. URI nodes may be exact match string, the token "*"
|
||||||
|
* that matches any text, or the token "#" that matches only
|
||||||
|
* numbers.
|
||||||
|
* <p>
|
||||||
|
* Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
|
||||||
|
* this method will accept a leading slash in the path.
|
||||||
|
*
|
||||||
|
* @param authority the authority to match
|
||||||
|
* @param path the path to match. * may be used as a wild card for
|
||||||
|
* any text, and # may be used as a wild card for numbers.
|
||||||
|
* @param code the code that is returned when a URI is matched
|
||||||
|
* against the given components. Must be positive.
|
||||||
|
*/
|
||||||
|
public void addURI(String scheme, String authority, String path, Object code) {
|
||||||
|
if (code == null) {
|
||||||
|
throw new IllegalArgumentException("Code can't be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] tokens = null;
|
||||||
|
if (path != null) {
|
||||||
|
String newPath = path;
|
||||||
|
// Strip leading slash if present.
|
||||||
|
if (path.length() > 0 && path.charAt(0) == '/') {
|
||||||
|
newPath = path.substring(1);
|
||||||
|
}
|
||||||
|
tokens = PATH_SPLIT_PATTERN.split(newPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
int numTokens = tokens != null ? tokens.length : 0;
|
||||||
|
UriMatcher node = this;
|
||||||
|
for (int i = -2; i < numTokens; i++) {
|
||||||
|
String token;
|
||||||
|
if (i == -2)
|
||||||
|
token = scheme;
|
||||||
|
else if (i == -1)
|
||||||
|
token = authority;
|
||||||
|
else
|
||||||
|
token = tokens[i];
|
||||||
|
ArrayList<UriMatcher> children = node.mChildren;
|
||||||
|
int numChildren = children.size();
|
||||||
|
UriMatcher child;
|
||||||
|
int j;
|
||||||
|
for (j = 0; j < numChildren; j++) {
|
||||||
|
child = children.get(j);
|
||||||
|
if (token.equals(child.mText)) {
|
||||||
|
node = child;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (j == numChildren) {
|
||||||
|
// Child not found, create it
|
||||||
|
child = new UriMatcher();
|
||||||
|
if (token.equals("**")) {
|
||||||
|
child.mWhich = REST;
|
||||||
|
} else if (token.equals("*")) {
|
||||||
|
child.mWhich = TEXT;
|
||||||
|
} else {
|
||||||
|
child.mWhich = EXACT;
|
||||||
|
}
|
||||||
|
child.mText = token;
|
||||||
|
node.mChildren.add(child);
|
||||||
|
node = child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.mCode = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final Pattern PATH_SPLIT_PATTERN = Pattern.compile("/");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to match against the path in a url.
|
||||||
|
*
|
||||||
|
* @param uri The url whose path we will match against.
|
||||||
|
* @return The code for the matched node (added using addURI),
|
||||||
|
* or null if there is no matched node.
|
||||||
|
*/
|
||||||
|
public Object match(Uri uri) {
|
||||||
|
final List<String> pathSegments = uri.getPathSegments();
|
||||||
|
final int li = pathSegments.size();
|
||||||
|
|
||||||
|
UriMatcher node = this;
|
||||||
|
|
||||||
|
if (li == 0 && uri.getAuthority() == null) {
|
||||||
|
return this.mCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = -2; i < li; i++) {
|
||||||
|
String u;
|
||||||
|
if (i == -2)
|
||||||
|
u = uri.getScheme();
|
||||||
|
else if (i == -1)
|
||||||
|
u = uri.getAuthority();
|
||||||
|
else
|
||||||
|
u = pathSegments.get(i);
|
||||||
|
ArrayList<UriMatcher> list = node.mChildren;
|
||||||
|
if (list == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
node = null;
|
||||||
|
int lj = list.size();
|
||||||
|
for (int j = 0; j < lj; j++) {
|
||||||
|
UriMatcher n = list.get(j);
|
||||||
|
which_switch:
|
||||||
|
switch (n.mWhich) {
|
||||||
|
case EXACT:
|
||||||
|
if (n.mText.equals(u)) {
|
||||||
|
node = n;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TEXT:
|
||||||
|
node = n;
|
||||||
|
break;
|
||||||
|
case REST:
|
||||||
|
return n.mCode;
|
||||||
|
}
|
||||||
|
if (node != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (node == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return node.mCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int EXACT = 0;
|
||||||
|
private static final int TEXT = 1;
|
||||||
|
private static final int REST = 2;
|
||||||
|
|
||||||
|
private Object mCode;
|
||||||
|
private int mWhich;
|
||||||
|
private String mText;
|
||||||
|
private ArrayList<UriMatcher> mChildren;
|
||||||
|
}
|
||||||
@@ -0,0 +1,645 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2015 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.ionicframework.cordova.webview;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.webkit.WebResourceRequest;
|
||||||
|
import android.webkit.WebResourceResponse;
|
||||||
|
|
||||||
|
import org.apache.cordova.ConfigXmlParser;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class meant to be used with the android.webkit.WebView class to enable hosting assets,
|
||||||
|
* resources and other data on 'virtual' http(s):// URL.
|
||||||
|
* Hosting assets and resources on http(s):// URLs is desirable as it is compatible with the
|
||||||
|
* Same-Origin policy.
|
||||||
|
* <p>
|
||||||
|
* This class is intended to be used from within the
|
||||||
|
* {@link android.webkit.WebViewClient#shouldInterceptRequest(android.webkit.WebView, String)} and
|
||||||
|
* {@link android.webkit.WebViewClient#shouldInterceptRequest(android.webkit.WebView,
|
||||||
|
* android.webkit.WebResourceRequest)}
|
||||||
|
* methods.
|
||||||
|
*/
|
||||||
|
public class WebViewLocalServer {
|
||||||
|
private static String TAG = "WebViewAssetServer";
|
||||||
|
private String basePath;
|
||||||
|
public final static String httpScheme = "http";
|
||||||
|
public final static String httpsScheme = "https";
|
||||||
|
public final static String fileStart = "/_app_file_";
|
||||||
|
public final static String contentStart = "/_app_content_";
|
||||||
|
|
||||||
|
private final UriMatcher uriMatcher;
|
||||||
|
private final AndroidProtocolHandler protocolHandler;
|
||||||
|
private final String authority;
|
||||||
|
private final String customScheme;
|
||||||
|
// Whether we're serving local files or proxying (for example, when doing livereload on a
|
||||||
|
// non-local endpoint (will be false in that case)
|
||||||
|
private boolean isAsset;
|
||||||
|
// Whether to route all requests to paths without extensions back to `index.html`
|
||||||
|
private final boolean html5mode;
|
||||||
|
private ConfigXmlParser parser;
|
||||||
|
|
||||||
|
public String getAuthority() { return authority; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A handler that produces responses for paths on the virtual asset server.
|
||||||
|
* <p>
|
||||||
|
* Methods of this handler will be invoked on a background thread and care must be taken to
|
||||||
|
* correctly synchronize access to any shared state.
|
||||||
|
* <p>
|
||||||
|
* On Android KitKat and above these methods may be called on more than one thread. This thread
|
||||||
|
* may be different than the thread on which the shouldInterceptRequest method was invoke.
|
||||||
|
* This means that on Android KitKat and above it is possible to block in this method without
|
||||||
|
* blocking other resources from loading. The number of threads used to parallelize loading
|
||||||
|
* is an internal implementation detail of the WebView and may change between updates which
|
||||||
|
* means that the amount of time spend blocking in this method should be kept to an absolute
|
||||||
|
* minimum.
|
||||||
|
*/
|
||||||
|
public abstract static class PathHandler {
|
||||||
|
protected String mimeType;
|
||||||
|
private String encoding;
|
||||||
|
private String charset;
|
||||||
|
private int statusCode;
|
||||||
|
private String reasonPhrase;
|
||||||
|
private Map<String, String> responseHeaders;
|
||||||
|
|
||||||
|
public PathHandler() {
|
||||||
|
this(null, null, 200, "OK", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PathHandler(String encoding, String charset, int statusCode,
|
||||||
|
String reasonPhrase, Map<String, String> responseHeaders) {
|
||||||
|
this.encoding = encoding;
|
||||||
|
this.charset = charset;
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
this.reasonPhrase = reasonPhrase;
|
||||||
|
Map<String, String> tempResponseHeaders;
|
||||||
|
if (responseHeaders == null) {
|
||||||
|
tempResponseHeaders = new HashMap<String, String>();
|
||||||
|
} else {
|
||||||
|
tempResponseHeaders = responseHeaders;
|
||||||
|
}
|
||||||
|
tempResponseHeaders.put("Cache-Control", "no-cache");
|
||||||
|
this.responseHeaders = tempResponseHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract public InputStream handle(Uri url);
|
||||||
|
|
||||||
|
public String getEncoding() {
|
||||||
|
return encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCharset() {
|
||||||
|
return charset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStatusCode() {
|
||||||
|
return statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReasonPhrase() {
|
||||||
|
return reasonPhrase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getResponseHeaders() {
|
||||||
|
return responseHeaders;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information about the URLs used to host the assets in the WebView.
|
||||||
|
*/
|
||||||
|
public static class AssetHostingDetails {
|
||||||
|
private Uri httpPrefix;
|
||||||
|
private Uri httpsPrefix;
|
||||||
|
|
||||||
|
/*package*/ AssetHostingDetails(Uri httpPrefix, Uri httpsPrefix) {
|
||||||
|
this.httpPrefix = httpPrefix;
|
||||||
|
this.httpsPrefix = httpsPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the http: scheme prefix at which assets are hosted.
|
||||||
|
*
|
||||||
|
* @return the http: scheme prefix at which assets are hosted. Can return null.
|
||||||
|
*/
|
||||||
|
public Uri getHttpPrefix() {
|
||||||
|
return httpPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the https: scheme prefix at which assets are hosted.
|
||||||
|
*
|
||||||
|
* @return the https: scheme prefix at which assets are hosted. Can return null.
|
||||||
|
*/
|
||||||
|
public Uri getHttpsPrefix() {
|
||||||
|
return httpsPrefix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WebViewLocalServer(Context context, String authority, boolean html5mode, ConfigXmlParser parser, String customScheme) {
|
||||||
|
uriMatcher = new UriMatcher(null);
|
||||||
|
this.html5mode = html5mode;
|
||||||
|
this.parser = parser;
|
||||||
|
this.protocolHandler = new AndroidProtocolHandler(context.getApplicationContext());
|
||||||
|
this.authority = authority;
|
||||||
|
this.customScheme = customScheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Uri parseAndVerifyUrl(String url) {
|
||||||
|
if (url == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Uri uri = Uri.parse(url);
|
||||||
|
if (uri == null) {
|
||||||
|
Log.e(TAG, "Malformed URL: " + url);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String path = uri.getPath();
|
||||||
|
if (path == null || path.length() == 0) {
|
||||||
|
Log.e(TAG, "URL does not have a path: " + url);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static WebResourceResponse createWebResourceResponse(String mimeType, String encoding, int statusCode, String reasonPhrase, Map<String, String> responseHeaders, InputStream data) {
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
int finalStatusCode = statusCode;
|
||||||
|
try {
|
||||||
|
if (data.available() == 0) {
|
||||||
|
finalStatusCode = 404;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
finalStatusCode = 500;
|
||||||
|
}
|
||||||
|
return new WebResourceResponse(mimeType, encoding, finalStatusCode, reasonPhrase, responseHeaders, data);
|
||||||
|
} else {
|
||||||
|
return new WebResourceResponse(mimeType, encoding, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to retrieve the WebResourceResponse associated with the given <code>request</code>.
|
||||||
|
* This method should be invoked from within
|
||||||
|
* {@link android.webkit.WebViewClient#shouldInterceptRequest(android.webkit.WebView,
|
||||||
|
* android.webkit.WebResourceRequest)}.
|
||||||
|
*
|
||||||
|
* @param uri the request Uri to process.
|
||||||
|
* @return a response if the request URL had a matching handler, null if no handler was found.
|
||||||
|
*/
|
||||||
|
public WebResourceResponse shouldInterceptRequest(Uri uri, WebResourceRequest request) {
|
||||||
|
PathHandler handler;
|
||||||
|
synchronized (uriMatcher) {
|
||||||
|
handler = (PathHandler) uriMatcher.match(uri);
|
||||||
|
}
|
||||||
|
if (handler == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLocalFile(uri) || uri.getAuthority().equals(this.authority)) {
|
||||||
|
Log.d("SERVER", "Handling local request: " + uri.toString());
|
||||||
|
return handleLocalRequest(uri, handler, request);
|
||||||
|
} else {
|
||||||
|
return handleProxyRequest(uri, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLocalFile(Uri uri) {
|
||||||
|
String path = uri.getPath();
|
||||||
|
if (path.startsWith(contentStart) || path.startsWith(fileStart)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private WebResourceResponse handleLocalRequest(Uri uri, PathHandler handler, WebResourceRequest request) {
|
||||||
|
String path = uri.getPath();
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && request != null && request.getRequestHeaders().get("Range") != null) {
|
||||||
|
InputStream responseStream = new LollipopLazyInputStream(handler, uri);
|
||||||
|
String mimeType = getMimeType(path, responseStream);
|
||||||
|
Map<String, String> tempResponseHeaders = handler.getResponseHeaders();
|
||||||
|
int statusCode = 206;
|
||||||
|
try {
|
||||||
|
int totalRange = responseStream.available();
|
||||||
|
String rangeString = request.getRequestHeaders().get("Range");
|
||||||
|
String[] parts = rangeString.split("=");
|
||||||
|
String[] streamParts = parts[1].split("-");
|
||||||
|
String fromRange = streamParts[0];
|
||||||
|
int range = totalRange-1;
|
||||||
|
if (streamParts.length > 1) {
|
||||||
|
range = Integer.parseInt(streamParts[1]);
|
||||||
|
}
|
||||||
|
tempResponseHeaders.put("Accept-Ranges", "bytes");
|
||||||
|
tempResponseHeaders.put("Content-Range", "bytes " + fromRange + "-" + range + "/" + totalRange);
|
||||||
|
} catch (IOException e) {
|
||||||
|
statusCode = 404;
|
||||||
|
}
|
||||||
|
return createWebResourceResponse(mimeType, handler.getEncoding(),
|
||||||
|
statusCode, handler.getReasonPhrase(), tempResponseHeaders, responseStream);
|
||||||
|
}
|
||||||
|
if (isLocalFile(uri)) {
|
||||||
|
InputStream responseStream = new LollipopLazyInputStream(handler, uri);
|
||||||
|
String mimeType = getMimeType(path, responseStream);
|
||||||
|
return createWebResourceResponse(mimeType, handler.getEncoding(),
|
||||||
|
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), responseStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.equals("") || path.equals("/") || (!uri.getLastPathSegment().contains(".") && html5mode)) {
|
||||||
|
InputStream stream;
|
||||||
|
String launchURL = parser.getLaunchUrl();
|
||||||
|
String launchFile = launchURL.substring(launchURL.lastIndexOf("/") + 1, launchURL.length());
|
||||||
|
try {
|
||||||
|
String startPath = this.basePath + "/" + launchFile;
|
||||||
|
if (isAsset) {
|
||||||
|
stream = protocolHandler.openAsset(startPath);
|
||||||
|
} else {
|
||||||
|
stream = protocolHandler.openFile(startPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Unable to open " + launchFile, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return createWebResourceResponse("text/html", handler.getEncoding(),
|
||||||
|
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
int periodIndex = path.lastIndexOf(".");
|
||||||
|
if (periodIndex >= 0) {
|
||||||
|
InputStream responseStream = new LollipopLazyInputStream(handler, uri);
|
||||||
|
String mimeType = getMimeType(path, responseStream);
|
||||||
|
return createWebResourceResponse(mimeType, handler.getEncoding(),
|
||||||
|
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), responseStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instead of reading files from the filesystem/assets, proxy through to the URL
|
||||||
|
* and let an external server handle it.
|
||||||
|
* @param uri
|
||||||
|
* @param handler
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private WebResourceResponse handleProxyRequest(Uri uri, PathHandler handler) {
|
||||||
|
try {
|
||||||
|
String path = uri.getPath();
|
||||||
|
URL url = new URL(uri.toString());
|
||||||
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
|
conn.setRequestMethod("GET");
|
||||||
|
conn.setReadTimeout(30 * 1000);
|
||||||
|
conn.setConnectTimeout(30 * 1000);
|
||||||
|
|
||||||
|
InputStream stream = conn.getInputStream();
|
||||||
|
|
||||||
|
if (path.equals("/") || (!uri.getLastPathSegment().contains(".") && html5mode)) {
|
||||||
|
return createWebResourceResponse("text/html", handler.getEncoding(),
|
||||||
|
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
int periodIndex = path.lastIndexOf(".");
|
||||||
|
if (periodIndex >= 0) {
|
||||||
|
String ext = path.substring(path.lastIndexOf("."), path.length());
|
||||||
|
|
||||||
|
// TODO: Conjure up a bit more subtlety than this
|
||||||
|
if (ext.equals(".html")) {
|
||||||
|
}
|
||||||
|
|
||||||
|
String mimeType = getMimeType(path, stream);
|
||||||
|
|
||||||
|
return createWebResourceResponse(mimeType, handler.getEncoding(),
|
||||||
|
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
return createWebResourceResponse("", handler.getEncoding(),
|
||||||
|
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), stream);
|
||||||
|
|
||||||
|
} catch (SocketTimeoutException ex) {
|
||||||
|
// bridge.handleAppUrlLoadError(ex);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// bridge.handleAppUrlLoadError(ex);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getMimeType(String path, InputStream stream) {
|
||||||
|
String mimeType = null;
|
||||||
|
try {
|
||||||
|
mimeType = URLConnection.guessContentTypeFromName(path); // Does not recognize *.js
|
||||||
|
if (mimeType != null && path.endsWith(".js") && mimeType.equals("image/x-icon")) {
|
||||||
|
Log.d(IonicWebViewEngine.TAG, "We shouldn't be here");
|
||||||
|
}
|
||||||
|
if (mimeType == null) {
|
||||||
|
if (path.endsWith(".js") || path.endsWith(".mjs")) {
|
||||||
|
// Make sure JS files get the proper mimetype to support ES modules
|
||||||
|
mimeType = "application/javascript";
|
||||||
|
} else if (path.endsWith(".wasm")) {
|
||||||
|
mimeType = "application/wasm";
|
||||||
|
} else {
|
||||||
|
mimeType = URLConnection.guessContentTypeFromStream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.e(TAG, "Unable to get mime type" + path, ex);
|
||||||
|
}
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a handler for the given <code>uri</code>. The <code>handler</code> will be invoked
|
||||||
|
* every time the <code>shouldInterceptRequest</code> method of the instance is called with
|
||||||
|
* a matching <code>uri</code>.
|
||||||
|
*
|
||||||
|
* @param uri the uri to use the handler for. The scheme and authority (domain) will be matched
|
||||||
|
* exactly. The path may contain a '*' element which will match a single element of
|
||||||
|
* a path (so a handler registered for /a/* will be invoked for /a/b and /a/c.html
|
||||||
|
* but not for /a/b/b) or the '**' element which will match any number of path
|
||||||
|
* elements.
|
||||||
|
* @param handler the handler to use for the uri.
|
||||||
|
*/
|
||||||
|
void register(Uri uri, PathHandler handler) {
|
||||||
|
synchronized (uriMatcher) {
|
||||||
|
uriMatcher.addURI(uri.getScheme(), uri.getAuthority(), uri.getPath(), handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hosts the application's assets on an http(s):// URL. Assets from the local path
|
||||||
|
* <code>assetPath/...</code> will be available under
|
||||||
|
* <code>http(s)://{uuid}.androidplatform.net/assets/...</code>.
|
||||||
|
*
|
||||||
|
* @param assetPath the local path in the application's asset folder which will be made
|
||||||
|
* available by the server (for example "/www").
|
||||||
|
*/
|
||||||
|
public void hostAssets(String assetPath) {
|
||||||
|
hostAssets(authority, assetPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hosts the application's assets on an http(s):// URL. Assets from the local path
|
||||||
|
* <code>assetPath/...</code> will be available under
|
||||||
|
* <code>http(s)://{domain}/{virtualAssetPath}/...</code>.
|
||||||
|
*
|
||||||
|
* @param domain custom domain on which the assets should be hosted (for example "example.com").
|
||||||
|
* @param assetPath the local path in the application's asset folder which will be made
|
||||||
|
* available by the server (for example "/www").
|
||||||
|
* @return prefixes under which the assets are hosted.
|
||||||
|
*/
|
||||||
|
public void hostAssets(final String domain,
|
||||||
|
final String assetPath) {
|
||||||
|
this.isAsset = true;
|
||||||
|
this.basePath = assetPath;
|
||||||
|
|
||||||
|
createHostingDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createHostingDetails() {
|
||||||
|
final String assetPath = this.basePath;
|
||||||
|
|
||||||
|
if (assetPath.indexOf('*') != -1) {
|
||||||
|
throw new IllegalArgumentException("assetPath cannot contain the '*' character.");
|
||||||
|
}
|
||||||
|
|
||||||
|
PathHandler handler = new PathHandler() {
|
||||||
|
@Override
|
||||||
|
public InputStream handle(Uri url) {
|
||||||
|
InputStream stream = null;
|
||||||
|
String path = url.getPath();
|
||||||
|
try {
|
||||||
|
if (path.startsWith(contentStart)) {
|
||||||
|
stream = protocolHandler.openContentUrl(url);
|
||||||
|
} else if (path.startsWith(fileStart) || !isAsset) {
|
||||||
|
if (!path.startsWith(fileStart)) {
|
||||||
|
path = basePath + url.getPath();
|
||||||
|
}
|
||||||
|
stream = protocolHandler.openFile(path);
|
||||||
|
} else {
|
||||||
|
stream = protocolHandler.openAsset(assetPath + path);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Unable to open asset URL: " + url);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
registerUriForScheme(httpScheme, handler, authority);
|
||||||
|
registerUriForScheme(httpsScheme, handler, authority);
|
||||||
|
if (!customScheme.equals(httpScheme) && !customScheme.equals(httpsScheme)) {
|
||||||
|
registerUriForScheme(customScheme, handler, authority);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerUriForScheme(String scheme, PathHandler handler, String authority) {
|
||||||
|
Uri.Builder uriBuilder = new Uri.Builder();
|
||||||
|
uriBuilder.scheme(scheme);
|
||||||
|
uriBuilder.authority(authority);
|
||||||
|
uriBuilder.path("");
|
||||||
|
Uri uriPrefix = uriBuilder.build();
|
||||||
|
|
||||||
|
register(Uri.withAppendedPath(uriPrefix, "/"), handler);
|
||||||
|
register(Uri.withAppendedPath(uriPrefix, "**"), handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hosts the application's resources on an http(s):// URL. Resources
|
||||||
|
* <code>http(s)://{uuid}.androidplatform.net/res/{resource_type}/{resource_name}</code>.
|
||||||
|
*
|
||||||
|
* @return prefixes under which the resources are hosted.
|
||||||
|
*/
|
||||||
|
public AssetHostingDetails hostResources() {
|
||||||
|
return hostResources(authority, "/res", true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hosts the application's resources on an http(s):// URL. Resources
|
||||||
|
* <code>http(s)://{uuid}.androidplatform.net/{virtualResourcesPath}/{resource_type}/{resource_name}</code>.
|
||||||
|
*
|
||||||
|
* @param virtualResourcesPath the path on the local server under which the resources
|
||||||
|
* should be hosted.
|
||||||
|
* @param enableHttp whether to enable hosting using the http scheme.
|
||||||
|
* @param enableHttps whether to enable hosting using the https scheme.
|
||||||
|
* @return prefixes under which the resources are hosted.
|
||||||
|
*/
|
||||||
|
public AssetHostingDetails hostResources(final String virtualResourcesPath, boolean enableHttp,
|
||||||
|
boolean enableHttps) {
|
||||||
|
return hostResources(authority, virtualResourcesPath, enableHttp, enableHttps);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hosts the application's resources on an http(s):// URL. Resources
|
||||||
|
* <code>http(s)://{domain}/{virtualResourcesPath}/{resource_type}/{resource_name}</code>.
|
||||||
|
*
|
||||||
|
* @param domain custom domain on which the assets should be hosted (for example "example.com").
|
||||||
|
* If untrusted content is to be loaded into the WebView it is advised to make
|
||||||
|
* this random.
|
||||||
|
* @param virtualResourcesPath the path on the local server under which the resources
|
||||||
|
* should be hosted.
|
||||||
|
* @param enableHttp whether to enable hosting using the http scheme.
|
||||||
|
* @param enableHttps whether to enable hosting using the https scheme.
|
||||||
|
* @return prefixes under which the resources are hosted.
|
||||||
|
*/
|
||||||
|
public AssetHostingDetails hostResources(final String domain,
|
||||||
|
final String virtualResourcesPath, boolean enableHttp,
|
||||||
|
boolean enableHttps) {
|
||||||
|
if (virtualResourcesPath.indexOf('*') != -1) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"virtualResourcesPath cannot contain the '*' character.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri.Builder uriBuilder = new Uri.Builder();
|
||||||
|
uriBuilder.scheme(httpScheme);
|
||||||
|
uriBuilder.authority(domain);
|
||||||
|
uriBuilder.path(virtualResourcesPath);
|
||||||
|
|
||||||
|
Uri httpPrefix = null;
|
||||||
|
Uri httpsPrefix = null;
|
||||||
|
|
||||||
|
PathHandler handler = new PathHandler() {
|
||||||
|
@Override
|
||||||
|
public InputStream handle(Uri url) {
|
||||||
|
InputStream stream = protocolHandler.openResource(url);
|
||||||
|
String mimeType = null;
|
||||||
|
try {
|
||||||
|
mimeType = URLConnection.guessContentTypeFromStream(stream);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.e(TAG, "Unable to get mime type" + url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (enableHttp) {
|
||||||
|
httpPrefix = uriBuilder.build();
|
||||||
|
register(Uri.withAppendedPath(httpPrefix, "**"), handler);
|
||||||
|
}
|
||||||
|
if (enableHttps) {
|
||||||
|
uriBuilder.scheme(httpsScheme);
|
||||||
|
httpsPrefix = uriBuilder.build();
|
||||||
|
register(Uri.withAppendedPath(httpsPrefix, "**"), handler);
|
||||||
|
}
|
||||||
|
return new AssetHostingDetails(httpPrefix, httpsPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hosts the application's files on an http(s):// URL. Files from the basePath
|
||||||
|
* <code>basePath/...</code> will be available under
|
||||||
|
* <code>http(s)://{uuid}.androidplatform.net/...</code>.
|
||||||
|
*
|
||||||
|
* @param basePath the local path in the application's data folder which will be made
|
||||||
|
* available by the server (for example "/www").
|
||||||
|
*/
|
||||||
|
public void hostFiles(final String basePath) {
|
||||||
|
this.isAsset = false;
|
||||||
|
this.basePath = basePath;
|
||||||
|
createHostingDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The KitKat WebView reads the InputStream on a separate threadpool. We can use that to
|
||||||
|
* parallelize loading.
|
||||||
|
*/
|
||||||
|
private static abstract class LazyInputStream extends InputStream {
|
||||||
|
protected final PathHandler handler;
|
||||||
|
private InputStream is = null;
|
||||||
|
|
||||||
|
public LazyInputStream(PathHandler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputStream getInputStream() {
|
||||||
|
if (is == null) {
|
||||||
|
is = handle();
|
||||||
|
}
|
||||||
|
return is;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract InputStream handle();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int available() throws IOException {
|
||||||
|
InputStream is = getInputStream();
|
||||||
|
return (is != null) ? is.available() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
InputStream is = getInputStream();
|
||||||
|
return (is != null) ? is.read() : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte b[]) throws IOException {
|
||||||
|
InputStream is = getInputStream();
|
||||||
|
return (is != null) ? is.read(b) : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte b[], int off, int len) throws IOException {
|
||||||
|
InputStream is = getInputStream();
|
||||||
|
return (is != null) ? is.read(b, off, len) : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long skip(long n) throws IOException {
|
||||||
|
InputStream is = getInputStream();
|
||||||
|
return (is != null) ? is.skip(n) : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For L and above.
|
||||||
|
private static class LollipopLazyInputStream extends LazyInputStream {
|
||||||
|
private Uri uri;
|
||||||
|
private InputStream is;
|
||||||
|
|
||||||
|
public LollipopLazyInputStream(PathHandler handler, Uri uri) {
|
||||||
|
super(handler);
|
||||||
|
this.uri = uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected InputStream handle() {
|
||||||
|
return handler.handle(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBasePath(){
|
||||||
|
return this.basePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
package io.ionic.keyboard;
|
||||||
|
|
||||||
|
import org.apache.cordova.CallbackContext;
|
||||||
|
import org.apache.cordova.CordovaInterface;
|
||||||
|
import org.apache.cordova.CordovaPlugin;
|
||||||
|
import org.apache.cordova.CordovaWebView;
|
||||||
|
import org.apache.cordova.PluginResult;
|
||||||
|
import org.apache.cordova.PluginResult.Status;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewTreeObserver;
|
||||||
|
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
|
||||||
|
// import additionally required classes for calculating screen height
|
||||||
|
import android.view.Display;
|
||||||
|
import android.graphics.Point;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
public class CDVIonicKeyboard extends CordovaPlugin {
|
||||||
|
private OnGlobalLayoutListener list;
|
||||||
|
private View rootView;
|
||||||
|
private View mChildOfContent;
|
||||||
|
private int usableHeightPrevious;
|
||||||
|
private FrameLayout.LayoutParams frameLayoutParams;
|
||||||
|
|
||||||
|
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
|
||||||
|
super.initialize(cordova, webView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
|
||||||
|
if ("hide".equals(action)) {
|
||||||
|
cordova.getThreadPool().execute(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
//http://stackoverflow.com/a/7696791/1091751
|
||||||
|
InputMethodManager inputManager = (InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
View v = cordova.getActivity().getCurrentFocus();
|
||||||
|
|
||||||
|
if (v == null) {
|
||||||
|
callbackContext.error("No current focus");
|
||||||
|
} else {
|
||||||
|
inputManager.hideSoftInputFromWindow(v.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
|
||||||
|
callbackContext.success(); // Thread-safe.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ("show".equals(action)) {
|
||||||
|
cordova.getThreadPool().execute(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
((InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE)).toggleSoftInput(0, InputMethodManager.HIDE_IMPLICIT_ONLY);
|
||||||
|
callbackContext.success(); // Thread-safe.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ("init".equals(action)) {
|
||||||
|
cordova.getThreadPool().execute(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
//calculate density-independent pixels (dp)
|
||||||
|
//http://developer.android.com/guide/practices/screens_support.html
|
||||||
|
DisplayMetrics dm = new DisplayMetrics();
|
||||||
|
cordova.getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);
|
||||||
|
final float density = dm.density;
|
||||||
|
|
||||||
|
//http://stackoverflow.com/a/4737265/1091751 detect if keyboard is showing
|
||||||
|
FrameLayout content = (FrameLayout) cordova.getActivity().findViewById(android.R.id.content);
|
||||||
|
rootView = content.getRootView();
|
||||||
|
list = new OnGlobalLayoutListener() {
|
||||||
|
int previousHeightDiff = 0;
|
||||||
|
@Override
|
||||||
|
public void onGlobalLayout() {
|
||||||
|
boolean resize = preferences.getBoolean("resizeOnFullScreen", false);
|
||||||
|
if (resize) {
|
||||||
|
possiblyResizeChildOfContent();
|
||||||
|
}
|
||||||
|
Rect r = new Rect();
|
||||||
|
//r will be populated with the coordinates of your view that area still visible.
|
||||||
|
rootView.getWindowVisibleDisplayFrame(r);
|
||||||
|
|
||||||
|
PluginResult result;
|
||||||
|
|
||||||
|
// cache properties for later use
|
||||||
|
int rootViewHeight = rootView.getRootView().getHeight();
|
||||||
|
int resultBottom = r.bottom;
|
||||||
|
|
||||||
|
// calculate screen height differently for android versions >= 21: Lollipop 5.x, Marshmallow 6.x
|
||||||
|
//http://stackoverflow.com/a/29257533/3642890 beware of nexus 5
|
||||||
|
int screenHeight;
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
Display display = cordova.getActivity().getWindowManager().getDefaultDisplay();
|
||||||
|
Point size = new Point();
|
||||||
|
display.getSize(size);
|
||||||
|
screenHeight = size.y;
|
||||||
|
} else {
|
||||||
|
screenHeight = rootViewHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
int heightDiff = screenHeight - resultBottom;
|
||||||
|
|
||||||
|
int pixelHeightDiff = (int)(heightDiff / density);
|
||||||
|
if (pixelHeightDiff > 100 && pixelHeightDiff != previousHeightDiff) { // if more than 100 pixels, its probably a keyboard...
|
||||||
|
String msg = "S" + Integer.toString(pixelHeightDiff);
|
||||||
|
result = new PluginResult(PluginResult.Status.OK, msg);
|
||||||
|
result.setKeepCallback(true);
|
||||||
|
callbackContext.sendPluginResult(result);
|
||||||
|
}
|
||||||
|
else if ( pixelHeightDiff != previousHeightDiff && ( previousHeightDiff - pixelHeightDiff ) > 100 ){
|
||||||
|
String msg = "H";
|
||||||
|
result = new PluginResult(PluginResult.Status.OK, msg);
|
||||||
|
result.setKeepCallback(true);
|
||||||
|
callbackContext.sendPluginResult(result);
|
||||||
|
}
|
||||||
|
previousHeightDiff = pixelHeightDiff;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void possiblyResizeChildOfContent() {
|
||||||
|
int usableHeightNow = computeUsableHeight();
|
||||||
|
if (usableHeightNow != usableHeightPrevious) {
|
||||||
|
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
|
||||||
|
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
|
||||||
|
if (heightDifference > (usableHeightSansKeyboard/4)) {
|
||||||
|
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
|
||||||
|
} else {
|
||||||
|
frameLayoutParams.height = usableHeightSansKeyboard;
|
||||||
|
}
|
||||||
|
mChildOfContent.requestLayout();
|
||||||
|
usableHeightPrevious = usableHeightNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int computeUsableHeight() {
|
||||||
|
Rect r = new Rect();
|
||||||
|
mChildOfContent.getWindowVisibleDisplayFrame(r);
|
||||||
|
return (r.bottom - r.top);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mChildOfContent = content.getChildAt(0);
|
||||||
|
rootView.getViewTreeObserver().addOnGlobalLayoutListener(list);
|
||||||
|
frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
|
||||||
|
PluginResult dataResult = new PluginResult(PluginResult.Status.OK);
|
||||||
|
dataResult.setKeepCallback(true);
|
||||||
|
callbackContext.sendPluginResult(dataResult);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false; // Returning false results in a "MethodNotFound" error.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
rootView.getViewTreeObserver().removeOnGlobalLayoutListener(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ir.xFramework.xFrameworkTest;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import org.apache.cordova.*;
|
||||||
|
|
||||||
|
public class MainActivity extends CordovaActivity
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
// enable Cordova apps to be started in the background
|
||||||
|
Bundle extras = getIntent().getExtras();
|
||||||
|
if (extras != null && extras.getBoolean("cdvStartInBackground", false)) {
|
||||||
|
moveTaskToBack(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set by <content src="index.html" /> in config.xml
|
||||||
|
loadUrl(launchUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova.device;
|
||||||
|
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import org.apache.cordova.CordovaWebView;
|
||||||
|
import org.apache.cordova.CallbackContext;
|
||||||
|
import org.apache.cordova.CordovaPlugin;
|
||||||
|
import org.apache.cordova.CordovaInterface;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import android.provider.Settings;
|
||||||
|
|
||||||
|
public class Device extends CordovaPlugin {
|
||||||
|
public static final String TAG = "Device";
|
||||||
|
|
||||||
|
public static String platform; // Device OS
|
||||||
|
public static String uuid; // Device UUID
|
||||||
|
|
||||||
|
private static final String ANDROID_PLATFORM = "Android";
|
||||||
|
private static final String AMAZON_PLATFORM = "amazon-fireos";
|
||||||
|
private static final String AMAZON_DEVICE = "Amazon";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public Device() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the context of the Command. This can then be used to do things like
|
||||||
|
* get file paths associated with the Activity.
|
||||||
|
*
|
||||||
|
* @param cordova The context of the main Activity.
|
||||||
|
* @param webView The CordovaWebView Cordova is running in.
|
||||||
|
*/
|
||||||
|
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
|
||||||
|
super.initialize(cordova, webView);
|
||||||
|
Device.uuid = getUuid();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the request and returns PluginResult.
|
||||||
|
*
|
||||||
|
* @param action The action to execute.
|
||||||
|
* @param args JSONArry of arguments for the plugin.
|
||||||
|
* @param callbackContext The callback id used when calling back into JavaScript.
|
||||||
|
* @return True if the action was valid, false if not.
|
||||||
|
*/
|
||||||
|
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
|
||||||
|
if ("getDeviceInfo".equals(action)) {
|
||||||
|
JSONObject r = new JSONObject();
|
||||||
|
r.put("uuid", Device.uuid);
|
||||||
|
r.put("version", this.getOSVersion());
|
||||||
|
r.put("platform", this.getPlatform());
|
||||||
|
r.put("model", this.getModel());
|
||||||
|
r.put("manufacturer", this.getManufacturer());
|
||||||
|
r.put("isVirtual", this.isVirtual());
|
||||||
|
r.put("serial", this.getSerialNumber());
|
||||||
|
callbackContext.success(r);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// LOCAL METHODS
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the OS name.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getPlatform() {
|
||||||
|
String platform;
|
||||||
|
if (isAmazonDevice()) {
|
||||||
|
platform = AMAZON_PLATFORM;
|
||||||
|
} else {
|
||||||
|
platform = ANDROID_PLATFORM;
|
||||||
|
}
|
||||||
|
return platform;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the device's Universally Unique Identifier (UUID).
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getUuid() {
|
||||||
|
String uuid = Settings.Secure.getString(this.cordova.getActivity().getContentResolver(), android.provider.Settings.Secure.ANDROID_ID);
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getModel() {
|
||||||
|
String model = android.os.Build.MODEL;
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProductName() {
|
||||||
|
String productname = android.os.Build.PRODUCT;
|
||||||
|
return productname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getManufacturer() {
|
||||||
|
String manufacturer = android.os.Build.MANUFACTURER;
|
||||||
|
return manufacturer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSerialNumber() {
|
||||||
|
String serial = android.os.Build.SERIAL;
|
||||||
|
return serial;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the OS version.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getOSVersion() {
|
||||||
|
String osversion = android.os.Build.VERSION.RELEASE;
|
||||||
|
return osversion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSDKVersion() {
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
String sdkversion = android.os.Build.VERSION.SDK;
|
||||||
|
return sdkversion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTimeZoneID() {
|
||||||
|
TimeZone tz = TimeZone.getDefault();
|
||||||
|
return (tz.getID());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to check if the device is manufactured by Amazon
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isAmazonDevice() {
|
||||||
|
if (android.os.Build.MANUFACTURER.equals(AMAZON_DEVICE)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVirtual() {
|
||||||
|
return android.os.Build.FINGERPRINT.contains("generic") ||
|
||||||
|
android.os.Build.PRODUCT.contains("sdk");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,413 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cordova.splashscreen;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.drawable.ColorDrawable;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.view.Display;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup.LayoutParams;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.view.animation.Animation;
|
||||||
|
import android.view.animation.AlphaAnimation;
|
||||||
|
import android.view.animation.DecelerateInterpolator;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
|
||||||
|
import org.apache.cordova.CallbackContext;
|
||||||
|
import org.apache.cordova.CordovaPlugin;
|
||||||
|
import org.apache.cordova.CordovaWebView;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
public class SplashScreen extends CordovaPlugin {
|
||||||
|
private static final String LOG_TAG = "SplashScreen";
|
||||||
|
// Cordova 3.x.x has a copy of this plugin bundled with it (SplashScreenInternal.java).
|
||||||
|
// Enable functionality only if running on 4.x.x.
|
||||||
|
private static final boolean HAS_BUILT_IN_SPLASH_SCREEN = Integer.valueOf(CordovaWebView.CORDOVA_VERSION.split("\\.")[0]) < 4;
|
||||||
|
private static final int DEFAULT_SPLASHSCREEN_DURATION = 3000;
|
||||||
|
private static final int DEFAULT_FADE_DURATION = 500;
|
||||||
|
private static Dialog splashDialog;
|
||||||
|
private static ProgressDialog spinnerDialog;
|
||||||
|
private static boolean firstShow = true;
|
||||||
|
private static boolean lastHideAfterDelay; // https://issues.apache.org/jira/browse/CB-9094
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the splash drawable.
|
||||||
|
*/
|
||||||
|
private ImageView splashImageView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remember last device orientation to detect orientation changes.
|
||||||
|
*/
|
||||||
|
private int orientation;
|
||||||
|
|
||||||
|
// Helper to be compile-time compatible with both Cordova 3.x and 4.x.
|
||||||
|
private View getView() {
|
||||||
|
try {
|
||||||
|
return (View)webView.getClass().getMethod("getView").invoke(webView);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return (View)webView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getSplashId() {
|
||||||
|
int drawableId = 0;
|
||||||
|
String splashResource = preferences.getString("SplashScreen", "screen");
|
||||||
|
if (splashResource != null) {
|
||||||
|
drawableId = cordova.getActivity().getResources().getIdentifier(splashResource, "drawable", cordova.getActivity().getClass().getPackage().getName());
|
||||||
|
if (drawableId == 0) {
|
||||||
|
drawableId = cordova.getActivity().getResources().getIdentifier(splashResource, "drawable", cordova.getActivity().getPackageName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return drawableId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void pluginInitialize() {
|
||||||
|
if (HAS_BUILT_IN_SPLASH_SCREEN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Make WebView invisible while loading URL
|
||||||
|
// CB-11326 Ensure we're calling this on UI thread
|
||||||
|
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
getView().setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
int drawableId = getSplashId();
|
||||||
|
|
||||||
|
// Save initial orientation.
|
||||||
|
orientation = cordova.getActivity().getResources().getConfiguration().orientation;
|
||||||
|
|
||||||
|
if (firstShow) {
|
||||||
|
boolean autoHide = preferences.getBoolean("AutoHideSplashScreen", true);
|
||||||
|
showSplashScreen(autoHide);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferences.getBoolean("SplashShowOnlyFirstTime", true)) {
|
||||||
|
firstShow = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorter way to check value of "SplashMaintainAspectRatio" preference.
|
||||||
|
*/
|
||||||
|
private boolean isMaintainAspectRatio () {
|
||||||
|
return preferences.getBoolean("SplashMaintainAspectRatio", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getFadeDuration () {
|
||||||
|
int fadeSplashScreenDuration = preferences.getBoolean("FadeSplashScreen", true) ?
|
||||||
|
preferences.getInteger("FadeSplashScreenDuration", DEFAULT_FADE_DURATION) : 0;
|
||||||
|
|
||||||
|
if (fadeSplashScreenDuration < 30) {
|
||||||
|
// [CB-9750] This value used to be in decimal seconds, so we will assume that if someone specifies 10
|
||||||
|
// they mean 10 seconds, and not the meaningless 10ms
|
||||||
|
fadeSplashScreenDuration *= 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fadeSplashScreenDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause(boolean multitasking) {
|
||||||
|
if (HAS_BUILT_IN_SPLASH_SCREEN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// hide the splash screen to avoid leaking a window
|
||||||
|
this.removeSplashScreen(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
if (HAS_BUILT_IN_SPLASH_SCREEN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// hide the splash screen to avoid leaking a window
|
||||||
|
this.removeSplashScreen(true);
|
||||||
|
// If we set this to true onDestroy, we lose track when we go from page to page!
|
||||||
|
//firstShow = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
|
||||||
|
if (action.equals("hide")) {
|
||||||
|
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
webView.postMessage("splashscreen", "hide");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (action.equals("show")) {
|
||||||
|
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
webView.postMessage("splashscreen", "show");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
callbackContext.success();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object onMessage(String id, Object data) {
|
||||||
|
if (HAS_BUILT_IN_SPLASH_SCREEN) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if ("splashscreen".equals(id)) {
|
||||||
|
if ("hide".equals(data.toString())) {
|
||||||
|
this.removeSplashScreen(false);
|
||||||
|
} else {
|
||||||
|
this.showSplashScreen(false);
|
||||||
|
}
|
||||||
|
} else if ("spinner".equals(id)) {
|
||||||
|
if ("stop".equals(data.toString())) {
|
||||||
|
getView().setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
} else if ("onReceivedError".equals(id)) {
|
||||||
|
this.spinnerStop();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't add @Override so that plugin still compiles on 3.x.x for a while
|
||||||
|
public void onConfigurationChanged(Configuration newConfig) {
|
||||||
|
if (newConfig.orientation != orientation) {
|
||||||
|
orientation = newConfig.orientation;
|
||||||
|
|
||||||
|
// Splash drawable may change with orientation, so reload it.
|
||||||
|
if (splashImageView != null) {
|
||||||
|
int drawableId = getSplashId();
|
||||||
|
if (drawableId != 0) {
|
||||||
|
splashImageView.setImageDrawable(cordova.getActivity().getResources().getDrawable(drawableId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeSplashScreen(final boolean forceHideImmediately) {
|
||||||
|
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (splashDialog != null && splashDialog.isShowing()) {
|
||||||
|
final int fadeSplashScreenDuration = getFadeDuration();
|
||||||
|
// CB-10692 If the plugin is being paused/destroyed, skip the fading and hide it immediately
|
||||||
|
if (fadeSplashScreenDuration > 0 && forceHideImmediately == false) {
|
||||||
|
AlphaAnimation fadeOut = new AlphaAnimation(1, 0);
|
||||||
|
fadeOut.setInterpolator(new DecelerateInterpolator());
|
||||||
|
fadeOut.setDuration(fadeSplashScreenDuration);
|
||||||
|
|
||||||
|
splashImageView.setAnimation(fadeOut);
|
||||||
|
splashImageView.startAnimation(fadeOut);
|
||||||
|
|
||||||
|
fadeOut.setAnimationListener(new Animation.AnimationListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationStart(Animation animation) {
|
||||||
|
spinnerStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animation animation) {
|
||||||
|
if (splashDialog != null && splashDialog.isShowing()) {
|
||||||
|
splashDialog.dismiss();
|
||||||
|
splashDialog = null;
|
||||||
|
splashImageView = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationRepeat(Animation animation) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
spinnerStop();
|
||||||
|
splashDialog.dismiss();
|
||||||
|
splashDialog = null;
|
||||||
|
splashImageView = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the splash screen over the full Activity
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private void showSplashScreen(final boolean hideAfterDelay) {
|
||||||
|
final int splashscreenTime = preferences.getInteger("SplashScreenDelay", DEFAULT_SPLASHSCREEN_DURATION);
|
||||||
|
final int drawableId = getSplashId();
|
||||||
|
|
||||||
|
final int fadeSplashScreenDuration = getFadeDuration();
|
||||||
|
final int effectiveSplashDuration = Math.max(0, splashscreenTime - fadeSplashScreenDuration);
|
||||||
|
|
||||||
|
lastHideAfterDelay = hideAfterDelay;
|
||||||
|
|
||||||
|
// Prevent to show the splash dialog if the activity is in the process of finishing
|
||||||
|
if (cordova.getActivity().isFinishing()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If the splash dialog is showing don't try to show it again
|
||||||
|
if (splashDialog != null && splashDialog.isShowing()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (drawableId == 0 || (splashscreenTime <= 0 && hideAfterDelay)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
// Get reference to display
|
||||||
|
Display display = cordova.getActivity().getWindowManager().getDefaultDisplay();
|
||||||
|
Context context = webView.getContext();
|
||||||
|
|
||||||
|
// Use an ImageView to render the image because of its flexible scaling options.
|
||||||
|
splashImageView = new ImageView(context);
|
||||||
|
splashImageView.setImageResource(drawableId);
|
||||||
|
LayoutParams layoutParams = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
|
||||||
|
splashImageView.setLayoutParams(layoutParams);
|
||||||
|
|
||||||
|
splashImageView.setMinimumHeight(display.getHeight());
|
||||||
|
splashImageView.setMinimumWidth(display.getWidth());
|
||||||
|
|
||||||
|
// TODO: Use the background color of the webView's parent instead of using the preference.
|
||||||
|
splashImageView.setBackgroundColor(preferences.getInteger("backgroundColor", Color.BLACK));
|
||||||
|
|
||||||
|
if (isMaintainAspectRatio()) {
|
||||||
|
// CENTER_CROP scale mode is equivalent to CSS "background-size:cover"
|
||||||
|
splashImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// FIT_XY scales image non-uniformly to fit into image view.
|
||||||
|
splashImageView.setScaleType(ImageView.ScaleType.FIT_XY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and show the dialog
|
||||||
|
splashDialog = new Dialog(context, android.R.style.Theme_Translucent_NoTitleBar);
|
||||||
|
// check to see if the splash screen should be full screen
|
||||||
|
if ((cordova.getActivity().getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||||
|
== WindowManager.LayoutParams.FLAG_FULLSCREEN) {
|
||||||
|
splashDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||||
|
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||||
|
}
|
||||||
|
splashDialog.setContentView(splashImageView);
|
||||||
|
splashDialog.setCancelable(false);
|
||||||
|
splashDialog.show();
|
||||||
|
|
||||||
|
if (preferences.getBoolean("ShowSplashScreenSpinner", true)) {
|
||||||
|
spinnerStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set Runnable to remove splash screen just in case
|
||||||
|
if (hideAfterDelay) {
|
||||||
|
final Handler handler = new Handler();
|
||||||
|
handler.postDelayed(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (lastHideAfterDelay) {
|
||||||
|
removeSplashScreen(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, effectiveSplashDuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show only spinner in the center of the screen
|
||||||
|
private void spinnerStart() {
|
||||||
|
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
spinnerStop();
|
||||||
|
|
||||||
|
spinnerDialog = new ProgressDialog(webView.getContext());
|
||||||
|
spinnerDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||||
|
public void onCancel(DialogInterface dialog) {
|
||||||
|
spinnerDialog = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
spinnerDialog.setCancelable(false);
|
||||||
|
spinnerDialog.setIndeterminate(true);
|
||||||
|
|
||||||
|
RelativeLayout centeredLayout = new RelativeLayout(cordova.getActivity());
|
||||||
|
centeredLayout.setGravity(Gravity.CENTER);
|
||||||
|
centeredLayout.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
|
||||||
|
|
||||||
|
ProgressBar progressBar = new ProgressBar(webView.getContext());
|
||||||
|
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||||
|
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
|
||||||
|
progressBar.setLayoutParams(layoutParams);
|
||||||
|
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
String colorName = preferences.getString("SplashScreenSpinnerColor", null);
|
||||||
|
if(colorName != null){
|
||||||
|
int[][] states = new int[][] {
|
||||||
|
new int[] { android.R.attr.state_enabled}, // enabled
|
||||||
|
new int[] {-android.R.attr.state_enabled}, // disabled
|
||||||
|
new int[] {-android.R.attr.state_checked}, // unchecked
|
||||||
|
new int[] { android.R.attr.state_pressed} // pressed
|
||||||
|
};
|
||||||
|
int progressBarColor = Color.parseColor(colorName);
|
||||||
|
int[] colors = new int[] {
|
||||||
|
progressBarColor,
|
||||||
|
progressBarColor,
|
||||||
|
progressBarColor,
|
||||||
|
progressBarColor
|
||||||
|
};
|
||||||
|
ColorStateList colorStateList = new ColorStateList(states, colors);
|
||||||
|
progressBar.setIndeterminateTintList(colorStateList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
centeredLayout.addView(progressBar);
|
||||||
|
|
||||||
|
spinnerDialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
|
||||||
|
spinnerDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
|
||||||
|
|
||||||
|
spinnerDialog.show();
|
||||||
|
spinnerDialog.setContentView(centeredLayout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void spinnerStop() {
|
||||||
|
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (spinnerDialog != null && spinnerDialog.isShowing()) {
|
||||||
|
spinnerDialog.dismiss();
|
||||||
|
spinnerDialog = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,276 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package org.apache.cordova.statusbar;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import org.apache.cordova.CallbackContext;
|
||||||
|
import org.apache.cordova.CordovaArgs;
|
||||||
|
import org.apache.cordova.CordovaInterface;
|
||||||
|
import org.apache.cordova.CordovaPlugin;
|
||||||
|
import org.apache.cordova.CordovaWebView;
|
||||||
|
import org.apache.cordova.LOG;
|
||||||
|
import org.apache.cordova.PluginResult;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class StatusBar extends CordovaPlugin {
|
||||||
|
private static final String TAG = "StatusBar";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the context of the Command. This can then be used to do things like
|
||||||
|
* get file paths associated with the Activity.
|
||||||
|
*
|
||||||
|
* @param cordova The context of the main Activity.
|
||||||
|
* @param webView The CordovaWebView Cordova is running in.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void initialize(final CordovaInterface cordova, CordovaWebView webView) {
|
||||||
|
LOG.v(TAG, "StatusBar: initialization");
|
||||||
|
super.initialize(cordova, webView);
|
||||||
|
|
||||||
|
this.cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// Clear flag FLAG_FORCE_NOT_FULLSCREEN which is set initially
|
||||||
|
// by the Cordova.
|
||||||
|
Window window = cordova.getActivity().getWindow();
|
||||||
|
window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
|
||||||
|
|
||||||
|
// Read 'StatusBarBackgroundColor' from config.xml, default is #000000.
|
||||||
|
setStatusBarBackgroundColor(preferences.getString("StatusBarBackgroundColor", "#000000"));
|
||||||
|
|
||||||
|
// Read 'StatusBarStyle' from config.xml, default is 'lightcontent'.
|
||||||
|
setStatusBarStyle(preferences.getString("StatusBarStyle", "lightcontent"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the request and returns PluginResult.
|
||||||
|
*
|
||||||
|
* @param action The action to execute.
|
||||||
|
* @param args JSONArry of arguments for the plugin.
|
||||||
|
* @param callbackContext The callback id used when calling back into JavaScript.
|
||||||
|
* @return True if the action was valid, false otherwise.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean execute(final String action, final CordovaArgs args, final CallbackContext callbackContext) throws JSONException {
|
||||||
|
LOG.v(TAG, "Executing action: " + action);
|
||||||
|
final Activity activity = this.cordova.getActivity();
|
||||||
|
final Window window = activity.getWindow();
|
||||||
|
|
||||||
|
if ("_ready".equals(action)) {
|
||||||
|
boolean statusBarVisible = (window.getAttributes().flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) == 0;
|
||||||
|
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, statusBarVisible));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("show".equals(action)) {
|
||||||
|
this.cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// SYSTEM_UI_FLAG_FULLSCREEN is available since JellyBean, but we
|
||||||
|
// use KitKat here to be aligned with "Fullscreen" preference
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
|
int uiOptions = window.getDecorView().getSystemUiVisibility();
|
||||||
|
uiOptions &= ~View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
|
||||||
|
uiOptions &= ~View.SYSTEM_UI_FLAG_FULLSCREEN;
|
||||||
|
|
||||||
|
window.getDecorView().setSystemUiVisibility(uiOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CB-11197 We still need to update LayoutParams to force status bar
|
||||||
|
// to be hidden when entering e.g. text fields
|
||||||
|
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("hide".equals(action)) {
|
||||||
|
this.cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// SYSTEM_UI_FLAG_FULLSCREEN is available since JellyBean, but we
|
||||||
|
// use KitKat here to be aligned with "Fullscreen" preference
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
|
int uiOptions = window.getDecorView().getSystemUiVisibility()
|
||||||
|
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
|
| View.SYSTEM_UI_FLAG_FULLSCREEN;
|
||||||
|
|
||||||
|
window.getDecorView().setSystemUiVisibility(uiOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CB-11197 We still need to update LayoutParams to force status bar
|
||||||
|
// to be hidden when entering e.g. text fields
|
||||||
|
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("backgroundColorByHexString".equals(action)) {
|
||||||
|
this.cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
setStatusBarBackgroundColor(args.getString(0));
|
||||||
|
} catch (JSONException ignore) {
|
||||||
|
LOG.e(TAG, "Invalid hexString argument, use f.i. '#777777'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("overlaysWebView".equals(action)) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
this.cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
setStatusBarTransparent(args.getBoolean(0));
|
||||||
|
} catch (JSONException ignore) {
|
||||||
|
LOG.e(TAG, "Invalid boolean argument");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else return args.getBoolean(0) == false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("styleDefault".equals(action)) {
|
||||||
|
this.cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
setStatusBarStyle("default");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("styleLightContent".equals(action)) {
|
||||||
|
this.cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
setStatusBarStyle("lightcontent");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("styleBlackTranslucent".equals(action)) {
|
||||||
|
this.cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
setStatusBarStyle("blacktranslucent");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("styleBlackOpaque".equals(action)) {
|
||||||
|
this.cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
setStatusBarStyle("blackopaque");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setStatusBarBackgroundColor(final String colorPref) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
if (colorPref != null && !colorPref.isEmpty()) {
|
||||||
|
final Window window = cordova.getActivity().getWindow();
|
||||||
|
// Method and constants not available on all SDKs but we want to be able to compile this code with any SDK
|
||||||
|
window.clearFlags(0x04000000); // SDK 19: WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||||
|
window.addFlags(0x80000000); // SDK 21: WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
||||||
|
try {
|
||||||
|
// Using reflection makes sure any 5.0+ device will work without having to compile with SDK level 21
|
||||||
|
window.getClass().getMethod("setStatusBarColor", int.class).invoke(window, Color.parseColor(colorPref));
|
||||||
|
} catch (IllegalArgumentException ignore) {
|
||||||
|
LOG.e(TAG, "Invalid hexString argument, use f.i. '#999999'");
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
// this should not happen, only in case Android removes this method in a version > 21
|
||||||
|
LOG.w(TAG, "Method window.setStatusBarColor not found for SDK level " + Build.VERSION.SDK_INT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setStatusBarTransparent(final boolean transparent) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
final Window window = cordova.getActivity().getWindow();
|
||||||
|
if (transparent) {
|
||||||
|
window.getDecorView().setSystemUiVisibility(
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
|
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
|
||||||
|
window.setStatusBarColor(Color.TRANSPARENT);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
window.getDecorView().setSystemUiVisibility(
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
|
| View.SYSTEM_UI_FLAG_VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setStatusBarStyle(final String style) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
if (style != null && !style.isEmpty()) {
|
||||||
|
View decorView = cordova.getActivity().getWindow().getDecorView();
|
||||||
|
int uiOptions = decorView.getSystemUiVisibility();
|
||||||
|
|
||||||
|
String[] darkContentStyles = {
|
||||||
|
"default",
|
||||||
|
};
|
||||||
|
|
||||||
|
String[] lightContentStyles = {
|
||||||
|
"lightcontent",
|
||||||
|
"blacktranslucent",
|
||||||
|
"blackopaque",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Arrays.asList(darkContentStyles).contains(style.toLowerCase())) {
|
||||||
|
decorView.setSystemUiVisibility(uiOptions | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Arrays.asList(lightContentStyles).contains(style.toLowerCase())) {
|
||||||
|
decorView.setSystemUiVisibility(uiOptions & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.e(TAG, "Invalid style, must be either 'default', 'lightcontent' or the deprecated 'blacktranslucent' and 'blackopaque'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cordova.whitelist;
|
||||||
|
|
||||||
|
import org.apache.cordova.CordovaPlugin;
|
||||||
|
import org.apache.cordova.ConfigXmlParser;
|
||||||
|
import org.apache.cordova.LOG;
|
||||||
|
import org.apache.cordova.Whitelist;
|
||||||
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public class WhitelistPlugin extends CordovaPlugin {
|
||||||
|
private static final String LOG_TAG = "WhitelistPlugin";
|
||||||
|
private Whitelist allowedNavigations;
|
||||||
|
private Whitelist allowedIntents;
|
||||||
|
private Whitelist allowedRequests;
|
||||||
|
|
||||||
|
// Used when instantiated via reflection by PluginManager
|
||||||
|
public WhitelistPlugin() {
|
||||||
|
}
|
||||||
|
// These can be used by embedders to allow Java-configuration of whitelists.
|
||||||
|
public WhitelistPlugin(Context context) {
|
||||||
|
this(new Whitelist(), new Whitelist(), null);
|
||||||
|
new CustomConfigXmlParser().parse(context);
|
||||||
|
}
|
||||||
|
public WhitelistPlugin(XmlPullParser xmlParser) {
|
||||||
|
this(new Whitelist(), new Whitelist(), null);
|
||||||
|
new CustomConfigXmlParser().parse(xmlParser);
|
||||||
|
}
|
||||||
|
public WhitelistPlugin(Whitelist allowedNavigations, Whitelist allowedIntents, Whitelist allowedRequests) {
|
||||||
|
if (allowedRequests == null) {
|
||||||
|
allowedRequests = new Whitelist();
|
||||||
|
allowedRequests.addWhiteListEntry("file:///*", false);
|
||||||
|
allowedRequests.addWhiteListEntry("data:*", false);
|
||||||
|
}
|
||||||
|
this.allowedNavigations = allowedNavigations;
|
||||||
|
this.allowedIntents = allowedIntents;
|
||||||
|
this.allowedRequests = allowedRequests;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void pluginInitialize() {
|
||||||
|
if (allowedNavigations == null) {
|
||||||
|
allowedNavigations = new Whitelist();
|
||||||
|
allowedIntents = new Whitelist();
|
||||||
|
allowedRequests = new Whitelist();
|
||||||
|
new CustomConfigXmlParser().parse(webView.getContext());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CustomConfigXmlParser extends ConfigXmlParser {
|
||||||
|
@Override
|
||||||
|
public void handleStartTag(XmlPullParser xml) {
|
||||||
|
String strNode = xml.getName();
|
||||||
|
if (strNode.equals("content")) {
|
||||||
|
String startPage = xml.getAttributeValue(null, "src");
|
||||||
|
allowedNavigations.addWhiteListEntry(startPage, false);
|
||||||
|
} else if (strNode.equals("allow-navigation")) {
|
||||||
|
String origin = xml.getAttributeValue(null, "href");
|
||||||
|
if ("*".equals(origin)) {
|
||||||
|
allowedNavigations.addWhiteListEntry("http://*/*", false);
|
||||||
|
allowedNavigations.addWhiteListEntry("https://*/*", false);
|
||||||
|
allowedNavigations.addWhiteListEntry("data:*", false);
|
||||||
|
} else {
|
||||||
|
allowedNavigations.addWhiteListEntry(origin, false);
|
||||||
|
}
|
||||||
|
} else if (strNode.equals("allow-intent")) {
|
||||||
|
String origin = xml.getAttributeValue(null, "href");
|
||||||
|
allowedIntents.addWhiteListEntry(origin, false);
|
||||||
|
} else if (strNode.equals("access")) {
|
||||||
|
String origin = xml.getAttributeValue(null, "origin");
|
||||||
|
String subdomains = xml.getAttributeValue(null, "subdomains");
|
||||||
|
boolean external = (xml.getAttributeValue(null, "launch-external") != null);
|
||||||
|
if (origin != null) {
|
||||||
|
if (external) {
|
||||||
|
LOG.w(LOG_TAG, "Found <access launch-external> within config.xml. Please use <allow-intent> instead.");
|
||||||
|
allowedIntents.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
|
||||||
|
} else {
|
||||||
|
if ("*".equals(origin)) {
|
||||||
|
allowedRequests.addWhiteListEntry("http://*/*", false);
|
||||||
|
allowedRequests.addWhiteListEntry("https://*/*", false);
|
||||||
|
} else {
|
||||||
|
allowedRequests.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void handleEndTag(XmlPullParser xml) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean shouldAllowNavigation(String url) {
|
||||||
|
if (allowedNavigations.isUrlWhiteListed(url)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return null; // Default policy
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean shouldAllowRequest(String url) {
|
||||||
|
if (Boolean.TRUE == shouldAllowNavigation(url)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (allowedRequests.isUrlWhiteListed(url)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return null; // Default policy
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean shouldOpenExternalUrl(String url) {
|
||||||
|
if (allowedIntents.isUrlWhiteListed(url)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return null; // Default policy
|
||||||
|
}
|
||||||
|
|
||||||
|
public Whitelist getAllowedNavigations() {
|
||||||
|
return allowedNavigations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllowedNavigations(Whitelist allowedNavigations) {
|
||||||
|
this.allowedNavigations = allowedNavigations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Whitelist getAllowedIntents() {
|
||||||
|
return allowedIntents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllowedIntents(Whitelist allowedIntents) {
|
||||||
|
this.allowedIntents = allowedIntents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Whitelist getAllowedRequests() {
|
||||||
|
return allowedRequests;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllowedRequests(Whitelist allowedRequests) {
|
||||||
|
this.allowedRequests = allowedRequests;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 475 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 216 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 432 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 198 KiB |
|
After Width: | Height: | Size: 920 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 60 KiB |
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">xFrameworkTest</string>
|
||||||
|
<string name="launcher_name">@string/app_name</string>
|
||||||
|
<string name="activity_name">@string/launcher_name</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<widget id="ir.xFramework.xFrameworkTest" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
||||||
|
<feature name="Device">
|
||||||
|
<param name="android-package" value="org.apache.cordova.device.Device" />
|
||||||
|
</feature>
|
||||||
|
<feature name="SplashScreen">
|
||||||
|
<param name="android-package" value="org.apache.cordova.splashscreen.SplashScreen" />
|
||||||
|
<param name="onload" value="true" />
|
||||||
|
</feature>
|
||||||
|
<feature name="StatusBar">
|
||||||
|
<param name="android-package" value="org.apache.cordova.statusbar.StatusBar" />
|
||||||
|
<param name="onload" value="true" />
|
||||||
|
</feature>
|
||||||
|
<feature name="Whitelist">
|
||||||
|
<param name="android-package" value="org.apache.cordova.whitelist.WhitelistPlugin" />
|
||||||
|
<param name="onload" value="true" />
|
||||||
|
</feature>
|
||||||
|
<feature name="CDVIonicKeyboard">
|
||||||
|
<param name="android-package" onload="true" value="io.ionic.keyboard.CDVIonicKeyboard" />
|
||||||
|
</feature>
|
||||||
|
<allow-navigation href="http://localhost/*" />
|
||||||
|
<allow-navigation href="https://localhost/*" />
|
||||||
|
<allow-navigation href="ionic://*" />
|
||||||
|
<feature name="IonicWebView">
|
||||||
|
<param name="android-package" value="com.ionicframework.cordova.webview.IonicWebView" />
|
||||||
|
</feature>
|
||||||
|
<name>xFrameworkTest</name>
|
||||||
|
<description>test all xFramework capabilities</description>
|
||||||
|
<author email="hadi_khazaee_asl@yahoo.com" href="https://x-framework.ir">Hadi Khazaee Asl</author>
|
||||||
|
<content src="index.html" />
|
||||||
|
<access origin="*" />
|
||||||
|
<allow-intent href="http://*/*" />
|
||||||
|
<allow-intent href="https://*/*" />
|
||||||
|
<allow-intent href="tel:*" />
|
||||||
|
<allow-intent href="sms:*" />
|
||||||
|
<allow-intent href="mailto:*" />
|
||||||
|
<allow-intent href="geo:*" />
|
||||||
|
<edit-config file="app/src/main/AndroidManifest.xml" mode="merge" target="/manifest/application" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<application android:networkSecurityConfig="@xml/network_security_config" />
|
||||||
|
</edit-config>
|
||||||
|
<resource-file src="resources/android/xml/network_security_config.xml" target="app/src/main/res/xml/network_security_config.xml" />
|
||||||
|
<allow-intent href="market:*" />
|
||||||
|
<icon density="ldpi" src="resources/android/icon/drawable-ldpi-icon.png" />
|
||||||
|
<icon density="mdpi" src="resources/android/icon/drawable-mdpi-icon.png" />
|
||||||
|
<icon density="hdpi" src="resources/android/icon/drawable-hdpi-icon.png" />
|
||||||
|
<icon density="xhdpi" src="resources/android/icon/drawable-xhdpi-icon.png" />
|
||||||
|
<icon density="xxhdpi" src="resources/android/icon/drawable-xxhdpi-icon.png" />
|
||||||
|
<icon density="xxxhdpi" src="resources/android/icon/drawable-xxxhdpi-icon.png" />
|
||||||
|
<splash density="land-ldpi" src="resources/android/splash/drawable-land-ldpi-screen.png" />
|
||||||
|
<splash density="land-mdpi" src="resources/android/splash/drawable-land-mdpi-screen.png" />
|
||||||
|
<splash density="land-hdpi" src="resources/android/splash/drawable-land-hdpi-screen.png" />
|
||||||
|
<splash density="land-xhdpi" src="resources/android/splash/drawable-land-xhdpi-screen.png" />
|
||||||
|
<splash density="land-xxhdpi" src="resources/android/splash/drawable-land-xxhdpi-screen.png" />
|
||||||
|
<splash density="land-xxxhdpi" src="resources/android/splash/drawable-land-xxxhdpi-screen.png" />
|
||||||
|
<splash density="port-ldpi" src="resources/android/splash/drawable-port-ldpi-screen.png" />
|
||||||
|
<splash density="port-mdpi" src="resources/android/splash/drawable-port-mdpi-screen.png" />
|
||||||
|
<splash density="port-hdpi" src="resources/android/splash/drawable-port-hdpi-screen.png" />
|
||||||
|
<splash density="port-xhdpi" src="resources/android/splash/drawable-port-xhdpi-screen.png" />
|
||||||
|
<splash density="port-xxhdpi" src="resources/android/splash/drawable-port-xxhdpi-screen.png" />
|
||||||
|
<splash density="port-xxxhdpi" src="resources/android/splash/drawable-port-xxxhdpi-screen.png" />
|
||||||
|
<preference name="loglevel" value="DEBUG" />
|
||||||
|
<preference name="webView" value="com.ionicframework.cordova.webview.IonicWebViewEngine" />
|
||||||
|
<preference name="ScrollEnabled" value="false" />
|
||||||
|
<preference name="BackupWebStorage" value="none" />
|
||||||
|
<preference name="SplashMaintainAspectRatio" value="true" />
|
||||||
|
<preference name="FadeSplashScreenDuration" value="300" />
|
||||||
|
<preference name="SplashShowOnlyFirstTime" value="false" />
|
||||||
|
<preference name="SplashScreen" value="screen" />
|
||||||
|
<preference name="SplashScreenDelay" value="3000" />
|
||||||
|
</widget>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<network-security-config>
|
||||||
|
<domain-config cleartextTrafficPermitted="true">
|
||||||
|
<domain includeSubdomains="true">localhost</domain>
|
||||||
|
</domain-config>
|
||||||
|
</network-security-config>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.3.50'
|
||||||
|
apply from: 'repositories.gradle'
|
||||||
|
repositories repos
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:4.0.0'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
// in the individual module build.gradle files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
apply from: 'repositories.gradle'
|
||||||
|
repositories repos
|
||||||
|
|
||||||
|
//This replaces project.properties w.r.t. build settings
|
||||||
|
project.ext {
|
||||||
|
defaultBuildToolsVersion="29.0.2" //String
|
||||||
|
defaultMinSdkVersion=22 //Integer - Minimum requirement is Android 5.1
|
||||||
|
defaultTargetSdkVersion=29 //Integer - We ALWAYS target the latest by default
|
||||||
|
defaultCompileSdkVersion=29 //Integer - We ALWAYS compile with the latest by default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task clean(type: Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
||||||
@@ -0,0 +1,386 @@
|
|||||||
|
/**
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo update coho to update this line.
|
||||||
|
* @todo use `package.json` instead but first
|
||||||
|
* figure out how this fit in with the platform-centered workflow structure.
|
||||||
|
* This workflow would not have the `package.json` file.
|
||||||
|
*/
|
||||||
|
// Coho updates this line
|
||||||
|
const VERSION = '9.1.0';
|
||||||
|
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
var AndroidProject = require('./lib/AndroidProject');
|
||||||
|
var PluginManager = require('cordova-common').PluginManager;
|
||||||
|
|
||||||
|
var CordovaLogger = require('cordova-common').CordovaLogger;
|
||||||
|
var selfEvents = require('cordova-common').events;
|
||||||
|
var ConfigParser = require('cordova-common').ConfigParser;
|
||||||
|
const prepare = require('./lib/prepare').prepare;
|
||||||
|
|
||||||
|
var PLATFORM = 'android';
|
||||||
|
|
||||||
|
function setupEvents (externalEventEmitter) {
|
||||||
|
if (externalEventEmitter) {
|
||||||
|
// This will make the platform internal events visible outside
|
||||||
|
selfEvents.forwardEventsTo(externalEventEmitter);
|
||||||
|
return externalEventEmitter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is no logger if external emitter is not present,
|
||||||
|
// so attach a console logger
|
||||||
|
CordovaLogger.get().subscribe(selfEvents);
|
||||||
|
return selfEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class, that acts as abstraction over particular platform. Encapsulates the
|
||||||
|
* platform's properties and methods.
|
||||||
|
*
|
||||||
|
* Platform that implements own PlatformApi instance _should implement all
|
||||||
|
* prototype methods_ of this class to be fully compatible with cordova-lib.
|
||||||
|
*
|
||||||
|
* The PlatformApi instance also should define the following field:
|
||||||
|
*
|
||||||
|
* * platform: String that defines a platform name.
|
||||||
|
* @class Api
|
||||||
|
*/
|
||||||
|
class Api {
|
||||||
|
constructor (platform, platformRootDir, events) {
|
||||||
|
this.platform = PLATFORM;
|
||||||
|
this.root = path.resolve(__dirname, '..');
|
||||||
|
|
||||||
|
setupEvents(events);
|
||||||
|
|
||||||
|
const appMain = path.join(this.root, 'app', 'src', 'main');
|
||||||
|
const appRes = path.join(appMain, 'res');
|
||||||
|
|
||||||
|
this.locations = {
|
||||||
|
root: this.root,
|
||||||
|
www: path.join(appMain, 'assets', 'www'),
|
||||||
|
res: appRes,
|
||||||
|
platformWww: path.join(this.root, 'platform_www'),
|
||||||
|
configXml: path.join(appRes, 'xml', 'config.xml'),
|
||||||
|
defaultConfigXml: path.join(this.root, 'cordova', 'defaults.xml'),
|
||||||
|
strings: path.join(appRes, 'values', 'strings.xml'),
|
||||||
|
manifest: path.join(appMain, 'AndroidManifest.xml'),
|
||||||
|
build: path.join(this.root, 'build'),
|
||||||
|
javaSrc: path.join(appMain, 'java')
|
||||||
|
};
|
||||||
|
|
||||||
|
this._builder = require('./lib/builders/builders').getBuilder(this.root);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a CordovaPlatform object, that represents the platform structure.
|
||||||
|
*
|
||||||
|
* @return {CordovaPlatform} A structure that contains the description of
|
||||||
|
* platform's file structure and other properties of platform.
|
||||||
|
*/
|
||||||
|
getPlatformInfo () {
|
||||||
|
var result = {};
|
||||||
|
result.locations = this.locations;
|
||||||
|
result.root = this.root;
|
||||||
|
result.name = this.platform;
|
||||||
|
result.version = Api.version();
|
||||||
|
result.projectConfig = this._config;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates installed platform with provided www assets and new app
|
||||||
|
* configuration. This method is required for CLI workflow and will be called
|
||||||
|
* each time before build, so the changes, made to app configuration and www
|
||||||
|
* code, will be applied to platform.
|
||||||
|
*
|
||||||
|
* @param {CordovaProject} cordovaProject A CordovaProject instance, that defines a
|
||||||
|
* project structure and configuration, that should be applied to platform
|
||||||
|
* (contains project's www location and ConfigParser instance for project's
|
||||||
|
* config).
|
||||||
|
*
|
||||||
|
* @return {Promise} Return a promise either fulfilled, or rejected with
|
||||||
|
* CordovaError instance.
|
||||||
|
*/
|
||||||
|
prepare (cordovaProject, prepareOptions) {
|
||||||
|
cordovaProject.projectConfig = new ConfigParser(cordovaProject.locations.rootConfigXml || cordovaProject.projectConfig.path);
|
||||||
|
|
||||||
|
return prepare.call(this, cordovaProject, prepareOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs a new plugin into platform. This method only copies non-www files
|
||||||
|
* (sources, libs, etc.) to platform. It also doesn't resolves the
|
||||||
|
* dependencies of plugin. Both of handling of www files, such as assets and
|
||||||
|
* js-files and resolving dependencies are the responsibility of caller.
|
||||||
|
*
|
||||||
|
* @param {PluginInfo} plugin A PluginInfo instance that represents plugin
|
||||||
|
* that will be installed.
|
||||||
|
* @param {Object} installOptions An options object. Possible options below:
|
||||||
|
* @param {Boolean} installOptions.link: Flag that specifies that plugin
|
||||||
|
* sources will be symlinked to app's directory instead of copying (if
|
||||||
|
* possible).
|
||||||
|
* @param {Object} installOptions.variables An object that represents
|
||||||
|
* variables that will be used to install plugin. See more details on plugin
|
||||||
|
* variables in documentation:
|
||||||
|
* https://cordova.apache.org/docs/en/4.0.0/plugin_ref_spec.md.html
|
||||||
|
*
|
||||||
|
* @return {Promise} Return a promise either fulfilled, or rejected with
|
||||||
|
* CordovaError instance.
|
||||||
|
*/
|
||||||
|
addPlugin (plugin, installOptions) {
|
||||||
|
var project = AndroidProject.getProjectFile(this.root);
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
installOptions = installOptions || {};
|
||||||
|
installOptions.variables = installOptions.variables || {};
|
||||||
|
// Add PACKAGE_NAME variable into vars
|
||||||
|
if (!installOptions.variables.PACKAGE_NAME) {
|
||||||
|
installOptions.variables.PACKAGE_NAME = project.getPackageName();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve().then(function () {
|
||||||
|
return PluginManager.get(self.platform, self.locations, project).addPlugin(plugin, installOptions);
|
||||||
|
}).then(function () {
|
||||||
|
if (plugin.getFrameworks(this.platform).length === 0) return;
|
||||||
|
selfEvents.emit('verbose', 'Updating build files since android plugin contained <framework>');
|
||||||
|
// This should pick the correct builder, not just get gradle
|
||||||
|
this._builder.prepBuildFiles();
|
||||||
|
}.bind(this))
|
||||||
|
// CB-11022 Return truthy value to prevent running prepare after
|
||||||
|
.then(() => true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an installed plugin from platform.
|
||||||
|
*
|
||||||
|
* Since method accepts PluginInfo instance as input parameter instead of plugin
|
||||||
|
* id, caller shoud take care of managing/storing PluginInfo instances for
|
||||||
|
* future uninstalls.
|
||||||
|
*
|
||||||
|
* @param {PluginInfo} plugin A PluginInfo instance that represents plugin
|
||||||
|
* that will be installed.
|
||||||
|
*
|
||||||
|
* @return {Promise} Return a promise either fulfilled, or rejected with
|
||||||
|
* CordovaError instance.
|
||||||
|
*/
|
||||||
|
removePlugin (plugin, uninstallOptions) {
|
||||||
|
var project = AndroidProject.getProjectFile(this.root);
|
||||||
|
|
||||||
|
if (uninstallOptions && uninstallOptions.usePlatformWww === true) {
|
||||||
|
uninstallOptions.usePlatformWww = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PluginManager.get(this.platform, this.locations, project)
|
||||||
|
.removePlugin(plugin, uninstallOptions)
|
||||||
|
.then(function () {
|
||||||
|
if (plugin.getFrameworks(this.platform).length === 0) return;
|
||||||
|
|
||||||
|
selfEvents.emit('verbose', 'Updating build files since android plugin contained <framework>');
|
||||||
|
this._builder.prepBuildFiles();
|
||||||
|
}.bind(this))
|
||||||
|
// CB-11022 Return truthy value to prevent running prepare after
|
||||||
|
.then(() => true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an application package for current platform.
|
||||||
|
*
|
||||||
|
* @param {Object} buildOptions A build options. This object's structure is
|
||||||
|
* highly depends on platform's specific. The most common options are:
|
||||||
|
* @param {Boolean} buildOptions.debug Indicates that packages should be
|
||||||
|
* built with debug configuration. This is set to true by default unless the
|
||||||
|
* 'release' option is not specified.
|
||||||
|
* @param {Boolean} buildOptions.release Indicates that packages should be
|
||||||
|
* built with release configuration. If not set to true, debug configuration
|
||||||
|
* will be used.
|
||||||
|
* @param {Boolean} buildOptions.device Specifies that built app is intended
|
||||||
|
* to run on device
|
||||||
|
* @param {Boolean} buildOptions.emulator: Specifies that built app is
|
||||||
|
* intended to run on emulator
|
||||||
|
* @param {String} buildOptions.target Specifies the device id that will be
|
||||||
|
* used to run built application.
|
||||||
|
* @param {Boolean} buildOptions.nobuild Indicates that this should be a
|
||||||
|
* dry-run call, so no build artifacts will be produced.
|
||||||
|
* @param {String[]} buildOptions.archs Specifies chip architectures which
|
||||||
|
* app packages should be built for. List of valid architectures is depends on
|
||||||
|
* platform.
|
||||||
|
* @param {String} buildOptions.buildConfig The path to build configuration
|
||||||
|
* file. The format of this file is depends on platform.
|
||||||
|
* @param {String[]} buildOptions.argv Raw array of command-line arguments,
|
||||||
|
* passed to `build` command. The purpose of this property is to pass a
|
||||||
|
* platform-specific arguments, and eventually let platform define own
|
||||||
|
* arguments processing logic.
|
||||||
|
*
|
||||||
|
* @return {Promise<Object[]>} A promise either fulfilled with an array of build
|
||||||
|
* artifacts (application packages) if package was built successfully,
|
||||||
|
* or rejected with CordovaError. The resultant build artifact objects is not
|
||||||
|
* strictly typed and may conatin arbitrary set of fields as in sample below.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* architecture: 'x86',
|
||||||
|
* buildType: 'debug',
|
||||||
|
* path: '/path/to/build',
|
||||||
|
* type: 'app'
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* The return value in most cases will contain only one item but in some cases
|
||||||
|
* there could be multiple items in output array, e.g. when multiple
|
||||||
|
* arhcitectures is specified.
|
||||||
|
*/
|
||||||
|
build (buildOptions) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
return require('./lib/check_reqs').run().then(function () {
|
||||||
|
return require('./lib/build').run.call(self, buildOptions);
|
||||||
|
}).then(function (buildResults) {
|
||||||
|
// Cast build result to array of build artifacts
|
||||||
|
return buildResults.paths.map(function (apkPath) {
|
||||||
|
return {
|
||||||
|
buildType: buildResults.buildType,
|
||||||
|
buildMethod: buildResults.buildMethod,
|
||||||
|
path: apkPath,
|
||||||
|
type: path.extname(apkPath).replace(/\./g, '')
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an application package for current platform and runs it on
|
||||||
|
* specified/default device. If no 'device'/'emulator'/'target' options are
|
||||||
|
* specified, then tries to run app on default device if connected, otherwise
|
||||||
|
* runs the app on emulator.
|
||||||
|
*
|
||||||
|
* @param {Object} runOptions An options object. The structure is the same
|
||||||
|
* as for build options.
|
||||||
|
*
|
||||||
|
* @return {Promise} A promise either fulfilled if package was built and ran
|
||||||
|
* successfully, or rejected with CordovaError.
|
||||||
|
*/
|
||||||
|
run (runOptions) {
|
||||||
|
var self = this;
|
||||||
|
return require('./lib/check_reqs').run().then(function () {
|
||||||
|
return require('./lib/run').run.call(self, runOptions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans out the build artifacts from platform's directory, and also
|
||||||
|
* cleans out the platform www directory if called without options specified.
|
||||||
|
*
|
||||||
|
* @return {Promise} Return a promise either fulfilled, or rejected with
|
||||||
|
* CordovaError.
|
||||||
|
*/
|
||||||
|
clean (cleanOptions) {
|
||||||
|
var self = this;
|
||||||
|
// This will lint, checking for null won't
|
||||||
|
if (typeof cleanOptions === 'undefined') {
|
||||||
|
cleanOptions = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return require('./lib/check_reqs').run().then(function () {
|
||||||
|
return require('./lib/build').runClean.call(self, cleanOptions);
|
||||||
|
}).then(function () {
|
||||||
|
return require('./lib/prepare').clean.call(self, cleanOptions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a requirements check for current platform. Each platform defines its
|
||||||
|
* own set of requirements, which should be resolved before platform can be
|
||||||
|
* built successfully.
|
||||||
|
*
|
||||||
|
* @return {Promise<Requirement[]>} Promise, resolved with set of Requirement
|
||||||
|
* objects for current platform.
|
||||||
|
*/
|
||||||
|
requirements () {
|
||||||
|
return require('./lib/check_reqs').check_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs platform to specified directory and creates a platform project.
|
||||||
|
*
|
||||||
|
* @param {String} destination Destination directory, where insatll platform to
|
||||||
|
* @param {ConfigParser} [config] ConfgiParser instance, used to retrieve
|
||||||
|
* project creation options, such as package id and project name.
|
||||||
|
* @param {Object} [options] An options object. The most common options are:
|
||||||
|
* @param {String} [options.customTemplate] A path to custom template, that
|
||||||
|
* should override the default one from platform.
|
||||||
|
* @param {Boolean} [options.link] Flag that indicates that platform's
|
||||||
|
* sources will be linked to installed platform instead of copying.
|
||||||
|
* @param {EventEmitter} [events] An EventEmitter instance that will be used for
|
||||||
|
* logging purposes. If no EventEmitter provided, all events will be logged to
|
||||||
|
* console
|
||||||
|
*
|
||||||
|
* @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi
|
||||||
|
* instance or rejected with CordovaError.
|
||||||
|
*/
|
||||||
|
static createPlatform (destination, config, options, events) {
|
||||||
|
events = setupEvents(events);
|
||||||
|
var result;
|
||||||
|
try {
|
||||||
|
result = require('../../lib/create').create(destination, config, options, events).then(function (destination) {
|
||||||
|
return new Api(PLATFORM, destination, events);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
events.emit('error', 'createPlatform is not callable from the android project API.');
|
||||||
|
throw (e);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates already installed platform.
|
||||||
|
*
|
||||||
|
* @param {String} destination Destination directory, where platform installed
|
||||||
|
* @param {Object} [options] An options object. The most common options are:
|
||||||
|
* @param {String} [options.customTemplate] A path to custom template, that
|
||||||
|
* should override the default one from platform.
|
||||||
|
* @param {Boolean} [options.link] Flag that indicates that platform's
|
||||||
|
* sources will be linked to installed platform instead of copying.
|
||||||
|
* @param {EventEmitter} [events] An EventEmitter instance that will be used for
|
||||||
|
* logging purposes. If no EventEmitter provided, all events will be logged to
|
||||||
|
* console
|
||||||
|
*
|
||||||
|
* @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi
|
||||||
|
* instance or rejected with CordovaError.
|
||||||
|
*/
|
||||||
|
static updatePlatform (destination, options, events) {
|
||||||
|
events = setupEvents(events);
|
||||||
|
var result;
|
||||||
|
try {
|
||||||
|
result = require('../../lib/create').update(destination, options, events).then(function (destination) {
|
||||||
|
return new Api(PLATFORM, destination, events);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
events.emit('error', 'updatePlatform is not callable from the android project API, you will need to do this manually.');
|
||||||
|
throw (e);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static version () {
|
||||||
|
return VERSION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Api;
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var android_sdk = require('./lib/android_sdk');
|
||||||
|
|
||||||
|
android_sdk.print_newest_available_sdk_target().catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
process.exit(2);
|
||||||
|
});
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
:: Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
:: or more contributor license agreements. See the NOTICE file
|
||||||
|
:: distributed with this work for additional information
|
||||||
|
:: regarding copyright ownership. The ASF licenses this file
|
||||||
|
:: to you under the Apache License, Version 2.0 (the
|
||||||
|
:: "License"); you may not use this file except in compliance
|
||||||
|
:: with the License. You may obtain a copy of the License at
|
||||||
|
::
|
||||||
|
:: http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
::
|
||||||
|
:: Unless required by applicable law or agreed to in writing,
|
||||||
|
:: software distributed under the License is distributed on an
|
||||||
|
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
:: KIND, either express or implied. See the License for the
|
||||||
|
:: specific language governing permissions and limitations
|
||||||
|
:: under the License.
|
||||||
|
|
||||||
|
@ECHO OFF
|
||||||
|
SET script_path="%~dp0android_sdk_version"
|
||||||
|
IF EXIST %script_path% (
|
||||||
|
node %script_path% %*
|
||||||
|
) ELSE (
|
||||||
|
ECHO.
|
||||||
|
ECHO ERROR: Could not find 'android_sdk_version' script in 'bin' folder, aborting...>&2
|
||||||
|
EXIT /B 1
|
||||||
|
)
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var args = process.argv;
|
||||||
|
var Api = require('./Api');
|
||||||
|
var nopt = require('nopt');
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
// Support basic help commands
|
||||||
|
if (['--help', '/?', '-h', 'help', '-help', '/help'].indexOf(args[2]) >= 0) {
|
||||||
|
require('./lib/build').help();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do some basic argument parsing
|
||||||
|
var buildOpts = nopt({
|
||||||
|
verbose: Boolean,
|
||||||
|
silent: Boolean,
|
||||||
|
debug: Boolean,
|
||||||
|
release: Boolean,
|
||||||
|
nobuild: Boolean,
|
||||||
|
buildConfig: path
|
||||||
|
}, { d: '--verbose' });
|
||||||
|
|
||||||
|
// Make buildOptions compatible with PlatformApi build method spec
|
||||||
|
buildOpts.argv = buildOpts.argv.original;
|
||||||
|
|
||||||
|
require('./loggingHelper').adjustLoggerLevel(buildOpts);
|
||||||
|
|
||||||
|
new Api().build(buildOpts)
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error(err.stack);
|
||||||
|
process.exit(2);
|
||||||
|
});
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
:: Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
:: or more contributor license agreements. See the NOTICE file
|
||||||
|
:: distributed with this work for additional information
|
||||||
|
:: regarding copyright ownership. The ASF licenses this file
|
||||||
|
:: to you under the Apache License, Version 2.0 (the
|
||||||
|
:: "License"); you may not use this file except in compliance
|
||||||
|
:: with the License. You may obtain a copy of the License at
|
||||||
|
::
|
||||||
|
:: http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
::
|
||||||
|
:: Unless required by applicable law or agreed to in writing,
|
||||||
|
:: software distributed under the License is distributed on an
|
||||||
|
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
:: KIND, either express or implied. See the License for the
|
||||||
|
:: specific language governing permissions and limitations
|
||||||
|
:: under the License.
|
||||||
|
|
||||||
|
@ECHO OFF
|
||||||
|
SET script_path="%~dp0build"
|
||||||
|
IF EXIST %script_path% (
|
||||||
|
node %script_path% %*
|
||||||
|
) ELSE (
|
||||||
|
ECHO.
|
||||||
|
ECHO ERROR: Could not find 'build' script in 'cordova' folder, aborting...>&2
|
||||||
|
EXIT /B 1
|
||||||
|
)
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var check_reqs = require('./lib/check_reqs');
|
||||||
|
|
||||||
|
check_reqs.run().then(
|
||||||
|
function success () {
|
||||||
|
console.log('Looks like your environment fully supports cordova-android development!');
|
||||||
|
},
|
||||||
|
function fail (err) {
|
||||||
|
console.log(err);
|
||||||
|
process.exit(2);
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
:: Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
:: or more contributor license agreements. See the NOTICE file
|
||||||
|
:: distributed with this work for additional information
|
||||||
|
:: regarding copyright ownership. The ASF licenses this file
|
||||||
|
:: to you under the Apache License, Version 2.0 (the
|
||||||
|
:: "License"); you may not use this file except in compliance
|
||||||
|
:: with the License. You may obtain a copy of the License at
|
||||||
|
::
|
||||||
|
:: http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
::
|
||||||
|
:: Unless required by applicable law or agreed to in writing,
|
||||||
|
:: software distributed under the License is distributed on an
|
||||||
|
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
:: KIND, either express or implied. See the License for the
|
||||||
|
:: specific language governing permissions and limitations
|
||||||
|
:: under the License.
|
||||||
|
|
||||||
|
@ECHO OFF
|
||||||
|
SET script_path="%~dp0check_reqs"
|
||||||
|
IF EXIST %script_path% (
|
||||||
|
node %script_path% %*
|
||||||
|
) ELSE (
|
||||||
|
ECHO.
|
||||||
|
ECHO ERROR: Could not find 'check_reqs' script in 'bin' folder, aborting...>&2
|
||||||
|
EXIT /B 1
|
||||||
|
)
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Api = require('./Api');
|
||||||
|
var path = require('path');
|
||||||
|
var nopt = require('nopt');
|
||||||
|
|
||||||
|
// Support basic help commands
|
||||||
|
if (['--help', '/?', '-h', 'help', '-help', '/help'].indexOf(process.argv[2]) >= 0) {
|
||||||
|
console.log('Usage: ' + path.relative(process.cwd(), process.argv[1]));
|
||||||
|
console.log('Cleans the project directory.');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do some basic argument parsing
|
||||||
|
var opts = nopt({
|
||||||
|
verbose: Boolean,
|
||||||
|
silent: Boolean
|
||||||
|
}, { d: '--verbose' });
|
||||||
|
|
||||||
|
// Make buildOptions compatible with PlatformApi clean method spec
|
||||||
|
opts.argv = opts.argv.original;
|
||||||
|
|
||||||
|
// Skip cleaning prepared files when not invoking via cordova CLI.
|
||||||
|
opts.noPrepare = true;
|
||||||
|
|
||||||
|
require('./loggingHelper').adjustLoggerLevel(opts);
|
||||||
|
|
||||||
|
new Api().clean(opts)
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error(err.stack);
|
||||||
|
process.exit(2);
|
||||||
|
});
|
||||||