code:
//Variables that are missing, not expected or invalid will be ignored
//"starting" values will only import when on the start screen
DolSettingsExport = {
	"starting":{
		"player":{
			"gender":"m",
			"gender_body":"f",
			"ballsExist":true,
			"freckles":false
		},
		"skinColor":{
			"natural":"light",
			"range":0
		},
		"bodysize":2,
		"penissize":0,
		"breastsize":0,
		"bottomsize":0,
		"breastsensitivity":3,
		"genitalsensitivity":3,
		"eyeselect":"purple",
		"hairselect":"red",
		"hairlength":200,
		"awareselect":"innocent",
		"background":"waif",
		"gamemode":"normal"
	},
	"general":{
		"malechance":50,
		"dgchance":0,
		"cbchance":0,
		"malevictimchance":50,
		"homochance":4,
		"breast_mod":0,
		"penis_mod":0,
		"whitechance":90,
		"blackchance":10,
		"alluremod":1,
		"clothesPrice": 1,
		"clothesPriceUnderwear": 1,
		"clothesPriceSchool": 1,
		"clothesPriceLewd": 1,
		"rentmod": 1,
		"beastmalechance":50,
		"monsterchance":20,
		"monsterhallucinations":true,
		"blackwolfmonster": 0,
		"bestialitydisable":false,
		"swarmdisable":false,
		"slimedisable":false,
		"voredisable":false,
		"tentacledisable":false,
		"plantdisable":false,
		"analdisable":false,
		"analdoubledisable":false,
		"vaginaldoubledisable":false,
		"transformdisable":false,
		"hirsutedisable":true,
		"pbdisable":true,
		"breastfeedingdisable":false,
		"analpregdisable":false,
		"watersportsdisable":false,
		"spiderdisable":false,
		"bodywritingdisable":false,
		"breastsizemin":0,
		"breastsizemax":12,
		"bottomsizemax":8,
		"penissizemax":4,
		"penissizemin":-2,
		"images":1,
		"sidebarAnimations": true,
		"combatAnimations": true,
		"bodywritingImages": true,
		"silhouettedisable":false,
		"numberify_enabled":1,
		"timestyle":"military",
		"tipdisable":false,
		"debugdisable":true,
		"cheatdisabletoggle":true,
		"showCaptionText":true,
		"confirmSave":false,
		"confirmLoad":false,
		"confirmDelete":false,
		"imgLighten":"",
		"sidebarStats":"Disabled",
		"sidebarTime":"Disabled",
		"combatControls":"radio",
		"reducedLineHeight":false,
		"neverNudeMenus":false,
		"skipStatisticsConfirmation":false,
		"multipleWardrobes":"isolated",
		"outfitEditorPerPage":10,
		"map":{
			"movement":true,
			"top":false,
			"markers":false
		},
		"skinColor":{
			"tanImgEnabled": true,
			"tanningEnabled": true,
		},
		"shopDefaults": {
			"alwaysBackToShopButton":false,
			"color":"black",
			"secColor":"black",
			"colourItems":"random",
			"compactMode":false,
			"disableReturn":false,
			"highContrast":false,
			"mannequinGender":"same",
			"mannequinGenderFromClothes":false,
			"noHelp":false,
			"noTraits":false,
		},
	},
	"npc":{
		"Avery":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Bailey":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Briar":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Charlie":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Darryl":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Doren":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Eden":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Gwylan":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Harper":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Jordan":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Kylar":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Landry":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Leighton":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Mason":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Morgan":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":5},
		"River":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Robin":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Sam":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Sirris":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Whitney":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Winter":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Black Wolf":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Niki":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Quinn":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Remy":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Alex":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Great Hawk":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Wren":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Sydney":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Piper":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
		"Raylan":{"pronoun":"none","gender":"none", "penissize":0, "breastsize":0},
	}
}


