├── .gitignore ├── AndroidCupsPrint.iml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro ├── src │ ├── androidTest │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── benoitduffez │ │ │ └── cupsprint │ │ │ └── ExampleInstrumentedTest.kt │ ├── fdroid │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── benoitduffez │ │ │ └── cupsprint │ │ │ └── CupsPrintApp.kt │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ ├── ch │ │ │ │ └── ethz │ │ │ │ │ └── vppserver │ │ │ │ │ ├── ippclient │ │ │ │ │ ├── EmumItem.kt │ │ │ │ │ ├── EnumItemMap.kt │ │ │ │ │ ├── EnumMap.kt │ │ │ │ │ ├── IppLists.kt │ │ │ │ │ ├── IppResponse.kt │ │ │ │ │ ├── IppResult.kt │ │ │ │ │ ├── IppTag.kt │ │ │ │ │ └── IppUtil.kt │ │ │ │ │ └── schema │ │ │ │ │ └── ippclient │ │ │ │ │ ├── Attribute.kt │ │ │ │ │ ├── AttributeGroup.kt │ │ │ │ │ ├── AttributeValue.kt │ │ │ │ │ ├── Enum.kt │ │ │ │ │ ├── Keyword.kt │ │ │ │ │ ├── SetOfEnum.kt │ │ │ │ │ ├── SetOfKeyword.kt │ │ │ │ │ └── Tag.kt │ │ │ ├── io │ │ │ │ └── github │ │ │ │ │ └── benoitduffez │ │ │ │ │ └── cupsprint │ │ │ │ │ ├── AppExecutors.kt │ │ │ │ │ ├── HttpConnectionManagement.kt │ │ │ │ │ ├── app │ │ │ │ │ ├── AddPrintersActivity.kt │ │ │ │ │ ├── BasicAuthActivity.kt │ │ │ │ │ ├── HostNotVerifiedActivity.kt │ │ │ │ │ ├── ManageManualPrintersActivity.kt │ │ │ │ │ └── UntrustedCertActivity.kt │ │ │ │ │ ├── detect │ │ │ │ │ ├── MdnsServices.kt │ │ │ │ │ ├── Merger.kt │ │ │ │ │ ├── PrinterRec.kt │ │ │ │ │ └── PrinterResult.kt │ │ │ │ │ ├── printservice │ │ │ │ │ ├── CupsPrinterDiscoverySession.kt │ │ │ │ │ ├── CupsPrinterDiscoveryUtils.kt │ │ │ │ │ └── CupsService.kt │ │ │ │ │ └── ssl │ │ │ │ │ ├── AdditionalKeyManager.kt │ │ │ │ │ ├── AdditionalKeyStoresSSLSocketFactory.kt │ │ │ │ │ ├── AdditionalKeyStoresTrustManager.kt │ │ │ │ │ └── AndroidCupsHostnameVerifier.kt │ │ │ └── org │ │ │ │ └── cups4j │ │ │ │ ├── CupsClient.kt │ │ │ │ ├── CupsPrinter.kt │ │ │ │ ├── JobStateEnum.kt │ │ │ │ ├── PrintJob.kt │ │ │ │ ├── PrintJobAttributes.kt │ │ │ │ ├── PrintRequestResult.kt │ │ │ │ ├── WhichJobsEnum.kt │ │ │ │ └── operations │ │ │ │ ├── IppOperation.kt │ │ │ │ ├── cups │ │ │ │ ├── CupsGetDefaultOperation.kt │ │ │ │ ├── CupsGetPPDOperation.kt │ │ │ │ ├── CupsGetPrintersOperation.kt │ │ │ │ └── CupsMoveJobOperation.kt │ │ │ │ └── ipp │ │ │ │ ├── IppCancelJobOperation.kt │ │ │ │ ├── IppGetJobAttributesOperation.kt │ │ │ │ ├── IppGetJobsOperation.kt │ │ │ │ ├── IppGetPrinterAttributesOperation.kt │ │ │ │ ├── IppHoldJobOperation.kt │ │ │ │ ├── IppPrintJobOperation.kt │ │ │ │ └── IppReleaseJobOperation.kt │ │ ├── play │ │ │ ├── contactEmail │ │ │ ├── contactPhone │ │ │ ├── contactWebsite │ │ │ ├── defaultLanguage │ │ │ ├── en-US │ │ │ │ ├── listing │ │ │ │ │ ├── fulldescription │ │ │ │ │ ├── shortdescription │ │ │ │ │ ├── title │ │ │ │ │ └── video │ │ │ │ └── whatsnew │ │ │ └── fr-FR │ │ │ │ ├── listing │ │ │ │ ├── fulldescription │ │ │ │ ├── shortdescription │ │ │ │ ├── title │ │ │ │ └── video │ │ │ │ └── whatsnew │ │ └── res │ │ │ ├── drawable-hdpi │ │ │ └── ic_no_printers.png │ │ │ ├── drawable-mdpi │ │ │ └── ic_no_printers.png │ │ │ ├── drawable-xhdpi │ │ │ └── ic_no_printers.png │ │ │ ├── drawable-xxhdpi │ │ │ └── ic_no_printers.png │ │ │ ├── drawable-xxxhdpi │ │ │ └── ic_no_printers.png │ │ │ ├── drawable │ │ │ └── ic_launcher_background.xml │ │ │ ├── layout │ │ │ ├── activity_manage_manual_printers.xml │ │ │ ├── add_printers.xml │ │ │ ├── basic_auth.xml │ │ │ ├── host_not_verified.xml │ │ │ ├── manage_printers_list_item.xml │ │ │ └── untrusted_cert.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── values-de │ │ │ └── strings.xml │ │ │ ├── values-es │ │ │ └── strings.xml │ │ │ ├── values-fr │ │ │ └── strings.xml │ │ │ ├── values-ja │ │ │ └── strings.xml │ │ │ ├── values-ru │ │ │ └── strings.xml │ │ │ ├── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ │ └── xml │ │ │ └── printservice.xml │ ├── playstore │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── benoitduffez │ │ │ └── cupsprint │ │ │ └── CupsPrintApp.kt │ └── test │ │ └── java │ │ └── io │ │ └── github │ │ └── benoitduffez │ │ └── cupsprint │ │ └── ExampleTests.kt └── versioning.gradle ├── beta.sh ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | /captures 8 | .idea 9 | key.properties 10 | signing.* 11 | GooglePlayAPI.json 12 | .idea 13 | -------------------------------------------------------------------------------- /AndroidCupsPrint.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidCupsPrint 2 | 3 | Port of cups4j to Android. 4 | 5 | See it live on the Play Store: https://play.google.com/store/apps/details?id=io.github.benoitduffez.cupsprint 6 | See it live on f-droid.org: https://f-droid.org/repository/browse/?fdid=io.github.benoitduffez.cupsprint 7 | 8 | ## Original work 9 | 10 | Original work was created by Jon Freeman, it included an app that reacts to the SEND intent to print documents. 11 | 12 | Original work can be found here: http://mobd.jonbanjo.com/jfcupsprint/default.php 13 | Original work found via: http://android.stackexchange.com/q/43774/63883 14 | 15 | ## Modifications 16 | 17 | This app was modified in several ways: 18 | 19 | * project structure converted to gradle format 20 | * added support for Android PrintService so that it can print documents straight from almost all apps 21 | * removed all legacy code that allowed printing without the use of Android PrintService (this is removed because of `minSdkVersion=19`, meaning all targets of this app are `PrintService`-compliant) 22 | * fixed SSL code to properly handle self-signed certificates (as it is likely the case with home printers) 23 | * removed jars and added source code to be compatible with an f-droid.org publication 24 | 25 | ### Print Service 26 | 27 | Print service works at the framework level: 28 | 29 | Android framework Print Services 30 | 31 | See the [Wiki](https://github.com/BenoitDuffez/AndroidCupsPrint/wiki) for more information about how to use it. 32 | 33 | As per the code, the following has been added: 34 | 35 | * a service in the AndroidManifest.xml file that registers the app as a PrintService 36 | * `CupsPrinterDiscoverySession.java`: handles printer discovery and printer management 37 | * `CupsService.java`: handles Android framework connectivity and print jobs management 38 | * removed all legacy code (pre API 19) 39 | 40 | # Contribute 41 | 42 | This app wasn't widely tested, it needs your help for better quality. If you find bugs, either submit a new issue or fork/fix/submit PR. 43 | 44 | Please use the `develop` branch for testing and troubleshooting. 45 | 46 | Also, you can subscribe on the Play Store to receive beta versions of this app: https://play.google.com/apps/testing/io.github.benoitduffez.cupsprint 47 | 48 | ## Branches 49 | 50 | * The `master` branch is code published to Google Play. 51 | * The `jonbanjo` branch is the app containing all the legacy code written by Jon Freeman 52 | * The `fdroid` branch was created in an effort to be compatible with f-droid.org; however, this effort was merged into develop and is intended to be merged into master. 53 | 54 | # License 55 | 56 | LGPL 57 | 58 | ``` 59 | This library is free software; you can redistribute it and/or 60 | modify it under the terms of the GNU Lesser General Public 61 | License as published by the Free Software Foundation; either 62 | version 2.1 of the License, or (at your option) any later version. 63 | This library is distributed in the hope that it will be useful, 64 | but WITHOUT ANY WARRANTY; without even the implied warranty of 65 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 66 | Lesser General Public License for more details. 67 | You should have received a copy of the GNU Lesser General Public 68 | License along with this library; if not, write to the Free Software 69 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 70 | MA 02110-1301 USA 71 | ``` 72 | 73 | Original license information by Jon Freeman: 74 | 75 | ``` 76 | Redistribution and use of AndroidCupsPrint in source and binary forms, 77 | with or without modification, is permitted provided this notice 78 | is retained in source code redistributions and that recipients 79 | agree that AndroidCupsPrint is provided "as is", without warranty of 80 | any kind, express or " implied, including but not limited to the 81 | warranties of merchantability, fitness for a particular purpose, 82 | title and non-infringement. In no event shall the copyright holders 83 | or anyone distributing the software be liable for any damages or 84 | other liability, whether in contract, tort or otherwise, arising 85 | from, out of or in connection with the software or the use or 86 | other dealings in the software. 87 | ``` 88 | 89 | ## External libraries 90 | 91 | * A modified version of cups4j 0.63. The original source code and further details about cups4j may be found at http://www.cups4j.org/ (licensed under the LGPL license) 92 | * JmDNS This is licensed under the Apache License 93 | 94 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | versions = [ 3 | koin: "1.0.0" 4 | ] 5 | } 6 | 7 | def keystoreProperties = new Properties() 8 | try { 9 | def keystorePropertiesFile = rootProject.file("signing.properties") 10 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 11 | } catch (FileNotFoundException ignored) { 12 | println 'signing.properties file was not found, no release builds will be available' 13 | // Put dummy values so that the debug builds work 14 | keystoreProperties['storeFile'] = rootProject.file('build.gradle') // bogus file, I know 15 | keystoreProperties['keyAlias'] = '' 16 | keystoreProperties['storePassword'] = '' 17 | } 18 | 19 | apply from: 'versioning.gradle' 20 | apply plugin: 'com.android.application' 21 | apply plugin: 'kotlin-android' 22 | apply plugin: 'com.github.triplet.play' 23 | 24 | repositories { 25 | maven { url 'https://maven.fabric.io/public' } 26 | mavenCentral() 27 | } 28 | 29 | android { 30 | compileSdkVersion 30 31 | packagingOptions { 32 | exclude 'META-INF/LICENSE.txt' 33 | exclude 'META-INF/NOTICE.txt' 34 | } 35 | defaultConfig { 36 | applicationId "io.github.benoitduffez.cupsprint" 37 | minSdkVersion 19 38 | targetSdkVersion 30 39 | versionCode buildVersionCode() 40 | versionName version 41 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 42 | dimension "type" 43 | } 44 | signingConfigs { 45 | release { 46 | keyAlias keystoreProperties['keyAlias'] 47 | keyPassword keystoreProperties['keyPassword'] 48 | storeFile rootProject.file(keystoreProperties['storeFile']) 49 | storePassword keystoreProperties['storePassword'] 50 | } 51 | } 52 | buildTypes { 53 | release { 54 | minifyEnabled false 55 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 56 | signingConfig signingConfigs.release 57 | } 58 | debug { 59 | testCoverageEnabled true 60 | } 61 | } 62 | flavorDimensions "type" 63 | productFlavors { 64 | fdroid { 65 | dimension "type" 66 | ext.useFabric = false 67 | } 68 | playstore { 69 | dimension "type" 70 | ext.useFabric = true 71 | } 72 | } 73 | } 74 | 75 | dependencies { 76 | implementation 'javax.jmdns:jmdns:3.4.1' 77 | implementation 'androidx.appcompat:appcompat:1.0.0' 78 | implementation 'com.google.firebase:firebase-crashlytics:17.2.1' 79 | implementation 'com.google.firebase:firebase-analytics:17.5.0' 80 | androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0-alpha4', { 81 | exclude group: 'com.android.support', module: 'support-annotations' 82 | }) 83 | testImplementation 'junit:junit:4.12' 84 | androidTestImplementation 'junit:junit:4.12' 85 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 86 | implementation 'com.jakewharton.timber:timber:4.7.1' 87 | // Koin DI 88 | implementation "org.koin:koin-core:${versions.koin}" 89 | implementation "org.koin:koin-core-ext:${versions.koin}" 90 | implementation "org.koin:koin-android:${versions.koin}" 91 | apply plugin: 'kotlin-android-extensions' 92 | } 93 | 94 | apply plugin: 'jacoco' 95 | task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'createDebugCoverageReport']) { 96 | reports { 97 | xml.enabled = true 98 | html.enabled = true 99 | } 100 | 101 | def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*'] 102 | def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/debug", excludes: fileFilter) 103 | def mainSrc = "${project.projectDir}/src/main/java" 104 | 105 | getSourceDirectories().setFrom(files([mainSrc])) 106 | getClassDirectories().setFrom(files([debugTree])) 107 | getExecutionData().setFrom(fileTree(dir: "$buildDir", includes: [ 108 | "jacoco/testDebugUnitTest.exec", 109 | "outputs/code-coverage/connected/*coverage.ec" 110 | ])) 111 | } 112 | 113 | play { 114 | def envTrack = System.getenv('PLAY_STORE_RELEASE_CHANNEL') 115 | if ("production" == envTrack || "beta" == envTrack || "alpha" == envTrack || "rollout" == envTrack) { 116 | track = envTrack 117 | } else { 118 | println "The PLAY_STORE_RELEASE_CHANNEL environment variable is invalid, it was ignored" 119 | } 120 | jsonFile = rootProject.file('GooglePlayAPI.json') 121 | } 122 | 123 | android.productFlavors.each { 124 | flavor -> if (getGradle().getStartParameter().getTaskRequests().toString().toLowerCase().contains(flavor.name) && flavor.ext.useFabric){ 125 | apply plugin: 'io.fabric' 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/bicou/AndroidSDK/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/io/github/benoitduffez/cupsprint/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.benoitduffez.cupsprint 2 | 3 | import androidx.test.runner.AndroidJUnit4 4 | 5 | import org.junit.Test 6 | import org.junit.runner.RunWith 7 | 8 | /** 9 | * Instrumentation test, which will execute on an Android device. 10 | * 11 | * @see [Testing documentation](http://d.android.com/tools/testing) 12 | */ 13 | @RunWith(AndroidJUnit4::class) 14 | class ExampleInstrumentedTest { 15 | @Test 16 | @Throws(Exception::class) 17 | fun dummy() { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/fdroid/java/io/github/benoitduffez/cupsprint/CupsPrintApp.kt: -------------------------------------------------------------------------------- 1 | package io.github.benoitduffez.cupsprint 2 | 3 | import android.app.Application 4 | import org.koin.android.ext.android.startKoin 5 | import org.koin.dsl.module.module 6 | 7 | val applicationModule = module { 8 | single { AppExecutors() } 9 | } 10 | 11 | class CupsPrintApp : Application() { 12 | override fun onCreate() { 13 | super.onCreate() 14 | 15 | startKoin(this, listOf(applicationModule)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 16 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 31 | 32 | 38 | 39 | 45 | 46 | 52 | 53 | 56 | 57 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /app/src/main/java/ch/ethz/vppserver/ippclient/EmumItem.kt: -------------------------------------------------------------------------------- 1 | package ch.ethz.vppserver.ippclient 2 | 3 | /*Copyright (C) 2013 Jon Freeman 4 | 5 | This program is free software; you can redistribute it and/or modify it under 6 | the terms of the GNU Lesser General Public License as published by the Free 7 | Software Foundation; either version 3 of the License, or (at your option) any 8 | later version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. 13 | 14 | See the GNU Lesser General Public License for more details. You should have 15 | received a copy of the GNU Lesser General Public License along with this 16 | program; if not, see . 17 | */ 18 | 19 | class EnumItem { 20 | var name: String 21 | var description: String 22 | 23 | constructor(name: String) { 24 | this.name = name 25 | this.description = name 26 | } 27 | 28 | constructor(name: String, description: String) { 29 | this.name = name 30 | this.description = description 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/ch/ethz/vppserver/ippclient/EnumItemMap.kt: -------------------------------------------------------------------------------- 1 | package ch.ethz.vppserver.ippclient 2 | 3 | /*Copyright (C) 2013 Jon Freeman 4 | 5 | This program is free software; you can redistribute it and/or modify it under 6 | the terms of the GNU Lesser General Public License as published by the Free 7 | Software Foundation; either version 3 of the License, or (at your option) any 8 | later version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. 13 | 14 | See the GNU Lesser General Public License for more details. You should have 15 | received a copy of the GNU Lesser General Public License along with this 16 | program; if not, see . 17 | */ 18 | 19 | import java.util.LinkedHashMap 20 | 21 | class EnumItemMap(internal var tag: String, internal var tagName: String, internal var description: String) 22 | : LinkedHashMap() 23 | -------------------------------------------------------------------------------- /app/src/main/java/ch/ethz/vppserver/ippclient/EnumMap.kt: -------------------------------------------------------------------------------- 1 | package ch.ethz.vppserver.ippclient 2 | 3 | /*Copyright (C) 2013 Jon Freeman 4 | 5 | This program is free software; you can redistribute it and/or modify it under 6 | the terms of the GNU Lesser General Public License as published by the Free 7 | Software Foundation; either version 3 of the License, or (at your option) any 8 | later version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. 13 | 14 | See the GNU Lesser General Public License for more details. You should have 15 | received a copy of the GNU Lesser General Public License along with this 16 | program; if not, see . 17 | */ 18 | 19 | import java.util.LinkedHashMap 20 | 21 | class EnumMap : LinkedHashMap() 22 | -------------------------------------------------------------------------------- /app/src/main/java/ch/ethz/vppserver/ippclient/IppResult.kt: -------------------------------------------------------------------------------- 1 | package ch.ethz.vppserver.ippclient 2 | 3 | import ch.ethz.vppserver.schema.ippclient.AttributeGroup 4 | 5 | /** 6 | * Copyright (C) 2008 ITS of ETH Zurich, Switzerland, Sarah Windler Burri 7 | * 8 | * 9 | * This program is free software; you can redistribute it and/or modify it under the terms of the 10 | * GNU Lesser General Public License as published by the Free Software Foundation; either version 3 11 | * of the License, or (at your option) any later version. 12 | * 13 | * 14 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 15 | * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 16 | * 17 | * 18 | * See the GNU Lesser General Public License for more details. You should have received a copy of 19 | * the GNU Lesser General Public License along with this program; if not, see 20 | * //www.gnu.org/licenses/>. 21 | */ 22 | 23 | /*Notice 24 | * This file has been modified. It is not the original. 25 | * ppd op patch as suggested at 26 | * http://www.cups4j.org/forum/viewtopic.php?f=6&t=40 27 | * has been applied. 28 | */ 29 | 30 | class IppResult { 31 | var httpStatusResponse: String? = null 32 | var ippStatusResponse: String? = null 33 | var attributeGroupList: List? = null 34 | var buf: ByteArray? = null 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/ch/ethz/vppserver/ippclient/IppUtil.kt: -------------------------------------------------------------------------------- 1 | package ch.ethz.vppserver.ippclient 2 | 3 | import java.io.IOException 4 | import java.io.UnsupportedEncodingException 5 | import java.net.InetAddress 6 | import java.nio.ByteBuffer 7 | import java.nio.CharBuffer 8 | import java.nio.charset.CharacterCodingException 9 | import java.nio.charset.Charset 10 | import java.nio.charset.CodingErrorAction 11 | import java.util.ArrayList 12 | import kotlin.experimental.and 13 | 14 | /** 15 | * Copyright (C) 2008 ITS of ETH Zurich, Switzerland, Sarah Windler Burri 16 | * 17 | * This program is free software; you can redistribute it and/or modify it under the terms of the 18 | * GNU Lesser General Public License as published by the Free Software Foundation; either version 3 19 | * of the License, or (at your option) any later version. 20 | * 21 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 22 | * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 23 | * 24 | * See the GNU Lesser General Public License for more details. You should have received a copy of 25 | * the GNU Lesser General Public License along with this program; if not, see 26 | * //www.gnu.org/licenses/>. 27 | */ 28 | object IppUtil { 29 | private const val DEFAULT_CHARSET = "UTF-8" 30 | 31 | /** 32 | * 33 | * @param a 34 | * high byte 35 | * @param b 36 | * low byte 37 | * @return short value 38 | */ 39 | private fun toShort(a: Byte, b: Byte): Short = 40 | ((a and (0x00ff shl 8).toByte()) + (b and 0x00ff.toByte())).toShort() 41 | 42 | /** 43 | * 44 | * @param b 45 | * byte 46 | * @return String 47 | */ 48 | fun toHex(b: Int): String { 49 | val st = Integer.toHexString(b) 50 | return if (st.length == 1) "0$st" else st 51 | } 52 | 53 | /** 54 | * 55 | * @param b 56 | * byte 57 | * @return String with Marker '0x' ahead 58 | */ 59 | fun toHexWithMarker(b: Int): String { 60 | val sb = StringBuilder() 61 | sb.append("0x").append(toHex(b)) 62 | return sb.toString() 63 | } 64 | 65 | /** 66 | * 67 | * @param dst 68 | * array of byte 69 | * @return String representation 70 | */ 71 | internal fun toString(dst: ByteArray): String { 72 | val l = dst.size 73 | val sb = StringBuilder(l) 74 | for (i in 0 until l) { 75 | val b = dst[i].toInt() 76 | val ival = b and 0xff 77 | val c = ival.toChar() 78 | sb.append(c) 79 | } 80 | return sb.toString() 81 | } 82 | 83 | /** 84 | * 85 | * @param str 86 | * String to encode 87 | * @param encoding 88 | * @return byte array 89 | * @throws UnsupportedEncodingException 90 | */ 91 | @Throws(UnsupportedEncodingException::class) 92 | @JvmOverloads 93 | fun toBytes(str: String, encoding: String? = null): ByteArray = 94 | str.toByteArray(charset(encoding ?: DEFAULT_CHARSET)) 95 | 96 | /** 97 | * see RFC 2579 for DateAndTime byte length and explanation of byte fields IPP datetime must have 98 | * a length of eleven bytes 99 | * 100 | * @param dst 101 | * byte array 102 | * @return String representation of dateTime 103 | */ 104 | internal fun toDateTime(dst: ByteArray): String { 105 | val sb = StringBuffer() 106 | val year = toShort(dst[0], dst[1]) 107 | sb.append(year.toInt()).append("-") 108 | val month = dst[2] 109 | sb.append(month.toInt()).append("-") 110 | val day = dst[3] 111 | sb.append(day.toInt()).append(",") 112 | var hours = dst[4] 113 | sb.append(hours.toInt()).append(":") 114 | var min = dst[5] 115 | sb.append(min.toInt()).append(":") 116 | val sec = dst[6] 117 | sb.append(sec.toInt()).append(".") 118 | val decSec = dst[7] 119 | sb.append(decSec.toInt()).append(",") 120 | 121 | val b = dst[8].toInt() 122 | val ival = b and 0xff 123 | val c = ival.toChar() 124 | sb.append(c) 125 | 126 | hours = dst[9] 127 | sb.append(hours.toInt()).append(":") 128 | min = dst[10] 129 | sb.append(min.toInt()) 130 | return sb.toString() 131 | } 132 | 133 | /** 134 | * See RFC2910, http://www.ietf.org/rfc/rfc2910 IPP boolean is defined as SIGNED-BYTE where 0x00 135 | * is 'false' and 0x01 is 'true' 136 | * 137 | * @param b 138 | * byte 139 | * @return String representation of boolean: i.e. true, false 140 | */ 141 | fun toBoolean(b: Byte): String = if (b.toInt() == 0) "false" else "true" 142 | 143 | /** 144 | * 145 | * @param ipAddress 146 | * @return true or false 147 | * @throws IOException 148 | */ 149 | @Throws(IOException::class) 150 | fun isAlive(ipAddress: String): Boolean = InetAddress.getByName(ipAddress).isReachable(2000) 151 | 152 | /** 153 | * 154 | * @param str 155 | * @param charsetName 156 | * @return 157 | * @throws CharacterCodingException 158 | */ 159 | @Throws(CharacterCodingException::class) 160 | @JvmOverloads 161 | fun getTranslatedString(str: String, charsetName: String? = null): String { 162 | val charset = Charset.forName(charsetName ?: DEFAULT_CHARSET) 163 | val decoder = charset.newDecoder() 164 | val encoder = charset.newEncoder() 165 | decoder.onUnmappableCharacter(CodingErrorAction.REPLACE) 166 | encoder.onUnmappableCharacter(CodingErrorAction.REPLACE) 167 | // Convert a string to charsetName bytes in a ByteBuffer 168 | // The new ByteBuffer is ready to be read. 169 | val buf = encoder.encode(CharBuffer.wrap(str)) 170 | // Convert charsetName bytes in a ByteBuffer to a character ByteBuffer 171 | // and then to a string. The new ByteBuffer is ready to be read. 172 | val cbuf = decoder.decode(buf) 173 | return cbuf.toString() 174 | } 175 | 176 | /** 177 | * concatenate nio-ByteBuffers 178 | * 179 | * @param buffers 180 | * ArrayList 181 | * @return ByteBuffer 182 | */ 183 | fun concatenateBytebuffers(buffers: ArrayList): ByteBuffer { 184 | var n = 0 185 | for (b in buffers) 186 | n += b.remaining() 187 | 188 | val buf = if (n > 0 && buffers[0].isDirect) ByteBuffer.allocateDirect(n) else ByteBuffer.allocate(n) 189 | if (n > 0) 190 | buf.order(buffers[0].order()) 191 | 192 | for (b in buffers) 193 | buf.put(b.duplicate()) 194 | 195 | buf.flip() 196 | return buf 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /app/src/main/java/ch/ethz/vppserver/schema/ippclient/Attribute.kt: -------------------------------------------------------------------------------- 1 | package ch.ethz.vppserver.schema.ippclient 2 | 3 | /** 4 | * Copyright (C) 2008 ITS of ETH Zurich, Switzerland, Sarah Windler Burri 5 | * 6 | * This program is free software; you can redistribute it and/or modify it under 7 | * the terms of the GNU Lesser General Public License as published by the Free 8 | * Software Foundation; either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. 14 | * 15 | * See the GNU Lesser General Public License for more details. You should have 16 | * received a copy of the GNU Lesser General Public License along with this 17 | * program; if not, see //www.gnu.org/licenses/>. 18 | */ 19 | 20 | /*Notice 21 | * This file has been modified. It is not the original. 22 | * XML parsing annotations, etc. have been removed Jon Freeman - 2013 */ 23 | 24 | import java.util.ArrayList 25 | 26 | class Attribute { 27 | var attributeValue: ArrayList = ArrayList() 28 | var name: String? = null 29 | var description: String? = null 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/ch/ethz/vppserver/schema/ippclient/AttributeGroup.kt: -------------------------------------------------------------------------------- 1 | package ch.ethz.vppserver.schema.ippclient 2 | 3 | /** 4 | * Copyright (C) 2008 ITS of ETH Zurich, Switzerland, Sarah Windler Burri 5 | * 6 | * This program is free software; you can redistribute it and/or modify it under 7 | * the terms of the GNU Lesser General Public License as published by the Free 8 | * Software Foundation; either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. 14 | * 15 | * See the GNU Lesser General Public License for more details. You should have 16 | * received a copy of the GNU Lesser General Public License along with this 17 | * program; if not, see //www.gnu.org/licenses/>. 18 | */ 19 | 20 | /*Notice 21 | * This file has been modified. It is not the original. 22 | * XML parsing annotations, etc. have been removed Jon Freeman - 2013 */ 23 | 24 | import java.util.ArrayList 25 | 26 | class AttributeGroup { 27 | var attribute: ArrayList = ArrayList() 28 | var tag: String? = null 29 | var tagName: String? = null 30 | var description: String? = null 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/ch/ethz/vppserver/schema/ippclient/AttributeValue.kt: -------------------------------------------------------------------------------- 1 | package ch.ethz.vppserver.schema.ippclient 2 | 3 | /** 4 | * Copyright (C) 2008 ITS of ETH Zurich, Switzerland, Sarah Windler Burri 5 | * 6 | * This program is free software; you can redistribute it and/or modify it under 7 | * the terms of the GNU Lesser General Public License as published by the Free 8 | * Software Foundation; either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. 14 | * 15 | * See the GNU Lesser General Public License for more details. You should have 16 | * received a copy of the GNU Lesser General Public License along with this 17 | * program; if not, see //www.gnu.org/licenses/>. 18 | */ 19 | 20 | /*Notice 21 | * This file has been modified. It is not the original. 22 | * XML parsing annotations, etc. have been removed Jon Freeman - 2013 */ 23 | 24 | class AttributeValue { 25 | var setOfKeyword: SetOfKeyword? = null 26 | var setOfEnum: SetOfEnum? = null 27 | var tag: String? = null 28 | var tagName: String? = null 29 | var value: String? = null 30 | var description: String? = null 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/ch/ethz/vppserver/schema/ippclient/Enum.kt: -------------------------------------------------------------------------------- 1 | package ch.ethz.vppserver.schema.ippclient 2 | 3 | /** 4 | * Copyright (C) 2008 ITS of ETH Zurich, Switzerland, Sarah Windler Burri 5 | * 6 | * This program is free software; you can redistribute it and/or modify it under 7 | * the terms of the GNU Lesser General Public License as published by the Free 8 | * Software Foundation; either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. 14 | * 15 | * See the GNU Lesser General Public License for more details. You should have 16 | * received a copy of the GNU Lesser General Public License along with this 17 | * program; if not, see //www.gnu.org/licenses/>. 18 | */ 19 | 20 | /*Notice 21 | * This file has been modified. It is not the original. 22 | * XML parsing annotations, etc. have been removed Jon Freeman - 2013 */ 23 | 24 | class Enum { 25 | var name: String? = null 26 | var value: String? = null 27 | var description: String? = null 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/ch/ethz/vppserver/schema/ippclient/Keyword.kt: -------------------------------------------------------------------------------- 1 | package ch.ethz.vppserver.schema.ippclient 2 | 3 | /** 4 | * Copyright (C) 2008 ITS of ETH Zurich, Switzerland, Sarah Windler Burri 5 | * 6 | * This program is free software; you can redistribute it and/or modify it under 7 | * the terms of the GNU Lesser General Public License as published by the Free 8 | * Software Foundation; either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. 14 | * 15 | * See the GNU Lesser General Public License for more details. You should have 16 | * received a copy of the GNU Lesser General Public License along with this 17 | * program; if not, see //www.gnu.org/licenses/>. 18 | */ 19 | 20 | /*Notice 21 | * This file has been modified. It is not the original. 22 | * XML parsing annotations, etc. have been removed Jon Freeman - 2013 */ 23 | 24 | class Keyword { 25 | var value: String? = null 26 | var description: String? = null 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/ch/ethz/vppserver/schema/ippclient/SetOfEnum.kt: -------------------------------------------------------------------------------- 1 | package ch.ethz.vppserver.schema.ippclient 2 | 3 | /** 4 | * Copyright (C) 2008 ITS of ETH Zurich, Switzerland, Sarah Windler Burri 5 | * 6 | * This program is free software; you can redistribute it and/or modify it under 7 | * the terms of the GNU Lesser General Public License as published by the Free 8 | * Software Foundation; either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. 14 | * 15 | * See the GNU Lesser General Public License for more details. You should have 16 | * received a copy of the GNU Lesser General Public License along with this 17 | * program; if not, see //www.gnu.org/licenses/>. 18 | */ 19 | 20 | /*Notice 21 | * This file has been modified. It is not the original. 22 | * XML parsing annotations, etc. have been removed Jon Freeman - 2013 */ 23 | 24 | import java.util.ArrayList 25 | 26 | class SetOfEnum { 27 | val enum: List = ArrayList() 28 | var description: String? = null 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/ch/ethz/vppserver/schema/ippclient/SetOfKeyword.kt: -------------------------------------------------------------------------------- 1 | package ch.ethz.vppserver.schema.ippclient 2 | 3 | /** 4 | * Copyright (C) 2008 ITS of ETH Zurich, Switzerland, Sarah Windler Burri 5 | * 6 | * This program is free software; you can redistribute it and/or modify it under 7 | * the terms of the GNU Lesser General Public License as published by the Free 8 | * Software Foundation; either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. 14 | * 15 | * See the GNU Lesser General Public License for more details. You should have 16 | * received a copy of the GNU Lesser General Public License along with this 17 | * program; if not, see //www.gnu.org/licenses/>. 18 | */ 19 | 20 | /*Notice 21 | * This file has been modified. It is not the original. 22 | * XML parsing annotations, etc. have been removed Jon Freeman - 2013 */ 23 | 24 | import java.util.ArrayList 25 | 26 | class SetOfKeyword { 27 | val keyword: List = ArrayList() 28 | var description: String? = null 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/ch/ethz/vppserver/schema/ippclient/Tag.kt: -------------------------------------------------------------------------------- 1 | package ch.ethz.vppserver.schema.ippclient 2 | 3 | /** 4 | * Copyright (C) 2008 ITS of ETH Zurich, Switzerland, Sarah Windler Burri 5 | * 6 | * This program is free software; you can redistribute it and/or modify it under 7 | * the terms of the GNU Lesser General Public License as published by the Free 8 | * Software Foundation; either version 3 of the License, or (at your option) any 9 | * later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. 14 | * 15 | * See the GNU Lesser General Public License for more details. You should have 16 | * received a copy of the GNU Lesser General Public License along with this 17 | * program; if not, see //www.gnu.org/licenses/>. 18 | */ 19 | 20 | /*Notice 21 | * This file has been modified. It is not the original. 22 | * XML parsing annotations, etc. have been removed Jon Freeman - 2013 */ 23 | 24 | class Tag { 25 | var value: String 26 | var name: String 27 | var description: String? = null 28 | var max: Short? = null 29 | 30 | constructor(value: String, name: String) { 31 | this.value = value 32 | this.name = name 33 | } 34 | 35 | constructor(value: String, name: String, description: String) { 36 | this.value = value 37 | this.name = name 38 | this.description = description 39 | } 40 | 41 | constructor(value: String, name: String, description: String, max: String) { 42 | this.value = value 43 | this.name = name 44 | this.description = description 45 | this.max = java.lang.Short.parseShort(max) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/benoitduffez/cupsprint/AppExecutors.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.benoitduffez.cupsprint 18 | 19 | import android.os.Handler 20 | import android.os.Looper 21 | 22 | import java.util.concurrent.Executor 23 | import java.util.concurrent.Executors 24 | 25 | private const val THREAD_COUNT = 3 26 | 27 | /** 28 | * Global executor pools for the whole application. 29 | * 30 | * 31 | * Grouping tasks like this avoids the effects of task starvation (e.g. disk reads don't wait behind 32 | * webservice requests). 33 | */ 34 | class AppExecutors 35 | private constructor(val diskIO: Executor, val networkIO: Executor, val mainThread: Executor) { 36 | constructor() : this(DiskIOThreadExecutor(), Executors.newFixedThreadPool(THREAD_COUNT), MainThreadExecutor()) 37 | 38 | private class MainThreadExecutor : Executor { 39 | private val mainThreadHandler = Handler(Looper.getMainLooper()) 40 | 41 | override fun execute(command: Runnable) { 42 | mainThreadHandler.post(command) 43 | } 44 | } 45 | 46 | /** 47 | * Executor that runs a task on a new background thread. 48 | */ 49 | private class DiskIOThreadExecutor 50 | internal constructor(private val executor: Executor = Executors.newSingleThreadExecutor()) : Executor { 51 | override fun execute(command: Runnable) = executor.execute(command) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/benoitduffez/cupsprint/HttpConnectionManagement.kt: -------------------------------------------------------------------------------- 1 | package io.github.benoitduffez.cupsprint 2 | 3 | import android.content.Context 4 | import android.util.Base64 5 | import io.github.benoitduffez.cupsprint.app.BasicAuthActivity 6 | import io.github.benoitduffez.cupsprint.ssl.AdditionalKeyManager 7 | import io.github.benoitduffez.cupsprint.ssl.AdditionalKeyStoresSSLSocketFactory 8 | import io.github.benoitduffez.cupsprint.ssl.AndroidCupsHostnameVerifier 9 | import timber.log.Timber 10 | import java.io.FileNotFoundException 11 | import java.io.FileOutputStream 12 | import java.io.IOException 13 | import java.io.UnsupportedEncodingException 14 | import java.net.HttpURLConnection 15 | import java.net.URL 16 | import java.security.KeyManagementException 17 | import java.security.KeyStore 18 | import java.security.KeyStoreException 19 | import java.security.NoSuchAlgorithmException 20 | import java.security.UnrecoverableKeyException 21 | import java.security.cert.CertificateException 22 | import java.security.cert.X509Certificate 23 | import javax.net.ssl.HttpsURLConnection 24 | import javax.net.ssl.KeyManager 25 | 26 | private const val KEYSTORE_FILE = "cupsprint-trustfile" 27 | private const val KEYSTORE_PASSWORD = "i6:[(mW*xh~=Ni;S|?8lz8eZ;!SU(S" 28 | 29 | object HttpConnectionManagement { 30 | /** 31 | * Will handle SSL related stuff to this connection so that certs are properly managed 32 | * 33 | * @param connection The target https connection 34 | */ 35 | fun handleHttpsUrlConnection(context: Context, connection: HttpsURLConnection) { 36 | connection.hostnameVerifier = AndroidCupsHostnameVerifier(context) 37 | 38 | try { 39 | val trustStore = loadKeyStore(context) ?: return 40 | 41 | var keyManager: KeyManager? = null 42 | try { 43 | keyManager = AdditionalKeyManager.fromAlias(context) 44 | } catch (e: CertificateException) { 45 | Timber.e(e, "Couldn't load system key store: ${e.localizedMessage}") 46 | } 47 | 48 | connection.sslSocketFactory = AdditionalKeyStoresSSLSocketFactory(keyManager, trustStore) 49 | } catch (e: NoSuchAlgorithmException) { 50 | Timber.e(e, "Couldn't handle SSL URL connection: ${e.localizedMessage}") 51 | } catch (e: UnrecoverableKeyException) { 52 | Timber.e(e, "Couldn't handle SSL URL connection: ${e.localizedMessage}") 53 | } catch (e: KeyStoreException) { 54 | Timber.e(e, "Couldn't handle SSL URL connection: ${e.localizedMessage}") 55 | } catch (e: KeyManagementException) { 56 | Timber.e(e, "Couldn't handle SSL URL connection: ${e.localizedMessage}") 57 | } 58 | } 59 | 60 | /** 61 | * Try to get the contents of the local key store 62 | * 63 | * @return A valid KeyStore object if nothing went wrong, null otherwise 64 | */ 65 | private fun loadKeyStore(context: Context): KeyStore? { 66 | val trustStore: KeyStore 67 | try { 68 | trustStore = KeyStore.getInstance(KeyStore.getDefaultType()) 69 | } catch (e: KeyStoreException) { 70 | Timber.e(e, "Couldn't open local key store") 71 | return null 72 | } 73 | 74 | // Load the local keystore into memory 75 | try { 76 | val fis = context.openFileInput(KEYSTORE_FILE) 77 | trustStore.load(fis, KEYSTORE_PASSWORD.toCharArray()) 78 | return trustStore 79 | } catch (e: FileNotFoundException) { 80 | // This one can be ignored safely - at least not sent to crashlytics 81 | Timber.e("Couldn't open local key store: ${e.localizedMessage}") 82 | } catch (e: IOException) { 83 | Timber.e(e, "Couldn't open local key store") 84 | } catch (e: NoSuchAlgorithmException) { 85 | Timber.e(e, "Couldn't open local key store") 86 | } catch (e: CertificateException) { 87 | Timber.e(e, "Couldn't open local key store") 88 | } 89 | 90 | // if we couldn't load local keystore file, create an new empty one 91 | try { 92 | trustStore.load(null, null) 93 | } catch (e: IOException) { 94 | Timber.e(e, "Couldn't create new key store") 95 | } catch (e: NoSuchAlgorithmException) { 96 | Timber.e(e, "Couldn't create new key store") 97 | } catch (e: CertificateException) { 98 | Timber.e(e, "Couldn't create new key store") 99 | } 100 | 101 | return trustStore 102 | } 103 | 104 | /** 105 | * Add certs to the keystore (thus trusting them) 106 | * 107 | * @param chain The chain of certs to trust 108 | * @return true if it was saved, false otherwise 109 | */ 110 | fun saveCertificates(context: Context, chain: Array): Boolean { 111 | // Load existing certs 112 | val trustStore = loadKeyStore(context) ?: return false 113 | 114 | // Add new certs 115 | try { 116 | for (c in chain) { 117 | trustStore.setCertificateEntry(c.subjectDN.toString(), c) 118 | } 119 | } catch (e: KeyStoreException) { 120 | Timber.e(e, "Couldn't store cert chain into key store") 121 | return false 122 | } 123 | 124 | // Save new keystore 125 | var fos: FileOutputStream? = null 126 | try { 127 | fos = context.openFileOutput(KEYSTORE_FILE, Context.MODE_PRIVATE) 128 | trustStore.store(fos, KEYSTORE_PASSWORD.toCharArray()) 129 | fos!!.close() 130 | } catch (e: Exception) { 131 | Timber.e(e, "Unable to save key store") 132 | } finally { 133 | if (fos != null) { 134 | try { 135 | fos.close() 136 | } catch (e: IOException) { 137 | Timber.e(e, "Couldn't close key store") 138 | } 139 | } 140 | } 141 | 142 | return true 143 | } 144 | 145 | /** 146 | * See if there are some basic auth credentials saved, and configure the connection 147 | * 148 | * @param url URL we're about to request 149 | * @param connection The connection to be configured 150 | */ 151 | fun handleBasicAuth(context: Context, url: URL, connection: HttpURLConnection) { 152 | val prefs = context.getSharedPreferences(BasicAuthActivity.CREDENTIALS_FILE, Context.MODE_PRIVATE) 153 | 154 | val id = BasicAuthActivity.findSavedCredentialsId(url.toString(), prefs) 155 | if (id < 0) { 156 | return 157 | } 158 | 159 | val username = prefs.getString(BasicAuthActivity.KEY_BASIC_AUTH_LOGIN + id, "") 160 | val password = prefs.getString(BasicAuthActivity.KEY_BASIC_AUTH_PASSWORD + id, "") 161 | try { 162 | val encoded = Base64.encodeToString(("$username:$password").toByteArray(charset("UTF-8")), Base64.NO_WRAP) 163 | connection.setRequestProperty("Authorization", "Basic $encoded") 164 | } catch (e: UnsupportedEncodingException) { 165 | Timber.e(e, "Couldn't base64 encode basic auth credentials") 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/benoitduffez/cupsprint/app/AddPrintersActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.benoitduffez.cupsprint.app 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.os.Bundle 6 | import android.os.Handler 7 | import android.text.TextUtils 8 | import android.view.View 9 | import io.github.benoitduffez.cupsprint.AppExecutors 10 | import io.github.benoitduffez.cupsprint.R 11 | import kotlinx.android.synthetic.main.add_printers.* 12 | import org.koin.android.ext.android.inject 13 | import timber.log.Timber 14 | import java.io.InputStreamReader 15 | import java.net.HttpURLConnection 16 | import java.net.URI 17 | import java.net.URL 18 | import java.util.regex.Pattern 19 | 20 | /** 21 | * Called when the system needs to manually add a printer 22 | */ 23 | class AddPrintersActivity : Activity() { 24 | private val executors: AppExecutors by inject() 25 | 26 | override fun onCreate(savedInstanceState: Bundle?) { 27 | super.onCreate(savedInstanceState) 28 | setContentView(R.layout.add_printers) 29 | } 30 | 31 | /** 32 | * Called when the button will be clicked 33 | */ 34 | fun addPrinter(@Suppress("UNUSED_PARAMETER") button: View) { 35 | val url = add_url.text.toString() 36 | val name = add_name.text.toString() 37 | 38 | if (TextUtils.isEmpty(name)) { 39 | add_name.error = getString(R.string.err_add_printer_empty_name) 40 | return 41 | } 42 | if (TextUtils.isEmpty(url)) { 43 | add_url.error = getString(R.string.err_add_printer_empty_url) 44 | return 45 | } 46 | try { 47 | URI(url) 48 | } catch (e: Exception) { 49 | add_url.error = e.localizedMessage 50 | return 51 | } 52 | 53 | val prefs = getSharedPreferences(SHARED_PREFS_MANUAL_PRINTERS, Context.MODE_PRIVATE) 54 | val editor = prefs.edit() 55 | val id = prefs.getInt(PREF_NUM_PRINTERS, 0) 56 | Timber.d("saving printer from input: $url") 57 | editor.putString(PREF_URL + id, url) 58 | editor.putString(PREF_NAME + id, name) 59 | editor.putInt(PREF_NUM_PRINTERS, id + 1) 60 | editor.apply() 61 | 62 | // TODO: inform user? 63 | 64 | // Allow the system to process the new printer addition before we get back to the list of printers 65 | Handler().postDelayed({ finish() }, 200) 66 | } 67 | 68 | fun searchPrinters(@Suppress("UNUSED_PARAMETER") button: View) { 69 | executors.networkIO.execute { 70 | searchPrinters("http") 71 | searchPrinters("https") 72 | } 73 | } 74 | 75 | /** 76 | * Will search for printers at the scheme://xxx/printers/ URL 77 | * 78 | * @param scheme The target scheme, http or https 79 | */ 80 | private fun searchPrinters(scheme: String) { 81 | var urlConnection: HttpURLConnection? = null 82 | val sb = StringBuilder() 83 | var server = add_server_ip.text.toString() 84 | if (!server.contains(":")) { 85 | server += ":631" 86 | } 87 | val baseHost = "$scheme://$server" 88 | val baseUrl = "$baseHost/printers/" 89 | try { 90 | urlConnection = URL(baseUrl).openConnection() as HttpURLConnection 91 | val isw = InputStreamReader(urlConnection.inputStream) 92 | var data = isw.read() 93 | while (data != -1) { 94 | val current = data.toChar() 95 | sb.append(current) 96 | data = isw.read() 97 | } 98 | } catch (e: Exception) { 99 | e.printStackTrace() 100 | return 101 | } finally { 102 | urlConnection?.disconnect() 103 | } 104 | 105 | /* 106 | * 1: URL 107 | * 2: Name 108 | * 3: Description 109 | * 4: Location 110 | * 5: Make and model 111 | * 6: Current state 112 | * pattern matching fields: 1 2 3 4 5 6 113 | */ 114 | val p = Pattern.compile("([^<]*)([^<]*)([^<]*)([^<]*)([^<]*)\n") 115 | val matcher = p.matcher(sb) 116 | var url: String 117 | var name: String 118 | val prefs = getSharedPreferences(SHARED_PREFS_MANUAL_PRINTERS, Context.MODE_PRIVATE) 119 | val editor = prefs.edit() 120 | var id = prefs.getInt(PREF_NUM_PRINTERS, 0) 121 | while (matcher.find()) { 122 | val path = matcher.group(1) 123 | url = if (path.startsWith("/")) { 124 | baseHost + path 125 | } else { 126 | baseUrl + path 127 | } 128 | Timber.d("saving printer from search on $scheme: $url") 129 | name = matcher.group(3) 130 | editor.putString(PREF_URL + id, url) 131 | editor.putString(PREF_NAME + id, name) 132 | id++ 133 | } 134 | editor.putInt(PREF_NUM_PRINTERS, id) 135 | editor.apply() 136 | } 137 | 138 | companion object { 139 | /** 140 | * Shared preferences file name 141 | */ 142 | const val SHARED_PREFS_MANUAL_PRINTERS = "printers" 143 | 144 | /** 145 | * Will store the number of printers manually added 146 | */ 147 | const val PREF_NUM_PRINTERS = "num" 148 | 149 | /** 150 | * Will be suffixed by the printer ID. Contains the URL. 151 | */ 152 | const val PREF_URL = "url" 153 | 154 | /** 155 | * Will be suffixed by the printer ID. Contains the name. 156 | */ 157 | const val PREF_NAME = "name" 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/benoitduffez/cupsprint/app/BasicAuthActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.benoitduffez.cupsprint.app 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.SharedPreferences 6 | import android.os.Bundle 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import io.github.benoitduffez.cupsprint.R 10 | import kotlinx.android.synthetic.main.basic_auth.* 11 | 12 | /** 13 | * Ask for the HTTP basic auth credentials 14 | */ 15 | class BasicAuthActivity : Activity() { 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | setContentView(R.layout.basic_auth) 19 | window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) 20 | 21 | val printersUrl = intent.getStringExtra(KEY_BASIC_AUTH_PRINTERS_URL) 22 | val prefs = getSharedPreferences(CREDENTIALS_FILE, Context.MODE_PRIVATE) 23 | 24 | val numCredentials = prefs.getInt(KEY_BASIC_AUTH_NUMBER, 0) 25 | val foundId = findSavedCredentialsId(printersUrl!!, prefs) 26 | val targetId: Int 27 | if (foundId >= 0) { 28 | targetId = foundId 29 | basic_auth_login.setText(prefs.getString(KEY_BASIC_AUTH_LOGIN + foundId, "")) 30 | basic_auth_password.setText(prefs.getString(KEY_BASIC_AUTH_PASSWORD + foundId, "")) 31 | } else { 32 | targetId = numCredentials 33 | } 34 | 35 | basic_auth_button.setOnClickListener { 36 | val editPrefs = getSharedPreferences(CREDENTIALS_FILE, Context.MODE_PRIVATE).edit() 37 | editPrefs.putString(KEY_BASIC_AUTH_LOGIN + targetId, basic_auth_login.text.toString()) 38 | editPrefs.putString(KEY_BASIC_AUTH_PASSWORD + targetId, basic_auth_password.text.toString()) 39 | editPrefs.putString(KEY_BASIC_AUTH_PRINTERS_URL + targetId, printersUrl) 40 | editPrefs.putInt(KEY_BASIC_AUTH_NUMBER, numCredentials + 1) 41 | editPrefs.apply() 42 | 43 | finish() 44 | } 45 | } 46 | 47 | companion object { 48 | const val CREDENTIALS_FILE = "basic_auth" 49 | val KEY_BASIC_AUTH_PRINTERS_URL = "${BasicAuthActivity::class.java.name}.PrinterUrl" 50 | val KEY_BASIC_AUTH_LOGIN = "${BasicAuthActivity::class.java.name}.Login" 51 | val KEY_BASIC_AUTH_PASSWORD = "${BasicAuthActivity::class.java.name}.Password" 52 | internal val KEY_BASIC_AUTH_NUMBER = "${BasicAuthActivity::class.java.name}.Number" 53 | 54 | /** 55 | * See if we have already saved credentials for this server 56 | * 57 | * @param fullUrl Server URL (may include the printer name) 58 | * @param prefs Shared preferences to search credentials from 59 | * @return The credentials position in the preferences files, or -1 if it wasn't found 60 | */ 61 | fun findSavedCredentialsId(fullUrl: String, prefs: SharedPreferences): Int { 62 | for (i in 0 until prefs.getInt(KEY_BASIC_AUTH_NUMBER, 0)) { 63 | val url = prefs.getString(KEY_BASIC_AUTH_PRINTERS_URL + i, null) 64 | if (url != null && fullUrl.startsWith(url)) { 65 | return i 66 | } 67 | } 68 | return -1 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/benoitduffez/cupsprint/app/HostNotVerifiedActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.benoitduffez.cupsprint.app 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.os.Bundle 6 | import android.view.ViewGroup 7 | import io.github.benoitduffez.cupsprint.R 8 | import kotlinx.android.synthetic.main.host_not_verified.* 9 | 10 | /** 11 | * Ask for host trust when it couldn't be verified 12 | */ 13 | class HostNotVerifiedActivity : Activity() { 14 | private var unverifiedHostname: String? = null 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | setContentView(R.layout.host_not_verified) 19 | window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) 20 | 21 | unverifiedHostname = intent.getStringExtra(KEY_HOST) 22 | host_not_verified_title.text = getString(R.string.host_not_verified_title, unverifiedHostname) 23 | 24 | host_not_verified_trust_button.setOnClickListener { validateTrust(true) } 25 | host_not_verified_abort_button.setOnClickListener { validateTrust(false) } 26 | } 27 | 28 | /** 29 | * Save choice and finish 30 | * 31 | * @param trusted Whether the host should be trusted or not 32 | */ 33 | private fun validateTrust(trusted: Boolean) { 34 | val prefs = getSharedPreferences(HOSTS_FILE, Context.MODE_PRIVATE).edit() 35 | prefs.putBoolean(unverifiedHostname, trusted) 36 | prefs.apply() 37 | finish() 38 | } 39 | 40 | companion object { 41 | val KEY_HOST = "${HostNotVerifiedActivity::class.java.name}.ErrorText" 42 | 43 | private const val HOSTS_FILE = "hosts_trust" 44 | 45 | /** 46 | * Check whether host is known and trusted 47 | * 48 | * @param context Used to retrieve the saved setting 49 | * @param hostname Hostname to be checked 50 | * @return true if the hostname was explicitly trusted, false otherwise or if unknown 51 | */ 52 | fun isHostnameTrusted(context: Context, hostname: String): Boolean { 53 | return context.getSharedPreferences(HOSTS_FILE, Context.MODE_PRIVATE).getBoolean(hostname, false) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/benoitduffez/cupsprint/app/ManageManualPrintersActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.benoitduffez.cupsprint.app 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import android.os.Bundle 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.AdapterView 10 | import android.widget.ArrayAdapter 11 | import android.widget.TextView 12 | import androidx.annotation.LayoutRes 13 | import androidx.appcompat.app.AppCompatActivity 14 | import io.github.benoitduffez.cupsprint.R 15 | import kotlinx.android.synthetic.main.activity_manage_manual_printers.* 16 | import kotlinx.android.synthetic.main.manage_printers_list_item.view.* 17 | import java.util.ArrayList 18 | 19 | class ManageManualPrintersActivity : AppCompatActivity() { 20 | override fun onCreate(savedInstanceState: Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | setContentView(R.layout.activity_manage_manual_printers) 23 | 24 | // Build adapter 25 | val prefs = getSharedPreferences(AddPrintersActivity.SHARED_PREFS_MANUAL_PRINTERS, Context.MODE_PRIVATE) 26 | val numPrinters = prefs.getInt(AddPrintersActivity.PREF_NUM_PRINTERS, 0) 27 | val printers = getPrinters(prefs, numPrinters) 28 | val adapter = ManualPrintersAdapter(this, R.layout.manage_printers_list_item, printers) 29 | 30 | // Setup adapter with click to remove 31 | manage_printers_list.adapter = adapter 32 | manage_printers_list.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ -> 33 | val editor = prefs.edit() 34 | val actualNumPrinters = prefs.getInt(AddPrintersActivity.PREF_NUM_PRINTERS, 0) 35 | editor.putInt(AddPrintersActivity.PREF_NUM_PRINTERS, actualNumPrinters - 1) 36 | editor.remove(AddPrintersActivity.PREF_NAME + position) 37 | editor.remove(AddPrintersActivity.PREF_URL + position) 38 | editor.apply() 39 | adapter.removeItem(position) 40 | } 41 | 42 | manage_printers_empty.visibility = if (numPrinters <= 0) View.VISIBLE else View.GONE 43 | } 44 | 45 | private fun getPrinters(prefs: SharedPreferences, numPrinters: Int): List { 46 | if (numPrinters <= 0) { 47 | return ArrayList() 48 | } 49 | val printers = ArrayList(numPrinters) 50 | var url: String? 51 | var name: String? 52 | for (i in 0 until numPrinters) { 53 | name = prefs.getString(AddPrintersActivity.PREF_NAME + i, null) 54 | url = prefs.getString(AddPrintersActivity.PREF_URL + i, null) 55 | if (name != null && url != null) { 56 | printers.add(ManualPrinterInfo(name, url)) 57 | } 58 | } 59 | return printers 60 | } 61 | 62 | private class ManualPrinterInfo(internal var name: String, internal var url: String) 63 | private class ManualPrinterInfoViews internal constructor(internal var name: TextView, internal var url: TextView) 64 | 65 | private class ManualPrintersAdapter(context: Context, @LayoutRes resource: Int, objects: List) : ArrayAdapter(context, resource, objects) { 66 | override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { 67 | val view = when (convertView) { 68 | null -> { 69 | val inflate = LayoutInflater.from(parent.context).inflate(R.layout.manage_printers_list_item, parent, false) 70 | inflate.tag = ManualPrinterInfoViews(inflate.manual_printer_name, inflate.manual_printer_url) 71 | inflate 72 | } 73 | else -> convertView 74 | } 75 | 76 | val views = view.tag as ManualPrinterInfoViews 77 | 78 | val info = getItem(position) 79 | if (info != null) { 80 | views.name.text = info.name 81 | views.url.text = info.url 82 | } else { 83 | throw IllegalStateException("Manual printers list can't have invalid items") 84 | } 85 | 86 | return view 87 | } 88 | 89 | fun removeItem(position: Int) = remove(getItem(position)) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/benoitduffez/cupsprint/app/UntrustedCertActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.benoitduffez.cupsprint.app 2 | 3 | import android.app.Activity 4 | import android.os.Bundle 5 | import android.view.ViewGroup 6 | import android.widget.Toast 7 | import io.github.benoitduffez.cupsprint.HttpConnectionManagement 8 | import io.github.benoitduffez.cupsprint.R 9 | import kotlinx.android.synthetic.main.untrusted_cert.* 10 | import java.security.cert.X509Certificate 11 | 12 | /** 13 | * Show an untrusted cert info + two buttons to accept or refuse to trust said cert 14 | */ 15 | class UntrustedCertActivity : Activity() { 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | setContentView(R.layout.untrusted_cert) 19 | window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) 20 | 21 | val cert = intent.getSerializableExtra(KEY_CERT) as X509Certificate 22 | 23 | // Build short cert description 24 | val sb = StringBuilder() 25 | sb.append("Issuer: ").append(cert.issuerX500Principal.toString()) 26 | sb.append("\nValidity: not before ").append(cert.notBefore.toString()) 27 | sb.append("\nValidity: not after ").append(cert.notAfter.toString()) 28 | sb.append("\nSubject: ").append(cert.subjectX500Principal.name) 29 | sb.append("\nKey algo: ").append(cert.sigAlgName) 30 | untrusted_certinfo.text = sb 31 | 32 | untrusted_trust_button.setOnClickListener { 33 | if (HttpConnectionManagement.saveCertificates(this, arrayOf(cert))) { 34 | Toast.makeText(this@UntrustedCertActivity, R.string.untrusted_trusted, Toast.LENGTH_LONG).show() 35 | } else { 36 | Toast.makeText(this@UntrustedCertActivity, R.string.untrusted_couldnt_trust, Toast.LENGTH_LONG).show() 37 | } 38 | finish() 39 | } 40 | 41 | untrusted_abort_button.setOnClickListener { finish() } 42 | } 43 | 44 | companion object { 45 | val KEY_CERT = "${UntrustedCertActivity::class.java.name}.Certs" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/benoitduffez/cupsprint/detect/Merger.kt: -------------------------------------------------------------------------------- 1 | package io.github.benoitduffez.cupsprint.detect 2 | 3 | import timber.log.Timber 4 | import java.util.ArrayList 5 | 6 | internal class Merger { 7 | fun merge(httpRecs: List, httpsRecs: MutableList) { 8 | val tmpRecs = ArrayList() 9 | for (httpRec in httpRecs) { 10 | var match = false 11 | for (httpsRec in httpsRecs) { 12 | try { 13 | if (httpRec.queue == httpsRec.queue && 14 | httpRec.host == httpsRec.host && 15 | httpRec.port == httpsRec.port) { 16 | match = true 17 | break 18 | } 19 | } catch (e: Exception) { 20 | Timber.e(e, "Invalid record in merge") 21 | } 22 | } 23 | if (!match) { 24 | tmpRecs.add(httpRec) 25 | } 26 | } 27 | for (rec in tmpRecs) { 28 | httpsRecs.add(rec) 29 | } 30 | httpsRecs.sort() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/benoitduffez/cupsprint/detect/PrinterRec.kt: -------------------------------------------------------------------------------- 1 | package io.github.benoitduffez.cupsprint.detect 2 | 3 | class PrinterRec internal constructor(val nickname: String, val protocol: String, val host: String, val port: Int, val queue: String) : Comparable { 4 | override fun toString(): String = "$nickname ($protocol on $host)" 5 | override fun compareTo(other: PrinterRec): Int = toString().compareTo(other.toString()) 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/benoitduffez/cupsprint/detect/PrinterResult.kt: -------------------------------------------------------------------------------- 1 | package io.github.benoitduffez.cupsprint.detect 2 | 3 | import java.util.ArrayList 4 | import java.util.Collections 5 | 6 | class PrinterResult internal constructor() { 7 | var printers: List? = null 8 | private set 9 | 10 | private val errors: List 11 | 12 | init { 13 | printers = Collections.synchronizedList(ArrayList()) 14 | errors = Collections.synchronizedList(ArrayList()) 15 | } 16 | 17 | internal fun setPrinterRecs(printerRecs: ArrayList) { 18 | printers = printerRecs 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/benoitduffez/cupsprint/ssl/AdditionalKeyManager.kt: -------------------------------------------------------------------------------- 1 | package io.github.benoitduffez.cupsprint.ssl 2 | 3 | import android.content.Context 4 | import android.preference.PreferenceManager 5 | import android.security.KeyChain 6 | import android.security.KeyChainException 7 | import android.text.TextUtils 8 | import io.github.benoitduffez.cupsprint.CupsPrintApp 9 | import timber.log.Timber 10 | import java.net.Socket 11 | import java.security.Principal 12 | import java.security.PrivateKey 13 | import java.security.cert.CertificateException 14 | import java.security.cert.X509Certificate 15 | import javax.net.ssl.X509KeyManager 16 | 17 | /** 18 | * Uses the system keystore 19 | */ 20 | class AdditionalKeyManager private constructor(val context: Context, private val clientAlias: String, private val certificateChain: Array, private val privateKey: PrivateKey) : X509KeyManager { 21 | override fun chooseClientAlias(keyType: Array, issuers: Array, socket: Socket): String = clientAlias 22 | override fun getCertificateChain(alias: String): Array = certificateChain 23 | override fun getPrivateKey(alias: String): PrivateKey = privateKey 24 | 25 | override fun getClientAliases(keyType: String, issuers: Array): Array { 26 | throw UnsupportedOperationException() 27 | } 28 | 29 | override fun chooseServerAlias(keyType: String, issuers: Array, socket: Socket): String { 30 | throw UnsupportedOperationException() 31 | } 32 | 33 | override fun getServerAliases(keyType: String, issuers: Array): Array { 34 | throw UnsupportedOperationException() 35 | } 36 | 37 | companion object { 38 | private val KEY_CERTIFICATE_ALIAS = AdditionalKeyManager::class.java.name + ".CertificateAlias" 39 | 40 | /** 41 | * Builds an instance of a KeyChainKeyManager using the given certificate alias. If for any reason retrieval of the credentials from the system 42 | * KeyChain fails, a null value will be returned. 43 | * 44 | * @return System-wide KeyManager, or null if alias is empty 45 | * @throws CertificateException 46 | */ 47 | @Throws(CertificateException::class) 48 | fun fromAlias(context: Context): AdditionalKeyManager? { 49 | val alias = PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_CERTIFICATE_ALIAS, null) 50 | 51 | if (TextUtils.isEmpty(alias) || alias == null) { 52 | return null 53 | } 54 | 55 | val certificateChain = getCertificateChain(context, alias) 56 | val privateKey = getPrivateKey(context, alias) 57 | 58 | if (certificateChain == null || privateKey == null) { 59 | throw CertificateException("Can't access certificate from keystore") 60 | } 61 | 62 | return AdditionalKeyManager(context, alias, certificateChain, privateKey) 63 | } 64 | 65 | @Throws(CertificateException::class) 66 | private fun getCertificateChain(context: Context, alias: String): Array? { 67 | val certificateChain: Array? 68 | try { 69 | certificateChain = KeyChain.getCertificateChain(context, alias) 70 | } catch (e: KeyChainException) { 71 | logError(alias, "certificate chain", e) 72 | throw CertificateException(e) 73 | } catch (e: InterruptedException) { 74 | logError(alias, "certificate chain", e) 75 | throw CertificateException(e) 76 | } 77 | 78 | return certificateChain 79 | } 80 | 81 | @Throws(CertificateException::class) 82 | private fun getPrivateKey(context: Context, alias: String): PrivateKey? { 83 | val privateKey: PrivateKey? 84 | try { 85 | privateKey = KeyChain.getPrivateKey(context, alias) 86 | } catch (e: KeyChainException) { 87 | logError(alias, "private key", e) 88 | throw CertificateException(e) 89 | } catch (e: InterruptedException) { 90 | logError(alias, "private key", e) 91 | throw CertificateException(e) 92 | } 93 | 94 | return privateKey 95 | } 96 | 97 | private fun logError(alias: String, type: String, e: Exception) = 98 | Timber.e(e, "Unable to retrieve $type for [$alias]") 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/benoitduffez/cupsprint/ssl/AdditionalKeyStoresSSLSocketFactory.kt: -------------------------------------------------------------------------------- 1 | package io.github.benoitduffez.cupsprint.ssl 2 | 3 | import java.io.IOException 4 | import java.net.InetAddress 5 | import java.net.Socket 6 | import java.security.KeyManagementException 7 | import java.security.KeyStore 8 | import java.security.KeyStoreException 9 | import java.security.NoSuchAlgorithmException 10 | import java.security.UnrecoverableKeyException 11 | import java.security.cert.X509Certificate 12 | 13 | import javax.net.ssl.KeyManager 14 | import javax.net.ssl.SSLContext 15 | import javax.net.ssl.SSLSocketFactory 16 | import javax.net.ssl.TrustManager 17 | 18 | /** 19 | * Allows you to trust certificates from additional KeyStores in addition to 20 | * the default KeyStore 21 | */ 22 | class AdditionalKeyStoresSSLSocketFactory 23 | /** 24 | * Create the SSL socket factory 25 | * 26 | * @param keyManager Key manager, can be null 27 | * @param keyStore Keystore, can't be null 28 | * @throws NoSuchAlgorithmException 29 | * @throws KeyManagementException 30 | * @throws KeyStoreException 31 | * @throws UnrecoverableKeyException 32 | */ 33 | @Throws(NoSuchAlgorithmException::class, KeyManagementException::class, KeyStoreException::class, UnrecoverableKeyException::class) 34 | constructor(keyManager: KeyManager?, keyStore: KeyStore) : SSLSocketFactory() { 35 | private val sslContext = SSLContext.getInstance("TLS") 36 | private val trustManager: AdditionalKeyStoresTrustManager = AdditionalKeyStoresTrustManager(keyStore) 37 | 38 | val serverCert: Array? 39 | get() = trustManager.certChain 40 | 41 | init { 42 | // Ensure we don't pass an empty array. Array must contain a valid key manager, or must be null 43 | val managers: Array? = when (keyManager) { 44 | null -> null 45 | else -> arrayOf(keyManager) 46 | } 47 | 48 | sslContext.init(managers, arrayOf(trustManager), null) 49 | } 50 | 51 | override fun getDefaultCipherSuites(): Array = arrayOfNulls(0) 52 | override fun getSupportedCipherSuites(): Array = arrayOfNulls(0) 53 | 54 | @Throws(IOException::class) 55 | override fun createSocket(socket: Socket, host: String, port: Int, autoClose: Boolean): Socket = 56 | sslContext.socketFactory.createSocket(socket, host, port, autoClose) 57 | 58 | @Throws(IOException::class) 59 | override fun createSocket(s: String, i: Int): Socket = 60 | sslContext.socketFactory.createSocket(s, i) 61 | 62 | @Throws(IOException::class) 63 | override fun createSocket(s: String, i: Int, inetAddress: InetAddress, i1: Int): Socket = 64 | sslContext.socketFactory.createSocket(s, i, inetAddress, i1) 65 | 66 | @Throws(IOException::class) 67 | override fun createSocket(inetAddress: InetAddress, i: Int): Socket = 68 | sslContext.socketFactory.createSocket(inetAddress, i) 69 | 70 | @Throws(IOException::class) 71 | override fun createSocket(inetAddress: InetAddress, i: Int, inetAddress1: InetAddress, i1: Int): Socket = 72 | sslContext.socketFactory.createSocket(inetAddress, i, inetAddress1, i1) 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/benoitduffez/cupsprint/ssl/AdditionalKeyStoresTrustManager.kt: -------------------------------------------------------------------------------- 1 | package io.github.benoitduffez.cupsprint.ssl 2 | 3 | import java.security.KeyStore 4 | import java.security.cert.CertificateException 5 | import java.security.cert.X509Certificate 6 | import java.util.ArrayList 7 | import java.util.Arrays 8 | import javax.net.ssl.TrustManagerFactory 9 | import javax.net.ssl.X509TrustManager 10 | 11 | private const val UNTRUSTED_CERTIFICATE = "Untrusted Certificate" 12 | 13 | internal class AdditionalKeyStoresTrustManager(vararg additionalKeyStores: KeyStore) : X509TrustManager { 14 | private val x509TrustManagers = ArrayList() 15 | var certChain: Array? = null 16 | private set 17 | 18 | init { 19 | val factories = ArrayList() 20 | 21 | try { 22 | // The default Trust manager with default keystore 23 | val original = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) 24 | original.init(null as KeyStore?) 25 | factories.add(original) 26 | 27 | for (keyStore in additionalKeyStores) { 28 | val additionalCerts = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) 29 | additionalCerts.init(keyStore) 30 | factories.add(additionalCerts) 31 | } 32 | } catch (e: Exception) { 33 | throw RuntimeException(e) 34 | } 35 | 36 | /* 37 | * Iterate over the returned trust managers, and hold on 38 | * to any that are X509TrustManagers 39 | */ 40 | for (tmf in factories) { 41 | for (tm in tmf.trustManagers) { 42 | if (tm is X509TrustManager) { 43 | x509TrustManagers.add(tm) 44 | } 45 | } 46 | } 47 | 48 | if (x509TrustManagers.size == 0) { 49 | throw RuntimeException("Couldn't find any X509TrustManagers") 50 | } 51 | } 52 | 53 | /* 54 | * Delegate to the default trust manager. 55 | */ 56 | @Throws(CertificateException::class) 57 | override fun checkClientTrusted(chain: Array, authType: String) { 58 | val defaultX509TrustManager = x509TrustManagers[0] 59 | defaultX509TrustManager.checkClientTrusted(chain, authType) 60 | } 61 | 62 | /* 63 | * Loop over the trustmanagers until we find one that accepts our server 64 | */ 65 | @Throws(CertificateException::class) 66 | override fun checkServerTrusted(chain: Array, authType: String) { 67 | certChain = chain 68 | for (tm in x509TrustManagers) { 69 | try { 70 | tm.checkServerTrusted(chain, authType) 71 | return 72 | } catch (e: CertificateException) { 73 | // ignore 74 | } 75 | } 76 | throw CertificateException(UNTRUSTED_CERTIFICATE) 77 | } 78 | 79 | override fun getAcceptedIssuers(): Array { 80 | val list = ArrayList() 81 | for (tm in x509TrustManagers) { 82 | list.addAll(Arrays.asList(*tm.acceptedIssuers)) 83 | } 84 | return list.toTypedArray() 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/benoitduffez/cupsprint/ssl/AndroidCupsHostnameVerifier.kt: -------------------------------------------------------------------------------- 1 | package io.github.benoitduffez.cupsprint.ssl 2 | 3 | import android.content.Context 4 | import javax.net.ssl.HostnameVerifier 5 | import javax.net.ssl.SSLSession 6 | 7 | import io.github.benoitduffez.cupsprint.app.HostNotVerifiedActivity 8 | 9 | /** 10 | * Used with [HostNotVerifiedActivity] to trust certain hosts 11 | */ 12 | class AndroidCupsHostnameVerifier(val context: Context) : HostnameVerifier { 13 | override fun verify(hostname: String, session: SSLSession): Boolean = 14 | HostNotVerifiedActivity.isHostnameTrusted(context, hostname) 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/org/cups4j/CupsClient.kt: -------------------------------------------------------------------------------- 1 | package org.cups4j 2 | 3 | /** 4 | * Copyright (C) 2009 Harald Weyhing 5 | * 6 | * 7 | * This program is free software; you can redistribute it and/or modify it under the terms of the 8 | * GNU Lesser General Public License as published by the Free Software Foundation; either version 3 9 | * of the License, or (at your option) any later version. 10 | * 11 | * 12 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 13 | * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * 16 | * See the GNU Lesser General Public License for more details. You should have received a copy of 17 | * the GNU Lesser General Public License along with this program; if not, see 18 | * //www.gnu.org/licenses/>. 19 | */ 20 | 21 | /*Notice 22 | * This file has been modified. It is not the original. 23 | * Jon Freeman - 2013 24 | */ 25 | 26 | 27 | import android.content.Context 28 | import org.cups4j.operations.cups.CupsGetDefaultOperation 29 | import org.cups4j.operations.cups.CupsGetPrintersOperation 30 | import org.cups4j.operations.cups.CupsMoveJobOperation 31 | import org.cups4j.operations.ipp.IppCancelJobOperation 32 | import org.cups4j.operations.ipp.IppGetJobAttributesOperation 33 | import org.cups4j.operations.ipp.IppGetJobsOperation 34 | import org.cups4j.operations.ipp.IppHoldJobOperation 35 | import org.cups4j.operations.ipp.IppReleaseJobOperation 36 | import java.net.URL 37 | import java.security.cert.X509Certificate 38 | import timber.log.Timber 39 | 40 | /** 41 | * Main Client for accessing CUPS features like 42 | * - get printers 43 | * - print documents 44 | * - get job attributes 45 | * - ... 46 | */ 47 | class CupsClient @JvmOverloads constructor( 48 | val context: Context, 49 | private val url: URL = URL(DEFAULT_URL), 50 | private val userName: String = DEFAULT_USER 51 | ) { 52 | var serverCerts: Array? = null 53 | private set // Storage for server certificates if they're not trusted 54 | 55 | var lastResponseCode: Int = 0 56 | private set 57 | 58 | /** 59 | * Path to list of printers, like xxx://ip:port/printers/printer_name => will contain '/printers/' 60 | * seen in issue: https://github.com/BenoitDuffez/AndroidCupsPrint/issues/40 61 | */ 62 | private var path = "/printers/" 63 | 64 | // add default printer if available 65 | @Throws(Exception::class) 66 | fun getPrinters(firstName: String? = null, limit: Int? = null): List { 67 | val cupsGetPrintersOperation = CupsGetPrintersOperation(context) 68 | val printers: List 69 | try { 70 | printers = cupsGetPrintersOperation.getPrinters(url, path, firstName, limit) 71 | } finally { 72 | serverCerts = cupsGetPrintersOperation.serverCerts 73 | lastResponseCode = cupsGetPrintersOperation.lastResponseCode 74 | } 75 | val defaultPrinter = defaultPrinter 76 | 77 | for (p in printers) { 78 | if (defaultPrinter != null && p.printerURL.toString() == defaultPrinter.printerURL.toString()) { 79 | p.isDefault = true 80 | } 81 | } 82 | 83 | return printers 84 | } 85 | 86 | private val defaultPrinter: CupsPrinter? 87 | @Throws(Exception::class) 88 | get() = CupsGetDefaultOperation(context).getDefaultPrinter(url, path) 89 | 90 | val host: String 91 | get() = url.host 92 | 93 | @Throws(Exception::class) 94 | fun getPrinter(printerURL: URL): CupsPrinter? { 95 | // Extract the printer name 96 | var name = printerURL.path 97 | val pos = name.indexOf('/', 1) 98 | if (pos > 0) { 99 | name = name.substring(pos + 1) 100 | } 101 | 102 | val printers = getPrinters(name, 1) 103 | 104 | Timber.d("getPrinter: Found ${printers.size} possible CupsPrinters") 105 | for (p in printers) { 106 | if (p.printerURL.path == printerURL.path) 107 | return p 108 | } 109 | return null 110 | } 111 | 112 | @Throws(Exception::class) 113 | fun getJobAttributes(jobID: Int): PrintJobAttributes = getJobAttributes(url, userName, jobID) 114 | 115 | @Throws(Exception::class) 116 | fun getJobAttributes(userName: String, jobID: Int): PrintJobAttributes = 117 | getJobAttributes(url, userName, jobID) 118 | 119 | @Throws(Exception::class) 120 | private fun getJobAttributes(url: URL, userName: String, jobID: Int): PrintJobAttributes = 121 | IppGetJobAttributesOperation(context).getPrintJobAttributes(url, userName, jobID) 122 | 123 | @Throws(Exception::class) 124 | fun getJobs(printer: CupsPrinter, whichJobs: WhichJobsEnum, userName: String, myJobs: Boolean): List = 125 | IppGetJobsOperation(context).getPrintJobs(printer, whichJobs, userName, myJobs) 126 | 127 | @Throws(Exception::class) 128 | fun cancelJob(jobID: Int): Boolean = IppCancelJobOperation(context).cancelJob(url, userName, jobID) 129 | 130 | @Throws(Exception::class) 131 | fun cancelJob(url: URL, userName: String, jobID: Int): Boolean = 132 | IppCancelJobOperation(context).cancelJob(url, userName, jobID) 133 | 134 | @Throws(Exception::class) 135 | fun holdJob(jobID: Int): Boolean = IppHoldJobOperation(context).holdJob(url, userName, jobID) 136 | 137 | @Throws(Exception::class) 138 | fun holdJob(url: URL, userName: String, jobID: Int): Boolean = 139 | IppHoldJobOperation(context).holdJob(url, userName, jobID) 140 | 141 | @Throws(Exception::class) 142 | fun releaseJob(jobID: Int): Boolean = IppReleaseJobOperation(context).releaseJob(url, userName, jobID) 143 | 144 | @Throws(Exception::class) 145 | fun releaseJob(url: URL, userName: String, jobID: Int): Boolean = 146 | IppReleaseJobOperation(context).releaseJob(url, userName, jobID) 147 | 148 | @Throws(Exception::class) 149 | fun moveJob(jobID: Int, userName: String, currentPrinter: CupsPrinter, targetPrinter: CupsPrinter): Boolean { 150 | val currentHost = currentPrinter.printerURL.host 151 | 152 | return CupsMoveJobOperation(context).moveJob(currentHost, userName, jobID, targetPrinter.printerURL) 153 | } 154 | 155 | /** 156 | * Ensure path starts and ends with a slash 157 | * 158 | * @param path Path to printers on server 159 | * @return Self for easy chained calls 160 | */ 161 | fun setPath(path: String): CupsClient { 162 | this.path = path 163 | if (!path.startsWith("/")) { 164 | this.path = "/$path" 165 | } 166 | if (!path.endsWith("/")) { 167 | this.path += "/" 168 | } 169 | return this 170 | } 171 | 172 | companion object { 173 | const val DEFAULT_USER = "anonymous" 174 | private const val DEFAULT_URL = "http://localhost:631" 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /app/src/main/java/org/cups4j/JobStateEnum.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2009 Harald Weyhing 3 | * 4 | * 5 | * This program is free software; you can redistribute it and/or modify it under 6 | * the terms of the GNU Lesser General Public License as published by the Free 7 | * Software Foundation; either version 3 of the License, or (at your option) any 8 | * later version. 9 | * 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. 14 | * 15 | * 16 | * See the GNU Lesser General Public License for more details. You should have 17 | * received a copy of the GNU Lesser General Public License along with this 18 | * program; if not, see //www.gnu.org/licenses/>. 19 | */ 20 | package org.cups4j 21 | 22 | /** 23 | * State of print jobs 24 | */ 25 | enum class JobStateEnum(val text: String) { 26 | PENDING("pending"), 27 | PENDING_HELD("pending-held"), 28 | PROCESSING("processing"), 29 | PROCESSING_STOPPED("processing-stopped"), 30 | CANCELED("canceled"), 31 | ABORTED("aborted"), 32 | COMPLETED("completed"); 33 | 34 | override fun toString(): String = text 35 | 36 | companion object { 37 | fun fromString(value: String?): JobStateEnum? = JobStateEnum.values().firstOrNull { value?.equals(it.text, ignoreCase = true) == true } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/org/cups4j/PrintJob.kt: -------------------------------------------------------------------------------- 1 | package org.cups4j 2 | 3 | /** 4 | * Copyright (C) 2009 Harald Weyhing 5 | * 6 | * 7 | * This file is part of Cups4J. Cups4J is free software: you can redistribute it and/or modify it 8 | * under the terms of the GNU Lesser General Public License as published by the Free Software 9 | * Foundation, either version 3 of the License, or (at your option) any later version. 10 | * 11 | * 12 | * Cups4J is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even 13 | * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 14 | * General Public License for more details. 15 | * 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License along with Cups4J. If 18 | * not, see //www.gnu.org/licenses/>. 19 | */ 20 | 21 | import java.io.ByteArrayInputStream 22 | import java.io.InputStream 23 | 24 | /** 25 | * Print job class 26 | */ 27 | class PrintJob internal constructor(builder: Builder) { 28 | val document: InputStream 29 | val copies: Int 30 | val pageRanges: String? 31 | val userName: String? 32 | val jobName: String? 33 | var isDuplex = false 34 | var attributes: MutableMap? = null 35 | 36 | init { 37 | this.document = builder.document 38 | this.jobName = builder.jobName 39 | this.copies = builder.copies 40 | this.pageRanges = builder.pageRanges 41 | this.userName = builder.userName 42 | this.isDuplex = builder.duplex 43 | this.attributes = builder.attributes 44 | } 45 | 46 | /** 47 | * 48 | * 49 | * Builds PrintJob objects like so: 50 | * 51 | * 52 | * 53 | * PrintJob printJob = new 54 | * PrintJob.Builder(document).jobName("jobXY").userName 55 | * ("harald").copies(2).build(); 56 | * 57 | * 58 | * 59 | * documents are supplied as byte[] or as InputStream 60 | * 61 | */ 62 | class Builder { 63 | var document: InputStream 64 | var copies = 1 65 | var pageRanges: String? = null 66 | var userName: String? = null 67 | var jobName: String? = null 68 | var duplex = false 69 | var attributes: MutableMap? = null 70 | 71 | /** 72 | * Constructor 73 | * 74 | * @param document Printed document 75 | */ 76 | constructor(document: ByteArray) { 77 | this.document = ByteArrayInputStream(document) 78 | } 79 | 80 | /** 81 | * Constructor 82 | * 83 | * @param document Printed document 84 | */ 85 | constructor(document: InputStream) { 86 | this.document = document 87 | } 88 | 89 | /** 90 | * @param copies Number of copies - 0 and 1 are both treated as one copy 91 | * @return Builder 92 | */ 93 | fun copies(copies: Int): Builder { 94 | this.copies = copies 95 | return this 96 | } 97 | 98 | /** 99 | * @param pageRanges Page ranges 1-3, 5, 8, 10-13 100 | * @return Builder 101 | */ 102 | fun pageRanges(pageRanges: String): Builder { 103 | this.pageRanges = pageRanges 104 | return this 105 | } 106 | 107 | /** 108 | * @param userName Requesting user name 109 | * @return Builder 110 | */ 111 | fun userName(userName: String): Builder { 112 | this.userName = userName 113 | return this 114 | } 115 | 116 | /** 117 | * @param jobName Job name 118 | * @return Builder 119 | */ 120 | fun jobName(jobName: String): Builder { 121 | this.jobName = jobName 122 | return this 123 | } 124 | 125 | /** 126 | * @param duplex Duplex mode 127 | * @return Builder 128 | */ 129 | fun duplex(duplex: Boolean): Builder { 130 | this.duplex = duplex 131 | return this 132 | } 133 | 134 | /** 135 | * Additional attributes for the print operation and the print job 136 | * 137 | * @param attributes provide operation attributes and/or a String of job-attributes 138 | * 139 | * job attributes are seperated by "#" 140 | * 141 | * example: 142 | * ` 143 | * attributes.put("compression","none"); 144 | * attributes.put("job-attributes", 145 | * "print-quality:enum:3#sheet-collate:keyword:collated#sides:keyword:two-sided-long-edge" 146 | * ); 147 | * ` 148 | * -> take a look config/ippclient/list-of-attributes.xml for more information 149 | * 150 | * @return Builder 151 | */ 152 | fun attributes(attributes: MutableMap): Builder { 153 | this.attributes = attributes 154 | return this 155 | } 156 | 157 | /** 158 | * Builds the PrintJob object. 159 | * 160 | * @return PrintJob 161 | */ 162 | fun build(): PrintJob = PrintJob(this) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /app/src/main/java/org/cups4j/PrintJobAttributes.kt: -------------------------------------------------------------------------------- 1 | package org.cups4j 2 | 3 | /** 4 | * Copyright (C) 2009 Harald Weyhing 5 | * 6 | * 7 | * This program is free software; you can redistribute it and/or modify it under the terms of the 8 | * GNU Lesser General Public License as published by the Free Software Foundation; either version 3 9 | * of the License, or (at your option) any later version. 10 | * 11 | * 12 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 13 | * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * 16 | * See the GNU Lesser General Public License for more details. You should have received a copy of 17 | * the GNU Lesser General Public License along with this program; if not, see 18 | * //www.gnu.org/licenses/>. 19 | */ 20 | 21 | import java.net.URL 22 | import java.util.Date 23 | 24 | /** 25 | * Holds print job attributes 26 | */ 27 | class PrintJobAttributes { 28 | var jobURL: URL? = null 29 | var printerURL: URL? = null 30 | var jobID = -1 31 | var jobState: JobStateEnum? = null 32 | var jobName: String? = null 33 | var userName: String? = null 34 | var jobCreateTime: Date? = null 35 | var jobCompleteTime: Date? = null 36 | var pagesPrinted = 0 37 | 38 | // Size of the job in kb (this value is rounded up by the IPP server) 39 | // This value is optional and might not be reported by your IPP server 40 | var size = -1 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/org/cups4j/PrintRequestResult.kt: -------------------------------------------------------------------------------- 1 | package org.cups4j 2 | 3 | /** 4 | * Copyright (C) 2009 Harald Weyhing 5 | * 6 | * 7 | * This program is free software; you can redistribute it and/or modify it under the terms of the 8 | * GNU Lesser General Public License as published by the Free Software Foundation; either version 3 9 | * of the License, or (at your option) any later version. 10 | * 11 | * 12 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 13 | * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * 16 | * See the GNU Lesser General Public License for more details. You should have received a copy of 17 | * the GNU Lesser General Public License along with this program; if not, see 18 | * //www.gnu.org/licenses/>. 19 | */ 20 | 21 | import ch.ethz.vppserver.ippclient.IppResult 22 | import java.util.regex.Pattern 23 | 24 | /** 25 | * Result of a print request 26 | */ 27 | class PrintRequestResult(ippResult: IppResult?) { 28 | var jobId: Int = 0 29 | private var resultCode: String? = "" 30 | private var resultDescription = "" 31 | val isSuccessfulResult: Boolean 32 | get() = resultCode != null && resultCode!!.startsWith("0x00") 33 | 34 | init { 35 | if (ippResult != null && !isNullOrEmpty(ippResult.httpStatusResponse)) { 36 | initializeFromHttpStatusResponse(ippResult) 37 | if (ippResult.ippStatusResponse != null) { 38 | initializeFromIppStatusResponse(ippResult) 39 | } 40 | } 41 | } 42 | 43 | private fun initializeFromIppStatusResponse(ippResult: IppResult) { 44 | val p = Pattern.compile("Status Code:(0x\\d+)(.*)") 45 | val m = p.matcher(ippResult.ippStatusResponse!!) 46 | if (m.find()) { 47 | resultCode = m.group(1) 48 | resultDescription = m.group(2) 49 | } 50 | } 51 | 52 | private fun initializeFromHttpStatusResponse(ippResult: IppResult) { 53 | val p = Pattern.compile("HTTP/1.0 (\\d+) (.*)") 54 | val m = p.matcher(ippResult.httpStatusResponse!!) 55 | if (m.find()) { 56 | resultCode = m.group(1) 57 | resultDescription = m.group(2) 58 | } 59 | } 60 | 61 | private fun isNullOrEmpty(string: String?): Boolean = string == null || "" == string.trim { it <= ' ' } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/org/cups4j/WhichJobsEnum.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2009 Harald Weyhing 3 | * 4 | * 5 | * This program is free software; you can redistribute it and/or modify it under 6 | * the terms of the GNU Lesser General Public License as published by the Free 7 | * Software Foundation; either version 3 of the License, or (at your option) any 8 | * later version. 9 | * 10 | * 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. 14 | * 15 | * 16 | * See the GNU Lesser General Public License for more details. You should have 17 | * received a copy of the GNU Lesser General Public License along with this 18 | * program; if not, see //www.gnu.org/licenses/>. 19 | */ 20 | package org.cups4j 21 | 22 | /** 23 | * Used while querying print jobs to define which jobs should be returned. 24 | */ 25 | enum class WhichJobsEnum(val value: String) { 26 | COMPLETED("completed"), 27 | NOT_COMPLETED("not-completed"), 28 | ALL("all") 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/org/cups4j/operations/cups/CupsGetDefaultOperation.kt: -------------------------------------------------------------------------------- 1 | package org.cups4j.operations.cups 2 | 3 | /** 4 | * Copyright (C) 2009 Harald Weyhing 5 | * 6 | * 7 | * This program is free software; you can redistribute it and/or modify it under the terms of the 8 | * GNU Lesser General Public License as published by the Free Software Foundation; either version 3 9 | * of the License, or (at your option) any later version. 10 | * 11 | * 12 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 13 | * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * 16 | * See the GNU Lesser General Public License for more details. You should have received a copy of 17 | * the GNU Lesser General Public License along with this program; if not, see 18 | * //www.gnu.org/licenses/>. 19 | */ 20 | 21 | /*Notice 22 | * This file has been modified. It is not the original. 23 | * Jon Freeman - 2013 24 | */ 25 | 26 | import android.content.Context 27 | import org.cups4j.CupsPrinter 28 | import org.cups4j.operations.IppOperation 29 | 30 | import java.net.URL 31 | import java.util.HashMap 32 | 33 | const val DEFAULT_PRINTER_NAME = "Unknown printer" 34 | 35 | class CupsGetDefaultOperation(context: Context) : IppOperation(context) { 36 | init { 37 | operationID = 0x4001 38 | bufferSize = 8192 39 | } 40 | 41 | @Throws(Exception::class) 42 | fun getDefaultPrinter(url: URL, path: String): CupsPrinter? { 43 | var defaultPrinter: CupsPrinter? = null 44 | val command = CupsGetDefaultOperation(context) 45 | 46 | val map = HashMap() 47 | map["requested-attributes"] = "printer-name printer-uri-supported printer-location" 48 | 49 | val result = command.request(URL(url.toString() + path), map) 50 | for (group in result!!.attributeGroupList!!) { 51 | if (group.tagName == "printer-attributes-tag") { 52 | var printerURL: String? = null 53 | var printerName: String? = null 54 | var location: String? = null 55 | for (attr in group.attribute) { 56 | when (attr.name) { 57 | "printer-uri-supported" -> printerURL = attr.attributeValue[0].value!!.replace("ipps?://".toRegex(), url.protocol + "://") 58 | "printer-name" -> printerName = attr.attributeValue[0].value 59 | "printer-location" -> if (attr.attributeValue.size > 0) { 60 | location = attr.attributeValue[0].value 61 | } 62 | } 63 | } 64 | defaultPrinter = CupsPrinter(URL(printerURL), printerName ?: DEFAULT_PRINTER_NAME, true) 65 | defaultPrinter.location = location 66 | } 67 | } 68 | 69 | return defaultPrinter 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/org/cups4j/operations/cups/CupsGetPPDOperation.kt: -------------------------------------------------------------------------------- 1 | package org.cups4j.operations.cups 2 | 3 | /** 4 | * @author Frank Carnevale 5 | * / * 6 | * 7 | * 8 | * This program is free software; you can redistribute it and/or modify it under 9 | * the terms of the GNU Lesser General Public License as published by the Free 10 | * Software Foundation; either version 3 of the License, or (at your option) any 11 | * later version. 12 | * 13 | * 14 | * This program is distributed in the hope that it will be useful, but WITHOUT 15 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 16 | * FOR A PARTICULAR PURPOSE. 17 | * 18 | * 19 | * See the GNU Lesser General Public License for more details. You should have 20 | * received a copy of the GNU Lesser General Public License along with this 21 | * program; if not, see //www.gnu.org/licenses/>. 22 | */ 23 | 24 | /*Notice. This file is not part of the original cups4j. It is an implementaion 25 | * of a patch to cups4j suggested by Frank Carnevale 26 | */ 27 | 28 | import android.content.Context 29 | import ch.ethz.vppserver.ippclient.IppTag 30 | import org.cups4j.operations.IppOperation 31 | import java.io.UnsupportedEncodingException 32 | import java.net.URL 33 | import java.nio.ByteBuffer 34 | import java.util.HashMap 35 | 36 | class CupsGetPPDOperation(context: Context) : IppOperation(context) { 37 | init { 38 | operationID = 0x400F 39 | bufferSize = 8192 40 | } 41 | 42 | @Throws(UnsupportedEncodingException::class) 43 | override fun getIppHeader(url: URL, map: Map?): ByteBuffer { 44 | var ippBuf = ByteBuffer.allocateDirect(bufferSize.toInt()) 45 | ippBuf = IppTag.getOperation(ippBuf, operationID) 46 | 47 | if (map == null) { 48 | ippBuf = IppTag.getEnd(ippBuf) 49 | ippBuf.flip() 50 | return ippBuf 51 | } 52 | 53 | ippBuf = IppTag.getUri(ippBuf, "printer-uri", map["printer-uri"]) 54 | ippBuf = IppTag.getEnd(ippBuf) 55 | ippBuf.flip() 56 | return ippBuf 57 | } 58 | 59 | @Throws(Exception::class) 60 | fun getPPDFile(printerUrl: URL): String { 61 | val url = URL(printerUrl.protocol + "://" + printerUrl.host + ":" + printerUrl.port) 62 | val map = HashMap() 63 | map["printer-uri"] = printerUrl.path 64 | val result = request(url, map) 65 | val buf = String(result!!.buf!!) 66 | return buf.substring(buf.indexOf("*")) // Remove request attributes when returning the string 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/org/cups4j/operations/cups/CupsGetPrintersOperation.kt: -------------------------------------------------------------------------------- 1 | package org.cups4j.operations.cups 2 | 3 | /** 4 | * Copyright (C) 2009 Harald Weyhing 5 | * 6 | * 7 | * This program is free software; you can redistribute it and/or modify it under the terms of the 8 | * GNU Lesser General Public License as published by the Free Software Foundation; either version 3 9 | * of the License, or (at your option) any later version. 10 | * 11 | * 12 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 13 | * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * 16 | * See the GNU Lesser General Public License for more details. You should have received a copy of 17 | * the GNU Lesser General Public License along with this program; if not, see 18 | * //www.gnu.org/licenses/>. 19 | */ 20 | 21 | /*Notice 22 | * This file has been modified. It is not the original. 23 | * Jon Freeman - 2013 24 | */ 25 | 26 | import android.content.Context 27 | import org.cups4j.CupsPrinter 28 | import org.cups4j.operations.IppOperation 29 | import timber.log.Timber 30 | import java.net.URL 31 | import java.util.ArrayList 32 | import java.util.HashMap 33 | 34 | class CupsGetPrintersOperation(context: Context) : IppOperation(context) { 35 | init { 36 | operationID = 0x4002 37 | bufferSize = 8192 38 | } 39 | 40 | @Throws(Exception::class) 41 | fun getPrinters(url: URL, path: String, firstName: String? = null, limit: Int? = null): List { 42 | val printers = ArrayList() 43 | 44 | val map = HashMap() 45 | map["requested-attributes"] = "copies-supported page-ranges-supported printer-name printer-info printer-location printer-make-and-model printer-uri-supported" 46 | 47 | // When a firstName is given, the returned list starts with this printer 48 | if (firstName != null) { 49 | map["first-printer-name"] = firstName 50 | } 51 | 52 | // When a limit is given, this is the maximum length of the returned list 53 | if (limit != null) { 54 | if (limit >= 1) { 55 | map["limit"] = limit.toString() 56 | } 57 | } 58 | 59 | val result = request(URL(url.toString() + path), map) 60 | 61 | if (result == null) { 62 | Timber.e("Couldn't get printers from URL: $url with path: $path") 63 | return printers 64 | } 65 | 66 | for (group in result.attributeGroupList!!) { 67 | val printer: CupsPrinter 68 | if (group.tagName == "printer-attributes-tag") { 69 | var printerURI: String? = null 70 | var printerName: String? = null 71 | var printerLocation: String? = null 72 | var printerDescription: String? = null 73 | for (attr in group.attribute) { 74 | when (attr.name) { 75 | "printer-uri-supported" -> printerURI = attr.attributeValue[0].value!!.replace("ipps?://".toRegex(), url.protocol + "://") 76 | "printer-name" -> printerName = attr.attributeValue[0].value 77 | "printer-location" -> if (attr.attributeValue.size > 0) { 78 | printerLocation = attr.attributeValue[0].value 79 | } 80 | "printer-info" -> if (attr.attributeValue.size > 0) { 81 | printerDescription = attr.attributeValue[0].value 82 | } 83 | } 84 | } 85 | val printerUrl: URL 86 | try { 87 | printerUrl = URL(printerURI) 88 | } catch (t: Throwable) { 89 | t.printStackTrace() 90 | System.err.println("Error encountered building URL from printer uri of printer " + printerName 91 | + ", uri returned was [" + printerURI + "]. Attribute group tag/description: [" + group.tagName 92 | + "/" + group.description) 93 | throw Exception(t) 94 | } 95 | 96 | printer = CupsPrinter(printerUrl, printerName ?: DEFAULT_PRINTER_NAME, false) 97 | printer.location = printerLocation 98 | printer.description = printerDescription 99 | printers.add(printer) 100 | } 101 | } 102 | 103 | return printers 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /app/src/main/java/org/cups4j/operations/cups/CupsMoveJobOperation.kt: -------------------------------------------------------------------------------- 1 | package org.cups4j.operations.cups 2 | 3 | /** 4 | * Copyright (C) 2011 Harald Weyhing 5 | * 6 | * 7 | * This program is free software; you can redistribute it and/or modify it under the terms of the 8 | * GNU Lesser General Public License as published by the Free Software Foundation; either version 3 9 | * of the License, or (at your option) any later version. 10 | * 11 | * 12 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 13 | * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * 16 | * See the GNU Lesser General Public License for more details. You should have received a copy of 17 | * the GNU Lesser General Public License along with this program; if not, see 18 | * //www.gnu.org/licenses/>. 19 | */ 20 | 21 | /*Notice 22 | * This file has been modified. It is not the original. 23 | * Jon Freeman - 2013 24 | */ 25 | 26 | import android.content.Context 27 | import ch.ethz.vppserver.ippclient.IppTag 28 | import org.cups4j.CupsClient 29 | import org.cups4j.PrintRequestResult 30 | import org.cups4j.operations.IppOperation 31 | import java.io.UnsupportedEncodingException 32 | import java.net.URL 33 | import java.nio.ByteBuffer 34 | import java.util.HashMap 35 | 36 | class CupsMoveJobOperation(context: Context) : IppOperation(context) { 37 | init { 38 | operationID = 0x400D 39 | bufferSize = 8192 40 | } 41 | 42 | @Throws(UnsupportedEncodingException::class) 43 | override fun getIppHeader(url: URL, map: Map?): ByteBuffer { 44 | var ippBuf = ByteBuffer.allocateDirect(bufferSize.toInt()) 45 | ippBuf = IppTag.getOperation(ippBuf, operationID) 46 | // ippBuf = IppTag.getUri(ippBuf, "job-uri", stripPortNumber(url)); 47 | 48 | if (map == null) { 49 | ippBuf = IppTag.getEnd(ippBuf) 50 | ippBuf.flip() 51 | return ippBuf 52 | } 53 | 54 | map["job-id"]?.let { 55 | ippBuf = IppTag.getUri(ippBuf, "printer-uri", stripPortNumber(url)) 56 | ippBuf = IppTag.getInteger(ippBuf, "job-id", it.toInt()) 57 | } ?: run { 58 | ippBuf = IppTag.getUri(ippBuf, "job-uri", stripPortNumber(url)) 59 | } 60 | 61 | ippBuf = IppTag.getNameWithoutLanguage(ippBuf, "requesting-user-name", map["requesting-user-name"]) 62 | ippBuf = IppTag.getUri(ippBuf, "job-printer-uri", map["target-printer-uri"]) 63 | ippBuf = IppTag.getEnd(ippBuf) 64 | ippBuf?.flip() 65 | return ippBuf 66 | } 67 | 68 | @Throws(Exception::class) 69 | fun moveJob(hostname: String, userName: String?, jobID: Int, targetPrinterURL: URL): Boolean { 70 | val url = URL("http://" + hostname + "/jobs/" + Integer.toString(jobID)) 71 | val map = HashMap() 72 | map["requesting-user-name"] = userName ?: CupsClient.DEFAULT_USER 73 | map["job-uri"] = url.toString() 74 | map["target-printer-uri"] = stripPortNumber(targetPrinterURL) 75 | 76 | val result = request(url, map) 77 | // IppResultPrinter.print(result); 78 | return PrintRequestResult(result).isSuccessfulResult 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/org/cups4j/operations/ipp/IppCancelJobOperation.kt: -------------------------------------------------------------------------------- 1 | package org.cups4j.operations.ipp 2 | 3 | /** 4 | * Copyright (C) 2011 Harald Weyhing 5 | * 6 | * 7 | * This program is free software; you can redistribute it and/or modify it under the terms of the 8 | * GNU Lesser General Public License as published by the Free Software Foundation; either version 3 9 | * of the License, or (at your option) any later version. 10 | * 11 | * 12 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 13 | * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * 16 | * See the GNU Lesser General Public License for more details. You should have received a copy of 17 | * the GNU Lesser General Public License along with this program; if not, see 18 | * //www.gnu.org/licenses/>. 19 | */ 20 | 21 | /*Notice 22 | * This file has been modified. It is not the original. 23 | * Jon Freeman - 2013 24 | */ 25 | 26 | import android.content.Context 27 | import org.cups4j.CupsClient 28 | import org.cups4j.PrintRequestResult 29 | import org.cups4j.operations.IppOperation 30 | 31 | import java.io.UnsupportedEncodingException 32 | import java.net.URL 33 | import java.nio.ByteBuffer 34 | import java.util.HashMap 35 | 36 | import ch.ethz.vppserver.ippclient.IppTag 37 | 38 | class IppCancelJobOperation(context: Context) : IppOperation(context) { 39 | init { 40 | operationID = 0x0008 41 | bufferSize = 8192 42 | } 43 | 44 | @Throws(UnsupportedEncodingException::class) 45 | override fun getIppHeader(url: URL, map: Map?): ByteBuffer { 46 | var ippBuf = ByteBuffer.allocateDirect(bufferSize.toInt()) 47 | ippBuf = IppTag.getOperation(ippBuf, operationID) 48 | 49 | if (map == null) { 50 | ippBuf = IppTag.getEnd(ippBuf) 51 | ippBuf.flip() 52 | return ippBuf 53 | } 54 | 55 | map["job-id"]?.let { 56 | ippBuf = IppTag.getUri(ippBuf, "printer-uri", stripPortNumber(url)) 57 | ippBuf = IppTag.getInteger(ippBuf, "job-id", it.toInt()) 58 | } ?: run { 59 | ippBuf = IppTag.getUri(ippBuf, "job-uri", stripPortNumber(url)) 60 | } 61 | 62 | ippBuf = IppTag.getNameWithoutLanguage(ippBuf, "requesting-user-name", map["requesting-user-name"]) 63 | 64 | if (map["message"] != null) { 65 | ippBuf = IppTag.getTextWithoutLanguage(ippBuf, "message", map["message"]) 66 | } 67 | 68 | ippBuf = IppTag.getEnd(ippBuf) 69 | ippBuf?.flip() 70 | return ippBuf 71 | } 72 | 73 | @Throws(Exception::class) 74 | fun cancelJob(url: URL, userName: String?, jobID: Int): Boolean { 75 | val requestUrl = URL(url.toString() + "/jobs/" + Integer.toString(jobID)) 76 | 77 | val map = HashMap() 78 | map["requesting-user-name"] = userName?:CupsClient.DEFAULT_USER 79 | map["job-uri"] = requestUrl.toString() 80 | 81 | return PrintRequestResult(request(requestUrl, map)).isSuccessfulResult 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/org/cups4j/operations/ipp/IppGetJobAttributesOperation.kt: -------------------------------------------------------------------------------- 1 | package org.cups4j.operations.ipp 2 | 3 | /** 4 | * Copyright (C) 2009 Harald Weyhing 5 | * 6 | * 7 | * This program is free software; you can redistribute it and/or modify it under the terms of the 8 | * GNU Lesser General Public License as published by the Free Software Foundation; either version 3 9 | * of the License, or (at your option) any later version. 10 | * 11 | * 12 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 13 | * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * 16 | * See the GNU Lesser General Public License for more details. You should have received a copy of 17 | * the GNU Lesser General Public License along with this program; if not, see 18 | * //www.gnu.org/licenses/>. 19 | */ 20 | 21 | /*Notice 22 | * This file has been modified. It is not the original. 23 | * Jon Freeman - 2013 24 | */ 25 | 26 | import android.content.Context 27 | import ch.ethz.vppserver.ippclient.IppTag 28 | import org.cups4j.JobStateEnum 29 | import org.cups4j.PrintJobAttributes 30 | import org.cups4j.operations.IppOperation 31 | import java.io.UnsupportedEncodingException 32 | import java.net.URL 33 | import java.nio.ByteBuffer 34 | import java.util.Date 35 | import java.util.HashMap 36 | 37 | class IppGetJobAttributesOperation(context: Context) : IppOperation(context) { 38 | init { 39 | operationID = 0x0009 40 | bufferSize = 8192 41 | } 42 | 43 | @Throws(UnsupportedEncodingException::class) 44 | override fun getIppHeader(url: URL, map: Map?): ByteBuffer { 45 | var ippBuf = ByteBuffer.allocateDirect(bufferSize.toInt()) 46 | ippBuf = IppTag.getOperation(ippBuf, operationID) 47 | 48 | if (map == null) { 49 | ippBuf = IppTag.getUri(ippBuf, "job-uri", stripPortNumber(url)) 50 | ippBuf = IppTag.getEnd(ippBuf) 51 | ippBuf.flip() 52 | return ippBuf 53 | } 54 | 55 | map["job-id"]?.let { 56 | ippBuf = IppTag.getUri(ippBuf, "printer-uri", stripPortNumber(url)) 57 | ippBuf = IppTag.getInteger(ippBuf, "job-id", it.toInt()) 58 | } ?: run { 59 | ippBuf = IppTag.getUri(ippBuf, "job-uri", stripPortNumber(url)) 60 | } 61 | 62 | ippBuf = IppTag.getNameWithoutLanguage(ippBuf, "requesting-user-name", map["requesting-user-name"]) 63 | 64 | map["requested-attributes"]?.let { requestedAttributes -> 65 | val sta = requestedAttributes.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 66 | ippBuf = IppTag.getKeyword(ippBuf, "requested-attributes", sta[0]) 67 | val l = sta.size 68 | for (i in 1 until l) { 69 | ippBuf = IppTag.getKeyword(ippBuf, null, sta[i]) 70 | } 71 | } 72 | 73 | if (map["which-jobs"] != null) { 74 | ippBuf = IppTag.getKeyword(ippBuf, "which-jobs", map["which-jobs"]) 75 | } 76 | 77 | if (map["my-jobs"] != null) { 78 | var value = false 79 | if (map["my-jobs"] == "true") { 80 | value = true 81 | } 82 | ippBuf = IppTag.getBoolean(ippBuf, "my-jobs", value) 83 | } 84 | ippBuf = IppTag.getEnd(ippBuf) 85 | ippBuf?.flip() 86 | return ippBuf 87 | } 88 | 89 | @Throws(Exception::class) 90 | fun getPrintJobAttributes(url: URL, userName: String, jobID: Int): PrintJobAttributes { 91 | val job = PrintJobAttributes() 92 | 93 | val map = HashMap() 94 | // map.put("requested-attributes", 95 | // "page-ranges print-quality sides job-uri job-id job-state job-printer-uri job-name job-originating-user-name job-k-octets time-at-creation time-at-processing time-at-completed job-media-sheets-completed"); 96 | 97 | map["requested-attributes"] = "all" 98 | map["requesting-user-name"] = userName 99 | val result = request(URL(url.toString() + "/jobs/" + jobID), map) 100 | 101 | // IppResultPrinter.print(result); 102 | for (group in result!!.attributeGroupList!!) { 103 | if ("job-attributes-tag" == group.tagName || "unassigned" == group.tagName) { 104 | for (attr in group.attribute) { 105 | if (!attr.attributeValue.isEmpty()) { 106 | val attValue = getAttributeValue(attr) 107 | 108 | when { 109 | "job-uri" == attr.name -> job.jobURL = URL(attValue.replace("ipp://", "http://")) 110 | "job-id" == attr.name -> job.jobID = Integer.parseInt(attValue) 111 | "job-state" == attr.name -> { 112 | println("job-state $attValue") 113 | job.jobState = JobStateEnum.fromString(attValue) 114 | } 115 | "job-printer-uri" == attr.name -> job.printerURL = URL(attValue.replace("ipp://", "http://")) 116 | "job-name" == attr.name -> job.jobName = attValue 117 | "job-originating-user-name" == attr.name -> job.userName = attValue 118 | "job-k-octets" == attr.name -> job.size = Integer.parseInt(attValue) 119 | "time-at-creation" == attr.name -> job.jobCreateTime = Date(1000 * java.lang.Long.parseLong(attValue)) 120 | "time-at-completed" == attr.name -> job.jobCompleteTime = Date(1000 * java.lang.Long.parseLong(attValue)) 121 | "job-media-sheets-completed" == attr.name -> job.pagesPrinted = Integer.parseInt(attValue) 122 | } 123 | } 124 | } 125 | } 126 | } 127 | 128 | // IppResultPrinter.print(result); 129 | return job 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /app/src/main/java/org/cups4j/operations/ipp/IppGetJobsOperation.kt: -------------------------------------------------------------------------------- 1 | package org.cups4j.operations.ipp 2 | 3 | /** 4 | * Copyright (C) 2009 Harald Weyhing 5 | * 6 | * 7 | * This program is free software; you can redistribute it and/or modify it under the terms of the 8 | * GNU Lesser General Public License as published by the Free Software Foundation; either version 3 9 | * of the License, or (at your option) any later version. 10 | * 11 | * 12 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 13 | * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * 16 | * See the GNU Lesser General Public License for more details. You should have received a copy of 17 | * the GNU Lesser General Public License along with this program; if not, see 18 | * //www.gnu.org/licenses/>. 19 | * 20 | * 21 | * Notice this file has been modified. It is not the original. 22 | * Job Creation Time added Jon Freeman 2013 23 | */ 24 | 25 | /** 26 | * Notice this file has been modified. It is not the original. 27 | * Job Creation Time added Jon Freeman 2013 28 | */ 29 | 30 | import android.content.Context 31 | import ch.ethz.vppserver.ippclient.IppTag 32 | import org.cups4j.CupsClient 33 | import org.cups4j.CupsPrinter 34 | import org.cups4j.JobStateEnum 35 | import org.cups4j.PrintJobAttributes 36 | import org.cups4j.WhichJobsEnum 37 | import org.cups4j.operations.IppOperation 38 | import java.io.UnsupportedEncodingException 39 | import java.net.URL 40 | import java.nio.ByteBuffer 41 | import java.util.ArrayList 42 | import java.util.Date 43 | import java.util.HashMap 44 | 45 | class IppGetJobsOperation(context: Context) : IppOperation(context) { 46 | init { 47 | operationID = 0x000a 48 | bufferSize = 8192 49 | } 50 | 51 | @Throws(UnsupportedEncodingException::class) 52 | override fun getIppHeader(url: URL, map: Map?): ByteBuffer { 53 | var ippBuf = ByteBuffer.allocateDirect(bufferSize.toInt()) 54 | 55 | //not sure why next line is here, it overwrites job attributes in map parameter - JF 56 | //map.put("requested-attributes", "job-name job-id job-state job-originating-user-name job-printer-uri copies"); 57 | 58 | ippBuf = IppTag.getOperation(ippBuf, operationID) 59 | ippBuf = IppTag.getUri(ippBuf, "printer-uri", stripPortNumber(url)) 60 | 61 | ippBuf = IppTag.getNameWithoutLanguage(ippBuf, "requesting-user-name", map!!["requesting-user-name"]) 62 | 63 | map["limit"]?.let { 64 | ippBuf = IppTag.getInteger(ippBuf, "limit", it.toInt()) 65 | } 66 | 67 | map["requested-attributes"]?.let { requestedAttributes -> 68 | val sta = requestedAttributes.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 69 | ippBuf = IppTag.getKeyword(ippBuf, "requested-attributes", sta[0]) 70 | val l = sta.size 71 | for (i in 1 until l) { 72 | ippBuf = IppTag.getKeyword(ippBuf, null, sta[i]) 73 | } 74 | } 75 | 76 | if (map["which-jobs"] != null) { 77 | ippBuf = IppTag.getKeyword(ippBuf, "which-jobs", map["which-jobs"]) 78 | } 79 | 80 | if (map["my-jobs"] != null) { 81 | var value = false 82 | if (map["my-jobs"] == "true") { 83 | value = true 84 | } 85 | ippBuf = IppTag.getBoolean(ippBuf, "my-jobs", value) 86 | } 87 | 88 | ippBuf = IppTag.getEnd(ippBuf) 89 | ippBuf?.flip() 90 | return ippBuf 91 | } 92 | 93 | @Throws(Exception::class) 94 | fun getPrintJobs(printer: CupsPrinter, whichJobs: WhichJobsEnum, userName: String?, 95 | myJobs: Boolean): List { 96 | val jobs = ArrayList() 97 | var jobAttributes: PrintJobAttributes 98 | val map = HashMap() 99 | 100 | map["requesting-user-name"] = userName ?: CupsClient.DEFAULT_USER 101 | map["which-jobs"] = whichJobs.value 102 | if (myJobs) { 103 | map["my-jobs"] = "true" 104 | } 105 | 106 | //time-at-creation added JF 107 | map["requested-attributes"] = "page-ranges print-quality sides time-at-creation job-uri job-id job-state job-printer-uri job-name job-originating-user-name" 108 | 109 | val result = request(printer.printerURL, map) 110 | val protocol = printer.printerURL.protocol + "://" 111 | 112 | for (group in result!!.attributeGroupList!!) { 113 | if ("job-attributes-tag" == group.tagName) { 114 | jobAttributes = PrintJobAttributes() 115 | for (attr in group.attribute) { 116 | if (!attr.attributeValue.isEmpty()) { 117 | val attValue = getAttributeValue(attr) 118 | 119 | when (attr.name) { 120 | "job-uri" -> jobAttributes.jobURL = URL(attValue.replace("ipp://", protocol)) 121 | "job-id" -> jobAttributes.jobID = Integer.parseInt(attValue) 122 | "job-state" -> jobAttributes.jobState = JobStateEnum.fromString(attValue) 123 | "job-printer-uri" -> jobAttributes.printerURL = URL(attValue.replace("ipp://", protocol)) 124 | "job-name" -> jobAttributes.jobName = attValue 125 | "job-originating-user-name" -> jobAttributes.userName = attValue 126 | "time-at-creation" -> { 127 | val unixTime = java.lang.Long.parseLong(attValue) 128 | val dt = Date(unixTime * 1000) 129 | jobAttributes.jobCreateTime = dt 130 | } 131 | } 132 | } 133 | } 134 | jobs.add(jobAttributes) 135 | } 136 | } 137 | 138 | return jobs 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /app/src/main/java/org/cups4j/operations/ipp/IppGetPrinterAttributesOperation.kt: -------------------------------------------------------------------------------- 1 | package org.cups4j.operations.ipp 2 | 3 | /** 4 | * Copyright (C) 2009 Harald Weyhing 5 | * 6 | * 7 | * This program is free software; you can redistribute it and/or modify it under 8 | * the terms of the GNU Lesser General Public License as published by the Free 9 | * Software Foundation; either version 3 of the License, or (at your option) any 10 | * later version. 11 | * 12 | * 13 | * This program is distributed in the hope that it will be useful, but WITHOUT 14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 | * FOR A PARTICULAR PURPOSE. 16 | * 17 | * 18 | * See the GNU Lesser General Public License for more details. You should have 19 | * received a copy of the GNU Lesser General Public License along with this 20 | * program; if not, see //www.gnu.org/licenses/>. 21 | */ 22 | 23 | /*Notice 24 | * This file has been modified. It is not the original. 25 | * Jon Freeman - 2013 26 | */ 27 | 28 | import android.content.Context 29 | import ch.ethz.vppserver.ippclient.IppTag 30 | import org.cups4j.operations.IppOperation 31 | import java.io.UnsupportedEncodingException 32 | import java.nio.ByteBuffer 33 | 34 | class IppGetPrinterAttributesOperation(context: Context) : IppOperation(context) { 35 | init { 36 | operationID = 0x000b 37 | bufferSize = 8192 38 | } 39 | 40 | @Throws(UnsupportedEncodingException::class) 41 | @JvmOverloads 42 | fun getIppHeader(url: String, map: Map? = null): ByteBuffer? { 43 | var ippBuf = ByteBuffer.allocateDirect(bufferSize.toInt()) 44 | 45 | ippBuf = IppTag.getOperation(ippBuf, operationID) 46 | ippBuf = IppTag.getUri(ippBuf, "printer-uri", url) 47 | 48 | if (map == null) { 49 | ippBuf = IppTag.getKeyword(ippBuf, "requested-attributes", "all") 50 | ippBuf = IppTag.getEnd(ippBuf) 51 | ippBuf.flip() 52 | return ippBuf 53 | } 54 | 55 | ippBuf = IppTag.getNameWithoutLanguage(ippBuf, "requesting-user-name", map["requesting-user-name"]) 56 | map["requested-attributes"]?.let { requestedAttributes -> 57 | val sta = requestedAttributes.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 58 | ippBuf = IppTag.getKeyword(ippBuf, "requested-attributes", sta[0]) 59 | val l = sta.size 60 | for (i in 1 until l) { 61 | ippBuf = IppTag.getKeyword(ippBuf, null, sta[i]) 62 | } 63 | } 64 | 65 | ippBuf = IppTag.getNameWithoutLanguage(ippBuf, "document-format", map["document-format"]) 66 | 67 | ippBuf = IppTag.getEnd(ippBuf) 68 | ippBuf?.flip() 69 | return ippBuf 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/org/cups4j/operations/ipp/IppHoldJobOperation.kt: -------------------------------------------------------------------------------- 1 | package org.cups4j.operations.ipp 2 | 3 | /** 4 | * Copyright (C) 2011 Harald Weyhing 5 | * 6 | * 7 | * This program is free software; you can redistribute it and/or modify it under the terms of the 8 | * GNU Lesser General Public License as published by the Free Software Foundation; either version 3 9 | * of the License, or (at your option) any later version. 10 | * 11 | * 12 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 13 | * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * 16 | * See the GNU Lesser General Public License for more details. You should have received a copy of 17 | * the GNU Lesser General Public License along with this program; if not, see 18 | * //www.gnu.org/licenses/>. 19 | */ 20 | 21 | /*Notice 22 | * This file has been modified. It is not the original. 23 | * Jon Freeman - 2013 24 | */ 25 | 26 | import android.content.Context 27 | import ch.ethz.vppserver.ippclient.IppTag 28 | import org.cups4j.CupsClient 29 | import org.cups4j.PrintRequestResult 30 | import org.cups4j.operations.IppOperation 31 | import java.io.UnsupportedEncodingException 32 | import java.net.URL 33 | import java.nio.ByteBuffer 34 | import java.util.HashMap 35 | 36 | class IppHoldJobOperation(context: Context) : IppOperation(context) { 37 | init { 38 | operationID = 0x000C 39 | bufferSize = 8192 40 | } 41 | 42 | @Throws(UnsupportedEncodingException::class) 43 | override fun getIppHeader(url: URL, map: Map?): ByteBuffer { 44 | var ippBuf = ByteBuffer.allocateDirect(bufferSize.toInt()) 45 | ippBuf = IppTag.getOperation(ippBuf, operationID) 46 | // ippBuf = IppTag.getUri(ippBuf, "job-uri", stripPortNumber(url)); 47 | 48 | if (map == null) { 49 | ippBuf = IppTag.getEnd(ippBuf) 50 | ippBuf.flip() 51 | return ippBuf 52 | } 53 | 54 | map["job-id"]?.let { 55 | ippBuf = IppTag.getUri(ippBuf, "printer-uri", stripPortNumber(url)) 56 | ippBuf = IppTag.getInteger(ippBuf, "job-id", it.toInt()) 57 | } ?: run { 58 | ippBuf = IppTag.getUri(ippBuf, "job-uri", stripPortNumber(url)) 59 | } 60 | ippBuf = IppTag.getNameWithoutLanguage(ippBuf, "requesting-user-name", map["requesting-user-name"]) 61 | 62 | ippBuf = IppTag.getEnd(ippBuf) 63 | ippBuf.flip() 64 | return ippBuf 65 | } 66 | 67 | @Throws(Exception::class) 68 | fun holdJob(url: URL, userName: String?, jobID: Int): Boolean { 69 | val requestUrl = URL(url.toString() + "/jobs/" + Integer.toString(jobID)) 70 | val map = HashMap() 71 | map["requesting-user-name"] = userName?:CupsClient.DEFAULT_USER 72 | map["job-uri"] = requestUrl.toString() 73 | val result = request(requestUrl, map) 74 | // IppResultPrinter.print(result); 75 | return PrintRequestResult(result).isSuccessfulResult 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/org/cups4j/operations/ipp/IppPrintJobOperation.kt: -------------------------------------------------------------------------------- 1 | package org.cups4j.operations.ipp 2 | 3 | /** 4 | * Copyright (C) 2009 Harald Weyhing 5 | * 6 | * 7 | * This program is free software; you can redistribute it and/or modify it under the terms of the 8 | * GNU Lesser General Public License as published by the Free Software Foundation; either version 3 9 | * of the License, or (at your option) any later version. 10 | * 11 | * 12 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 13 | * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * 16 | * See the GNU Lesser General Public License for more details. You should have received a copy of 17 | * the GNU Lesser General Public License along with this program; if not, see 18 | * //www.gnu.org/licenses/>. 19 | */ 20 | 21 | /*Notice 22 | * This file has been modified. It is not the original. 23 | * Jon Freeman - 2013 24 | */ 25 | 26 | import android.content.Context 27 | import ch.ethz.vppserver.ippclient.IppTag 28 | import org.cups4j.operations.IppOperation 29 | import java.io.UnsupportedEncodingException 30 | import java.net.URL 31 | import java.nio.ByteBuffer 32 | 33 | class IppPrintJobOperation(context: Context) : IppOperation(context) { 34 | init { 35 | operationID = 0x0002 36 | bufferSize = 8192 37 | } 38 | 39 | /** 40 | * TODO: not all possibilities implemented 41 | * 42 | * @param ippBuf IPP buffer 43 | * @param attributeBlocks Job attributes 44 | * @return Modified IPP buffer 45 | * @throws UnsupportedEncodingException 46 | */ 47 | @Throws(UnsupportedEncodingException::class) 48 | private fun getJobAttributes(inputIppBuf: ByteBuffer, attributeBlocks: Array?): ByteBuffer { 49 | if (attributeBlocks == null) { 50 | return inputIppBuf 51 | } 52 | 53 | var ippBuf = IppTag.getJobAttributesTag(inputIppBuf) 54 | for (attributeBlock in attributeBlocks) { 55 | val attr = attributeBlock.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 56 | if (attr.size != 3) { 57 | return ippBuf 58 | } 59 | var name: String? = attr[0] 60 | val tagName = attr[1] 61 | val value = attr[2] 62 | 63 | when (tagName) { 64 | "boolean" -> ippBuf = if (value == "true") { 65 | IppTag.getBoolean(ippBuf, name, true) 66 | } else { 67 | IppTag.getBoolean(ippBuf, name, false) 68 | } 69 | 70 | "integer" -> ippBuf = IppTag.getInteger(ippBuf, name, Integer.parseInt(value)) 71 | 72 | "rangeOfInteger" -> { 73 | val range = value.split("-".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 74 | val low = Integer.parseInt(range[0]) 75 | val high = Integer.parseInt(range[1]) 76 | ippBuf = IppTag.getRangeOfInteger(ippBuf, name, low, high) 77 | } 78 | 79 | "setOfRangeOfInteger" -> { 80 | val ranges = value.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 81 | 82 | for (r in ranges) { 83 | val range = r.trim { it <= ' ' } 84 | val values = range.split("-".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 85 | 86 | val value1 = Integer.parseInt(values[0]) 87 | var value2 = value1 88 | // two values provided? 89 | if (values.size == 2) { 90 | value2 = Integer.parseInt(values[1]) 91 | } 92 | 93 | // first attribute value needs name, additional values need to get the "null" name 94 | ippBuf = IppTag.getRangeOfInteger(ippBuf, name, value1, value2) 95 | name = null 96 | } 97 | } 98 | 99 | "keyword" -> ippBuf = IppTag.getKeyword(ippBuf, name, value) 100 | 101 | "name" -> ippBuf = IppTag.getNameWithoutLanguage(ippBuf, name, value) 102 | 103 | "enum" -> ippBuf = IppTag.getEnum(ippBuf, name, Integer.parseInt(value)) 104 | 105 | "resolution" -> { 106 | val resolution = value.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 107 | val value1 = Integer.parseInt(resolution[0]) 108 | val value2 = Integer.parseInt(resolution[1]) 109 | val value3 = java.lang.Byte.valueOf(resolution[2]) 110 | ippBuf = IppTag.getResolution(ippBuf, name, value1, value2, value3) 111 | } 112 | } 113 | } 114 | return ippBuf 115 | } 116 | 117 | @Throws(UnsupportedEncodingException::class) 118 | override fun getIppHeader(url: URL, map: Map?): ByteBuffer { 119 | var ippBuf = ByteBuffer.allocateDirect(bufferSize.toInt()) 120 | ippBuf = IppTag.getOperation(ippBuf, operationID) 121 | ippBuf = IppTag.getUri(ippBuf, "printer-uri", stripPortNumber(url)) 122 | 123 | if (map == null) { 124 | ippBuf = IppTag.getEnd(ippBuf) 125 | ippBuf.flip() 126 | return ippBuf 127 | } 128 | 129 | ippBuf = IppTag.getNameWithoutLanguage(ippBuf, "requesting-user-name", map["requesting-user-name"]) 130 | 131 | map["job-name"]?.let { ippBuf = IppTag.getNameWithoutLanguage(ippBuf, "job-name", it) } 132 | map["ipp-attribute-fidelity"]?.let { ippBuf = IppTag.getBoolean(ippBuf, "ipp-attribute-fidelity", it == "true") } 133 | map["document-name"]?.let { ippBuf = IppTag.getNameWithoutLanguage(ippBuf, "document-name", it) } 134 | map["compression"]?.let { ippBuf = IppTag.getKeyword(ippBuf, "compression", it) } 135 | map["document-format"]?.let { ippBuf = IppTag.getMimeMediaType(ippBuf, "document-format", it) } 136 | map["document-natural-language"]?.let { ippBuf = IppTag.getNaturalLanguage(ippBuf, "document-natural-language", it) } 137 | map["job-k-octets"]?.let { ippBuf = IppTag.getInteger(ippBuf, "job-k-octets", it.toInt()) } 138 | map["job-impressions"]?.let { ippBuf = IppTag.getInteger(ippBuf, "job-impressions", it.toInt()) } 139 | map["job-media-sheets"]?.let { ippBuf = IppTag.getInteger(ippBuf, "job-media-sheets", it.toInt()) } 140 | map["job-attributes"]?.let { jobAttributes -> 141 | val attributeBlocks = jobAttributes.split("#".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 142 | ippBuf = getJobAttributes(ippBuf, attributeBlocks) 143 | } 144 | 145 | ippBuf = IppTag.getEnd(ippBuf) 146 | ippBuf.flip() 147 | return ippBuf 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /app/src/main/java/org/cups4j/operations/ipp/IppReleaseJobOperation.kt: -------------------------------------------------------------------------------- 1 | package org.cups4j.operations.ipp 2 | 3 | /** 4 | * Copyright (C) 2011 Harald Weyhing 5 | * 6 | * 7 | * This program is free software; you can redistribute it and/or modify it under the terms of the 8 | * GNU Lesser General Public License as published by the Free Software Foundation; either version 3 9 | * of the License, or (at your option) any later version. 10 | * 11 | * 12 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 13 | * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * 16 | * See the GNU Lesser General Public License for more details. You should have received a copy of 17 | * the GNU Lesser General Public License along with this program; if not, see 18 | * //www.gnu.org/licenses/>. 19 | */ 20 | 21 | /*Notice 22 | * This file has been modified. It is not the original. 23 | * Jon Freeman - 2013 24 | */ 25 | 26 | import android.content.Context 27 | import ch.ethz.vppserver.ippclient.IppTag 28 | import org.cups4j.CupsClient 29 | import org.cups4j.PrintRequestResult 30 | import org.cups4j.operations.IppOperation 31 | import java.io.UnsupportedEncodingException 32 | import java.net.URL 33 | import java.nio.ByteBuffer 34 | import java.util.HashMap 35 | 36 | class IppReleaseJobOperation(context: Context) : IppOperation(context) { 37 | init { 38 | operationID = 0x000D 39 | bufferSize = 8192 40 | } 41 | 42 | @Throws(UnsupportedEncodingException::class) 43 | override fun getIppHeader(url: URL, map: Map?): ByteBuffer { 44 | var ippBuf = ByteBuffer.allocateDirect(bufferSize.toInt()) 45 | ippBuf = IppTag.getOperation(ippBuf, operationID) 46 | 47 | if (map == null) { 48 | ippBuf = IppTag.getEnd(ippBuf) 49 | ippBuf.flip() 50 | return ippBuf 51 | } 52 | 53 | map["job-id"]?.let { 54 | ippBuf = IppTag.getUri(ippBuf, "printer-uri", stripPortNumber(url)) 55 | ippBuf = IppTag.getInteger(ippBuf, "job-id", it.toInt()) 56 | } ?: run { 57 | ippBuf = IppTag.getUri(ippBuf, "job-uri", stripPortNumber(url)) 58 | } 59 | 60 | ippBuf = IppTag.getNameWithoutLanguage(ippBuf, "requesting-user-name", map["requesting-user-name"]) 61 | 62 | ippBuf = IppTag.getEnd(ippBuf) 63 | ippBuf.flip() 64 | return ippBuf 65 | } 66 | 67 | /** 68 | * Cancels a print job on the IPP server running on the given host. 69 | * 70 | * @param url Printer URL 71 | * @param userName Requesting user name 72 | * @param jobID Job ID 73 | * @return true on successful cancellation otherwise false. 74 | * @throws Exception 75 | */ 76 | @Throws(Exception::class) 77 | fun releaseJob(url: URL, userName: String?, jobID: Int): Boolean { 78 | val requestUrl = URL(url.toString() + "/jobs/" + Integer.toString(jobID)) 79 | val map = HashMap() 80 | map["requesting-user-name"] = userName ?: CupsClient.DEFAULT_USER 81 | map["job-uri"] = requestUrl.toString() 82 | 83 | val result = request(requestUrl, map) 84 | return PrintRequestResult(result).isSuccessfulResult 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/play/contactEmail: -------------------------------------------------------------------------------- 1 | support@upactivity.com -------------------------------------------------------------------------------- /app/src/main/play/contactPhone: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/play/contactPhone -------------------------------------------------------------------------------- /app/src/main/play/contactWebsite: -------------------------------------------------------------------------------- 1 | http://benoitduffez.github.io/AndroidCupsPrint -------------------------------------------------------------------------------- /app/src/main/play/defaultLanguage: -------------------------------------------------------------------------------- 1 | en-US -------------------------------------------------------------------------------- /app/src/main/play/en-US/listing/fulldescription: -------------------------------------------------------------------------------- 1 | Have a local printer shared over CUPS or IPP? Then this app allows you to directly print to it from your Android device. 2 | 3 | This app just provides a Print Service to Android. This means that once it's installed, you have to enable it from your 'Print' section of the settings app of you device. 4 | Once the service is enabled, the printers are automatically discovered using the mDNS protocol. 5 | You can print anything you want from any app, as long as the print service is enabled. 6 | 7 | Of course, printing document is an important matter. Because your documents are your most private information, you don't want anybody to have access to this information. 8 | This is why this app is completely open sourced and free to use, modify and redistribute (under the LGPL license). You can check more on the app website or GitHub repository: http://benoitduffez.github.io/AndroidCupsPrint 9 | 10 | This app was only tested with a single CUPS server, running on Debian 8; and with a single printer, an HP Deskjet connected over http. 11 | This means that there is a high probability that you may encounter bugs; in which case you are quite welcome to submit an issue on the GitHub project page: https://github.com/BenoitDuffez/AndroidCupsPrint/issues/new 12 | 13 | You can also fork the project and contribute in any way you want. 14 | 15 | This software uses jmdns, licensed under the Apache Licence. 16 | This software uses a modified version of the cups4j library under the GNU LGPL license. 17 | This software is based off of Jon Freeman's work. Further details may be found at http://mobd.jonbanjo.com/jfcupsprint/ and http://benoitduffez.github.io/AndroidCupsPrint 18 | 19 | Redistribution and use of this app in source and binary forms, with or without modification, is permitted provided this notice is retained in source code redistributions and that recipients agree that JfCupsPrint is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, title and non-infringement. In no event shall the copyright holders or anyone distributing the software be liable for any damages or other liability, whether in contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software. -------------------------------------------------------------------------------- /app/src/main/play/en-US/listing/shortdescription: -------------------------------------------------------------------------------- 1 | Print directly from your Android device to your local CUPS printers -------------------------------------------------------------------------------- /app/src/main/play/en-US/listing/title: -------------------------------------------------------------------------------- 1 | CUPS Printing -------------------------------------------------------------------------------- /app/src/main/play/en-US/listing/video: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/play/en-US/listing/video -------------------------------------------------------------------------------- /app/src/main/play/en-US/whatsnew: -------------------------------------------------------------------------------- 1 | v1.3 — october 2016 2 | * full SSL compatibility 3 | * support for HTTP Basic Auth 4 | * bug fixes 5 | 6 | v1.2 — august 29th 2015 7 | * added a form to add by hand printers that don't have mDNS enabled on the CUPS server 8 | 9 | v1.1 — june 11th 10 | * slimmed down the app size 11 | * removed unused UI elements -------------------------------------------------------------------------------- /app/src/main/play/fr-FR/listing/fulldescription: -------------------------------------------------------------------------------- 1 | Vous avez une imprimante partagée sur le réseau local via CUPS ou IPP ? Cette appli vous permet d'imprimer directement depuis votre mobile. 2 | 3 | Cette appli fournit un Service d'Impression pour Android, ainsi lorsque vous l'installez il faut activer le service d'impression CUPS (voir captures d'écran). Une fois ceci réalisé, vous pourrez imprimer depuis n'importe quelle application ! 4 | Vos imprimantes sont détectées automatiquement en utilisant le protocole mDNS. 5 | 6 | Bien entendu, l'impression de documents est une chose importante. Vos documents représentent une information très privée, et vous ne voulez pas que quiconque y ait accès. 7 | C'est pourquoi cette appli est complètement open-source et libre d'utilisation, modification et distribution (selon les termes de la licence LGPL). Vous pouvez obtenir plus d'informations sur le site de l'appli et la page GitHub : http://benoitduffez.github.io/AndroidCupsPrint 8 | 9 | Cette appli n'a été testée que sur un seul serveur CUPS (tournant sur une Debian 8), avec une seule imprimante (une HP Deskjet via http). 10 | Vous avez donc de grandes chances de tomber sur un éventuel bug, auquel cas vous êtes invités à ouvrir un bug sur la page GitHub : https://github.com/BenoitDuffez/AndroidCupsPrint/issues/new 11 | 12 | Vous pouvez aussi forker le projet et contribuer de la façon que vous souhaitez. 13 | 14 | Ce logiciel utilise jmdns, sous licence Apache. 15 | Ce logiciel utilise une version modifiée de la bibliothèque cups4j, sous licence GNU LGPL. 16 | Ce logiciel est basé sur le travail de Jon Freeman. Plus de détails sur http://mobd.jonbanjo.com/jfcupsprint/ et http://benoitduffez.github.io/AndroidCupsPrint 17 | 18 | Texte de licence original écrit par Jon Freeman (laissé tel que) : 19 | Redistribution and use of this app in source and binary forms, with or without modification, is permitted provided this notice is retained in source code redistributions and that recipients agree that JfCupsPrint is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, title and non-infringement. In no event shall the copyright holders or anyone distributing the software be liable for any damages or other liability, whether in contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software. -------------------------------------------------------------------------------- /app/src/main/play/fr-FR/listing/shortdescription: -------------------------------------------------------------------------------- 1 | Imprimez directement depuis votre mobile vers une imprimante réseau -------------------------------------------------------------------------------- /app/src/main/play/fr-FR/listing/title: -------------------------------------------------------------------------------- 1 | Impression via CUPS -------------------------------------------------------------------------------- /app/src/main/play/fr-FR/listing/video: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/play/fr-FR/listing/video -------------------------------------------------------------------------------- /app/src/main/play/fr-FR/whatsnew: -------------------------------------------------------------------------------- 1 | 1.3.0 — Novembre 2016 2 | * support complet du SSL 3 | * compatible avec l'authentification HTTP Basic Auth 4 | * correction de bugs -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_no_printers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/res/drawable-hdpi/ic_no_printers.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_no_printers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/res/drawable-mdpi/ic_no_printers.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_no_printers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/res/drawable-xhdpi/ic_no_printers.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_no_printers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/res/drawable-xxhdpi/ic_no_printers.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_no_printers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/res/drawable-xxxhdpi/ic_no_printers.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_manage_manual_printers.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 21 | 22 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/add_printers.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 24 | 25 |