diff --git a/keystore-generator-plugin/.gitignore b/keystore-generator-plugin/.gitignore deleted file mode 100644 index 4bde668c5d..0000000000 --- a/keystore-generator-plugin/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -# Plugin-specific gitignore for keystore-generator-plugin - -# Gradle -.gradle/ -build/ -.kotlin/ - -# Local configuration -local.properties - -# IDE files -.idea/ -*.iml - -# OS files -.DS_Store -Thumbs.db - -# Log files -*.log - -# Temporary files -*.tmp -*.temp - -# Generated keystore files (for testing) -*.jks -*.keystore - -# Build outputs -*.apk -*.aar -*.dex \ No newline at end of file diff --git a/keystore-generator-plugin/build.gradle.kts b/keystore-generator-plugin/build.gradle.kts deleted file mode 100644 index e116c4c0d2..0000000000 --- a/keystore-generator-plugin/build.gradle.kts +++ /dev/null @@ -1,71 +0,0 @@ -plugins { - id("com.android.application") version "8.8.2" - id("org.jetbrains.kotlin.android") version "2.1.21" - id("com.itsaky.androidide.plugins.build") -} - -pluginBuilder { - pluginName = "keystore-generator" -} - -android { - namespace = "com.appdevforall.keygen.plugin" - compileSdk = 34 - - defaultConfig { - applicationId = "com.appdevforall.keygen.plugin" - minSdk = 26 - targetSdk = 34 - versionCode = 1 - versionName = "1.0.0" - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } - - - - packaging { - resources { - excludes += setOf( - "META-INF/versions/9/OSGI-INF/MANIFEST.MF", - "META-INF/DEPENDENCIES", - "META-INF/LICENSE", - "META-INF/LICENSE.txt", - "META-INF/NOTICE", - "META-INF/NOTICE.txt" - ) - } - } -} - -dependencies { - compileOnly(project(":plugin-api")) - - implementation("androidx.appcompat:appcompat:1.6.1") - implementation("com.google.android.material:material:1.10.0") - implementation("androidx.fragment:fragment-ktx:1.8.8") - implementation("org.jetbrains.kotlin:kotlin-stdlib:2.1.21") - implementation("androidx.fragment:fragment:1.8.8") - - // BouncyCastle for keystore generation - implementation("org.bouncycastle:bcprov-jdk18on:1.78") - implementation("org.bouncycastle:bcpkix-jdk18on:1.78") -} - -tasks.wrapper { - gradleVersion = "8.10.2" - distributionType = Wrapper.DistributionType.BIN -} \ No newline at end of file diff --git a/keystore-generator-plugin/gradle.properties b/keystore-generator-plugin/gradle.properties deleted file mode 100755 index a91ac582bb..0000000000 --- a/keystore-generator-plugin/gradle.properties +++ /dev/null @@ -1,14 +0,0 @@ -# Android properties -android.useAndroidX=true -android.enableJetifier=true - -# Enable R8 full mode optimizations -android.enableR8.fullMode=false - -# Kotlin -kotlin.code.style=official - -# Memory settings for builds -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -org.gradle.parallel=true -org.gradle.caching=true \ No newline at end of file diff --git a/keystore-generator-plugin/gradle/wrapper/gradle-wrapper.jar b/keystore-generator-plugin/gradle/wrapper/gradle-wrapper.jar deleted file mode 100755 index 8bdaf60c75..0000000000 Binary files a/keystore-generator-plugin/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/keystore-generator-plugin/gradle/wrapper/gradle-wrapper.properties b/keystore-generator-plugin/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index df97d72b8b..0000000000 --- a/keystore-generator-plugin/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/keystore-generator-plugin/gradlew b/keystore-generator-plugin/gradlew deleted file mode 100755 index ef07e0162b..0000000000 --- a/keystore-generator-plugin/gradlew +++ /dev/null @@ -1,251 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://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. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH="\\\"\\\"" - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/keystore-generator-plugin/gradlew.bat b/keystore-generator-plugin/gradlew.bat deleted file mode 100644 index 5eed7ee845..0000000000 --- a/keystore-generator-plugin/gradlew.bat +++ /dev/null @@ -1,94 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH= - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/keystore-generator-plugin/proguard-rules.pro b/keystore-generator-plugin/proguard-rules.pro deleted file mode 100644 index cff2ab6576..0000000000 --- a/keystore-generator-plugin/proguard-rules.pro +++ /dev/null @@ -1,29 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile - -# Keep plugin classes --keep class com.appdevforall.keygen.plugin.** { *; } - -# Keep BouncyCastle classes --keep class org.bouncycastle.** { *; } --dontwarn org.bouncycastle.** - -# Keep AndroidIDE plugin API classes --keep class com.itsaky.androidide.plugins.** { *; } --dontwarn com.itsaky.androidide.plugins.** \ No newline at end of file diff --git a/keystore-generator-plugin/settings.gradle.kts b/keystore-generator-plugin/settings.gradle.kts deleted file mode 100644 index df2526abc0..0000000000 --- a/keystore-generator-plugin/settings.gradle.kts +++ /dev/null @@ -1,22 +0,0 @@ -rootProject.name = "keystore-generator-plugin" - -pluginManagement { - includeBuild("../plugin-api/plugin-builder") - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) - repositories { - mavenCentral() - google() - gradlePluginPortal() - } -} - -include(":plugin-api") -project(":plugin-api").projectDir = file("../plugin-api") \ No newline at end of file diff --git a/keystore-generator-plugin/src/main/AndroidManifest.xml b/keystore-generator-plugin/src/main/AndroidManifest.xml deleted file mode 100644 index 06af739c38..0000000000 --- a/keystore-generator-plugin/src/main/AndroidManifest.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/keystore-generator-plugin/src/main/kotlin/com/appdevforall/keygen/plugin/KeystoreGenerator.kt b/keystore-generator-plugin/src/main/kotlin/com/appdevforall/keygen/plugin/KeystoreGenerator.kt deleted file mode 100644 index ac66abdb74..0000000000 --- a/keystore-generator-plugin/src/main/kotlin/com/appdevforall/keygen/plugin/KeystoreGenerator.kt +++ /dev/null @@ -1,225 +0,0 @@ -package com.appdevforall.keygen.plugin - -import org.bouncycastle.asn1.x500.X500Name -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter -import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder -import org.bouncycastle.jce.provider.BouncyCastleProvider -import org.bouncycastle.operator.ContentSigner -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder -import java.io.File -import java.io.FileOutputStream -import java.math.BigInteger -import java.security.KeyPairGenerator -import java.security.KeyStore -import java.security.Security -import java.util.Date - -/** - * Data class representing keystore configuration - */ -data class KeystoreConfig( - val keystoreName: String, - val keystorePassword: CharArray, - val keyAlias: String, - val keyPassword: CharArray, - val certificateName: String, - val organizationalUnit: String?, - val organization: String?, - val city: String?, - val state: String?, - val country: String -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as KeystoreConfig - - if (keystoreName != other.keystoreName) return false - if (!keystorePassword.contentEquals(other.keystorePassword)) return false - if (keyAlias != other.keyAlias) return false - if (!keyPassword.contentEquals(other.keyPassword)) return false - if (certificateName != other.certificateName) return false - if (organizationalUnit != other.organizationalUnit) return false - if (organization != other.organization) return false - if (city != other.city) return false - if (state != other.state) return false - if (country != other.country) return false - - return true - } - - override fun hashCode(): Int { - var result = keystoreName.hashCode() - result = 31 * result + keystorePassword.contentHashCode() - result = 31 * result + keyAlias.hashCode() - result = 31 * result + keyPassword.contentHashCode() - result = 31 * result + certificateName.hashCode() - result = 31 * result + (organizationalUnit?.hashCode() ?: 0) - result = 31 * result + (organization?.hashCode() ?: 0) - result = 31 * result + (city?.hashCode() ?: 0) - result = 31 * result + (state?.hashCode() ?: 0) - result = 31 * result + country.hashCode() - return result - } -} - -/** - * Result of keystore generation operation - */ -sealed class KeystoreGenerationResult { - data class Success(val keystoreFile: File) : KeystoreGenerationResult() - data class Error(val message: String, val exception: Throwable? = null) : KeystoreGenerationResult() -} - -/** - * Utility class for generating Android keystores using BouncyCastle - */ -object KeystoreGenerator { - - init { - // Remove the OS provided bouncy castle provider if it exists - try { - Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME) - } catch (e: Exception) { - // Provider might not be present, ignore - } - // Add the bouncy castle provider from the added library - Security.addProvider(BouncyCastleProvider()) - } - - /** - * Generates a new Android keystore with the specified configuration - * - * @param config The keystore configuration - * @param outputDirectory The directory where to save the keystore file - * @return Result of the generation operation - */ - fun generateKeystore(config: KeystoreConfig, outputDirectory: File): KeystoreGenerationResult { - return try { - // Ensure output directory exists - if (!outputDirectory.exists()) { - outputDirectory.mkdirs() - } - - // 1. Generate a key pair - val keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC") - keyPairGenerator.initialize(2048) - val keyPair = keyPairGenerator.genKeyPair() - - // 2. Create the self-signed certificate using Bouncy Castle - val issuerName = buildDistinguishedName(config) - val certificateBuilder = JcaX509v3CertificateBuilder( - issuerName, - BigInteger.valueOf(Date().time), - Date(System.currentTimeMillis()), - Date(System.currentTimeMillis() + 365L * 24 * 60 * 60 * 1000 * 25), // 25 years validity - issuerName, - keyPair.public - ) - - val signer: ContentSigner = JcaContentSignerBuilder("SHA256withRSA") - .setProvider("BC") - .build(keyPair.private) - - val certificate = certificateBuilder.build(signer) - - // 3. Create a keystore instance and store the key entry - val keyStore = KeyStore.getInstance("BKS") - keyStore.load(null, null) - - val javaCertificate = JcaX509CertificateConverter() - .setProvider("BC") - .getCertificate(certificate) - - keyStore.setKeyEntry( - config.keyAlias, - keyPair.private, - config.keyPassword, - arrayOf(javaCertificate) - ) - - // 4. Save the keystore to file - val keystoreFile = File(outputDirectory, config.keystoreName) - FileOutputStream(keystoreFile).use { fos -> - keyStore.store(fos, config.keystorePassword) - } - - KeystoreGenerationResult.Success(keystoreFile) - - } catch (e: Exception) { - KeystoreGenerationResult.Error("Failed to generate keystore: ${e.message}", e) - } - } - - /** - * Builds the X.500 distinguished name from the certificate configuration - */ - private fun buildDistinguishedName(config: KeystoreConfig): X500Name { - val components = mutableListOf() - - components.add("CN=${config.certificateName}") - - config.organizationalUnit?.takeIf { it.isNotBlank() }?.let { - components.add("OU=$it") - } - - config.organization?.takeIf { it.isNotBlank() }?.let { - components.add("O=$it") - } - - config.city?.takeIf { it.isNotBlank() }?.let { - components.add("L=$it") - } - - config.state?.takeIf { it.isNotBlank() }?.let { - components.add("ST=$it") - } - - components.add("C=${config.country}") - - return X500Name(components.joinToString(", ")) - } - - /** - * Validates the keystore configuration - * - * @param config The configuration to validate - * @return List of validation errors, empty if valid - */ - fun validateConfig(config: KeystoreConfig): List { - val errors = mutableListOf() - - if (config.keystoreName.isBlank()) { - errors.add("Keystore name cannot be empty") - } else if (!config.keystoreName.endsWith(".jks") && !config.keystoreName.endsWith(".keystore")) { - errors.add("Keystore name should end with .jks or .keystore") - } - - if (config.keystorePassword.isEmpty()) { - errors.add("Keystore password cannot be empty") - } else if (config.keystorePassword.size < 6) { - errors.add("Keystore password should be at least 6 characters") - } - - if (config.keyAlias.isBlank()) { - errors.add("Key alias cannot be empty") - } - - if (config.keyPassword.isEmpty()) { - errors.add("Key password cannot be empty") - } else if (config.keyPassword.size < 6) { - errors.add("Key password should be at least 6 characters") - } - - if (config.certificateName.isBlank()) { - errors.add("Certificate name cannot be empty") - } - - if (config.country.length != 2) { - errors.add("Country code must be exactly 2 characters") - } - - return errors - } -} \ No newline at end of file diff --git a/keystore-generator-plugin/src/main/kotlin/com/appdevforall/keygen/plugin/KeystoreGeneratorPlugin.kt b/keystore-generator-plugin/src/main/kotlin/com/appdevforall/keygen/plugin/KeystoreGeneratorPlugin.kt deleted file mode 100644 index d46a60bc21..0000000000 --- a/keystore-generator-plugin/src/main/kotlin/com/appdevforall/keygen/plugin/KeystoreGeneratorPlugin.kt +++ /dev/null @@ -1,328 +0,0 @@ -package com.appdevforall.keygen.plugin - -import android.app.AlertDialog -import android.view.LayoutInflater -import android.util.Log -import com.appdevforall.keygen.plugin.R -import com.itsaky.androidide.plugins.IPlugin -import com.itsaky.androidide.plugins.PluginContext -import com.itsaky.androidide.plugins.base.PluginFragmentHelper -import com.itsaky.androidide.plugins.extensions.NavigationItem -import com.itsaky.androidide.plugins.extensions.UIExtension -import com.itsaky.androidide.plugins.extensions.EditorTabExtension -import com.itsaky.androidide.plugins.extensions.EditorTabItem -import com.itsaky.androidide.plugins.extensions.TabItem -import com.itsaky.androidide.plugins.extensions.MenuItem -import com.itsaky.androidide.plugins.extensions.DocumentationExtension -import com.itsaky.androidide.plugins.extensions.PluginTooltipEntry -import com.itsaky.androidide.plugins.extensions.PluginTooltipButton -import com.itsaky.androidide.plugins.services.IdeProjectService -import com.itsaky.androidide.plugins.services.IdeUIService -import com.itsaky.androidide.plugins.services.IdeEditorTabService -import com.itsaky.androidide.plugins.services.IdeFileService -import com.itsaky.androidide.plugins.services.IdeBuildService -import com.itsaky.androidide.plugins.services.BuildStatusListener -import com.appdevforall.keygen.plugin.fragments.KeystoreGeneratorFragment -import androidx.fragment.app.Fragment -import android.widget.EditText -import java.io.File - -/** - * Code On the Go plugin for generating Android keystores for release builds - */ -class KeystoreGeneratorPlugin : IPlugin, UIExtension, EditorTabExtension, DocumentationExtension, BuildStatusListener { - - private lateinit var context: PluginContext - - override fun initialize(context: PluginContext): Boolean { - return try { - this.context = context - context.logger.info("KeystoreGeneratorPlugin initialized successfully") - true - } catch (e: Exception) { - context.logger.error("KeystoreGeneratorPlugin initialization failed", e) - false - } - } - - override fun activate(): Boolean { - context.logger.info("KeystoreGeneratorPlugin: Activating plugin") - - // Register for build status updates - val buildService = context.services.get(IdeBuildService::class.java) - buildService?.addBuildStatusListener(this) - - return true - } - - override fun deactivate(): Boolean { - context.logger.info("KeystoreGeneratorPlugin: Deactivating plugin") - return true - } - - override fun dispose() { - context.logger.info("KeystoreGeneratorPlugin: Disposing plugin") - } - - - // EditorTabExtension interface methods - for main editor tabs - override fun getMainEditorTabs(): List { - context.logger.debug("getMainEditorTabs() called - returning keystore generator main tab") - - val tabItem = EditorTabItem( - id = "keystore_generator_main", - title = "Keystore Generator", - icon = android.R.drawable.ic_lock_lock, - fragmentFactory = { - context.logger.debug("Creating KeystoreGeneratorFragment instance") - KeystoreGeneratorFragment() - }, - isCloseable = true, - isPersistent = false, - order = 0, - isEnabled = true, - isVisible = true, - tooltip = "Generate Android keystore for release builds" - ) - - context.logger.debug("Returning tab item: id=${tabItem.id}, title=${tabItem.title}, enabled=${tabItem.isEnabled}, visible=${tabItem.isVisible}") - - return listOf(tabItem) - } - - // UIExtension interface methods - for bottom sheet tabs - override fun getEditorTabs(): List { - return listOf( - TabItem( - id = "keystore_generator_bottom", - title = "Keystore Gen", - fragmentFactory = { KeystoreGeneratorFragment() }, - isEnabled = true, - isVisible = true, - order = 0 - ) - ) - } - - override fun getMainMenuItems(): List { - return emptyList() - } - - // Override sidebar action to open main editor tab instead of dialog - private fun openKeystoreGeneratorTab() { - context.logger.info("Opening keystore generator in main editor tab") - - val editorTabService = context.services.get(IdeEditorTabService::class.java) ?: run { - context.logger.error("Editor tab service not available") - return - } - - context.logger.debug("Editor tab service available: ${editorTabService.javaClass.name}") - - if (!editorTabService.isTabSystemAvailable()) { - context.logger.error("Editor tab system not available") - return - } - - val tabId = "keystore_generator_main" - context.logger.debug("Attempting to select plugin tab with ID: $tabId") - - // Check if this is actually a plugin tab - val isPluginTab = editorTabService.isPluginTab(tabId) - context.logger.debug("Is '$tabId' registered as plugin tab: $isPluginTab") - - // Get all available plugin tab IDs - val allPluginTabIds = editorTabService.getAllPluginTabIds() - context.logger.debug("All registered plugin tab IDs: $allPluginTabIds") - - try { - if (editorTabService.selectPluginTab(tabId)) { - context.logger.info("Successfully opened keystore generator tab") - } - } catch (e: Exception) { - context.logger.error("Error opening keystore generator tab", e) - } - } - - override fun getSideMenuItems(): List { - return listOf( - NavigationItem( - id = "generate_keystore", - title = "Generate Keystore", - icon = android.R.drawable.ic_lock_lock, - isEnabled = true, - isVisible = true, - group = "build", - order = 0, - action = { - openKeystoreGeneratorTab() - } - ) - ) - } - - // BuildStatusListener implementation - override fun onBuildStarted() { - context.logger.debug("Build started - disabling keystore generator actions") - } - - override fun onBuildFinished() { - context.logger.debug("Build finished successfully - enabling keystore generator actions") - } - - override fun onBuildFailed(error: String?) { - context.logger.debug("Build failed - keeping keystore generator actions disabled") - } - - // DocumentationExtension interface methods - override fun getTooltipCategory(): String = "plugin_keystore_generator" - - override fun getTooltipEntries(): List { - return listOf( - // Main feature documentation - PluginTooltipEntry( - tag = "keystore_generator.main_feature", - summary = "Keystore Generator
Generate Android keystores for release builds", - detail = """ -

