Added QR code scanning.

This commit is contained in:
Mikunj 2019-11-22 12:08:09 +11:00
parent 27c8b45ae3
commit b650ee6ebc
8 changed files with 107 additions and 11 deletions

View File

@ -96,6 +96,17 @@
app:labeledEditText_background="@color/loki_darkest_gray" app:labeledEditText_background="@color/loki_darkest_gray"
app:labeledEditText_label="@string/activity_key_pair_public_key_edit_text_label"/> app:labeledEditText_label="@string/activity_key_pair_public_key_edit_text_label"/>
<Button
android:id="@+id/scanQRButton"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/transparent"
android:elevation="0dp"
android:stateListAnimator="@null"
android:text="@string/fragment_scan_qr_code_title"
android:textColor="@color/signal_primary"
android:visibility="gone" />
<Button <Button
android:id="@+id/toggleRestoreModeButton" android:id="@+id/toggleRestoreModeButton"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -123,7 +134,7 @@
android:layout_height="50dp" android:layout_height="50dp"
android:background="@color/transparent" android:background="@color/transparent"
android:textColor="@color/signal_primary" android:textColor="@color/signal_primary"
android:text="Link Device" android:text="@string/activity_key_pair_toggle_mode_button_title_3"
android:elevation="0dp" android:elevation="0dp"
android:stateListAnimator="@null" /> android:stateListAnimator="@null" />

View File

@ -24,13 +24,14 @@
android:layout_height="match_parent"/> android:layout_height="match_parent"/>
<TextView <TextView
android:id="@+id/descriptionTextView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1" android:layout_weight="1"
android:padding="32dp" android:padding="32dp"
android:gravity="center" android:gravity="center"
android:background="@color/loki_darkest_gray" android:background="@color/loki_darkest_gray"
android:text="@string/fragment_scan_qr_code_explanation" android:text="@string/fragment_scan_qr_code_explanation_new_conversation"
android:textColor="?android:textColorPrimary" /> android:textColor="?android:textColorPrimary" />
</LinearLayout> </LinearLayout>

View File

@ -16,6 +16,13 @@
android:indeterminate="true" android:indeterminate="true"
android:progressTint="@color/white" /> android:progressTint="@color/white" />
<ImageView
android:id="@+id/qrCodeImageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@color/white" />
<TextView <TextView
android:id="@+id/titleTextView" android:id="@+id/titleTextView"
style="@style/Signal.Text.Headline" style="@style/Signal.Text.Headline"

View File

@ -1638,7 +1638,8 @@
<string name="view_device_linking_cancel_button_title">Cancel</string> <string name="view_device_linking_cancel_button_title">Cancel</string>
<!-- Scan QR code fragment --> <!-- Scan QR code fragment -->
<string name="fragment_scan_qr_code_title">Scan QR Code</string> <string name="fragment_scan_qr_code_title">Scan QR Code</string>
<string name="fragment_scan_qr_code_explanation">Scan the QR code of the person you\'d like to securely message. They can find their QR code by going into Loki Messenger\'s in-app settings and clicking \"Show QR Code\".</string> <string name="fragment_scan_qr_code_explanation_new_conversation">Scan the QR code of the person you\'d like to securely message. They can find their QR code by going into Loki Messenger\'s in-app settings and clicking \"Show QR Code\".</string>
<string name="fragment_scan_qr_code_explanation_link_device">Scan the QR code of the device you would like to link.</string>
<string name="fragment_scan_qr_code_camera_permission_dialog_message">Loki Messenger needs camera access to scan QR codes.</string> <string name="fragment_scan_qr_code_camera_permission_dialog_message">Loki Messenger needs camera access to scan QR codes.</string>
<!-- Conversation activity --> <!-- Conversation activity -->
<string name="activity_conversation_copy_public_key_button_title">Copy public key</string> <string name="activity_conversation_copy_public_key_button_title">Copy public key</string>

View File