code:
<NotepadPlus>
	<UserLang name="Sugarcube4" ext="twee tw" udlVersion="2.1">
		<Settings>
			<Global caseIgnored="no" allowFoldOfComments="no" foldCompact="yes" forcePureLC="0" decimalSeparator="0" />
			<Prefix Keywords1="yes" Keywords2="no" Keywords3="no" Keywords4="no" Keywords5="no" Keywords6="no" Keywords7="no" Keywords8="no" />
		</Settings>
		<KeywordLists>
			<Keywords name="Comments">00 01 02 03/% 03/* 03<!-- 04%/ 04*/ 04--></Keywords>
			<Keywords name="Numbers, prefix1"></Keywords>
			<Keywords name="Numbers, prefix2"></Keywords>
			<Keywords name="Numbers, extras1"></Keywords>
			<Keywords name="Numbers, extras2"></Keywords>
			<Keywords name="Numbers, suffix1"></Keywords>
			<Keywords name="Numbers, suffix2"></Keywords>
			<Keywords name="Numbers, range"></Keywords>
			<Keywords name="Operators1">+ - * / = += -= *= /= ++ -- == != === !== > < >= <= ! && || & | ( )</Keywords>
			<Keywords name="Operators2"></Keywords>
			<Keywords name="Folders in code1, open"><<if</Keywords>
			<Keywords name="Folders in code1, middle"></Keywords>
			<Keywords name="Folders in code1, close"><</if>></Keywords>
			<Keywords name="Folders in code2, open"></Keywords>
			<Keywords name="Folders in code2, middle"></Keywords>
			<Keywords name="Folders in code2, close"></Keywords>
			<Keywords name="Folders in comment, open"></Keywords>
			<Keywords name="Folders in comment, middle"></Keywords>
			<Keywords name="Folders in comment, close"></Keywords>
			<Keywords name="Keywords1">$ _</Keywords>
			<Keywords name="Keywords2"></Keywords>
			<Keywords name="Keywords3"></Keywords>
			<Keywords name="Keywords4"></Keywords>
			<Keywords name="Keywords5"></Keywords>
			<Keywords name="Keywords6"></Keywords>
			<Keywords name="Keywords7"></Keywords>
			<Keywords name="Keywords8"></Keywords>
			<Keywords name="Delimiters">00:: 01 02((EOL)) 03<< 04 05>> 06[[ 07 08]] 09< 10 11> 12'' 13 14'' 15// 16 17// 18@@ 19 20@@ 21[img[ 22 23]]</Keywords>
		</KeywordLists>
		<Styles>
			<WordsStyle name="DEFAULT" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
			<WordsStyle name="COMMENTS" fgColor="808080" bgColor="FFFFFF" fontName="" fontStyle="2" nesting="0" />
			<WordsStyle name="LINE COMMENTS" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
			<WordsStyle name="NUMBERS" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
			<WordsStyle name="KEYWORDS1" fgColor="004080" bgColor="FFFFFF" fontName="" fontStyle="1" nesting="0" />
			<WordsStyle name="KEYWORDS2" fgColor="8080C0" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
			<WordsStyle name="KEYWORDS3" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
			<WordsStyle name="KEYWORDS4" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
			<WordsStyle name="KEYWORDS5" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
			<WordsStyle name="KEYWORDS6" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
			<WordsStyle name="KEYWORDS7" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
			<WordsStyle name="KEYWORDS8" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
			<WordsStyle name="OPERATORS" fgColor="800040" bgColor="FFFFFF" fontName="" fontStyle="1" nesting="0" />
			<WordsStyle name="FOLDER IN CODE1" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="1" nesting="0" />
			<WordsStyle name="FOLDER IN CODE2" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
			<WordsStyle name="FOLDER IN COMMENT" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
			<WordsStyle name="DELIMITERS1" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="5" fontSize="12" nesting="0" />
			<WordsStyle name="DELIMITERS2" fgColor="800080" bgColor="FFFFFF" fontName="" fontStyle="1" nesting="50334724" />
			<WordsStyle name="DELIMITERS3" fgColor="0000FF" bgColor="FFFFFF" fontName="" fontStyle="1" nesting="1024" />
			<WordsStyle name="DELIMITERS4" fgColor="0000FF" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
			<WordsStyle name="DELIMITERS5" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="1" nesting="32" />
			<WordsStyle name="DELIMITERS6" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="2" nesting="16" />
			<WordsStyle name="DELIMITERS7" fgColor="008000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
			<WordsStyle name="DELIMITERS8" fgColor="8080FF" bgColor="FFFFFF" fontName="" fontStyle="1" nesting="0" />
		</Styles>
	</UserLang>
</NotepadPlus>

repo_name: Area/degrees

license: mit


code:
# Degrees of Lewdity

## How to build

### Optional Prerequisites

