diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/PrintingAdapter.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/PrintingAdapter.kt new file mode 100644 index 0000000000000000000000000000000000000000..2d20c87a1d4b6f1d63abc4e59f62c813740ff26a --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/PrintingAdapter.kt @@ -0,0 +1,75 @@ +package de.rki.coronawarnapp.test.eventregistration.ui + +import android.os.Bundle +import android.os.CancellationSignal +import android.os.ParcelFileDescriptor +import android.print.PageRange +import android.print.PrintAttributes +import android.print.PrintDocumentAdapter +import android.print.PrintDocumentInfo +import timber.log.Timber +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream + +/** + * Printing adapter for poster PDF files + */ +class PrintingAdapter( + private val file: File +) : PrintDocumentAdapter() { + + override fun onLayout( + oldAttributes: PrintAttributes?, + newAttributes: PrintAttributes?, + cancellationSignal: CancellationSignal, + callback: LayoutResultCallback, + extras: Bundle? + ) { + if (cancellationSignal.isCanceled) { + callback.onLayoutCancelled() + Timber.i("onLayoutCancelled") + return + } + + val info = PrintDocumentInfo.Builder(file.name) + .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT) + .setPageCount(PrintDocumentInfo.PAGE_COUNT_UNKNOWN) + .build() + Timber.i( + "onLayoutFinished(info:%s, oldAttributes:%s, newAttributes:%s)", + info, + oldAttributes, + newAttributes + ) + callback.onLayoutFinished(info, oldAttributes != newAttributes) + } + + override fun onWrite( + pages: Array<out PageRange>, + destination: ParcelFileDescriptor, + cancellationSignal: CancellationSignal, + callback: WriteResultCallback + ) = try { + FileInputStream(file).use { input -> + FileOutputStream(destination.fileDescriptor).use { output -> + val bytesCopied = input.copyTo(output) + Timber.i("bytesCopied:$bytesCopied") + } + } + + when { + cancellationSignal.isCanceled -> { + Timber.i("onWriteCancelled") + callback.onWriteCancelled() + } + else -> { + Timber.i("onWriteFinished") + callback.onWriteFinished(arrayOf(PageRange.ALL_PAGES)) + } + } + } catch (e: Exception) { + callback.onWriteFailed(e.message) + Timber.e(e, "Printing $file failed") + } +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestFragment.kt index 2ae2fafd1af5c4a3c640517a58b9fb2cf65fe3e2..abd5b1c1be9461567ee0dbf1fccc308797c2bb08 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestFragment.kt @@ -2,17 +2,22 @@ package de.rki.coronawarnapp.test.eventregistration.ui.qrcode import android.annotation.SuppressLint import android.os.Bundle +import android.print.PrintAttributes +import android.print.PrintManager import android.view.View import android.widget.Toast +import androidx.core.content.getSystemService import androidx.core.view.isVisible import androidx.fragment.app.Fragment import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentTestQrcodeCreationBinding +import de.rki.coronawarnapp.test.eventregistration.ui.PrintingAdapter import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.ui.observe2 import de.rki.coronawarnapp.util.ui.viewBindingLazy import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider import de.rki.coronawarnapp.util.viewmodel.cwaViewModels +import timber.log.Timber import javax.inject.Inject class QrCodeCreationTestFragment : Fragment(R.layout.fragment_test_qrcode_creation), AutoInject { @@ -25,13 +30,42 @@ class QrCodeCreationTestFragment : Fragment(R.layout.fragment_test_qrcode_creati @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - viewModel.sharingIntent.observe2(this) { - startActivity(it.get(requireActivity())) + viewModel.sharingIntent.observe2(this) { fileIntent -> + + binding.printPDF.isVisible = true + binding.printPDF.setOnClickListener { + // Context must be an Activity context + val printingManger = context?.getSystemService<PrintManager>() + Timber.i("PrintingManager: $printingManger") + printingManger?.apply { + val printingJob = print( + "CoronaWarnApp", + PrintingAdapter(fileIntent.file), + PrintAttributes + .Builder() + .setMediaSize(PrintAttributes.MediaSize.ISO_A3) + .build() + ) + + Timber.i("PrintingJob:$printingJob") + Timber.i("PrintingJob isBlocked:${printingJob.isBlocked}") + Timber.i("PrintingJob isCancelled:${printingJob.isCancelled}") + Timber.i("PrintingJob isCompleted:${printingJob.isCompleted}") + Timber.i("PrintingJob isFailed:${printingJob.isFailed}") + Timber.i("PrintingJob info:${printingJob.info}") + } + } + binding.sharePDF.isVisible = true + binding.sharePDF.setOnClickListener { + startActivity(fileIntent.intent(requireActivity())) + } } viewModel.qrCodeBitmap.observe2(this) { binding.qrCodeImage.setImageBitmap(it) - binding.printPDF.isVisible = it != null + if (it != null) { + viewModel.createPDF(binding.pdfPage) + } } viewModel.errorMessage.observe2(this) { @@ -46,9 +80,5 @@ class QrCodeCreationTestFragment : Fragment(R.layout.fragment_test_qrcode_creati binding.generateQrCode.setOnClickListener { viewModel.createQrCode(binding.qrCodeText.text.toString()) } - - binding.printPDF.setOnClickListener { - viewModel.createPDF(binding.pdfPage) - } } } diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestViewModel.kt index 835366cbc0f4c02f1ba125ace84986aa980b9089..6dd59f2e683ef8ba88781a3711a673e369c31b65 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestViewModel.kt @@ -31,7 +31,7 @@ class QrCodeCreationTestViewModel @AssistedInject constructor( val qrCodeBitmap = SingleLiveEvent<Bitmap>() val errorMessage = SingleLiveEvent<String>() - val sharingIntent = SingleLiveEvent<FileSharing.ShareIntentProvider>() + val sharingIntent = SingleLiveEvent<FileSharing.FileIntentProvider>() /** * Creates a QR Code [Bitmap] ,result is delivered by [qrCodeBitmap] @@ -68,7 +68,7 @@ class QrCodeCreationTestViewModel @AssistedInject constructor( } sharingIntent.postValue( - fileSharing.getIntentProvider(file, "Scan and Help") + fileSharing.getFileIntentProvider(file, "Scan and Help") ) } catch (e: Exception) { errorMessage.postValue(e.localizedMessage ?: "Creating pdf failed") @@ -85,8 +85,10 @@ class QrCodeCreationTestViewModel @AssistedInject constructor( private fun encodeAsBitmap(input: String, size: Int = 1000): Bitmap? { return try { val hints = mapOf( - EncodeHintType.ERROR_CORRECTION to ErrorCorrectionLevel.H, - EncodeHintType.CHARACTER_SET to Charsets.UTF_8 + EncodeHintType.ERROR_CORRECTION to ErrorCorrectionLevel.H + // This is not required in the specs and it should not be enabled + // it is causing crash on older Android versions ex:API 23 + // EncodeHintType.CHARACTER_SET to Charsets.UTF_8 ) MultiFormatWriter().encode( input, diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_qrcode_creation.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_qrcode_creation.xml index e6ec6eebc8e76de51ab72ad5c707a13261c96fec..6ae5f29730ea651f8a8e8bc0d61f010427562518 100644 --- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_qrcode_creation.xml +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_qrcode_creation.xml @@ -17,21 +17,33 @@ android:layout_height="wrap_content" android:layout_marginTop="24dp" android:text="QR Code" - app:layout_constraintEnd_toStartOf="@+id/printPDF" + app:layout_constraintEnd_toStartOf="@+id/sharePDF" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + <com.google.android.material.button.MaterialButton + android:id="@+id/sharePDF" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="24dp" + android:text="Share PDF" + android:visibility="invisible" + app:layout_constraintEnd_toStartOf="@+id/printPDF" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toEndOf="@+id/generateQrCode" + app:layout_constraintTop_toTopOf="parent" /> + <com.google.android.material.button.MaterialButton android:id="@+id/printPDF" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="24dp" - android:text="PDF" - android:visibility="gone" + android:text="Print PDF" + android:visibility="invisible" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" - app:layout_constraintStart_toEndOf="@+id/generateQrCode" + app:layout_constraintStart_toEndOf="@+id/sharePDF" app:layout_constraintTop_toTopOf="parent" /> <com.google.android.material.textfield.TextInputLayout diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/files/FileSharing.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/files/FileSharing.kt index 4303506d6e561362417e660cb3a0e2833f31af53..488c8a12f3a23a44cb9f8a7ad51cfb762d7f346b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/files/FileSharing.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/files/FileSharing.kt @@ -50,6 +50,38 @@ class FileSharing @Inject constructor( } } + fun getFileIntentProvider( + path: File, + title: String, + @StringRes chooserTitle: Int? = null + ): FileIntentProvider = object : FileIntentProvider { + override fun intent(activity: Activity): Intent { + val builder = ShareCompat.IntentBuilder.from(activity).apply { + setType(path.determineMimeType()) + setStream(getFileUri(path)) + setSubject(title) + chooserTitle?.let { setChooserTitle(it) } + } + + val intent = if (chooserTitle != null) { + builder.createChooserIntent() + } else { + builder.intent + } + return intent.apply { + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + Timber.tag(TAG).d("Intent created %s", this) + } + } + + override val file: File = path + } + + interface FileIntentProvider { + fun intent(activity: Activity): Intent + val file: File + } + interface ShareIntentProvider { fun get(activity: Activity): Intent }