@ -4,13 +4,17 @@ import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.os.Handler import android.os.Handler
import android.text.Editable
import android.text.TextWatcher
import android.util.AttributeSet import android.util.AttributeSet
import android.util.DisplayMetrics
import android.view.View import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
import kotlinx.android.synthetic.main.view_device_linking.view.* import kotlinx.android.synthetic.main.view_device_linking.view.*
import kotlinx.android.synthetic.main.view_device_linking.view.cancelButton
import kotlinx.android.synthetic.main.view_device_linking.view.explanationTextView
import kotlinx.android.synthetic.main.view_device_linking.view.titleTextView
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.qr.QrCode
import org.thoughtcrime.securesms.util.ServiceUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.api.PairingAuthorisation import org.whispersystems.signalservice.loki.api.PairingAuthorisation
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
@ -55,6 +59,19 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe
} }
authorizeButton.visibility = View.GONE authorizeButton.visibility = View.GONE
authorizeButton.setOnClickListener { authorizePairing() } authorizeButton.setOnClickListener { authorizePairing() }
// QR Code
spinner.visibility = if (mode == Mode.Master) View.GONE else View.VISIBLE
qrCodeImageView.visibility = if (mode == Mode.Master) View.VISIBLE else View.GONE
if (mode == Mode.Master) {
val hexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
val displayMetrics = DisplayMetrics()
ServiceUtil.getWindowManager(context).defaultDisplay.getMetrics(displayMetrics)
val size = displayMetrics.widthPixels - 2 * toPx(96, resources)
val qrCode = QrCode.create(hexEncodedPublicKey, size)
qrCodeImageView.setImageBitmap(qrCode)
}
cancelButton.setOnClickListener { cancel() } cancelButton.setOnClickListener { cancel() }
} }
// endregion // endregion
@ -64,6 +81,7 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe
if (mode != Mode.Master || pairingAuthorisation.type != PairingAuthorisation.Type.REQUEST || this.pairingAuthorisation != null) { return } if (mode != Mode.Master || pairingAuthorisation.type != PairingAuthorisation.Type.REQUEST || this.pairingAuthorisation != null) { return }
this.pairingAuthorisation = pairingAuthorisation this.pairingAuthorisation = pairingAuthorisation
spinner.visibility = View.GONE spinner.visibility = View.GONE
qrCodeImageView.visibility = View.GONE
val titleTextViewLayoutParams = titleTextView.layoutParams as LayoutParams val titleTextViewLayoutParams = titleTextView.layoutParams as LayoutParams
titleTextViewLayoutParams.topMargin = toPx(16, resources) titleTextViewLayoutParams.topMargin = toPx(16, resources)
titleTextView.layoutParams = titleTextViewLayoutParams titleTextView.layoutParams = titleTextViewLayoutParams

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.loki
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -14,8 +15,15 @@ import org.thoughtcrime.securesms.qr.ScanningThread
class ScanQRCodeFragment : Fragment() { class ScanQRCodeFragment : Fragment() {
private val scanningThread = ScanningThread() private val scanningThread = ScanningThread()
private var viewCreated = false
var scanListener: ScanListener? = null var scanListener: ScanListener? = null
set(value) { field = value; scanningThread.setScanListener(scanListener) } set(value) { field = value; scanningThread.setScanListener(scanListener) }
var mode: Mode = Mode.NewConversation
set(value) { field = value; updateDescription(); }
// region Types
enum class Mode { NewConversation, LinkDevice }
// endregion
override fun onCreateView(layoutInflater: LayoutInflater, viewGroup: ViewGroup?, bundle: Bundle?): View? { override fun onCreateView(layoutInflater: LayoutInflater, viewGroup: ViewGroup?, bundle: Bundle?): View? {
return layoutInflater.inflate(R.layout.fragment_scan_qr_code, viewGroup, false) return layoutInflater.inflate(R.layout.fragment_scan_qr_code, viewGroup, false)
@ -23,10 +31,12 @@ class ScanQRCodeFragment : Fragment() {
override fun onViewCreated(view: View, bundle: Bundle?) { override fun onViewCreated(view: View, bundle: Bundle?) {
super.onViewCreated(view, bundle) super.onViewCreated(view, bundle)
viewCreated = true
when (resources.configuration.orientation) { when (resources.configuration.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> overlayView.orientation = LinearLayout.HORIZONTAL Configuration.ORIENTATION_LANDSCAPE -> overlayView.orientation = LinearLayout.HORIZONTAL
else -> overlayView.orientation = LinearLayout.VERTICAL else -> overlayView.orientation = LinearLayout.VERTICAL
} }
updateDescription()
} }
override fun onResume() { override fun onResume() {
@ -35,8 +45,10 @@ class ScanQRCodeFragment : Fragment() {
this.cameraView.onResume() this.cameraView.onResume()
this.cameraView.setPreviewCallback(scanningThread) this.cameraView.setPreviewCallback(scanningThread)
this.scanningThread.start() this.scanningThread.start()
val activity = activity as NewConversationActivity if (activity is AppCompatActivity) {
activity.supportActionBar!!.setTitle(R.string.fragment_scan_qr_code_title) val activity = activity as AppCompatActivity
activity.supportActionBar?.setTitle(R.string.fragment_scan_qr_code_title)
}
} }
override fun onPause() { override fun onPause() {
@ -55,4 +67,13 @@ class ScanQRCodeFragment : Fragment() {
cameraView.onResume() cameraView.onResume()
cameraView.setPreviewCallback(scanningThread) cameraView.setPreviewCallback(scanningThread)
} }
fun updateDescription() {
if (!viewCreated) { return }
val text = when (mode) {
Mode.NewConversation -> R.string.fragment_scan_qr_code_explanation_new_conversation
Mode.LinkDevice -> R.string.fragment_scan_qr_code_explanation_link_device
}
descriptionTextView.setText(text)
}
} }

View File

@ -1,11 +1,13 @@
package org.thoughtcrime.securesms.loki package org.thoughtcrime.securesms.loki
import android.Manifest
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.AsyncTask import android.os.AsyncTask
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.FragmentManager
import android.view.View import android.view.View
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.Toast import android.widget.Toast
@ -19,6 +21,8 @@ import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.IdentityDatabase import org.thoughtcrime.securesms.database.IdentityDatabase
import org.thoughtcrime.securesms.logging.Log import org.thoughtcrime.securesms.logging.Log
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.qr.ScanListener
import org.thoughtcrime.securesms.util.Hex import org.thoughtcrime.securesms.util.Hex
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.curve25519.Curve25519 import org.whispersystems.curve25519.Curve25519
@ -32,7 +36,7 @@ import org.whispersystems.signalservice.loki.utilities.retryIfNeeded
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
class SeedActivity : BaseActionBarActivity(), DeviceLinkingDelegate { class SeedActivity : BaseActionBarActivity(), DeviceLinkingDelegate, ScanListener {
private lateinit var languageFileDirectory: File private lateinit var languageFileDirectory: File
private var mode = Mode.Register private var mode = Mode.Register
set(newValue) { field = newValue; updateUI() } set(newValue) { field = newValue; updateUI() }
@ -57,6 +61,23 @@ class SeedActivity : BaseActionBarActivity(), DeviceLinkingDelegate {
toggleRestoreModeButton.setOnClickListener { mode = Mode.Restore } toggleRestoreModeButton.setOnClickListener { mode = Mode.Restore }
toggleLinkModeButton.setOnClickListener { mode = Mode.Link } toggleLinkModeButton.setOnClickListener { mode = Mode.Link }
mainButton.setOnClickListener { handleMainButtonTapped() } mainButton.setOnClickListener { handleMainButtonTapped() }
scanQRButton.setOnClickListener {
Permissions.with(this)
.request(Manifest.permission.CAMERA)
.ifNecessary()
.withPermanentDenialDialog(getString(R.string.fragment_scan_qr_code_camera_permission_dialog_message))
.onAllGranted {
val fragment = ScanQRCodeFragment()
fragment.mode = ScanQRCodeFragment.Mode.LinkDevice
fragment.scanListener = this
supportFragmentManager.beginTransaction().replace(android.R.id.content, fragment).addToBackStack("QR").commitAllowingStateLoss()
publicKeyEditText.clearFocus()
val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(publicKeyEditText.windowToken, 0)
}
.onAnyDenied { Toast.makeText(this, R.string.fragment_scan_qr_code_camera_permission_dialog_message, Toast.LENGTH_SHORT).show() }
.execute()
}
Analytics.shared.track("Seed Screen Viewed") Analytics.shared.track("Seed Screen Viewed")
} }
// endregion // endregion
@ -106,6 +127,7 @@ class SeedActivity : BaseActionBarActivity(), DeviceLinkingDelegate {
mnemonicEditText.visibility = restoreModeVisibility mnemonicEditText.visibility = restoreModeVisibility
linkExplanationTextView.visibility = linkModeVisibility linkExplanationTextView.visibility = linkModeVisibility
publicKeyEditText.visibility = linkModeVisibility publicKeyEditText.visibility = linkModeVisibility
scanQRButton.visibility = linkModeVisibility
toggleRegisterModeButton.visibility = if (mode != Mode.Register) View.VISIBLE else View.GONE toggleRegisterModeButton.visibility = if (mode != Mode.Register) View.VISIBLE else View.GONE
toggleRestoreModeButton.visibility = if (mode != Mode.Restore) View.VISIBLE else View.GONE toggleRestoreModeButton.visibility = if (mode != Mode.Restore) View.VISIBLE else View.GONE
toggleLinkModeButton.visibility = if (mode != Mode.Link) View.VISIBLE else View.GONE toggleLinkModeButton.visibility = if (mode != Mode.Link) View.VISIBLE else View.GONE
@ -236,4 +258,14 @@ class SeedActivity : BaseActionBarActivity(), DeviceLinkingDelegate {
TextSecurePreferences.setPromptedPushRegistration(this, false) TextSecurePreferences.setPromptedPushRegistration(this, false)
} }
// endregion // endregion
override fun onQrDataFound(data: String?) {
runOnUiThread {
if (data != null && PublicKeyValidation.isValid(data.trim())) {
publicKeyEditText.setText(data.trim())
supportFragmentManager.popBackStackImmediate("QR", FragmentManager.POP_BACK_STACK_INCLUSIVE)
handleMainButtonTapped()
}
}
}
// endregion
} }

View File

@ -5,12 +5,15 @@ import android.graphics.Color;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import com.google.zxing.BarcodeFormat; import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException; import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix; import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter; import com.google.zxing.qrcode.QRCodeWriter;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import java.util.HashMap;
public class QrCode { public class QrCode {
public static final String TAG = QrCode.class.getSimpleName(); public static final String TAG = QrCode.class.getSimpleName();
@ -18,10 +21,12 @@ public class QrCode {
public static @NonNull Bitmap create(String data) { public static @NonNull Bitmap create(String data) {
return create(data, 1024); return create(data, 1024);
} }
public static @NonNull Bitmap create(String data, int size) { return create(data, size, 2); }
public static @NonNull Bitmap create(String data, int size) { public static @NonNull Bitmap create(String data, int size, int margin) {
try { try {
BitMatrix result = new QRCodeWriter().encode(data, BarcodeFormat.QR_CODE, size, size); HashMap<EncodeHintType, Integer> hintMap = new HashMap<>();
hintMap.put(EncodeHintType.MARGIN, margin);
BitMatrix result = new QRCodeWriter().encode(data, BarcodeFormat.QR_CODE, size, size, hintMap);
Bitmap bitmap = Bitmap.createBitmap(result.getWidth(), result.getHeight(), Bitmap.Config.ARGB_8888); Bitmap bitmap = Bitmap.createBitmap(result.getWidth(), result.getHeight(), Bitmap.Config.ARGB_8888);
for (int y = 0; y < result.getHeight(); y++) { for (int y = 0; y < result.getHeight(); y++) {