1. Install [Tweego](http://www.motoslave.net/tweego/) and remember the path where it was installed.
2. Add path to `tweego.exe` (e.g. `C:\Program Files\Twine\tweego-2.1.0-windows-x64`) to Windows `Path` environment variable.

### Changing the build version and type

1. Open `01-config\sugarcubeConfig.js`.
2. Edit the `window.StartConfig` object to the relevant config type.
	* Normal Build - `enableImages` needs to be `true` and `enableLinkNumberify` needs to be `true`.
	* Text Only Build - `enableImages` needs to be `false` and `enableLinkNumberify` needs to be `true`.
	* Android Build - `enableImages` needs to be `true` and `enableLinkNumberify` needs to be `false`.
3. `version` is optional between release versions but will be displayed on screen in several places and stored in the saves made.
4. `debug` is optional and will only effect new games.

### Compiling the html

1. Run `compile.bat` or `compile-watch.bat`.
2. Open `Degrees of Lewdity VERSION.html`.

### Build Android version (.apk)

?


code:
@echo off

:: Set working directory
pushd %~dp0
@set TWEEGO_PATH="%~dp0devTools\tweego\StoryFormats"

:: Run the appropriate compiler for the user's CPU architecture.
if %PROCESSOR_ARCHITECTURE% == AMD64 (
	CALL "%~dp0devTools\tweego\tweego_win64.exe" -w -o "%~dp0Degrees of Lewdity VERSION.html" --head "%~dp0devTools\head.html" "%~dp0game"
) else (
	CALL "%~dp0devTools\tweego\tweego_win86.exe" -w -o "%~dp0Degrees of Lewdity VERSION.html" --head "%~dp0devTools\head.html" "%~dp0game"
)

path: compile watch.bat


code:
@echo off

:: Set working directory
pushd %~dp0
@set TWEEGO_PATH="%~dp0devTools\tweego\StoryFormats"

:: Run the appropriate compiler for the user's CPU architecture.
if %PROCESSOR_ARCHITECTURE% == AMD64 (
	CALL "%~dp0devTools\tweego\tweego_win64.exe" -o "%~dp0Degrees of Lewdity VERSION.html" --head "%~dp0devTools\head.html" "%~dp0game"
) else (
	CALL "%~dp0devTools\tweego\tweego_win86.exe" -o "%~dp0Degrees of Lewdity VERSION.html" --head "%~dp0devTools\head.html" "%~dp0game"
)


code:
#!/usr/bin/env bash

output=/dev/stdout

#display an error message
function echoError() {
	echo -e "\033[0;31m$*\033[0m"
}

#display message
function echoMessage() {
	echo "$1" >"${output}"
}

function compile() {
	export TWEEGO_PATH=devTools/tweego/storyFormats
	if [ -z ${FORCE_VERSION+true} ]; then
		VERSION="$(git describe --tags --always --dirty)"
	else
		VERSION="$FORCE_VERSION"
	fi
	if [ -z "${VERSION}" ]; then
		TARGET="Degrees of Lewdity.html"
	else
		TARGET="Degrees of Lewdity $VERSION.html"
	fi

	TWEEGO_EXE="tweego"
	if [ ! -f "$(command -v tweego)" ]; then
		case "$(uname -m)" in
			x86_64 | amd64)
				echoMessage "x64 arch"
				if [ "$(uname -s)" = "Darwin" ]; then
					TWEEGO_EXE="./devTools/tweego/tweego_osx64"
				elif [ "$OSTYPE" = "msys" ]; then
					TWEEGO_EXE="./devTools/tweego/tweego_win64"
				else
					TWEEGO_EXE="./devTools/tweego/tweego_linux64"
				fi
				;;
			x86 | i[3-6]86)
				echoMessage "x86 arch"
				if [ "$(uname -s)" = "Darwin" ]; then
					TWEEGO_EXE="./devTools/tweego/tweego_osx86"
				elif [ "$OSTYPE" = "msys" ]; then
					TWEEGO_EXE="./devTools/tweego/tweego_win86"
				else
					TWEEGO_EXE="./devTools/tweego/tweego_linux86"
				fi
				;;
			*)
				echoError "No system tweego binary found, and no precompiled binary for your platform available."
				echoError "Please compile tweego and put the executable in PATH."
				exit 2
				;;
		esac
	fi

	$TWEEGO_EXE "$@" -o  "$TARGET" --head "devTools/head.html" game/ || build_failed="true"

	if [ "$build_failed" = "true" ]; then
		echoError "Build failed."
		exit 1
	else
		echo "Done: \"$TARGET\""
		exit 0
	fi
}

compile "$@"


code:
# IMPORTANT: this is the last debian version to support jdk8
# As android studio *still* requires 8, be sure to continue using debian:stretch
FROM node:14.11-stretch

# Add backports to make it easier to install npm
# RUN echo "deb http://deb.debian.org/debian buster-backports main" > /etc/apt/sources.list.d/buster-backports.list
RUN \
apt-get update && \
apt-get install -y --no-install-recommends \
    lib32stdc++6 lib32z1 \
    openjdk-8-jdk-headless \
    gradle \
    unzip

WORKDIR /tmp
ENV ANDROID_HOME /usr/local/android-sdk-linux
ENV ANDROID_SDK_ROOT ${ANDROID_HOME}
ENV PATH $PATH:$ANDROID_HOME/cmdline-tools/latest/bin
ADD commandlinetools-linux-6609375_latest.zip /tmp/
RUN unzip /tmp/commandlinetools-linux-6609375_latest.zip
RUN mkdir -p ${ANDROID_HOME}/cmdline-tools/
RUN mv /tmp/tools/ ${ANDROID_HOME}/cmdline-tools/latest/

RUN yes | sdkmanager --licenses
RUN sdkmanager "platforms;android-28" "build-tools;29.0.2"

VOLUME [ "/src" ]
COPY "cordova" "/cordovasrc"

WORKDIR "/cordovasrc"
RUN npm ci
RUN npm run env -- cordova telemetry off
RUN npm run build-debug-ci

path: devTools/androidsdk/image/Dockerfile


code:
<?xml version='1.0' encoding='utf-8'?>
<widget
    id="dol"
    version="0.3.6.1"
    xmlns="http://www.w3.org/ns/widgets"
    xmlns:cdv="http://cordova.apache.org/ns/1.0"
    android-packageName="com.vrelnir.DegreesOfLewdity"
>
    <name short="DoL">Degrees of Lewdity</name>
    <description>Degrees of Lewdity</description>
    <author email="9096-Vrelnir@users.noreply.gitgud.io" href="https://gitgud.io/Vrelnir/degrees-of-lewdity">
        Vrelnir
    </author>
    <content src="Degrees of Lewdity.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="loadUrlTimeoutValue" value="120000" />
    <platform name="android">
        <allow-intent href="market:*" />
        <hook type="before_compile" src="scripts/copyGameFiles.js" />
        <hook type="after_compile" src="scripts/copyForDist.js" />
        <icon src="www/res/ldpi.png" density="ldpi" />
        <icon src="www/res/mdpi.png" density="mdpi" />
        <icon src="www/res/hdpi.png" density="hdpi" />
        <icon src="www/res/xhdpi.png" density="xhdpi" />
        <icon src="www/res/xxhdpi.png" density="xxhdpi" />
        <icon src="www/res/xxxhdpi.png" density="xxxhdpi" />
    </platform>
    <!-- ios is currently unsupported -->
    <!-- <platform name="ios">
        <allow-intent href="itms:*" />
        <allow-intent href="itms-apps:*" />
    </platform> -->
</widget>


degrees-of-lewdity-android-app

description: This sub-project exists to build DoL as an Android app

main: Degrees of Lewdity.html

build-debug-ci: cordova build --debug

build-debug: npm run update-version && cordova build --debug

build-release: npm run update-version && node scripts/releaseBuildAskForPassword.js

update-version: node scripts/updateVersion.js

type: git

author: Vrelnir


code:
# 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


code:
<?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>


code:
/* 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'
        }
    }
}


code:
/*
       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) }
}


code:
# 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


code:
/*
       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;
    }

}


code:
/*
       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;
    }

}


code:
/*
       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));
    }
}


code:
/*
       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;
    }
}


code:
/*
       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;
    }
}


code:
/*
       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;
        }
    }
}


code:
/*
       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:
 *
 *

 *     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);
 *       }
 *     }
 *

 *
 * 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.
     *

     * 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();
        }

    }

}


code:
/*
       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);
    }
}


code:
/*
       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;
    }
}


code:
/*
       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);
    }
}


code:
/*
       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);
    }
}


code:
/*
       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);
    }
}


code:
/*
       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);

}