Android Keystore Generator

-

This plugin allows you to generate Android keystores (.jks files) directly within your project for signing release builds.

- -

How to use:

-
    -
  1. Sidebar: Click "Generate Keystore" in the sidebar to open the main editor tab
  2. -
  3. Main Menu: Use Tools → Generate Keystore for a dialog interface
  4. -
  5. Bottom Sheet: Access via the "Keystore Gen" tab in the editor bottom sheet
  6. -
  7. Fill in the keystore details and certificate information
  8. -
  9. Click "Generate Keystore" to create the file in your project's app directory
  10. -
- -

Features:

-
    -
  • RSA 2048-bit key generation with SHA256 signature
  • -
  • X.509 certificate creation with customizable details
  • -
  • Automatic project integration (saves to app/ directory)
  • -
  • Form validation and error handling
  • -
  • Multiple UI integration points
  • -
- -

💡 Tip: Keep your keystore and passwords secure - you'll need them to update your app!

- """.trimIndent(), - buttons = emptyList() - ), - - // Plugin overview - PluginTooltipEntry( - tag = "keystore_generator.overview", - summary = "Keystore Generator Plugin
Complete plugin integration showcase", - detail = """ -

Plugin Integration Showcase

-

This plugin demonstrates comprehensive integration with the Code on the Go plugin system.

