├── .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 |
7 |
8 |
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 |
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 |
31 |
32 |
36 |
37 |
46 |
47 |
56 |
57 |
62 |
63 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/basic_auth.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
22 |
23 |
30 |
31 |
38 |
39 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/host_not_verified.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
23 |
24 |
31 |
32 |
39 |
40 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/manage_printers_list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/untrusted_cert.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
24 |
25 |
34 |
35 |
42 |
43 |
50 |
51 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-de/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Android CUPS Print
5 | http://cups-server.mit.domäne:631/printers/Drucker_Name
6 | Bezeichnung des Druckers
7 | Drucker hinzufügen
8 | Nach Druckern suchen
9 | CUPS-Drucker hinzufügen
10 | Wahlweise kannst du auch die komplette URI des Druckers in den folgenden Feldern eingeben.
11 | IP-Nummer oder Hostname
12 | Du kannst die Drucker eines Servers auflisten lassen, indem du die IP-Nummer oder den Hostnamen des Servers angibst.
13 |
14 | Suche hat keinen Drucker gefunden
15 | Ein Drucker gefunden
16 | %d Drucker gefunden
17 |
18 | Der ausgewählte Drucker reagiert nicht. Prüfe ob er angeschaltet ist und unter %1$s verfügbar ist.
19 | Dokument konnte nicht gelesen werden
20 | Der Druckername darf nicht leer sein
21 | Die Drucker-URI darf nicht leer sein
22 | Anmeldeinformationen speichern
23 | Dein Server hat eine Basis-Authentifizierung angefordert, aber du musst die Anmeldeinformationen eingeben. Bitte fülle folgende Felder aus:
24 | Benutzername
25 | Passwort
26 | HTTP-Standardauthentifizierung
27 | Dieser Host lehnt einfaches HTTP ab, verwende bitte den HTTPS-Zugriff
28 | Host nicht überprüft
29 | Abbrechen
30 | Dieser Host präsentierte ein SSL-Zertifikat, für das dein Gerät den Hostnamen nicht überprüfen konnte. Alle Drucker, die von diesem Host bedient werden, werden deaktiviert, bis du ihm explizit vertraust. Bitte gib an, ob du diesem Host vertrauen möchtest oder nicht:
31 | Hostname %1$s nicht bestätigt
32 | Vertraue dem Host
33 | Verbindung abbrechen
34 | Serverzertifikate konnten nicht gespeichert werden. Bitte überprüfe die Anwendungsprotokolle und sende einen Bug-Report an die Entwickler.
35 | Dein Server hat ein Zertifikat gesendet, das von deinem Gerät nicht vertraut wurde. Bitte überprüfe die unten aufgeführten Zertifikate und wähle aus, ob du es akzeptierst oder nicht:
36 | Nicht vertrauenswürdige SSL-Verbindung
37 | Verbindung vertrauen
38 | Zertifikat wurde der List vertrauenswürdiger Zertifikate hinzugefügt. Bitte versuchen Sie den Verbindungsaufbau zu diesem Drucker noch einmal.
39 | Drucker konnte nicht gefunden werden, ist er verfügbar?
40 | Status des Auftrags #%1$d konnte nicht überprüft werden, da die Verbindung zum CUPS-Server zurückgesetzt wurde
41 | Job konnte nicht verarbeitet werden:%1$s (%2$s)
42 | Konnte den Job #%1$d nicht finden, ist er schon fertig?
43 | Dokument konnte nicht gedruckt werden: Socket-Timeout. Überprüfen Sie die Netzwerkverbindung.
44 | Druckerstatus konnte nicht abgerufen werden: Netzwerk nicht erreichbar. Überprüfen Sie die Netzwerkverbindung.
45 | Der Status des Druckers konnte nicht abgefragt werden: socket timeout. Überprüfen Sie die Netzwerkverbindung.
46 | Druckerstatus konnte nicht abgefragt werden: unbekannter Host. Überprüfen Sie die Netzwerkverbindung.
47 | Generischer CUPS-Client
48 | Der CUPS-Server gab an, dass wir eine schlechte Anforderung gesendet haben. Dies ist entweder eine schlechte Konfiguration auf dem Server oder eine falsche URL. Bitte überprüfen Sie beide.
49 | Der Druckername wurde auf dem Server nicht gefunden (HTTP 404). Bitte überprüfen Sie den Namen.
50 | Es sind keine manuell eingegebenen Drucker vorhanden. Sie können eine aus der App \"Einstellungen\" im Bereich \"Drucken\" hinzufügen.
51 | Hier sind die manuell hinzugefügten Drucker. Klicken Sie auf ein Element, um es zu entfernen (keine Bestätigung!)
52 | Der Auftrag konnte nicht zur Warteschlange hinzugefügt werden: %1$s
53 | Die URI konnte nicht verarbeitet werden: %1$s
54 |
55 |
56 |
--------------------------------------------------------------------------------
/app/src/main/res/values-es/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Impresión Android CUPS
4 | Añade una impresora CUPS
5 | Puede detectar automáticamente las impresoras en su servidor introduciendo su IP o nombre de host a continuación
6 | Como alternativa, puede introducir la URI completa de la impresora en los campos de abajo
7 | Añadir impresora
8 | Nombre de impresora
9 | Buscar impresoras
10 | Dirección IP o nombre de host
11 | http://serveur-cups.domaine:631/printers/Nombre_de_impresora
12 |
13 | No se encontraron impresoras
14 | Encontrada %d impresora
15 | Encontradas %d impresora
16 |
17 | La impresora no responde. Por favor, asegúrate de que esté encendido y disponible en %1$s
18 | Imposible leer el documento a imprimir
19 | El nombre de la impresora no puede estar vacío
20 | La URL de la impresora no puede estar vacía
21 | Autenticar
22 | Su servidor ha solicitado una autenticación básica, por eso necesita las credenciales. Por favor, introdúzcalos abajo:
23 | Nombre de usuario
24 | Contraseña
25 | Autenticación HTTP básica
26 | Este host rechaza las conexiones HTTP, por favor use el acceso HTTPS
27 | Abortar
28 | Anfitrión no verificado
29 | Este host presentó un certificado SSL para el cual su dispositivo no pudo verificar el nombre del host. Todas las impresoras ubicadas en este anfitrión serán rechazadas hasta que usted acepte explícitamente este anfitrión. Por favor, indique si desea confiar en este anfitrión:
30 | Host %1$s no verificado
31 | Confiar host
32 | Abortar la conexión
33 | No podemos guardar el certificado del servidor. Por favor, compruebe los registros de la aplicación y envíe una solicitud de fallo al desarrollador.
34 | Su servidor envió un certificado no confiable por su dispositivo. Por favor, revise los detalles del certificado a continuación, y elija si confía o no en él:
35 | Conexión SSL no fiable
36 | Aceptar la conexión
37 | Certificados añadidos a la lista de confianza. Por favor, intente imprimir de nuevo.
38 | No puedo encontrar la impresora, ¿está disponible?
39 | Imposible comprobar el estado del trabajo de impresión #%1$d porque la conexión con el servidor CUPS fue interrumpida
40 | Imposible manejar la impresión: %1$s (%2$s)
41 | Imposible encontrar la tarea #%1$d, ¿desea terminada?
42 | Imposible imprimir el documento: la conexión expiró. Por favor, compruebe su conexión de red.
43 | Imposible consultar el estado de la impresora: red no encontrada. Por favor, compruebe su conexión de red.
44 | Imposible consultar el estado de la impresora: conexión expirada. Por favor, compruebe su conexión de red.
45 | Imposible consultar el estado de la impresora: host desconocido. Por favor, compruebe su conexión de red.
46 | Cliente genérico CUPS
47 | El servidor CUPS indicó que enviamos una solicitud HTTP errónea. Esto se debe a una mala configuración del servidor o a una mala dirección URL. Por favor, compruebe ambos.
48 | El nombre de la impresora no se encontró en el servidor (HTTP 404). Por favor, compruebe el nombre de la impresora.
49 | No hay impresoras añadidas manualmente. Puedes añadir uno en los Ajustes la aplicación, en la sección de Impresión.
50 | Aquí está la lista de impresoras añadidas manualmente. Haga clic en uno de ellos para borrarlo (¡sin confirmación!)
51 | Imposible poner en cola la impresión: %1$s
52 | Imposible descifrar la dirección: %1$s
53 | Desde Android 9.0, las conexiones HTTP no seguras están prohibidas. Por favor, pase su servidor/impresora CUPS a SSL/TLS.
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/res/values-fr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Impression Android CUPS
4 | Ajouter une imprimante CUPS
5 | Vous pouvez soit détecter automatiquement les imprimantes de votre serveur en entrant son IP ou nom d\'hôte ci-dessous
6 | Soit, vous pouvez renseigner l\'URI complète de l\'imprimante dans les champs ci-dessous
7 | Ajouter l\'imprimante
8 | Nom de l\'imprimante
9 | Rechercher des imprimantes
10 | Adresse IP ou nom d\'hôte
11 | http://serveur-cups.domaine:631/printers/Nom_de_limprimante
12 |
13 | La recherche a trouvé %d imprimante
14 | La recherche a trouvé %d imprimantes
15 |
16 | L\'imprimante ne répond pas. Veuillez vérifier qu\'elle est allumée et disponible à l\'adresse %1$s
17 | Impossible de lire le document à imprimer
18 | Le nom de l\'imprimante ne peut être vide
19 | L\'URL de l\'imprimante ne peut être vide
20 | Enregistrer les identifiants
21 | Votre serveur a demandé une authentification basique, mais nous avons besoin des identifiants. Veuillez les saisir ci-dessous :
22 | Nom d\'utilisateur
23 | Mot de passe
24 | Authentification basique HTTP
25 | Cet hôte refuse les connexions HTTP, veuillez utiliser un accès HTTPS
26 | Annuler
27 | Hôte non vérifié
28 | Cet hôte a présenté un certificat SSL pour lequel votre appareil n\'a pas pu vérifier le nom d\'hôte. Toutes les imprimantes localisées sur cet hôte seront refusées jusqu\'à ce que vous acceptiez explicitement cet hôte. Veuillez indiquer si vous souhaitez faire confiance à cet hôte :
29 | Hôte %1$s non vérifié
30 | Faire confiance
31 | Annuler la connexion
32 | Impossible d\'enregistrer le certificat du serveur. Veuillez consulter les logs de l\'application et soumettre une demande de bug au développeur.
33 | Votre serveur a envoyé un certificat que votre appareil n\'a pas considéré comme de confirance. Veuillez constuler les détails du certificat ci-desous, et choisir si vous décidez d\'y faire confiance ou non :
34 | Connexion SSL non fiable
35 | Accepter la connexion
36 | Certificats ajoutés à la liste de confiance. Veuillez réessayer d\'imprimer.
37 | Impossible de trouver l\'imprimante, est-elle disponible ?
38 | Impossible de vérifier l\'état de la tâche d\'impression n°%1$d car la connexion au serveur CUPS a été interrompue
39 | Impossible de gérer l\'impression : %1$s (%2$s)
40 | Impossible de trouver la tâche n°%1$d, est-elle déjà terminée ?
41 | Impossible d\'imprimer le document : connexion expirée. Veuillez vérifier votre connexion au réseau.
42 | Impossible d\'interroger l\'état de l\'imprimante : réseau injoignable. Veuillez vérifier votre connexion au réseau.
43 | Impossible d\'interroger l\'état de l\'imprimante : connexion expirée. Veuillez vérifier votre connexion au réseau.
44 | Impossible d\'interroger l\'état de l\'imprimante : hôte inconnu. Veuillez vérifier votre connexion au réseau.
45 | Client CUPS générique
46 | Le serveur CUPS a indiqué que nous avons envoyé une mauvaise requête HTTP. Cela provient soit d\'une mauvaise configuration du serveur, ou une mauvaise URL. Veuillez vérifier les deux.
47 | Le nom de l\'imprimante n\'a pas été trouvé sur le serveur (HTTP 404). Veuillez vérifier son nom.
48 | Il n\'y a aucune imprimante ajoutée manuellement. Vous pouvez en ajouter une dans l\'appli Paramètres, dans la section Impression.
49 | Voici la liste des imprimantes ajoutées manuellement. Cliquez sur l\'une pour la supprimer (pas de confirmation !)
50 | Impossible de mettre l\'impression en file: %1$s
51 | Impossible de déchiffrer l\'adresse : %1$s
52 | À compter d\'Android 9.0, les connexions HTTP sans sécurité sont interdites. Merci de passer votre serveur CUPS/votre imprimante en SSL/TLS.
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ja/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Android CUPS Print
5 | http://server.tld:631/printers/Printer_Name
6 | プリンター名
7 | プリンターを追加
8 | プリンターの検索
9 | CUPS プリンターを追加
10 | または、以下のフィールドに完全な URI とプリンター名を入力することもできます
11 | サーバー IP またはホスト名
12 | 以下の IP またはホスト名を入力して、サーバーからプリンターを自動検出することができます
13 |
14 | プリンターが見つかりませんでした
15 | %d プリンターが見つかりました
16 | %d プリンターが見つかりました
17 |
18 | プリンターが応答していません。 電源がオンになっていて、%1$s で利用可能であることを確認してください
19 | 印刷するドキュメントが読み取りできません
20 | プリンター名は空にできません
21 | プリンター URL は空にできません
22 | 信頼できない SSL 接続
23 | デバイスによって信頼されていない証明書をサーバーが送信しました。 以下の証明書の詳細を確認し、信頼するかどうかを選択してください:
24 |
25 | 信頼できる接続
26 | 接続を中断
27 | 証明書を信頼できるリストに追加しました。 このプリンターでもう一度試してください。
28 | サーバー証明書を保存できませんでした。 アプリケーションログを確認し、開発者にバグリクエストを提出してください。
29 | HTTP ベーシック認証
30 | サーバーがベーシック認証のリクエストを送信しましたが、資格情報を提供する必要があります。 以下に入力してください:
31 | ユーザー名
32 | パスワード
33 | 資格情報を保存
34 | ホスト名 %1$s は確認されていません
35 | このホストは、デバイスがホスト名を確認できない SSL 証明書を提示しました。 このホストによって提供されるすべてのプリンターは、明示的に信頼するまで無効になります。 このホストを信頼するかどうかを教えてください:
36 | ホストを信頼する
37 | 中断
38 | ホストは確認されていません
39 | このホストはプレーンな HTTP を拒否します。 HTTPSアクセスを使用してください
40 | プリンターを見つけることができません。 利用可能ですか?
41 | プリンターのステータスを問い合わせできません: ソケットタイムアウト。 ネットワーク接続を確認してください。
42 | プリンターのステータスを問い合わできません: 不明なホスト。 ネットワーク接続を確認してください。
43 | プリンターのステータスを問い合わせできません: ネットワークに到達できません。 ネットワーク接続を確認してください。
44 | CUPS サーバーが、Bad リクエストを送信したことを示しました。 これはサーバー上の構成が正しくないか、URL が間違っています。 両方を確認してください。
45 | サーバーにプリンター名が見つかりませんでした (HTTP 404)。 名前を確認してください。
46 | CUPS サーバーとの接続がリセットされたため、ジョブ #%1$d のステータスを確認できませんでした
47 | ジョブ #%1$d が見つかりません。 すでに完了していませんか?
48 | ドキュメントを印刷できません: ソケットタイムアウト。 ネットワーク接続を確認してください。
49 | ジョブを処理できません: %1$s (%2$s)
50 | 汎用 CUPS クライアント
51 | 手動で入力したプリンターはありません。 アプリ設定の印刷セクションで追加できます。
52 | ここに手動で追加されたプリンターがあります。 削除するアイテムを 1 つクリックしてください (確認は表示されません!)
53 |
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ru/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Android CUPS Print
5 | http://server.tld:631/printers/Printer_Name
6 | Имя принтера
7 | Добавить принтер
8 | Поиск принтеров
9 | Добавить CUPS принтер
10 | В качестве альтернативы можно ввести полный URI и имя принтера в поля ниже
11 | IP-адрес сервера или имя хоста
12 | Вы можете либо автоматически определить принтеры с вашего сервера, введя его IP-адрес или введя имя хоста ниже
13 |
14 | Поиск не нашел принтер
15 | Найден %d принтер
16 | Найдено %d принтеров
17 |
18 | Принтер не отвечает. Пожалуйста, проверьте, включен и доступен ли он по адресу %1$s
19 | Невозможно прочитать документ для печати
20 | Имя принтера не может быть пустым
21 | URL-адрес принтера не может быть пустым
22 | Ненадежное SSL-соединение
23 | Ваш сервер отправил сертификат, которому не доверяет ваше устройство. Пожалуйста, просмотрите информацию о сертификате ниже и выберите, согласны ли вы доверять ему или нет:
24 |
26 | Issuer: CN=test-server\n
27 | Validity\n
28 | Not Before: Aug 21 19:36:21 2016 GMT\n
29 | Not After : Aug 19 19:36:21 2026 GMT\n
30 | Subject: CN=test-server\n
31 | Subject Public Key Info:\n
32 | Public Key Algorithm: rsaEncryption\n
33 | Public-Key: (2048 bit)\n
34 |
35 | Доверенное соединение
36 | Прервать соединение
37 | Сертификаты добавлены в список доверенных. Пожалуйста, повторите попытку с этим принтером.
38 | Не удалось сохранить сертификаты сервера. Пожалуйста, просмотрите журналы приложения и отправьте запрос об ошибке разработчику.
39 | HTTP Basic Auth
40 | Ваш сервер отправил базовый запрос auth, но нам нужно, чтобы вы предоставили учетные данные. Пожалуйста, заполните их ниже:
41 | Имя пользователя
42 | Пароль
43 | Сохранить учетные данные
44 | Имя хоста %1$s не проверено
45 | Этот хост представил сертификат SSL, для которого ваше устройство не смогло проверить имя хоста. Все принтеры, обслуживаемые этим хостом, будут отключены до тех пор, пока вы явно не начнете доверять ему. Пожалуйста, сообщите, хотите ли вы доверять этому узлу или нет:
46 | Доверенный хост
47 | Прервать
48 | Хост не проверен
49 | Этот хост отказывает в обычном HTTP, пожалуйста, используйте HTTPS доступ
50 | Не могу найти принтер, есть ли он в наличии?
51 | Не удалось запросить состояние принтера: таймаут сокета. Пожалуйста, проверьте подключение к сети.
52 | Не удалось запросить состояние принтера: неизвестный хост. Пожалуйста, проверьте подключение к сети.
53 | Не удалось запросить состояние принтера: сеть недоступна. Пожалуйста, проверьте подключение к сети.
54 | Сервер CUPS указал, что мы отправили плохой запрос. Это либо неправильная конфигурация сервера, либо неверный URL. Пожалуйста, проверьте оба варианта.
55 | Имя принтера не найдено на сервере (HTTP 404). Пожалуйста, проверьте его имя.
56 | Не удалось проверить статус задания #%1$d, потому что соединение с сервером CUPS было сброшено
57 | Не могу найти задание #%1$d, оно уже окончено?
58 | Не удалось распечатать документ: таймаут сокета. Пожалуйста, проверьте подключение к сети.
59 | Не удалось обработать задание: %1$s (%2$s)
60 | Общий клиент CUPS
61 | Принтеры, введенные вручную, отсутствуют. Их можно добавить в приложении "Настройки" в разделе "Печать".
62 | Вот принтеры, добавленные вручную. Нажмите на один элемент, чтобы удалить его (без подтверждения!)
63 | Не удалось поставить задание на печать в очередь: %1$s
64 | Не удалось обработать URI: %1$s
65 | Трафик с чистым текстом больше не разрешен в Android 9.0. Пожалуйста, включите SSL/TLS на сервере/принтере CUPS.
66 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Android CUPS Print
5 | http://server.tld:631/printers/Printer_Name
6 | Printer Name
7 | Add printer
8 | Search for printers
9 | Add a CUPS printer
10 | Alternatively, you can enter a complete URI and printer name in the fields below
11 | Server IP or host name
12 | You can either auto-detect the printers from your server by entering its IP or host name below
13 |
14 | Search didn\'t find any printer
15 | Search found %d printer
16 | Search found %d printers
17 |
18 | Printer is not responding. Please check it is powered on and available at %1$s
19 | Couldn\'t read document to be printed
20 | The printer name cannot be empty
21 | The printer URL cannot be empty
22 | Untrusted SSL connection
23 | Your server sent a certificate that was not trusted by your device. Please review the certificates details below, and choose whether you accept to trust it or not:
24 |
26 | Issuer: CN=test-server\n
27 | Validity\n
28 | Not Before: Aug 21 19:36:21 2016 GMT\n
29 | Not After : Aug 19 19:36:21 2026 GMT\n
30 | Subject: CN=test-server\n
31 | Subject Public Key Info:\n
32 | Public Key Algorithm: rsaEncryption\n
33 | Public-Key: (2048 bit)\n
34 |
35 | Trust connection
36 | Abort connection
37 | Certificates added to the trusted list. Please try again with this printer.
38 | Couldn\'t save server certificates. Please review the application logs and submit a bug request to the developer.
39 | HTTP Basic Auth
40 | Your server sent a basic auth request, but we need you to give the credentials. Please fill them below:
41 | Username
42 | Password
43 | Save credentials
44 | Hostname %1$s not verified
45 | This host presented an SSL certificate for which your device couldn\'t verify the hostname. All the printers served by this host will be disabled until you explicitly trust it. Please tell if you would like to trust this host or not:
46 | Trust host
47 | Abort
48 | Host not verified
49 | This host refuses plain HTTP, please use the HTTPS access
50 | Couldn\'t find printer, is it available?
51 | Couldn\'t query printer status: socket timeout. Please check network connectivity.
52 | Couldn\'t query printer status: unknown host. Please check network connectivity.
53 | Couldn\'t query printer status: network unreachable. Please check network connectivity.
54 | The CUPS server indicated that we sent a bad request. This is either a bad configuration on the server, or a wrong URL. Please check both.
55 | The printer name was not found on the server (HTTP 404). Please check its name.
56 | Couldn\'t check job #%1$d status because the connection with the CUPS server was reset
57 | Couldn\'t find job #%1$d, is it already finished?
58 | Couldn\'t print document: socket timeout. Please check network connectivity.
59 | Couldn\'t handle job: %1$s (%2$s)
60 | Generic CUPS client
61 | There are no manually entered printers. You can add one from the Settings app, in the Printing section.
62 | Here are the manually added printers. Click on one item to remove it (no confirmation!)
63 | Couldn\'t queue print job: %1$s
64 | Couldn\'t parse URI: %1$s
65 | Clear text traffic is no longer allowed in Android 9.0. Please enable SSL/TLS on the CUPS server/printer.
66 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/printservice.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/app/src/playstore/java/io/github/benoitduffez/cupsprint/CupsPrintApp.kt:
--------------------------------------------------------------------------------
1 | package io.github.benoitduffez.cupsprint
2 |
3 | import android.app.Application
4 | import com.crashlytics.android.Crashlytics
5 | import com.crashlytics.android.core.CrashlyticsCore
6 | import io.fabric.sdk.android.Fabric
7 | import org.koin.android.ext.android.startKoin
8 | import org.koin.dsl.module.module
9 | import timber.log.Timber
10 |
11 | val applicationModule = module {
12 | single { AppExecutors() }
13 | }
14 |
15 | class CupsPrintApp : Application() {
16 | override fun onCreate() {
17 | super.onCreate()
18 |
19 | val core = CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build()
20 | Fabric.with(this, Crashlytics.Builder().core(core).build())
21 |
22 | if (BuildConfig.DEBUG) {
23 | Timber.plant(Timber.DebugTree())
24 | } else {
25 | Timber.plant(CrashReportingTree())
26 | }
27 |
28 | startKoin(this, listOf(applicationModule))
29 | }
30 |
31 | /** A tree which logs important information for crash reporting. */
32 | private class CrashReportingTree : Timber.Tree() {
33 | override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
34 | Crashlytics.log(priority, tag, message)
35 | t.let { Crashlytics.logException(it) }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/test/java/io/github/benoitduffez/cupsprint/ExampleTests.kt:
--------------------------------------------------------------------------------
1 | package io.github.benoitduffez.cupsprint
2 |
3 | import org.junit.Test
4 |
5 | /**
6 | * Example local unit test, which will execute on the development machine (host).
7 | *
8 | * @see [Testing documentation](http://d.android.com/tools/testing)
9 | */
10 | class ExampleTests {
11 | @Test
12 | fun testDummy() {
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/versioning.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | /**
3 | * Build an Android version code from the version of the project.
4 | * This is designed to handle the -SNAPSHOT and -RC format.
5 | *
6 | * I.e. during development the version ends with -SNAPSHOT. As the code stabilizes and release nears
7 | * one or many Release Candidates are tagged. These all end with "-RC1", "-RC2" etc.
8 | * And the final release is without any suffix.
9 | *
10 | * https://blog.jayway.com/2015/03/11/automatic-versioncode-generation-in-android-gradle/
11 | *
12 | * @return
13 | */
14 | buildVersionCode = {
15 | //The rules is as follows:
16 | //-SNAPSHOT counts as 0
17 | //-RC* counts as the RC number, i.e. 1 to 98
18 | //final release counts as 99.
19 |
20 | //Thus you can only have 98 Release Candidates, which ought to be enough for everyone
21 |
22 |
23 | def candidate = "99"
24 |
25 | def (major, minor, patch) = version.toLowerCase().replaceAll('-', '').tokenize('.')
26 | if (patch.endsWith("snapshot")) {
27 | candidate = "0"
28 | patch = patch.replaceAll("[^0-9]", "")
29 | } else {
30 | def rc
31 | (patch, rc) = patch.tokenize("rc")
32 | if (rc) {
33 | candidate = rc
34 | }
35 | }
36 |
37 | (major, minor, patch, candidate) = [major, minor, patch, candidate].collect {
38 | it.toInteger()
39 | }
40 |
41 | (major * 1000000) + (minor * 10000) + (patch * 100) + candidate
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/beta.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 |
4 | # Check repo status
5 | BRANCH=develop
6 |
7 | if ! grep -q ${BRANCH} <<<"$(git branch | grep ^*)"; then
8 | echo "This must be invoked on ${BRANCH}" >&2
9 | echo " git checkout ${BRANCH} && $0 $@" >&2
10 | exit 1
11 | fi
12 |
13 | if ! grep -q "nothing to commit, working tree clean" <<<"$(git status)"; then
14 | echo "Directory must be clean. Please run git status and commit changes." >&2
15 | exit 1
16 | fi
17 |
18 | # Load last version
19 | version=$(awk -F= '/version/{print $2}' gradle.properties)
20 |
21 | # Go to beta
22 | echo "Comparing versions with develop..."
23 | git checkout beta 2>&1
24 | git pull
25 |
26 | # Build version base + next RC number
27 | versionBeta=$(awk -F= '/version/{print $2}' gradle.properties)
28 | if grep -qP "RC\d+$" <<<"$versionBeta"; then
29 | base=$(awk -F- '{print $1}' <<<"$versionBeta")
30 | rc=$(awk -FC '{print $NF+1}' <<<"$versionBeta")
31 | else
32 | base="$versionBeta"
33 | rc=1
34 | fi
35 |
36 | # If minor/major/patch has changed, reset RC number
37 | if [ ${base} != ${version} ]; then
38 | base=${version}
39 | rc=1
40 | fi
41 |
42 | # Merge and fix conflict
43 | echo "Merging develop into beta..."
44 | if grep -q "Already up.to.date" <<<$(git merge --no-edit develop); then
45 | echo "No changes on develop: do not release"
46 | exit 1
47 | fi
48 | git checkout --theirs gradle.properties
49 |
50 | # Change version
51 | newVersion="$base-RC$rc"
52 | echo "Changing version $version to $newVersion"
53 | sed -i'' "/version=/c version=$newVersion" gradle.properties
54 |
55 | # Set changelog
56 | lastBeta=$(git blame gradle.properties | awk '/version=/{print $1}')
57 | mkdir -p app/src/main/play/en-US
58 | whatsnew=app/src/main/play/en-US/whatsnew
59 | echo $(git describe --tags || git rev-parse --short HEAD) >${whatsnew}
60 | git log --pretty=format:' * %s' ${lastBeta}..HEAD | grep -v 'Merge branch' >>${whatsnew}
61 | echo "Saving changelog for release notes: ${whatsnew}"
62 |
63 | # Commit & push
64 | echo "Committing change & push:"
65 | git status
66 | git add gradle.properties ${whatsnew}
67 | git commit -m "beta $version --> $newVersion"
68 | git push
69 | git tag -a "$newVersion" -m "$newVersion"
70 | git push --tags
71 | git checkout develop
72 |
73 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.3.40'
5 | repositories {
6 | jcenter()
7 | maven {
8 | url 'https://maven.google.com/'
9 | name 'Google'
10 | }
11 | google()
12 | }
13 | dependencies {
14 | classpath 'com.android.tools.build:gradle:7.0.2'
15 |
16 | // NOTE: Do not place your application dependencies here; they belong
17 | // in the individual module build.gradle files
18 |
19 | // The Fabric Gradle plugin uses an open ended version to react
20 | // quickly to Android tooling updates
21 | classpath 'com.github.triplet.gradle:play-publisher:1.2.2'
22 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
23 | classpath 'com.google.gms:google-services:4.3.3'
24 | classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0'
25 | }
26 | }
27 |
28 | allprojects {
29 | repositories {
30 | jcenter()
31 | maven {
32 | url 'https://maven.google.com/'
33 | name 'Google'
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | android.enableJetifier=true
21 | android.useAndroidX=true
22 | version=1.5.0
23 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenoitDuffez/AndroidCupsPrint/8ef01cc8c4e4fcaad434c29a5472f7aad98d2f05/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Sep 06 16:51:16 CEST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------