From c1259161e58f798de20618be7471590bdd358699 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 22 Jan 2020 14:19:04 +0100 Subject: [PATCH] QRCode: generate and scan QRCodes --- vector/build.gradle | 6 +- .../riotx/features/debug/DebugMenuActivity.kt | 51 +++++++++++++ .../debug/res/layout/activity_debug_menu.xml | 13 ++++ vector/src/main/AndroidManifest.xml | 6 +- .../src/main/assets/open_source_licenses.html | 10 +++ .../im/vector/riotx/core/di/FragmentModule.kt | 35 +++++++-- .../vector/riotx/core/di/ScreenComponent.kt | 5 +- .../im/vector/riotx/core/qrcode/QrCode.kt | 47 ++++++++++++ .../features/qrcode/QrCodeScannerActivity.kt | 72 +++++++++++++++++++ .../features/qrcode/QrCodeScannerFragment.kt | 51 +++++++++++++ .../res/layout/fragment_qr_code_scanner.xml | 18 +++++ 11 files changed, 305 insertions(+), 9 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/core/qrcode/QrCode.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/qrcode/QrCodeScannerActivity.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/qrcode/QrCodeScannerFragment.kt create mode 100644 vector/src/main/res/layout/fragment_qr_code_scanner.xml diff --git a/vector/build.gradle b/vector/build.gradle index 14ec9f2c21..3bcb4a35b9 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -27,7 +27,7 @@ static def generateVersionCodeFromTimestamp() { // It's unix timestamp, minus timestamp of October 3rd 2018 (first commit date) divided by 100: It's incremented by one every 100 seconds. // plus 20_000_000 for compatibility reason with the previous way the Version Code was computed // Note that the result will be multiplied by 10 when adding the digit for the arch - return ((getGitTimestamp() - 1_538_524_800 ) / 100).toInteger() + 20_000_000 + return ((getGitTimestamp() - 1_538_524_800) / 100).toInteger() + 20_000_000 } def generateVersionCodeFromVersionName() { @@ -351,6 +351,10 @@ dependencies { implementation "androidx.emoji:emoji-appcompat:1.0.0" + // QR-code + implementation 'com.google.zxing:core:3.4.0' + implementation 'me.dm7.barcodescanner:zxing:1.9.13' + // TESTS testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' diff --git a/vector/src/debug/java/im/vector/riotx/features/debug/DebugMenuActivity.kt b/vector/src/debug/java/im/vector/riotx/features/debug/DebugMenuActivity.kt index db3c91d441..8d013ceb2d 100644 --- a/vector/src/debug/java/im/vector/riotx/features/debug/DebugMenuActivity.kt +++ b/vector/src/debug/java/im/vector/riotx/features/debug/DebugMenuActivity.kt @@ -16,6 +16,7 @@ package im.vector.riotx.features.debug +import android.app.Activity import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context @@ -37,7 +38,15 @@ import im.vector.riotx.R import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.platform.VectorBaseActivity +import im.vector.riotx.core.qrcode.createQrCode +import im.vector.riotx.core.utils.PERMISSIONS_FOR_TAKING_PHOTO +import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA +import im.vector.riotx.core.utils.allGranted +import im.vector.riotx.core.utils.checkPermissions +import im.vector.riotx.core.utils.toast import im.vector.riotx.features.debug.sas.DebugSasEmojiActivity +import im.vector.riotx.features.qrcode.QrCodeScannerActivity +import kotlinx.android.synthetic.debug.activity_debug_menu.* import javax.inject.Inject class DebugMenuActivity : VectorBaseActivity() { @@ -51,6 +60,15 @@ class DebugMenuActivity : VectorBaseActivity() { injector.inject(this) } + override fun initUiAndData() { + renderQrCode("https://www.example.org") + } + + private fun renderQrCode(text: String) { + val qrBitmap = createQrCode(text) + debug_qr_code.setImageBitmap(qrBitmap) + } + @OnClick(R.id.debug_test_text_view_link) fun testTextViewLink() { startActivity(Intent(this, TestLinkifyActivity::class.java)) @@ -214,4 +232,37 @@ class DebugMenuActivity : VectorBaseActivity() { } }) } + + @OnClick(R.id.debug_scan_qr_code) + fun scanQRCode() { + if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) { + doScanQRCode() + } + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + + if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA && allGranted(grantResults)) { + doScanQRCode() + } + } + + private fun doScanQRCode() { + QrCodeScannerActivity.startForResult(this) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (resultCode == Activity.RESULT_OK) { + when (requestCode) { + QrCodeScannerActivity.QR_CODE_SCANNER_REQUEST_CODE -> { + toast("QrCode: " + QrCodeScannerActivity.getResultText(data) + " is QRCode: " + QrCodeScannerActivity.getResultIsQrCode(data)) + + // Also update the current QR Code (reverse operation) + renderQrCode(QrCodeScannerActivity.getResultText(data) ?: "") + } + } + } + } } diff --git a/vector/src/debug/res/layout/activity_debug_menu.xml b/vector/src/debug/res/layout/activity_debug_menu.xml index 5d18121f5c..52b993e223 100644 --- a/vector/src/debug/res/layout/activity_debug_menu.xml +++ b/vector/src/debug/res/layout/activity_debug_menu.xml @@ -68,6 +68,19 @@ android:layout_height="wrap_content" android:text="Initialize XSigning" /> + + + + diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index c6e4b51c44..3207ab257a 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ + - + + Copyright 2017 Sergiy Kovalchuk +
  • + ZXing +
    + Copyright 2007 ZXing authors +
  • +
  • + barcodescanner +
    + Copyright (c) 2014 Dushyanth Maguluru +
  •  Apache License
    diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt
    index 53bd7d169f..52c8b840e3 100644
    --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt
    +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt
    @@ -22,32 +22,50 @@ import androidx.fragment.app.FragmentFactory
     import dagger.Binds
     import dagger.Module
     import dagger.multibindings.IntoMap
    +import im.vector.riotx.features.createdirect.CreateDirectRoomDirectoryUsersFragment
    +import im.vector.riotx.features.createdirect.CreateDirectRoomKnownUsersFragment
     import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment
     import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMethodFragment
     import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment
     import im.vector.riotx.features.crypto.verification.emoji.VerificationEmojiCodeFragment
     import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment
    +import im.vector.riotx.features.grouplist.GroupListFragment
     import im.vector.riotx.features.home.HomeDetailFragment
     import im.vector.riotx.features.home.HomeDrawerFragment
     import im.vector.riotx.features.home.LoadingFragment
    -import im.vector.riotx.features.createdirect.CreateDirectRoomDirectoryUsersFragment
    -import im.vector.riotx.features.createdirect.CreateDirectRoomKnownUsersFragment
    -import im.vector.riotx.features.grouplist.GroupListFragment
     import im.vector.riotx.features.home.room.breadcrumbs.BreadcrumbsFragment
     import im.vector.riotx.features.home.room.detail.RoomDetailFragment
     import im.vector.riotx.features.home.room.list.RoomListFragment
    -import im.vector.riotx.features.login.*
    +import im.vector.riotx.features.login.LoginCaptchaFragment
    +import im.vector.riotx.features.login.LoginFragment
    +import im.vector.riotx.features.login.LoginGenericTextInputFormFragment
    +import im.vector.riotx.features.login.LoginResetPasswordFragment
    +import im.vector.riotx.features.login.LoginResetPasswordMailConfirmationFragment
    +import im.vector.riotx.features.login.LoginResetPasswordSuccessFragment
    +import im.vector.riotx.features.login.LoginServerSelectionFragment
    +import im.vector.riotx.features.login.LoginServerUrlFormFragment
    +import im.vector.riotx.features.login.LoginSignUpSignInSelectionFragment
    +import im.vector.riotx.features.login.LoginSplashFragment
    +import im.vector.riotx.features.login.LoginWaitForEmailFragment
    +import im.vector.riotx.features.login.LoginWebFragment
     import im.vector.riotx.features.login.terms.LoginTermsFragment
    -import im.vector.riotx.features.roommemberprofile.RoomMemberProfileFragment
    +import im.vector.riotx.features.qrcode.QrCodeScannerFragment
     import im.vector.riotx.features.reactions.EmojiChooserFragment
     import im.vector.riotx.features.reactions.EmojiSearchResultFragment
     import im.vector.riotx.features.roomdirectory.PublicRoomsFragment
     import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment
     import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerFragment
     import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment
    +import im.vector.riotx.features.roommemberprofile.RoomMemberProfileFragment
     import im.vector.riotx.features.roomprofile.RoomProfileFragment
     import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment
    -import im.vector.riotx.features.settings.*
    +import im.vector.riotx.features.settings.VectorSettingsAdvancedNotificationPreferenceFragment
    +import im.vector.riotx.features.settings.VectorSettingsHelpAboutFragment
    +import im.vector.riotx.features.settings.VectorSettingsLabsFragment
    +import im.vector.riotx.features.settings.VectorSettingsNotificationPreferenceFragment
    +import im.vector.riotx.features.settings.VectorSettingsNotificationsTroubleshootFragment
    +import im.vector.riotx.features.settings.VectorSettingsPreferencesFragment
    +import im.vector.riotx.features.settings.VectorSettingsSecurityPrivacyFragment
     import im.vector.riotx.features.settings.devices.VectorSettingsDevicesFragment
     import im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment
     import im.vector.riotx.features.settings.push.PushGatewaysFragment
    @@ -296,4 +314,9 @@ interface FragmentModule {
         @IntoMap
         @FragmentKey(VerificationConclusionFragment::class)
         fun bindVerificationConclusionFragment(fragment: VerificationConclusionFragment): Fragment
    +
    +    @Binds
    +    @IntoMap
    +    @FragmentKey(QrCodeScannerFragment::class)
    +    fun bindQrCodeScannerFragment(fragment: QrCodeScannerFragment): Fragment
     }
    diff --git a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt
    index 4503143052..8f0b580775 100644
    --- a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt
    +++ b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt
    @@ -24,12 +24,12 @@ import dagger.Component
     import im.vector.riotx.core.error.ErrorFormatter
     import im.vector.riotx.core.preference.UserAvatarPreference
     import im.vector.riotx.features.MainActivity
    +import im.vector.riotx.features.createdirect.CreateDirectRoomActivity
     import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
     import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
     import im.vector.riotx.features.debug.DebugMenuActivity
     import im.vector.riotx.features.home.HomeActivity
     import im.vector.riotx.features.home.HomeModule
    -import im.vector.riotx.features.createdirect.CreateDirectRoomActivity
     import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
     import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet
     import im.vector.riotx.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
    @@ -44,6 +44,7 @@ import im.vector.riotx.features.media.ImageMediaViewerActivity
     import im.vector.riotx.features.media.VideoMediaViewerActivity
     import im.vector.riotx.features.navigation.Navigator
     import im.vector.riotx.features.permalink.PermalinkHandlerActivity
    +import im.vector.riotx.features.qrcode.QrCodeScannerActivity
     import im.vector.riotx.features.rageshake.BugReportActivity
     import im.vector.riotx.features.rageshake.BugReporter
     import im.vector.riotx.features.rageshake.RageShake
    @@ -140,6 +141,8 @@ interface ScreenComponent {
     
         fun inject(permalinkHandlerActivity: PermalinkHandlerActivity)
     
    +    fun inject(activity: QrCodeScannerActivity)
    +
         fun inject(activity: DebugMenuActivity)
     
         @Component.Factory
    diff --git a/vector/src/main/java/im/vector/riotx/core/qrcode/QrCode.kt b/vector/src/main/java/im/vector/riotx/core/qrcode/QrCode.kt
    new file mode 100644
    index 0000000000..8610c5a8af
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/riotx/core/qrcode/QrCode.kt
    @@ -0,0 +1,47 @@
    +/*
    + * Copyright 2020 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package im.vector.riotx.core.qrcode
    +
    +import android.graphics.Bitmap
    +import android.graphics.Color
    +import com.google.zxing.BarcodeFormat
    +import com.google.zxing.common.BitMatrix
    +import com.google.zxing.qrcode.QRCodeWriter
    +
    +fun createQrCode(url: String,
    +                 width: Int = 200,
    +                 height: Int = 200): Bitmap {
    +    return QRCodeWriter().encode(
    +            url,
    +            BarcodeFormat.QR_CODE,
    +            width,
    +            height
    +    ).toBitmap()
    +}
    +
    +fun BitMatrix.toBitmap(): Bitmap {
    +    val height: Int = height
    +    val width: Int = width
    +    val bmp = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
    +    for (x in 0 until width) {
    +        for (y in 0 until height) {
    +            bmp.setPixel(x, y, if (get(x, y)) Color.BLACK else Color.WHITE)
    +        }
    +    }
    +
    +    return bmp
    +}
    diff --git a/vector/src/main/java/im/vector/riotx/features/qrcode/QrCodeScannerActivity.kt b/vector/src/main/java/im/vector/riotx/features/qrcode/QrCodeScannerActivity.kt
    new file mode 100644
    index 0000000000..9e0dabc45e
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/riotx/features/qrcode/QrCodeScannerActivity.kt
    @@ -0,0 +1,72 @@
    +/*
    + * Copyright 2020 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package im.vector.riotx.features.qrcode
    +
    +import android.app.Activity
    +import android.content.Intent
    +import android.os.Bundle
    +import com.google.zxing.BarcodeFormat
    +import com.google.zxing.Result
    +import im.vector.riotx.R
    +import im.vector.riotx.core.di.ScreenComponent
    +import im.vector.riotx.core.extensions.replaceFragment
    +import im.vector.riotx.core.platform.VectorBaseActivity
    +
    +class QrCodeScannerActivity : VectorBaseActivity() {
    +
    +    override fun getLayoutRes() = R.layout.activity_simple
    +
    +    override fun injectWith(injector: ScreenComponent) {
    +        injector.inject(this)
    +    }
    +
    +    override fun onCreate(savedInstanceState: Bundle?) {
    +        super.onCreate(savedInstanceState)
    +        if (isFirstCreation()) {
    +            replaceFragment(R.id.simpleFragmentContainer, QrCodeScannerFragment::class.java)
    +        }
    +    }
    +
    +    fun setResultAndFinish(result: Result?) {
    +        result?.let {
    +            setResult(RESULT_OK, Intent().apply {
    +                putExtra(EXTRA_OUT_TEXT, it.text)
    +                putExtra(EXTRA_OUT_IS_QR_CODE, it.barcodeFormat == BarcodeFormat.QR_CODE)
    +            })
    +        }
    +        finish()
    +    }
    +
    +    companion object {
    +        private const val EXTRA_OUT_TEXT = "EXTRA_OUT_TEXT"
    +        private const val EXTRA_OUT_IS_QR_CODE = "EXTRA_OUT_IS_QR_CODE"
    +
    +        const val QR_CODE_SCANNER_REQUEST_CODE = 429
    +
    +        fun startForResult(activity: Activity, requestCode: Int = QR_CODE_SCANNER_REQUEST_CODE) {
    +            activity.startActivityForResult(Intent(activity, QrCodeScannerActivity::class.java), requestCode)
    +        }
    +
    +        fun getResultText(data: Intent?): String? {
    +            return data?.getStringExtra(EXTRA_OUT_TEXT)
    +        }
    +
    +        fun getResultIsQrCode(data: Intent?): Boolean {
    +            return data?.getBooleanExtra(EXTRA_OUT_IS_QR_CODE, false) == true
    +        }
    +    }
    +}
    diff --git a/vector/src/main/java/im/vector/riotx/features/qrcode/QrCodeScannerFragment.kt b/vector/src/main/java/im/vector/riotx/features/qrcode/QrCodeScannerFragment.kt
    new file mode 100644
    index 0000000000..2c6e9ed3d5
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/riotx/features/qrcode/QrCodeScannerFragment.kt
    @@ -0,0 +1,51 @@
    +/*
    + * Copyright 2020 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package im.vector.riotx.features.qrcode
    +
    +import com.google.zxing.Result
    +import im.vector.riotx.R
    +import im.vector.riotx.core.platform.VectorBaseFragment
    +import kotlinx.android.synthetic.main.fragment_qr_code_scanner.*
    +import me.dm7.barcodescanner.zxing.ZXingScannerView
    +import javax.inject.Inject
    +
    +class QrCodeScannerFragment @Inject constructor()
    +    : VectorBaseFragment(),
    +        ZXingScannerView.ResultHandler {
    +
    +    override fun getLayoutResId() = R.layout.fragment_qr_code_scanner
    +
    +    override fun onResume() {
    +        super.onResume()
    +        // Register ourselves as a handler for scan results.
    +        scannerView.setResultHandler(this)
    +        // Start camera on resume
    +        scannerView.startCamera()
    +    }
    +
    +    override fun onPause() {
    +        super.onPause()
    +        // Stop camera on pause
    +        scannerView.stopCamera()
    +    }
    +
    +    override fun handleResult(rawResult: Result?) {
    +        // Do something with the result here
    +        // This is not intended to be used outside of QrCodeScannerActivity for the moment
    +        (requireActivity() as? QrCodeScannerActivity)?.setResultAndFinish(rawResult)
    +    }
    +}
    diff --git a/vector/src/main/res/layout/fragment_qr_code_scanner.xml b/vector/src/main/res/layout/fragment_qr_code_scanner.xml
    new file mode 100644
    index 0000000000..589b7c73d4
    --- /dev/null
    +++ b/vector/src/main/res/layout/fragment_qr_code_scanner.xml
    @@ -0,0 +1,18 @@
    +
    +
    +
    +    
    +
    +    
    +
    +
    \ No newline at end of file