- -

Integration Points:

-
    -
  • Main Editor Tab - Full keystore generator interface in main tab bar
  • -
  • Bottom Sheet Tab - Same interface accessible in editor bottom sheet
  • -
  • Main Menu Integration - Dialog-based keystore generation
  • -
  • Sidebar Action - Quick access button that opens main editor tab
  • -
  • Documentation System - Integrated help and tooltips
  • -
- -

Technical Features:

-
    -
  • Common fragment reused across all integration points
  • -
  • Plugin resource loading with proper inflaters
  • -
  • Asynchronous keystore generation
  • -
  • IDE service integration for project access
  • -
  • Comprehensive error handling and validation
  • -
- -

This showcases how a single plugin can provide multiple ways to access its functionality.

- """.trimIndent(), - buttons = listOf( - PluginTooltipButton( - description = "Plugin Development Guide", - uri = "plugin/development/guide", - order = 0 - ) - ) - ), - - // Editor tab documentation - PluginTooltipEntry( - tag = "keystore_generator.editor_tab", - summary = "Keystore Generator Tab
Full-featured keystore generation interface", - detail = """ -

Editor Tab Interface

-

The main editor tab provides the complete keystore generation interface with real-time validation and progress tracking.

- -

Interface Features:

-
    -
  • Structured form with keystore and certificate sections
  • -
  • Real-time form validation with error display
  • -
  • Progress bar during keystore generation
  • -
  • Success/error status display
  • -
  • Clear button to reset the form
  • -
