| @@ -0,0 +1,187 @@ | |||
| { | |||
| "$schema": "./node_modules/@angular/cli/lib/config/schema.json", | |||
| "version": 1, | |||
| "defaultProject": "app", | |||
| "newProjectRoot": "projects", | |||
| "projects": { | |||
| "app": { | |||
| "root": "", | |||
| "sourceRoot": "src", | |||
| "projectType": "application", | |||
| "prefix": "app", | |||
| "schematics": {}, | |||
| "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", | |||
| "assets": [ | |||
| { | |||
| "glob": "**/*", | |||
| "input": "src/assets", | |||
| "output": "assets" | |||
| }, | |||
| { | |||
| "glob": "**/*.svg", | |||
| "input": "node_modules/ionicons/dist/ionicons/svg", | |||
| "output": "./svg" | |||
| } | |||
| ], | |||
| "styles": [ | |||
| { | |||
| "input": "src/theme/variables.scss" | |||
| }, | |||
| { | |||
| "input": "src/global.scss" | |||
| } | |||
| ], | |||
| "scripts": [ ] | |||
| }, | |||
| "configurations": { | |||
| "production": { | |||
| "fileReplacements": [ | |||
| { | |||
| "replace": "src/environments/environment.ts", | |||
| "with": "src/environments/environment.prod.ts" | |||
| } | |||
| ], | |||
| "optimization": true, | |||
| "outputHashing": "all", | |||
| "sourceMap": false, | |||
| "extractCss": true, | |||
| "namedChunks": false, | |||
| "aot": true, | |||
| "extractLicenses": true, | |||
| "vendorChunk": false, | |||
| "buildOptimizer": true, | |||
| "budgets": [ | |||
| { | |||
| "type": "initial", | |||
| "maximumWarning": "2mb", | |||
| "maximumError": "5mb" | |||
| } | |||
| ] | |||
| }, | |||
| "ci": { | |||
| "progress": false | |||
| } | |||
| } | |||
| }, | |||
| "serve": { | |||
| "builder": "@angular-devkit/build-angular:dev-server", | |||
| "options": { | |||
| "browserTarget": "app:build" | |||
| }, | |||
| "configurations": { | |||
| "production": { | |||
| "browserTarget": "app:build:production" | |||
| }, | |||
| "ci": { | |||
| "progress": false | |||
| } | |||
| } | |||
| }, | |||
| "extract-i18n": { | |||
| "builder": "@angular-devkit/build-angular:extract-i18n", | |||
| "options": { | |||
| "browserTarget": "app:build" | |||
| } | |||
| }, | |||
| "test": { | |||
| "builder": "@angular-devkit/build-angular:karma", | |||
| "options": { | |||
| "main": "src/test.ts", | |||
| "polyfills": "src/polyfills.ts", | |||
| "tsConfig": "tsconfig.spec.json", | |||
| "karmaConfig": "karma.conf.js", | |||
| "styles": [], | |||
| "scripts": [], | |||
| "assets": [ | |||
| { | |||
| "glob": "favicon.ico", | |||
| "input": "src/", | |||
| "output": "/" | |||
| }, | |||
| { | |||
| "glob": "**/*", | |||
| "input": "src/assets", | |||
| "output": "/assets" | |||
| } | |||
| ] | |||
| }, | |||
| "configurations": { | |||
| "ci": { | |||
| "progress": false, | |||
| "watch": false | |||
| } | |||
| } | |||
| }, | |||
| "lint": { | |||
| "builder": "@angular-devkit/build-angular:tslint", | |||
| "options": { | |||
| "tsConfig": [ | |||
| "tsconfig.app.json", | |||
| "tsconfig.spec.json", | |||
| "e2e/tsconfig.json" | |||
| ], | |||
| "exclude": ["**/node_modules/**"] | |||
| } | |||
| }, | |||
| "e2e": { | |||
| "builder": "@angular-devkit/build-angular:protractor", | |||
| "options": { | |||
| "protractorConfig": "e2e/protractor.conf.js", | |||
| "devServerTarget": "app:serve" | |||
| }, | |||
| "configurations": { | |||
| "production": { | |||
| "devServerTarget": "app:serve:production" | |||
| }, | |||
| "ci": { | |||
| "devServerTarget": "app:serve:ci" | |||
| } | |||
| } | |||
| }, | |||
| "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" | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| "cli": { | |||
| "defaultCollection": "@ionic/angular-toolkit" | |||
| }, | |||
| "schematics": { | |||
| "@ionic/angular-toolkit:component": { | |||
| "styleext": "scss" | |||
| }, | |||
| "@ionic/angular-toolkit:page": { | |||
| "styleext": "scss" | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| # 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 | |||
| # You can see what browsers were selected by your queries by running: | |||
| # npx browserslist | |||
| > 0.5% | |||
| last 2 versions | |||
| Firefox ESR | |||
| not dead | |||
| not IE 9-11 # For IE 9-11 support, remove 'not'. | |||
| @@ -0,0 +1,99 @@ | |||
| <?xml version='1.0' encoding='utf-8'?> | |||
| <widget id="com.verbosetech.quickpay" version="0.0.4" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0"> | |||
| <name>QickPay</name> | |||
| <description>An awesome Ionic/Cordova app.</description> | |||
| <author email="hi@ionicframework.com" href="http://ionicframework.com/">Ionic Framework Team</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="android-minSdkVersion" value="19" /> | |||
| <preference name="android-targetSdkVersion" value="28" /> | |||
| <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"> | |||
| <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="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" /> | |||
| <splash density="land-xxxhdpi" src="resources/android/splash/drawable-land-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" /> | |||
| </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": "quick_pay", | |||
| "integrations": { | |||
| "cordova": {} | |||
| }, | |||
| "type": "angular" | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| { | |||
| "name": "Tabs Starter", | |||
| "baseref": "master", | |||
| "tarignore": [ | |||
| "node_modules", | |||
| "package-lock.json", | |||
| "www" | |||
| ], | |||
| "scripts": { | |||
| "test": "npm run lint && npm run ng -- build --configuration=ci && npm run ng -- build --prod --progress=false && npm run ng -- test --configuration=ci && npm run ng -- e2e --configuration=ci && npm run ng -- g pg my-page --dry-run && npm run ng -- g c my-component --dry-run" | |||
| } | |||
| } | |||
| @@ -0,0 +1,31 @@ | |||
| // 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-istanbul-reporter'), | |||
| require('@angular-devkit/build-angular/plugins/karma') | |||
| ], | |||
| client: { | |||
| clearContext: false // leave Jasmine Spec Runner output visible in browser | |||
| }, | |||
| coverageIstanbulReporter: { | |||
| dir: require('path').join(__dirname, '../coverage'), | |||
| reports: ['html', 'lcovonly', 'text-summary'], | |||
| fixWebpackSourcePaths: true | |||
| }, | |||
| reporters: ['progress', 'kjhtml'], | |||
| port: 9876, | |||
| colors: true, | |||
| logLevel: config.LOG_INFO, | |||
| autoWatch: true, | |||
| browsers: ['Chrome'], | |||
| singleRun: false | |||
| }); | |||
| }; | |||
| @@ -0,0 +1,96 @@ | |||
| { | |||
| "name": "quick_pay", | |||
| "version": "0.0.1", | |||
| "author": "Ionic Framework", | |||
| "homepage": "https://ionicframework.com/", | |||
| "scripts": { | |||
| "ng": "ng", | |||
| "start": "ng serve", | |||
| "build": "ng build", | |||
| "test": "ng test", | |||
| "lint": "ng lint", | |||
| "e2e": "ng e2e" | |||
| }, | |||
| "private": true, | |||
| "dependencies": { | |||
| "@angular/common": "~8.2.14", | |||
| "@angular/core": "~8.2.14", | |||
| "@angular/forms": "~8.2.14", | |||
| "@angular/platform-browser": "~8.2.14", | |||
| "@angular/platform-browser-dynamic": "~8.2.14", | |||
| "@angular/router": "~8.2.14", | |||
| "@ionic-native/core": "^5.0.0", | |||
| "@ionic-native/sign-in-with-apple": "^5.31.1", | |||
| "@ionic-native/spinner-dialog": "^5.36.0", | |||
| "@ionic-native/splash-screen": "^5.0.0", | |||
| "@ionic-native/status-bar": "^5.0.0", | |||
| "@ionic/angular": "^5.0.0", | |||
| "@ionic/storage-angular": "^3.0.6", | |||
| "@ngx-translate/core": "^12.1.2", | |||
| "@ngx-translate/http-loader": "^4.0.0", | |||
| "chart.js": "^2.9.4", | |||
| "cordova-android": "^8.1.0", | |||
| "cordova-ios": "6.1.1", | |||
| "core-js": "^2.5.4", | |||
| "ng-circle-progress": "^1.5.1", | |||
| "rxjs": "~6.5.1", | |||
| "tslib": "^1.9.0", | |||
| "zone.js": "~0.9.1" | |||
| }, | |||
| "devDependencies": { | |||
| "@angular-devkit/build-angular": "~0.803.20", | |||
| "@angular/cli": "~8.3.23", | |||
| "@angular/compiler": "~8.2.14", | |||
| "@angular/compiler-cli": "~8.2.14", | |||
| "@angular/language-service": "~8.2.14", | |||
| "@babel/compat-data": "~7.8.0", | |||
| "@ionic/angular-toolkit": "^2.1.1", | |||
| "@ionic/lab": "3.2.10", | |||
| "@types/jasmine": "~3.3.8", | |||
| "@types/jasminewd2": "~2.0.3", | |||
| "@types/node": "~8.9.4", | |||
| "codelyzer": "^5.0.0", | |||
| "cordova-plugin-androidx": "^3.0.0", | |||
| "cordova-plugin-androidx-adapter": "^1.1.3", | |||
| "cordova-plugin-device": "^2.0.2", | |||
| "cordova-plugin-ionic-keyboard": "^2.2.0", | |||
| "cordova-plugin-ionic-webview": "^4.1.3", | |||
| "cordova-plugin-native-spinner": "^1.1.3", | |||
| "cordova-plugin-sign-in-with-apple": "^0.1.2", | |||
| "cordova-plugin-splashscreen": "^5.0.2", | |||
| "cordova-plugin-statusbar": "^2.4.2", | |||
| "cordova-plugin-whitelist": "^1.3.3", | |||
| "jasmine-core": "~3.4.0", | |||
| "jasmine-spec-reporter": "~4.2.1", | |||
| "karma": "~4.1.0", | |||
| "karma-chrome-launcher": "~2.2.0", | |||
| "karma-coverage-istanbul-reporter": "~2.0.1", | |||
| "karma-jasmine": "~2.0.1", | |||
| "karma-jasmine-html-reporter": "^1.4.0", | |||
| "protractor": "~5.4.0", | |||
| "ts-node": "~7.0.0", | |||
| "tslint": "~5.15.0", | |||
| "typescript": "~3.4.3" | |||
| }, | |||
| "description": "An Ionic project", | |||
| "cordova": { | |||
| "plugins": { | |||
| "cordova-plugin-whitelist": {}, | |||
| "cordova-plugin-statusbar": {}, | |||
| "cordova-plugin-device": {}, | |||
| "cordova-plugin-splashscreen": {}, | |||
| "cordova-plugin-ionic-webview": { | |||
| "ANDROID_SUPPORT_ANNOTATIONS_VERSION": "27.+" | |||
| }, | |||
| "cordova-plugin-ionic-keyboard": {}, | |||
| "cordova-plugin-sign-in-with-apple": {}, | |||
| "cordova-plugin-androidx": {}, | |||
| "cordova-plugin-androidx-adapter": {}, | |||
| "cordova-plugin-native-spinner": {} | |||
| }, | |||
| "platforms": [ | |||
| "android", | |||
| "ios" | |||
| ] | |||
| } | |||
| } | |||
| @@ -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,148 @@ | |||
| /* 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() | |||
| } | |||
| buildscript { | |||
| repositories { | |||
| google() | |||
| jcenter() | |||
| } | |||
| 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:3.3.0' | |||
| classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' | |||
| classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' | |||
| } | |||
| } | |||
| allprojects { | |||
| repositories { | |||
| google() | |||
| jcenter() | |||
| } | |||
| } | |||
| apply plugin: 'com.android.library' | |||
| apply plugin: 'com.github.dcendents.android-maven' | |||
| apply plugin: 'com.jfrog.bintray' | |||
| group = 'org.apache.cordova' | |||
| version = '8.1.0' | |||
| android { | |||
| compileSdkVersion cdvCompileSdkVersion | |||
| buildToolsVersion cdvBuildToolsVersion | |||
| compileOptions { | |||
| sourceCompatibility JavaVersion.VERSION_1_8 | |||
| targetCompatibility JavaVersion.VERSION_1_8 | |||
| } | |||
| // For the Android Cordova Lib, we will hardcode the minSdkVersion and not allow changes. | |||
| defaultConfig { | |||
| minSdkVersion 19 | |||
| } | |||
| 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 = '8.1.0' | |||
| released = new Date() | |||
| vcsTag = '8.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 | |||
| 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') | |||
| } | |||
| String[] getAvailableBuildTools() { | |||
| def buildToolsDir = new File(getAndroidSdkDir(), "build-tools") | |||
| buildToolsDir.list() | |||
| .findAll { it ==~ /[0-9.]+/ } | |||
| .sort { a, b -> compareVersions(b, a) } | |||
| } | |||
| String doFindLatestInstalledBuildTools(String minBuildToolsVersion) { | |||
| def availableBuildToolsVersions | |||
| try { | |||
| availableBuildToolsVersions = getAvailableBuildTools() | |||
| } catch (e) { | |||
| println "An exception occurred while trying to find the Android build tools." | |||
| throw e | |||
| } | |||
| if (availableBuildToolsVersions.length > 0) { | |||
| def highestBuildToolsVersion = availableBuildToolsVersions[0] | |||
| if (compareVersions(highestBuildToolsVersion, minBuildToolsVersion) < 0) { | |||
| throw new RuntimeException( | |||
| "No usable Android build tools found. Highest installed version is " + | |||
| highestBuildToolsVersion + "; minimum version required is " + | |||
| minBuildToolsVersion + ".") | |||
| } | |||
| highestBuildToolsVersion | |||
| } else { | |||
| throw new RuntimeException( | |||
| "No installed build tools found. Install the Android build tools version " + | |||
| minBuildToolsVersion + " or higher.") | |||
| } | |||
| } | |||
| // Return the first non-zero result of subtracting version list elements | |||
| // pairwise. If they are all identical, return the difference in length of | |||
| // the two lists. | |||
| int compareVersionList(Collection aParts, Collection bParts) { | |||
| def pairs = ([aParts, bParts]).transpose() | |||
| pairs.findResult(aParts.size()-bParts.size()) {it[0] - it[1] != 0 ? it[0] - it[1] : null} | |||
| } | |||
| // Compare two version strings, such as "19.0.0" and "18.1.1.0". If all matched | |||
| // elements are identical, the longer version is the largest by this method. | |||
| // Examples: | |||
| // "19.0.0" > "19" | |||
| // "19.0.1" > "19.0.0" | |||
| // "19.1.0" > "19.0.1" | |||
| // "19" > "18.999.999" | |||
| int compareVersions(String a, String b) { | |||
| def aParts = a.tokenize('.').collect {it.toInteger()} | |||
| def bParts = b.tokenize('.').collect {it.toInteger()} | |||
| compareVersionList(aParts, bParts) | |||
| } | |||
| String getAndroidSdkDir() { | |||
| def rootDir = project.rootDir | |||
| def androidSdkDir = null | |||
| String 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) } | |||
| } | |||
| @@ -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-28 | |||
| apk-configurations= | |||
| renderscript.opt.level=O0 | |||
| android.library=true | |||
| @@ -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,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; | |||
| /* | |||
| * 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.getPackageName() + ".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(); | |||
| } | |||
| 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,521 @@ | |||
| /* | |||
| 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; | |||
| } 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 | |||
| */ | |||
| @SuppressLint("InlinedApi") | |||
| @Override | |||
| public void onWindowFocusChanged(boolean hasFocus) { | |||
| super.onWindowFocusChanged(hasFocus); | |||
| if (hasFocus && immersiveMode) { | |||
| 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,187 @@ | |||
| /* | |||
| 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 | |||
| @SuppressLint("TrulyRandom") | |||
| 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.length() > 3 && 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,472 @@ | |||
| /* | |||
| 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; | |||
| /** | |||
| * 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.setDoInput(true); | |||
| String mimeType = conn.getHeaderField("Content-Type"); | |||
| if (mimeType != null) { | |||
| mimeType = mimeType.split(";")[0]; | |||
| } | |||
| int length = conn.getContentLength(); | |||
| InputStream 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 = "8.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,617 @@ | |||
| /* | |||
| 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.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.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 (loadUrlTimeout == currentLoadUrlTimeout) { | |||
| cordova.getActivity().runOnUiThread(loadError); | |||
| } | |||
| } | |||
| }; | |||
| final boolean _recreatePlugins = recreatePlugins; | |||
| cordova.getActivity().runOnUiThread(new Runnable() { | |||
| public void run() { | |||
| if (loadUrlTimeoutValue > 0) { | |||
| cordova.getThreadPool().execute(timeoutCheck); | |||
| } | |||
| engine.loadUrl(url, _recreatePlugins); | |||
| } | |||
| }); | |||
| } | |||
| @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; | |||
| } | |||
| try { | |||
| Intent 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); | |||
| } | |||
| cordova.getActivity().startActivity(intent); | |||
| } catch (android.content.ActivityNotFoundException e) { | |||
| LOG.e(TAG, "Error loading url " + url, e); | |||
| } | |||
| } | |||
| @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; | |||
| } | |||
| // Store the view and its callback for later (to kill it properly) | |||
| mCustomView = view; | |||
| mCustomViewCallback = callback; | |||
| // Add the custom view to its container. | |||
| ViewGroup parent = (ViewGroup) engine.getView().getParent(); | |||
| parent.addView(view, 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); | |||
| cordova.getActivity().runOnUiThread(new Runnable() { | |||
| public void run() { | |||
| pluginManager.postMessage("spinner", "stop"); | |||
| } | |||
| }); | |||
| } 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,407 @@ | |||
| /* | |||
| 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.getPackageName() + ".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(); | |||
| } | |||
| 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,542 @@ | |||
| /* | |||
| 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; | |||
| // Arbitrarily chosen upper limit for how much data to send to JS in one shot. | |||
| // This currently only chops up on message boundaries. It may be useful | |||
| // to allow it to break up messages. | |||
| private static int MAX_PAYLOAD_SIZE = 50 * 1024 * 10240; | |||
| /** | |||
| * 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, while staying under MAX_PAYLOAD_SIZE. | |||
| * 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 && totalPayloadLen + messageSize > MAX_PAYLOAD_SIZE && MAX_PAYLOAD_SIZE > 0) { | |||
| 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 && totalPayloadLen + messageSize > MAX_PAYLOAD_SIZE && MAX_PAYLOAD_SIZE > 0) { | |||
| 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,526 @@ | |||
| /* | |||
| 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.LinkedHashMap; | |||
| 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; | |||
| /** | |||
| * 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 LinkedHashMap<String, CordovaPlugin> pluginMap = new LinkedHashMap<String, CordovaPlugin>(); | |||
| private final LinkedHashMap<String, PluginEntry> entryMap = 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() { | |||
| 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 { | |||
| 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()); | |||
| 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()); | |||
| 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) { | |||
| 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) { | |||
| 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) { | |||
| 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) { | |||
| 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() { | |||
| 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() { | |||
| for (CordovaPlugin plugin : this.pluginMap.values()) { | |||
| if (plugin != null) { | |||
| plugin.onStop(); | |||
| } | |||
| } | |||
| } | |||
| /** | |||
| * The final call you receive before your activity is destroyed. | |||
| */ | |||
| public void onDestroy() { | |||
| 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) { | |||
| 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) { | |||
| 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) { | |||
| 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) { | |||
| 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) { | |||
| 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) { | |||
| 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) { | |||
| 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() { | |||
| for (CordovaPlugin plugin : this.pluginMap.values()) { | |||
| if (plugin != null) { | |||
| plugin.onReset(); | |||
| } | |||
| } | |||
| } | |||
| Uri remapUri(Uri uri) { | |||
| 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) { | |||
| for (CordovaPlugin plugin : this.pluginMap.values()) { | |||
| if (plugin != null) { | |||
| plugin.onConfigurationChanged(newConfig); | |||
| } | |||
| } | |||
| } | |||
| public Bundle onSaveInstanceState() { | |||
| Bundle state = new Bundle(); | |||
| 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,74 @@ | |||
| /* | |||
| 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; | |||
| //Added because lint can't see the conditional RIGHT ABOVE this | |||
| @TargetApi(Build.VERSION_CODES.LOLLIPOP) | |||
| public SystemCookieManager(WebView webview) { | |||
| webView = webview; | |||
| cookieManager = CookieManager.getInstance(); | |||
| //REALLY? Nobody has seen this UNTIL NOW? | |||
| cookieManager.setAcceptFileSchemeCookies(true); | |||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | |||
| 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() { | |||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | |||
| cookieManager.removeAllCookies(null); | |||
| } else { | |||
| cookieManager.removeAllCookie(); | |||
| } | |||
| } | |||
| public void flush() { | |||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | |||
| 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,301 @@ | |||
| /* | |||
| 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 | |||
| public boolean onConsoleMessage(ConsoleMessage consoleMessage) | |||
| { | |||
| if (consoleMessage.message() != null) | |||
| LOG.d(LOG_TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message()); | |||
| return super.onConsoleMessage(consoleMessage); | |||
| } | |||
| @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; | |||
| } | |||
| // <input type=file> support: | |||
| // openFileChooser() is for pre KitKat and in KitKat mr1 (it's known broken in KitKat). | |||
| // For Lollipop, we use onShowFileChooser(). | |||
| public void openFileChooser(ValueCallback<Uri> uploadMsg) { | |||
| this.openFileChooser(uploadMsg, "*/*"); | |||
| } | |||
| public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) { | |||
| this.openFileChooser(uploadMsg, acceptType, null); | |||
| } | |||
| public void openFileChooser(final ValueCallback<Uri> uploadMsg, String acceptType, String capture) | |||
| { | |||
| Intent intent = new Intent(Intent.ACTION_GET_CONTENT); | |||
| intent.addCategory(Intent.CATEGORY_OPENABLE); | |||
| intent.setType("*/*"); | |||
| parentEngine.cordova.startActivityForResult(new CordovaPlugin() { | |||
| @Override | |||
| public void onActivityResult(int requestCode, int resultCode, Intent intent) { | |||
| Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData(); | |||
| LOG.d(LOG_TAG, "Receive file chooser URL: " + result); | |||
| uploadMsg.onReceiveValue(result); | |||
| } | |||
| }, intent, FILECHOOSER_RESULTCODE); | |||
| } | |||
| @TargetApi(Build.VERSION_CODES.LOLLIPOP) | |||
| @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); | |||
| 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; | |||
| } | |||
| @TargetApi(Build.VERSION_CODES.LOLLIPOP) | |||
| @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,370 @@ | |||
| /* | |||
| 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 | |||
| @TargetApi(Build.VERSION_CODES.LOLLIPOP) | |||
| 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) || needsKitKatContentUrlFix(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 needsKitKatContentUrlFix(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,319 @@ | |||
| /* | |||
| 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); | |||
| 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,170 @@ | |||
| { | |||
| "prepare_queue": { | |||
| "installed": [], | |||
| "uninstalled": [] | |||
| }, | |||
| "config_munge": { | |||
| "files": { | |||
| "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 | |||
| }, | |||
| { | |||
| "xml": "<feature name=\"SpinnerDialog\"><param name=\"android-package\" value=\"com.greybax.spinnerdialog.SpinnerDialog\" /></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": "com.verbosetech.quickpay" | |||
| }, | |||
| "cordova-plugin-ionic-keyboard": { | |||
| "PACKAGE_NAME": "com.verbosetech.quickpay" | |||
| }, | |||
| "cordova-plugin-ionic-webview": { | |||
| "ANDROID_SUPPORT_ANNOTATIONS_VERSION": "27.+", | |||
| "PACKAGE_NAME": "com.verbosetech.quickpay" | |||
| }, | |||
| "cordova-plugin-sign-in-with-apple": { | |||
| "PACKAGE_NAME": "com.verbosetech.quickpay" | |||
| }, | |||
| "cordova-plugin-splashscreen": { | |||
| "PACKAGE_NAME": "com.verbosetech.quickpay" | |||
| }, | |||
| "cordova-plugin-statusbar": { | |||
| "PACKAGE_NAME": "com.verbosetech.quickpay" | |||
| }, | |||
| "cordova-plugin-whitelist": { | |||
| "PACKAGE_NAME": "com.verbosetech.quickpay" | |||
| }, | |||
| "cordova-plugin-androidx": { | |||
| "PACKAGE_NAME": "com.verbosetech.quickpay" | |||
| }, | |||
| "cordova-plugin-androidx-adapter": { | |||
| "PACKAGE_NAME": "com.verbosetech.quickpay" | |||
| }, | |||
| "cordova-plugin-native-spinner": { | |||
| "PACKAGE_NAME": "com.verbosetech.quickpay" | |||
| } | |||
| }, | |||
| "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-sign-in-with-apple.SignInWithApple", | |||
| "file": "plugins/cordova-plugin-sign-in-with-apple/www/sign-in-with-apple.js", | |||
| "pluginId": "cordova-plugin-sign-in-with-apple", | |||
| "clobbers": [ | |||
| "cordova.plugins.SignInWithApple" | |||
| ] | |||
| }, | |||
| { | |||
| "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" | |||
| ] | |||
| }, | |||
| { | |||
| "id": "cordova-plugin-native-spinner.SpinnerDialog", | |||
| "file": "plugins/cordova-plugin-native-spinner/www/SpinnerDialog.js", | |||
| "pluginId": "cordova-plugin-native-spinner", | |||
| "clobbers": [ | |||
| "SpinnerDialog" | |||
| ] | |||
| } | |||
| ], | |||
| "plugin_metadata": { | |||
| "cordova-plugin-device": "2.0.3", | |||
| "cordova-plugin-ionic-keyboard": "2.2.0", | |||
| "cordova-plugin-ionic-webview": "4.2.1", | |||
| "cordova-plugin-sign-in-with-apple": "0.1.2", | |||
| "cordova-plugin-splashscreen": "5.0.4", | |||
| "cordova-plugin-statusbar": "2.4.3", | |||
| "cordova-plugin-whitelist": "1.3.4", | |||
| "cordova-plugin-androidx": "3.0.0", | |||
| "cordova-plugin-androidx-adapter": "1.1.3", | |||
| "cordova-plugin-native-spinner": "1.1.3" | |||
| } | |||
| } | |||
| @@ -0,0 +1,358 @@ | |||
| /* | |||
| 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' | |||
| buildscript { | |||
| repositories { | |||
| mavenCentral() | |||
| google() | |||
| jcenter() | |||
| } | |||
| dependencies { | |||
| classpath 'com.android.tools.build:gradle:3.3.0' | |||
| } | |||
| } | |||
| // Allow plugins to declare Maven dependencies via build-extras.gradle. | |||
| allprojects { | |||
| repositories { | |||
| mavenCentral() | |||
| jcenter() | |||
| } | |||
| } | |||
| task wrapper(type: Wrapper) { | |||
| gradleVersion = '4.10.3' | |||
| } | |||
| // 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) | |||
| } | |||
| } | |||
| /* | |||
| * 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') | |||
| // SUB-PROJECT DEPENDENCIES START | |||
| implementation(project(path: ":CordovaLib")) | |||
| implementation "androidx.annotation:annotation:1.1.0" | |||
| // 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() | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| <?xml version='1.0' encoding='utf-8'?> | |||
| <manifest android:hardwareAccelerated="true" android:versionCode="4" android:versionName="0.0.4" package="com.verbosetech.quickpay" 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: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,10 @@ | |||
| /*! | |||
| * Chart.js v2.9.4 | |||
| * https://www.chartjs.org | |||
| * (c) 2020 Chart.js Contributors | |||
| * Released under the MIT License | |||
| */ | |||
| //! moment.js | |||
| //! moment.js locale configuration | |||
| @@ -0,0 +1 @@ | |||
| (window.webpackJsonp=window.webpackJsonp||[]).push([[28],{ClJA:function(l,n,u){"use strict";u.r(n);var a=u("8Y7J");class b{}var t=u("pMnS"),i=u("MKJQ"),c=u("sZkV");class o{constructor(l,n){this.alertCtrl=l,this.modalCtrl=n}ngOnInit(){}cadangan(){this.alertCtrl.create({header:"Cadangan",subHeader:"Sila Isi Cadangan Anda Dibawah",inputs:[{name:"Cadangan"}],buttons:[{text:"Okay",handler:()=>{}},{text:"Cancel"}]}).then(l=>{l.present()})}}var r=a.nb({encapsulation:0,styles:[[""]],data:{}});function e(l){return a.Ib(0,[(l()(),a.pb(0,0,null,null,6,"ion-header",[],null,null,null,i.V,i.m)),a.ob(1,49152,null,0,c.B,[a.h,a.k,a.x],null,null),(l()(),a.pb(2,0,null,0,4,"ion-toolbar",[],null,null,null,i.rb,i.I)),a.ob(3,49152,null,0,c.zb,[a.h,a.k,a.x],null,null),(l()(),a.pb(4,0,null,0,2,"ion-title",[],null,null,null,i.pb,i.G)),a.ob(5,49152,null,0,c.xb,[a.h,a.k,a.x],null,null),(l()(),a.Gb(-1,0,["Senarai Inisiatif"])),(l()(),a.pb(7,0,null,null,15,"ion-content",[["class","bg_color"],["fullscreen",""]],null,null,null,i.R,i.i)),a.ob(8,49152,null,0,c.u,[a.h,a.k,a.x],{fullscreen:[0,"fullscreen"]},null),(l()(),a.pb(9,0,null,0,13,"ion-card",[["style","background-color: white;"]],null,null,null,i.P,i.f)),a.ob(10,49152,null,0,c.m,[a.h,a.k,a.x],null,null),(l()(),a.pb(11,0,null,0,11,"ion-card-content",[],null,null,null,i.O,i.g)),a.ob(12,49152,null,0,c.n,[a.h,a.k,a.x],null,null),(l()(),a.pb(13,0,null,0,9,"ul",[],null,null,null,null,null)),(l()(),a.pb(14,0,null,null,1,"li",[["style","color: black;"]],null,[[null,"click"]],(function(l,n,u){var a=!0;return"click"===n&&(a=!1!==l.component.cadangan()&&a),a}),null,null)),(l()(),a.Gb(-1,null,["Inisiatif 1"])),(l()(),a.pb(16,0,null,null,0,"br",[],null,null,null,null,null)),(l()(),a.pb(17,0,null,null,1,"li",[["style","color: black;"]],null,[[null,"click"]],(function(l,n,u){var a=!0;return"click"===n&&(a=!1!==l.component.cadangan()&&a),a}),null,null)),(l()(),a.Gb(-1,null,["Inisiatif 2"])),(l()(),a.pb(19,0,null,null,0,"br",[],null,null,null,null,null)),(l()(),a.pb(20,0,null,null,1,"li",[["style","color: black;"]],null,[[null,"click"]],(function(l,n,u){var a=!0;return"click"===n&&(a=!1!==l.component.cadangan()&&a),a}),null,null)),(l()(),a.Gb(-1,null,["Inisiatif 3"])),(l()(),a.pb(22,0,null,null,0,"br",[],null,null,null,null,null))],(function(l,n){l(n,8,0,"")}),null)}function s(l){return a.Ib(0,[(l()(),a.pb(0,0,null,null,1,"app-inisiatif-list",[],null,null,null,e,r)),a.ob(1,114688,null,0,o,[c.a,c.Db],null,null)],(function(l,n){l(n,1,0)}),null)}var p=a.lb("app-inisiatif-list",o,s,{},{},[]),k=u("SVse"),d=u("s7LF"),f=u("iInd");class h{}u.d(n,"InisiatifListPageModuleNgFactory",(function(){return z}));var z=a.mb(b,[],(function(l){return a.yb([a.zb(512,a.j,a.X,[[8,[t.a,p]],[3,a.j],a.v]),a.zb(4608,k.l,k.k,[a.s,[2,k.v]]),a.zb(4608,d.l,d.l,[]),a.zb(4608,c.b,c.b,[a.x,a.g]),a.zb(4608,c.Db,c.Db,[c.b,a.j,a.p]),a.zb(4608,c.Hb,c.Hb,[c.b,a.j,a.p]),a.zb(1073742336,k.b,k.b,[]),a.zb(1073742336,d.k,d.k,[]),a.zb(1073742336,d.c,d.c,[]),a.zb(1073742336,c.Bb,c.Bb,[]),a.zb(1073742336,f.n,f.n,[[2,f.s],[2,f.m]]),a.zb(1073742336,h,h,[]),a.zb(1073742336,b,b,[]),a.zb(1024,f.k,(function(){return[[{path:"",component:o}]]}),[])])}))}}]); | |||
| @@ -0,0 +1 @@ | |||
| function _defineProperties(l,n){for(var u=0;u<n.length;u++){var t=n[u];t.enumerable=t.enumerable||!1,t.configurable=!0,"value"in t&&(t.writable=!0),Object.defineProperty(l,t.key,t)}}function _createClass(l,n,u){return n&&_defineProperties(l.prototype,n),u&&_defineProperties(l,u),l}function _classCallCheck(l,n){if(!(l instanceof n))throw new TypeError("Cannot call a class as a function")}(window.webpackJsonp=window.webpackJsonp||[]).push([[28],{ClJA:function(l,n,u){"use strict";u.r(n);var t=u("8Y7J"),a=function l(){_classCallCheck(this,l)},e=u("pMnS"),i=u("MKJQ"),c=u("sZkV"),b=function(){function l(n,u){_classCallCheck(this,l),this.alertCtrl=n,this.modalCtrl=u}return _createClass(l,[{key:"ngOnInit",value:function(){}},{key:"cadangan",value:function(){this.alertCtrl.create({header:"Cadangan",subHeader:"Sila Isi Cadangan Anda Dibawah",inputs:[{name:"Cadangan"}],buttons:[{text:"Okay",handler:function(){}},{text:"Cancel"}]}).then((function(l){l.present()}))}}]),l}(),o=t.nb({encapsulation:0,styles:[[""]],data:{}});function r(l){return t.Ib(0,[(l()(),t.pb(0,0,null,null,6,"ion-header",[],null,null,null,i.V,i.m)),t.ob(1,49152,null,0,c.B,[t.h,t.k,t.x],null,null),(l()(),t.pb(2,0,null,0,4,"ion-toolbar",[],null,null,null,i.rb,i.I)),t.ob(3,49152,null,0,c.zb,[t.h,t.k,t.x],null,null),(l()(),t.pb(4,0,null,0,2,"ion-title",[],null,null,null,i.pb,i.G)),t.ob(5,49152,null,0,c.xb,[t.h,t.k,t.x],null,null),(l()(),t.Gb(-1,0,["Senarai Inisiatif"])),(l()(),t.pb(7,0,null,null,15,"ion-content",[["class","bg_color"],["fullscreen",""]],null,null,null,i.R,i.i)),t.ob(8,49152,null,0,c.u,[t.h,t.k,t.x],{fullscreen:[0,"fullscreen"]},null),(l()(),t.pb(9,0,null,0,13,"ion-card",[["style","background-color: white;"]],null,null,null,i.P,i.f)),t.ob(10,49152,null,0,c.m,[t.h,t.k,t.x],null,null),(l()(),t.pb(11,0,null,0,11,"ion-card-content",[],null,null,null,i.O,i.g)),t.ob(12,49152,null,0,c.n,[t.h,t.k,t.x],null,null),(l()(),t.pb(13,0,null,0,9,"ul",[],null,null,null,null,null)),(l()(),t.pb(14,0,null,null,1,"li",[["style","color: black;"]],null,[[null,"click"]],(function(l,n,u){var t=!0;return"click"===n&&(t=!1!==l.component.cadangan()&&t),t}),null,null)),(l()(),t.Gb(-1,null,["Inisiatif 1"])),(l()(),t.pb(16,0,null,null,0,"br",[],null,null,null,null,null)),(l()(),t.pb(17,0,null,null,1,"li",[["style","color: black;"]],null,[[null,"click"]],(function(l,n,u){var t=!0;return"click"===n&&(t=!1!==l.component.cadangan()&&t),t}),null,null)),(l()(),t.Gb(-1,null,["Inisiatif 2"])),(l()(),t.pb(19,0,null,null,0,"br",[],null,null,null,null,null)),(l()(),t.pb(20,0,null,null,1,"li",[["style","color: black;"]],null,[[null,"click"]],(function(l,n,u){var t=!0;return"click"===n&&(t=!1!==l.component.cadangan()&&t),t}),null,null)),(l()(),t.Gb(-1,null,["Inisiatif 3"])),(l()(),t.pb(22,0,null,null,0,"br",[],null,null,null,null,null))],(function(l,n){l(n,8,0,"")}),null)}var s=t.lb("app-inisiatif-list",b,(function(l){return t.Ib(0,[(l()(),t.pb(0,0,null,null,1,"app-inisiatif-list",[],null,null,null,r,o)),t.ob(1,114688,null,0,b,[c.a,c.Db],null,null)],(function(l,n){l(n,1,0)}),null)}),{},{},[]),f=u("SVse"),p=u("s7LF"),k=u("iInd"),d=function l(){_classCallCheck(this,l)};u.d(n,"InisiatifListPageModuleNgFactory",(function(){return h}));var h=t.mb(a,[],(function(l){return t.yb([t.zb(512,t.j,t.X,[[8,[e.a,s]],[3,t.j],t.v]),t.zb(4608,f.l,f.k,[t.s,[2,f.v]]),t.zb(4608,p.l,p.l,[]),t.zb(4608,c.b,c.b,[t.x,t.g]),t.zb(4608,c.Db,c.Db,[c.b,t.j,t.p]),t.zb(4608,c.Hb,c.Hb,[c.b,t.j,t.p]),t.zb(1073742336,f.b,f.b,[]),t.zb(1073742336,p.k,p.k,[]),t.zb(1073742336,p.c,p.c,[]),t.zb(1073742336,c.Bb,c.Bb,[]),t.zb(1073742336,k.n,k.n,[[2,k.s],[2,k.m]]),t.zb(1073742336,d,d,[]),t.zb(1073742336,a,a,[]),t.zb(1024,k.k,(function(){return[[{path:"",component:b}]]}),[])])}))}}]); | |||
| @@ -0,0 +1 @@ | |||
| (window.webpackJsonp=window.webpackJsonp||[]).push([[33],{PUUY:function(n,l,u){"use strict";u.r(l);var t=u("8Y7J");class o{}var b=u("pMnS"),r=u("MKJQ"),s=u("sZkV"),e=u("TSSN");class c{constructor(n){this.navCtrl=n}ngOnInit(){}tabs(){this.navCtrl.navigateRoot(["./tabs"])}}var i=t.nb({encapsulation:0,styles:[[".img_box[_ngcontent-%COMP%]{width:180px;margin:85px auto 40px}h2[_ngcontent-%COMP%]{margin:0;font-size:1.4rem}p[_ngcontent-%COMP%]{font-size:1rem;font-weight:500;margin-top:32px}ion-footer[_ngcontent-%COMP%]{padding-bottom:25px}ion-footer[_ngcontent-%COMP%] h2[_ngcontent-%COMP%]{font-size:1.15rem;color:var(--primary);margin-bottom:14px}ion-footer[_ngcontent-%COMP%] h3[_ngcontent-%COMP%]{margin:0;color:var(--text-light);font-size:1.15rem}"]],data:{}});function a(n){return t.Ib(0,[(n()(),t.pb(0,0,null,null,12,"ion-content",[["class","ion-text-center"]],null,null,null,r.R,r.i)),t.ob(1,49152,null,0,s.u,[t.h,t.k,t.x],null,null),(n()(),t.pb(2,0,null,0,1,"div",[["class","img_box"]],null,null,null,null,null)),(n()(),t.pb(3,0,null,null,0,"img",[["src","assets/imgs/Successful.png"]],null,null,null,null,null)),(n()(),t.pb(4,0,null,0,2,"h2",[],null,null,null,null,null)),(n()(),t.Gb(5,null,["",""])),t.Cb(131072,e.j,[e.k,t.h]),(n()(),t.pb(7,0,null,0,5,"p",[],null,null,null,null,null)),(n()(),t.Gb(8,null,["",""])),t.Cb(131072,e.j,[e.k,t.h]),(n()(),t.pb(10,0,null,null,0,"br",[],null,null,null,null,null)),(n()(),t.Gb(11,null,["",""])),t.Cb(131072,e.j,[e.k,t.h]),(n()(),t.pb(13,0,null,null,7,"ion-footer",[["class","ion-text-center ion-no-border"]],null,null,null,r.T,r.k)),t.ob(14,49152,null,0,s.z,[t.h,t.k,t.x],null,null),(n()(),t.pb(15,0,null,0,2,"h2",[],null,null,null,null,null)),(n()(),t.Gb(16,null,["",""])),t.Cb(131072,e.j,[e.k,t.h]),(n()(),t.pb(18,0,null,0,2,"h3",[],null,[[null,"click"]],(function(n,l,u){var t=!0;return"click"===l&&(t=!1!==n.component.tabs()&&t),t}),null,null)),(n()(),t.Gb(19,null,["",""])),t.Cb(131072,e.j,[e.k,t.h])],null,(function(n,l){n(l,5,0,t.Hb(l,5,0,t.Bb(l,6).transform("payment_successful"))),n(l,8,0,t.Hb(l,8,0,t.Bb(l,9).transform("your_booking_is_confirmed"))),n(l,11,0,t.Hb(l,11,0,t.Bb(l,12).transform("with_quickpay"))),n(l,16,0,t.Hb(l,16,0,t.Bb(l,17).transform("share_your_booking_details"))),n(l,19,0,t.Hb(l,19,0,t.Bb(l,20).transform("continue")))}))}function p(n){return t.Ib(0,[(n()(),t.pb(0,0,null,null,1,"app-paymentsuccessful",[],null,null,null,a,i)),t.ob(1,114688,null,0,c,[s.Eb],null,null)],(function(n,l){n(l,1,0)}),null)}var m=t.lb("app-paymentsuccessful",c,p,{},{},[]),f=u("SVse"),g=u("s7LF"),h=u("iInd");class k{}u.d(l,"PaymentsuccessfulPageModuleNgFactory",(function(){return z}));var z=t.mb(o,[],(function(n){return t.yb([t.zb(512,t.j,t.X,[[8,[b.a,m]],[3,t.j],t.v]),t.zb(4608,f.l,f.k,[t.s,[2,f.v]]),t.zb(4608,g.l,g.l,[]),t.zb(4608,s.b,s.b,[t.x,t.g]),t.zb(4608,s.Db,s.Db,[s.b,t.j,t.p]),t.zb(4608,s.Hb,s.Hb,[s.b,t.j,t.p]),t.zb(1073742336,f.b,f.b,[]),t.zb(1073742336,g.k,g.k,[]),t.zb(1073742336,g.c,g.c,[]),t.zb(1073742336,s.Bb,s.Bb,[]),t.zb(1073742336,e.h,e.h,[]),t.zb(1073742336,h.n,h.n,[[2,h.s],[2,h.m]]),t.zb(1073742336,k,k,[]),t.zb(1073742336,o,o,[]),t.zb(1024,h.k,(function(){return[[{path:"",component:c}]]}),[])])}))}}]); | |||
| @@ -0,0 +1 @@ | |||
| function _defineProperties(n,l){for(var u=0;u<l.length;u++){var t=l[u];t.enumerable=t.enumerable||!1,t.configurable=!0,"value"in t&&(t.writable=!0),Object.defineProperty(n,t.key,t)}}function _createClass(n,l,u){return l&&_defineProperties(n.prototype,l),u&&_defineProperties(n,u),n}function _classCallCheck(n,l){if(!(n instanceof l))throw new TypeError("Cannot call a class as a function")}(window.webpackJsonp=window.webpackJsonp||[]).push([[33],{PUUY:function(n,l,u){"use strict";u.r(l);var t=u("8Y7J"),e=function n(){_classCallCheck(this,n)},o=u("pMnS"),r=u("MKJQ"),b=u("sZkV"),i=u("TSSN"),a=function(){function n(l){_classCallCheck(this,n),this.navCtrl=l}return _createClass(n,[{key:"ngOnInit",value:function(){}},{key:"tabs",value:function(){this.navCtrl.navigateRoot(["./tabs"])}}]),n}(),c=t.nb({encapsulation:0,styles:[[".img_box[_ngcontent-%COMP%]{width:180px;margin:85px auto 40px}h2[_ngcontent-%COMP%]{margin:0;font-size:1.4rem}p[_ngcontent-%COMP%]{font-size:1rem;font-weight:500;margin-top:32px}ion-footer[_ngcontent-%COMP%]{padding-bottom:25px}ion-footer[_ngcontent-%COMP%] h2[_ngcontent-%COMP%]{font-size:1.15rem;color:var(--primary);margin-bottom:14px}ion-footer[_ngcontent-%COMP%] h3[_ngcontent-%COMP%]{margin:0;color:var(--text-light);font-size:1.15rem}"]],data:{}});function s(n){return t.Ib(0,[(n()(),t.pb(0,0,null,null,12,"ion-content",[["class","ion-text-center"]],null,null,null,r.R,r.i)),t.ob(1,49152,null,0,b.u,[t.h,t.k,t.x],null,null),(n()(),t.pb(2,0,null,0,1,"div",[["class","img_box"]],null,null,null,null,null)),(n()(),t.pb(3,0,null,null,0,"img",[["src","assets/imgs/Successful.png"]],null,null,null,null,null)),(n()(),t.pb(4,0,null,0,2,"h2",[],null,null,null,null,null)),(n()(),t.Gb(5,null,["",""])),t.Cb(131072,i.j,[i.k,t.h]),(n()(),t.pb(7,0,null,0,5,"p",[],null,null,null,null,null)),(n()(),t.Gb(8,null,["",""])),t.Cb(131072,i.j,[i.k,t.h]),(n()(),t.pb(10,0,null,null,0,"br",[],null,null,null,null,null)),(n()(),t.Gb(11,null,["",""])),t.Cb(131072,i.j,[i.k,t.h]),(n()(),t.pb(13,0,null,null,7,"ion-footer",[["class","ion-text-center ion-no-border"]],null,null,null,r.T,r.k)),t.ob(14,49152,null,0,b.z,[t.h,t.k,t.x],null,null),(n()(),t.pb(15,0,null,0,2,"h2",[],null,null,null,null,null)),(n()(),t.Gb(16,null,["",""])),t.Cb(131072,i.j,[i.k,t.h]),(n()(),t.pb(18,0,null,0,2,"h3",[],null,[[null,"click"]],(function(n,l,u){var t=!0;return"click"===l&&(t=!1!==n.component.tabs()&&t),t}),null,null)),(n()(),t.Gb(19,null,["",""])),t.Cb(131072,i.j,[i.k,t.h])],null,(function(n,l){n(l,5,0,t.Hb(l,5,0,t.Bb(l,6).transform("payment_successful"))),n(l,8,0,t.Hb(l,8,0,t.Bb(l,9).transform("your_booking_is_confirmed"))),n(l,11,0,t.Hb(l,11,0,t.Bb(l,12).transform("with_quickpay"))),n(l,16,0,t.Hb(l,16,0,t.Bb(l,17).transform("share_your_booking_details"))),n(l,19,0,t.Hb(l,19,0,t.Bb(l,20).transform("continue")))}))}var p=t.lb("app-paymentsuccessful",a,(function(n){return t.Ib(0,[(n()(),t.pb(0,0,null,null,1,"app-paymentsuccessful",[],null,null,null,s,c)),t.ob(1,114688,null,0,a,[b.Eb],null,null)],(function(n,l){n(l,1,0)}),null)}),{},{},[]),f=u("SVse"),h=u("s7LF"),m=u("iInd"),g=function n(){_classCallCheck(this,n)};u.d(l,"PaymentsuccessfulPageModuleNgFactory",(function(){return k}));var k=t.mb(e,[],(function(n){return t.yb([t.zb(512,t.j,t.X,[[8,[o.a,p]],[3,t.j],t.v]),t.zb(4608,f.l,f.k,[t.s,[2,f.v]]),t.zb(4608,h.l,h.l,[]),t.zb(4608,b.b,b.b,[t.x,t.g]),t.zb(4608,b.Db,b.Db,[b.b,t.j,t.p]),t.zb(4608,b.Hb,b.Hb,[b.b,t.j,t.p]),t.zb(1073742336,f.b,f.b,[]),t.zb(1073742336,h.k,h.k,[]),t.zb(1073742336,h.c,h.c,[]),t.zb(1073742336,b.Bb,b.Bb,[]),t.zb(1073742336,i.h,i.h,[]),t.zb(1073742336,m.n,m.n,[[2,m.s],[2,m.m]]),t.zb(1073742336,g,g,[]),t.zb(1073742336,e,e,[]),t.zb(1024,m.k,(function(){return[[{path:"",component:a}]]}),[])])}))}}]); | |||