- -

Form Fields:

-
    -
  • Keystore Name - Filename for the .jks file
  • -
  • Keystore Password - Password to protect the keystore
  • -
  • Key Alias - Alias for the signing key
  • -
  • Key Password - Password for the signing key
  • -
  • Certificate Details - Distinguished name information
  • -
- -

Access from sidebar → Generate Keystore or bottom sheet → Keystore Gen tab.

- """.trimIndent() - ), - - // Keystore security documentation - PluginTooltipEntry( - tag = "keystore_generator.security", - summary = "Keystore Security
Best practices for keystore management", - detail = """ -

Keystore Security Best Practices

-

Your keystore is critical for app signing. Follow these security guidelines:

- -

Password Security:

-
    -
  • Use strong, unique passwords (12+ characters)
  • -
  • Include uppercase, lowercase, numbers, and symbols
  • -
  • Never reuse passwords from other accounts
  • -
  • Store passwords securely (password manager recommended)
  • -
- -

File Security:

-
    -
  • Keep multiple secure backups of your keystore file
  • -
  • Never commit keystores to version control
  • -
  • Store in secure, encrypted locations
  • -
  • Limit access to essential team members only
  • -
- -

Certificate Fields:

-
    -
  • CN (Common Name) - Your name or organization
  • -
  • OU (Organizational Unit) - Department (optional)
  • -
  • O (Organization) - Company name (optional)
  • -
  • L (Locality) - City (optional)
  • -
  • ST (State) - State/Province (optional)
  • -
  • C (Country) - Two-letter country code
  • -
- -

⚠️ Warning: Losing your keystore means you cannot update your published app!

- """.trimIndent() - ) - ) - } - - override fun onDocumentationInstall(): Boolean { - context.logger.info("Installing Keystore Generator Plugin documentation") - return true - } - - override fun onDocumentationUninstall() { - context.logger.info("Removing Keystore Generator Plugin documentation") - } -} diff --git a/keystore-generator-plugin/src/main/kotlin/com/appdevforall/keygen/plugin/fragments/KeystoreGeneratorFragment.kt b/keystore-generator-plugin/src/main/kotlin/com/appdevforall/keygen/plugin/fragments/KeystoreGeneratorFragment.kt deleted file mode 100644 index 3c49265912..0000000000 --- a/keystore-generator-plugin/src/main/kotlin/com/appdevforall/keygen/plugin/fragments/KeystoreGeneratorFragment.kt +++ /dev/null @@ -1,590 +0,0 @@ -package com.appdevforall.keygen.plugin.fragments - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.LinearLayout -import android.widget.TextView -import android.widget.Toast -import androidx.core.content.ContextCompat -import androidx.fragment.app.Fragment -import com.google.android.material.button.MaterialButton -import com.google.android.material.progressindicator.LinearProgressIndicator -import com.google.android.material.textfield.TextInputEditText -import com.google.android.material.textfield.TextInputLayout -import com.appdevforall.keygen.plugin.KeystoreConfig -import com.appdevforall.keygen.plugin.KeystoreGenerator -import com.appdevforall.keygen.plugin.KeystoreGenerationResult -import com.appdevforall.keygen.plugin.R -import com.itsaky.androidide.plugins.base.PluginFragmentHelper -import com.itsaky.androidide.plugins.services.IdeProjectService -import com.itsaky.androidide.plugins.services.IdeTooltipService -import com.itsaky.androidide.plugins.services.IdeFileService -import com.itsaky.androidide.plugins.services.IdeBuildService -import com.itsaky.androidide.plugins.services.BuildStatusListener -import android.util.Log -import androidx.lifecycle.lifecycleScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.File - -class KeystoreGeneratorFragment : Fragment(), BuildStatusListener { - - companion object { - private const val TAG = "KeystoreGenerator" - private const val PLUGIN_ID = "com.appdevforall.keygen.plugin" - } - - private var projectService: IdeProjectService? = null - private var tooltipService: IdeTooltipService? = null - private var fileService: IdeFileService? = null - private var buildService: IdeBuildService? = null - - private var isBuildRunning = false - private var lastBuildFailed = false - - private lateinit var statusContainer: LinearLayout - private lateinit var statusText: TextView - private lateinit var progressBar: LinearProgressIndicator - private lateinit var tilKeystoreName: TextInputLayout - private lateinit var tilKeystorePassword: TextInputLayout - private lateinit var tilKeyAlias: TextInputLayout - private lateinit var tilKeyPassword: TextInputLayout - private lateinit var tilCertificateName: TextInputLayout - private lateinit var tilCountry: TextInputLayout - private lateinit var keystoreNameInput: TextInputEditText - private lateinit var keystorePasswordInput: TextInputEditText - private lateinit var keyAliasInput: TextInputEditText - private lateinit var keyPasswordInput: TextInputEditText - private lateinit var certificateNameInput: TextInputEditText - private lateinit var organizationalUnitInput: TextInputEditText - private lateinit var organizationInput: TextInputEditText - private lateinit var cityInput: TextInputEditText - private lateinit var stateInput: TextInputEditText - private lateinit var countryInput: TextInputEditText - private lateinit var btnGenerate: MaterialButton - private lateinit var btnClear: MaterialButton - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - try { - val serviceRegistry = PluginFragmentHelper.getServiceRegistry(PLUGIN_ID) - projectService = serviceRegistry?.get(IdeProjectService::class.java) - tooltipService = serviceRegistry?.get(IdeTooltipService::class.java) - fileService = serviceRegistry?.get(IdeFileService::class.java) - buildService = serviceRegistry?.get(IdeBuildService::class.java) - } catch (e: Exception) { - Log.w(TAG, "Services not yet available", e) - } - } - - override fun onGetLayoutInflater(savedInstanceState: Bundle?): LayoutInflater { - val inflater = super.onGetLayoutInflater(savedInstanceState) - return PluginFragmentHelper.getPluginInflater(PLUGIN_ID, inflater) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_keystore_generator, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - initializeViews(view) - setupClickListeners() - updateButtonStates() - } - - override fun onResume() { - super.onResume() - buildService?.addBuildStatusListener(this) - } - - override fun onPause() { - super.onPause() - buildService?.removeBuildStatusListener(this) - } - - private fun initializeViews(view: View) { - statusContainer = view.findViewById(R.id.status_container) - statusText = view.findViewById(R.id.tv_status) - progressBar = view.findViewById(R.id.progress_bar) - - tilKeystoreName = view.findViewById(R.id.til_keystore_name) - tilKeystorePassword = view.findViewById(R.id.til_keystore_password) - tilKeyAlias = view.findViewById(R.id.til_key_alias) - tilKeyPassword = view.findViewById(R.id.til_key_password) - tilCertificateName = view.findViewById(R.id.til_certificate_name) - tilCountry = view.findViewById(R.id.til_country) - - keystoreNameInput = view.findViewById(R.id.et_keystore_name) - keystorePasswordInput = view.findViewById(R.id.et_keystore_password) - keyAliasInput = view.findViewById(R.id.et_key_alias) - keyPasswordInput = view.findViewById(R.id.et_key_password) - certificateNameInput = view.findViewById(R.id.et_certificate_name) - organizationalUnitInput = view.findViewById(R.id.et_organizational_unit) - organizationInput = view.findViewById(R.id.et_organization) - cityInput = view.findViewById(R.id.et_city) - stateInput = view.findViewById(R.id.et_state) - countryInput = view.findViewById(R.id.et_country) - - btnGenerate = view.findViewById(R.id.btn_generate) - btnClear = view.findViewById(R.id.btn_clear) - } - - private fun setupClickListeners() { - btnGenerate.setOnClickListener { - if (validateForm()) { - generateKeystore() - } - } - - btnClear.setOnClickListener { - clearForm() - } - - setupTooltipHandlers() - } - - private fun setupTooltipHandlers() { - btnGenerate.setOnLongClickListener { button -> - tooltipService?.showTooltip( - anchorView = button, - category = "plugin_keystore_generator", - tag = "keystore_generator.main_feature" - ) ?: run { - showToast(getString(R.string.tooltip_not_available)) - } - true - } - - statusContainer.setOnLongClickListener { view -> - tooltipService?.showTooltip( - anchorView = view, - category = "plugin_keystore_generator", - tag = "keystore_generator.editor_tab" - ) ?: run { - showToast(getString(R.string.docs_not_available)) - } - true - } - } - - private fun clearValidationErrors() { - tilKeystoreName.error = null - tilKeystorePassword.error = null - tilKeyAlias.error = null - tilKeyPassword.error = null - tilCertificateName.error = null - tilCountry.error = null - } - - private fun validateForm(): Boolean { - var valid = true - clearValidationErrors() - - if (keystoreNameInput.text.toString().trim().isEmpty()) { - tilKeystoreName.error = getString(R.string.error_required) - valid = false - } - - if (keystorePasswordInput.text.toString().isEmpty()) { - tilKeystorePassword.error = getString(R.string.error_required) - valid = false - } - - if (keyAliasInput.text.toString().trim().isEmpty()) { - tilKeyAlias.error = getString(R.string.error_required) - valid = false - } - - if (keyPasswordInput.text.toString().isEmpty()) { - tilKeyPassword.error = getString(R.string.error_required) - valid = false - } - - if (certificateNameInput.text.toString().trim().isEmpty()) { - tilCertificateName.error = getString(R.string.error_required) - valid = false - } - - val countryCode = countryInput.text.toString().trim() - if (countryCode.length != 2) { - tilCountry.error = getString(R.string.error_country_length) - valid = false - } - - return valid - } - - private fun generateKeystore() { - if (!isKeystoreGenerationEnabled()) { - showActionDisabledMessage() - return - } - val config = KeystoreConfig( - keystoreName = keystoreNameInput.text.toString().trim(), - keystorePassword = keystorePasswordInput.text.toString().toCharArray(), - keyAlias = keyAliasInput.text.toString().trim(), - keyPassword = keyPasswordInput.text.toString().toCharArray(), - certificateName = certificateNameInput.text.toString().trim(), - organizationalUnit = organizationalUnitInput.text.toString().trim().takeIf { it.isNotEmpty() }, - organization = organizationInput.text.toString().trim().takeIf { it.isNotEmpty() }, - city = cityInput.text.toString().trim().takeIf { it.isNotEmpty() }, - state = stateInput.text.toString().trim().takeIf { it.isNotEmpty() }, - country = countryInput.text.toString().trim() - ) - - showProgress(getString(R.string.generating_keystore)) - btnGenerate.isEnabled = false - - viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) { - try { - val result = performKeystoreGeneration(config) - - withContext(Dispatchers.Main) { - hideProgress() - btnGenerate.isEnabled = true - - when (result) { - is KeystoreGenerationResult.Success -> { - val currentProject = projectService?.getCurrentProject() - if (currentProject != null) { - addSigningConfigToBuildFile(currentProject.rootDir, config, result.keystoreFile) - } - showSuccess(getString(R.string.success_message) + "\n${result.keystoreFile.absolutePath}") - } - is KeystoreGenerationResult.Error -> { - showError(getString(R.string.error_generation_failed) + ": ${result.message}") - } - } - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - hideProgress() - btnGenerate.isEnabled = true - showError(getString(R.string.error_generation_failed) + (e.message?.let { ": $it" } ?: "")) - } - } - } - } - - private fun performKeystoreGeneration(config: KeystoreConfig): KeystoreGenerationResult { - val validationErrors = KeystoreGenerator.validateConfig(config) - if (validationErrors.isNotEmpty()) { - return KeystoreGenerationResult.Error(getString(R.string.error_validation_failed, validationErrors.joinToString(", "))) - } - - if (projectService == null) { - try { - val serviceRegistry = PluginFragmentHelper.getServiceRegistry(PLUGIN_ID) - projectService = serviceRegistry?.get(IdeProjectService::class.java) - fileService = serviceRegistry?.get(IdeFileService::class.java) - } catch (e: Exception) { - Log.e(TAG, "Failed to get project service", e) - return KeystoreGenerationResult.Error(getString(R.string.error_project_service_unavailable)) - } - } - - val currentProject = projectService?.getCurrentProject() - ?: return KeystoreGenerationResult.Error(getString(R.string.error_no_project)) - - return try { - val appDirectory = File(currentProject.rootDir, "app") - if (!appDirectory.exists()) { - appDirectory.mkdirs() - } - KeystoreGenerator.generateKeystore(config, appDirectory) - } catch (e: SecurityException) { - KeystoreGenerationResult.Error(getString(R.string.error_permission_denied, e.message ?: ""), e) - } catch (e: Exception) { - KeystoreGenerationResult.Error(getString(R.string.error_generation, e.message ?: ""), e) - } - } - - private fun clearForm() { - keystoreNameInput.setText("release.jks") - keystorePasswordInput.setText("") - keyAliasInput.setText("release") - keyPasswordInput.setText("") - certificateNameInput.setText("") - organizationalUnitInput.setText("") - organizationInput.setText("") - cityInput.setText("") - stateInput.setText("") - countryInput.setText("US") - - clearValidationErrors() - - hideStatus() - showToast(getString(R.string.form_cleared)) - } - - private fun showProgress(message: String) { - progressBar.visibility = View.VISIBLE - statusContainer.visibility = View.VISIBLE - statusText.text = message - val ctx = statusContainer.context - statusText.setTextColor(ContextCompat.getColor(ctx, R.color.status_text)) - statusContainer.setBackgroundColor(ContextCompat.getColor(ctx, R.color.status_background)) - } - - private fun hideProgress() { - progressBar.visibility = View.GONE - } - - private fun showSuccess(message: String) { - statusContainer.visibility = View.VISIBLE - statusText.text = message - val ctx = statusContainer.context - statusText.setTextColor(ContextCompat.getColor(ctx, R.color.status_success_text)) - statusContainer.setBackgroundColor(ContextCompat.getColor(ctx, R.color.status_success_background)) - } - - private fun showError(message: String) { - statusContainer.visibility = View.VISIBLE - statusText.text = message - val ctx = statusContainer.context - statusText.setTextColor(ContextCompat.getColor(ctx, R.color.status_error_text)) - statusContainer.setBackgroundColor(ContextCompat.getColor(ctx, R.color.status_error_background)) - } - - private fun hideStatus() { - statusContainer.visibility = View.GONE - } - - private fun showToast(message: String) { - activity?.runOnUiThread { - Toast.makeText(context, message, Toast.LENGTH_SHORT).show() - } - } - - private fun addSigningConfigToBuildFile(projectDir: File, config: KeystoreConfig, keystoreFile: File) { - if (fileService == null) { - try { - val serviceRegistry = PluginFragmentHelper.getServiceRegistry(PLUGIN_ID) - fileService = serviceRegistry?.get(IdeFileService::class.java) - } catch (e: Exception) { - Log.e(TAG, "Failed to get file service", e) - showToast(getString(R.string.error_file_service_unavailable)) - return - } - } - - val buildFiles = listOf( - File(projectDir, "app/build.gradle"), - File(projectDir, "app/build.gradle.kts") - ) - - val buildFile = buildFiles.find { it.exists() } ?: run { - showToast(getString(R.string.error_no_build_gradle)) - return - } - - try { - val isKotlinDsl = buildFile.name.endsWith(".kts") - val keystoreRelativePath = keystoreFile.name - val currentContent = fileService?.readFile(buildFile) ?: "" - - val hasReleaseConfig = if (isKotlinDsl) { - currentContent.contains("create(\"release\")") || - currentContent.contains("getByName(\"release\")") - } else { - currentContent.contains("release {") - } - - if (hasReleaseConfig) { - handleExistingReleaseConfig(buildFile, currentContent, config, keystoreRelativePath, isKotlinDsl) - } else { - handleNewReleaseConfig(buildFile, currentContent, config, keystoreRelativePath, isKotlinDsl) - } - - } catch (e: Exception) { - showToast(getString(R.string.error_build_file, e.message ?: "")) - } - } - - private fun handleExistingReleaseConfig( - buildFile: File, - currentContent: String, - config: KeystoreConfig, - keystoreRelativePath: String, - isKotlinDsl: Boolean - ) { - val updatedContent = updateExistingSigningConfig(currentContent, config, keystoreRelativePath, isKotlinDsl) - - when { - updatedContent == currentContent -> showToast(getString(R.string.error_signing_pattern_not_found)) - fileService?.writeFile(buildFile, updatedContent) == true -> showToast(getString(R.string.success_signing_updated)) - else -> showToast(getString(R.string.error_signing_update_failed)) - } - } - - private fun handleNewReleaseConfig( - buildFile: File, - currentContent: String, - config: KeystoreConfig, - keystoreRelativePath: String, - isKotlinDsl: Boolean - ) { - val signingConfig = when (isKotlinDsl) { - true -> generateKotlinSigningConfig(config, keystoreRelativePath) - false -> generateGroovySigningConfig(config, keystoreRelativePath) - } - - when (currentContent.contains("signingConfigs")) { - true -> insertIntoExistingSigningConfigs(buildFile, config, keystoreRelativePath, isKotlinDsl) - false -> insertNewSigningConfigsBlock(buildFile, signingConfig) - } - } - - private fun insertIntoExistingSigningConfigs( - buildFile: File, - config: KeystoreConfig, - keystoreRelativePath: String, - isKotlinDsl: Boolean - ) { - val releaseConfig = when (isKotlinDsl) { - true -> """ - create("release") { - storeFile = file("$keystoreRelativePath") - storePassword = "${String(config.keystorePassword)}" - keyAlias = "${config.keyAlias}" - keyPassword = "${String(config.keyPassword)}" - }""" - false -> """ - release { - storeFile file('$keystoreRelativePath') - storePassword '${String(config.keystorePassword)}' - keyAlias '${config.keyAlias}' - keyPassword '${String(config.keyPassword)}' - }""" - } - - val success = fileService?.insertAfterPattern(buildFile, "signingConfigs {", releaseConfig) == true - val message = if (success) getString(R.string.success_signing_added) else getString(R.string.error_signing_add_failed) - showToast(message) - } - - private fun insertNewSigningConfigsBlock(buildFile: File, signingConfig: String) { - val success = fileService?.insertAfterPattern(buildFile, "android {", signingConfig) == true - val message = if (success) getString(R.string.success_build_file_updated) else getString(R.string.error_no_android_block, buildFile.name) - showToast(message) - } - - private fun updateExistingSigningConfig( - content: String, - config: KeystoreConfig, - keystoreRelativePath: String, - isKotlinDsl: Boolean - ): String { - var result = content - - if (isKotlinDsl) { - val createPattern = """create\("release"\)\s*\{[^}]*\}""".toRegex(RegexOption.DOT_MATCHES_ALL) - if (createPattern.containsMatchIn(result)) { - val newConfig = """create("release") { - storeFile = file("$keystoreRelativePath") - storePassword = "${String(config.keystorePassword)}" - keyAlias = "${config.keyAlias}" - keyPassword = "${String(config.keyPassword)}" - }""" - result = result.replace(createPattern, newConfig) - } else { - val getByNamePattern = """getByName\("release"\)\s*\{[^}]*\}""".toRegex(RegexOption.DOT_MATCHES_ALL) - if (getByNamePattern.containsMatchIn(result)) { - val newConfig = """getByName("release") { - storeFile = file("$keystoreRelativePath") - storePassword = "${String(config.keystorePassword)}" - keyAlias = "${config.keyAlias}" - keyPassword = "${String(config.keyPassword)}" - }""" - result = result.replace(getByNamePattern, newConfig) - } - } - } else { - val groovyPattern = """release\s*\{[^}]*\}""".toRegex(RegexOption.DOT_MATCHES_ALL) - if (groovyPattern.containsMatchIn(result)) { - val newConfig = """release { - storeFile file('$keystoreRelativePath') - storePassword '${String(config.keystorePassword)}' - keyAlias '${config.keyAlias}' - keyPassword '${String(config.keyPassword)}' - }""" - result = result.replace(groovyPattern, newConfig) - } - } - - return result - } - - private fun generateKotlinSigningConfig(config: KeystoreConfig, keystoreRelativePath: String): String { - return """ - - signingConfigs { - create("release") { - storeFile = file("$keystoreRelativePath") - storePassword = "${String(config.keystorePassword)}" - keyAlias = "${config.keyAlias}" - keyPassword = "${String(config.keyPassword)}" - } - } -""" - } - - private fun generateGroovySigningConfig(config: KeystoreConfig, keystoreRelativePath: String): String { - return """ - - signingConfigs { - release { - storeFile file('$keystoreRelativePath') - storePassword '${String(config.keystorePassword)}' - keyAlias '${config.keyAlias}' - keyPassword '${String(config.keyPassword)}' - } - } -""" - } - - private fun isKeystoreGenerationEnabled(): Boolean { - return !isBuildRunning && !lastBuildFailed - } - - private fun showActionDisabledMessage() { - val message = when { - isBuildRunning -> getString(R.string.error_build_running) - lastBuildFailed -> getString(R.string.error_build_failed) - else -> getString(R.string.error_unavailable) - } - showToast(message) - } - - private fun updateButtonStates() { - val isEnabled = isKeystoreGenerationEnabled() - btnGenerate.isEnabled = isEnabled - btnGenerate.alpha = if (isEnabled) 1.0f else 0.6f - } - - override fun onBuildStarted() { - isBuildRunning = true - lastBuildFailed = false - activity?.runOnUiThread { updateButtonStates() } - } - - override fun onBuildFinished() { - isBuildRunning = false - lastBuildFailed = false - activity?.runOnUiThread { updateButtonStates() } - } - - override fun onBuildFailed(error: String?) { - isBuildRunning = false - lastBuildFailed = true - activity?.runOnUiThread { updateButtonStates() } - } -} diff --git a/keystore-generator-plugin/src/main/res/layout/dialog_keystore_generation.xml b/keystore-generator-plugin/src/main/res/layout/dialog_keystore_generation.xml deleted file mode 100644 index 5ba3bec5e6..0000000000 --- a/keystore-generator-plugin/src/main/res/layout/dialog_keystore_generation.xml +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/keystore-generator-plugin/src/main/res/layout/dialog_keystore_generation_simple.xml b/keystore-generator-plugin/src/main/res/layout/dialog_keystore_generation_simple.xml deleted file mode 100644 index 1c6c0336b6..0000000000 --- a/keystore-generator-plugin/src/main/res/layout/dialog_keystore_generation_simple.xml +++ /dev/null @@ -1,196 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/keystore-generator-plugin/src/main/res/layout/fragment_keystore_generator.xml b/keystore-generator-plugin/src/main/res/layout/fragment_keystore_generator.xml deleted file mode 100644 index a32cc571c0..0000000000 --- a/keystore-generator-plugin/src/main/res/layout/fragment_keystore_generator.xml +++ /dev/null @@ -1,273 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/keystore-generator-plugin/src/main/res/values-night/colors.xml b/keystore-generator-plugin/src/main/res/values-night/colors.xml deleted file mode 100644 index e3e590bc65..0000000000 --- a/keystore-generator-plugin/src/main/res/values-night/colors.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - #88D6B8 - #00382A - #00513D - #A4F2D3 - #B3CCBF - #1F352C - #191C1A - #E1E3DF - #BFC9C1 - #89938C - #404943 - - #E1E3DF - #2E3230 - #88D6B8 - #0D2E22 - #FFB4AB - #690005 - diff --git a/keystore-generator-plugin/src/main/res/values/colors.xml b/keystore-generator-plugin/src/main/res/values/colors.xml deleted file mode 100644 index b520b25246..0000000000 --- a/keystore-generator-plugin/src/main/res/values/colors.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - #1A6B52 - #FFFFFF - #A4F2D3 - #002117 - #4C6359 - #FFFFFF - #FBFDF9 - #191C1A - #404943 - #707973 - #BFC9C1 - - #191C1A - #E1E7E2 - #1A6B52 - #D4F5E6 - #BA1A1A - #FFDAD6 - diff --git a/keystore-generator-plugin/src/main/res/values/strings.xml b/keystore-generator-plugin/src/main/res/values/strings.xml deleted file mode 100644 index 6aab2e4bd9..0000000000 --- a/keystore-generator-plugin/src/main/res/values/strings.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - Keystore Generator Plugin - Keystore Generator - Generate Keystore - Keystore Name - e.g., release.jks - Keystore Password - Key Alias - e.g., release - Key Password - Full Name - e.g., John Doe - Organizational Unit - e.g., Development - Organization - e.g., My Company - City - e.g., San Francisco - State/Province - e.g., California - Country Code - e.g., US - Create - Cancel - Generate Release Keystore - Keystore Details - Certificate Details - Keystore created successfully! - Please fill in all required fields - Failed to generate keystore - No project is currently open - - Required - Must be exactly 2 characters - Generating keystore… - Form cleared - Long press detected! Tooltip service not available. - Long press detected! Documentation not available. - Validation failed: %s - IDE project service not available - Permission denied: %s - Generation error: %s - Error modifying build file: %s - IdeFileService not available - No build.gradle or build.gradle.kts found in app directory - Could not update signing config - pattern not found - Updated existing release signing config - Failed to update signing config - Added release signing config - Failed to add release config to signingConfigs block - Build file updated with signing config - Could not find android block in %s - Cannot generate keystore while project is building or syncing - Cannot generate keystore - please fix build errors first - Keystore generation is currently unavailable - diff --git a/keystore-generator-plugin/src/main/res/values/styles.xml b/keystore-generator-plugin/src/main/res/values/styles.xml deleted file mode 100644 index f38efd3427..0000000000 --- a/keystore-generator-plugin/src/main/res/values/styles.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - -