<NME> CameraActivity.kt
<BEF> package de.markusfisch.android.binaryeye.activity

import android.app.Activity
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Matrix
import android.graphics.Rect
import android.hardware.Camera
import android.media.AudioManager
import android.media.ToneGenerator
import android.net.Uri
import android.os.Bundle
import android.os.Vibrator
import android.support.design.widget.FloatingActionButton
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.view.Menu
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.widget.SeekBar
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.adapter.prettifyFormatName
import de.markusfisch.android.binaryeye.app.*
import de.markusfisch.android.binaryeye.content.copyToClipboard
import de.markusfisch.android.binaryeye.content.execShareIntent
import de.markusfisch.android.binaryeye.content.openUrl
import de.markusfisch.android.binaryeye.content.toHexString
import de.markusfisch.android.binaryeye.database.toScan
import de.markusfisch.android.binaryeye.graphics.FrameMetrics
import de.markusfisch.android.binaryeye.graphics.mapPosition
import de.markusfisch.android.binaryeye.graphics.setFrameRoi
import de.markusfisch.android.binaryeye.graphics.setFrameToView
import de.markusfisch.android.binaryeye.net.sendAsync
import de.markusfisch.android.binaryeye.net.urlEncode
import de.markusfisch.android.binaryeye.os.error
import de.markusfisch.android.binaryeye.os.getVibrator
import de.markusfisch.android.binaryeye.os.vibrate
import de.markusfisch.android.binaryeye.view.initSystemBars
import de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets
import de.markusfisch.android.binaryeye.widget.DetectorView
import de.markusfisch.android.binaryeye.widget.toast
import de.markusfisch.android.cameraview.widget.CameraView
import de.markusfisch.android.zxingcpp.ZxingCpp
import de.markusfisch.android.zxingcpp.ZxingCpp.Format
import de.markusfisch.android.zxingcpp.ZxingCpp.Result
import java.nio.charset.Charset
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt

class CameraActivity : AppCompatActivity() {
	private val frameRoi = Rect()
	private val matrix = Matrix()

	private lateinit var vibrator: Vibrator
	private lateinit var cameraView: CameraView
	private lateinit var detectorView: DetectorView
	private lateinit var zoomBar: SeekBar
	private lateinit var flashFab: FloatingActionButton

	private var formats = setOf<String>()
	private var frameMetrics = FrameMetrics()
	private var decoding = true
	private var returnResult = false
	private var returnUrlTemplate: String? = null
	private var finishAfterShowingResult = false
	private var frontFacing = false
	private var bulkMode = prefs.bulkMode
	private var restrictFormat: String? = null
	private var ignoreNext: String? = null
	private var fallbackBuffer: IntArray? = null

	override fun onRequestPermissionsResult(
		requestCode: Int,
		permissions: Array<String>,
		grantResults: IntArray
	) {
		when (requestCode) {
			PERMISSION_CAMERA -> if (grantResults.isNotEmpty() &&
				grantResults[0] != PackageManager.PERMISSION_GRANTED
			) {
				toast(R.string.no_camera_no_fun)
				finish()
			}
		}
	}

	override fun onActivityResult(
		requestCode: Int,
		resultCode: Int,
		resultData: Intent?
	) {
		when (requestCode) {
			PICK_FILE_RESULT_CODE -> {
				if (resultCode == Activity.RESULT_OK && resultData != null) {
					val pick = Intent(this, PickActivity::class.java)
					pick.action = Intent.ACTION_VIEW
					pick.setDataAndType(resultData.data, "image/*")
					startActivity(pick)
				}
			}
		}
	}

	override fun attachBaseContext(base: Context?) {
		base?.applyLocale(prefs.customLocale)
		super.attachBaseContext(base)
	}

	override fun onCreate(state: Bundle?) {
		super.onCreate(state)
		setContentView(R.layout.activity_camera)

		// Necessary to get the right translation after setting a
		// custom locale.
		setTitle(R.string.scan_code)

		vibrator = getVibrator()

		initSystemBars(this)
		setSupportActionBar(findViewById(R.id.toolbar) as Toolbar)

		cameraView = findViewById(R.id.camera_view) as CameraView
		detectorView = findViewById(R.id.detector_view) as DetectorView
		zoomBar = findViewById(R.id.zoom) as SeekBar
		flashFab = findViewById(R.id.flash) as FloatingActionButton

		initCameraView()
		initZoomBar()
		initDetectorView()

		if (intent?.action == Intent.ACTION_SEND &&
			intent.type == "text/plain"
		) {
			handleSendText(intent)
		}
	}

	override fun onDestroy() {
		super.onDestroy()
		fallbackBuffer = null
		saveZoom()
		detectorView.saveCropHandlePos()
	}

	override fun onResume() {
		super.onResume()
		System.gc()
		updateHints()
		if (prefs.bulkMode && bulkMode != prefs.bulkMode) {
			bulkMode = prefs.bulkMode
			invalidateOptionsMenu()
			ignoreNext = null
		}
		setReturnTarget(intent)
		if (hasCameraPermission()) {
			openCamera()
		}
	}

	private fun updateHints() {
		val restriction = restrictFormat
		formats = if (restriction != null) {
			title = getString(
				R.string.scan_format,
				prettifyFormatName(restriction)
			)
			setOf(restriction)
		} else {
			setTitle(R.string.scan_code)
			prefs.barcodeFormats
		}
	}

	private fun setReturnTarget(intent: Intent?) {
		when {
			intent?.action == "com.google.zxing.client.android.SCAN" -> {
				returnResult = true
			}
			intent?.dataString?.isReturnUrl() == true -> {
				finishAfterShowingResult = true
				returnUrlTemplate = intent.data?.getQueryParameter("ret")
			}
		}
	}

	private fun openCamera() {
		cameraView.openAsync(
			CameraView.findCameraId(
				@Suppress("DEPRECATION")
				if (frontFacing) {
					Camera.CameraInfo.CAMERA_FACING_FRONT
				} else {
					Camera.CameraInfo.CAMERA_FACING_BACK
				}
			)
		)
	}

	override fun onPause() {
		super.onPause()
		closeCamera()
	}

	private fun closeCamera() {
		cameraView.close()
	}

	override fun onRestoreInstanceState(savedState: Bundle) {
		super.onRestoreInstanceState(savedState)
		zoomBar.max = savedState.getInt(ZOOM_MAX)
		zoomBar.progress = savedState.getInt(ZOOM_LEVEL)
		frontFacing = savedState.getBoolean(FRONT_FACING)
		bulkMode = savedState.getBoolean(BULK_MODE)
		restrictFormat = savedState.getString(RESTRICT_FORMAT)
	}

	override fun onSaveInstanceState(outState: Bundle) {
		outState.putInt(ZOOM_MAX, zoomBar.max)
		outState.putInt(ZOOM_LEVEL, zoomBar.progress)
		outState.putBoolean(FRONT_FACING, frontFacing)
		outState.putBoolean(BULK_MODE, bulkMode)
		outState.putString(RESTRICT_FORMAT, restrictFormat)
		super.onSaveInstanceState(outState)
	}

	override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
		// Always give crop handle precedence over other controls
		// because it can easily overlap and would then be inaccessible.
		if (detectorView.onTouchEvent(ev)) {
			return true
		}
		return super.dispatchTouchEvent(ev)
	}

	override fun onCreateOptionsMenu(menu: Menu): Boolean {
		menuInflater.inflate(R.menu.activity_camera, menu)
		menu.findItem(R.id.bulk_mode).isChecked = bulkMode
		return true
	}

	override fun onOptionsItemSelected(item: MenuItem): Boolean {
		return when (item.itemId) {
			R.id.create -> {
				createBarcode()
				true
			}
			R.id.history -> {
				startActivity(MainActivity.getHistoryIntent(this))
				true
			}
			R.id.pick_file -> {
				startActivityForResult(
					Intent.createChooser(
						Intent(Intent.ACTION_GET_CONTENT).apply {
							type = "image/*"
						},
						getString(R.string.pick_file)
					),
					PICK_FILE_RESULT_CODE
				)
				true
			}
			R.id.switch_camera -> {
				switchCamera()
				true
			}
			R.id.bulk_mode -> {
				bulkMode = bulkMode xor true
				item.isChecked = bulkMode
				ignoreNext = null
				true
			}
			R.id.restrict_format -> {
				showRestrictionDialog()
				true
			}
			R.id.preferences -> {
				startActivity(MainActivity.getPreferencesIntent(this))
				true
			}
			R.id.info -> {
				openReadme()
				true
			}
			else -> super.onOptionsItemSelected(item)
		}
	}

	private fun createBarcode() {
		startActivity(MainActivity.getEncodeIntent(this))
	}

	private fun switchCamera() {
		closeCamera()
		frontFacing = frontFacing xor true
		openCamera()
	}

	private fun showRestrictionDialog() {
		val names = resources.getStringArray(
			R.array.barcode_formats_names
		).toMutableList()
		val formats = resources.getStringArray(
			R.array.barcode_formats_values
		).toMutableList()
		if (restrictFormat != null) {
			names.add(0, getString(R.string.remove_restriction))
			formats.add(0, null)
		}
		AlertDialog.Builder(this).apply {
			setTitle(R.string.restrict_format)
			setItems(names.toTypedArray()) { _, which ->
				restrictFormat = formats[which]
				updateHints()
			}
			show()
		}
	}

	private fun openReadme() {
		val intent = Intent(
			Intent.ACTION_VIEW,
			Uri.parse(getString(R.string.project_url))
		)
		execShareIntent(intent)
	}

	private fun handleSendText(intent: Intent) {
		val text = intent.getStringExtra(Intent.EXTRA_TEXT)
		if (text?.isEmpty() == false) {
			startActivity(MainActivity.getEncodeIntent(this, text, true))
			finish()
		}
	}

	private fun initCameraView() {
		cameraView.setUseOrientationListener(true)
		@Suppress("ClickableViewAccessibility")
		cameraView.setOnTouchListener(object : View.OnTouchListener {
			var focus = true
			var offset = -1f
			var progress = 0

			override fun onTouch(v: View?, event: MotionEvent?): Boolean {
				event ?: return false
				val pos = event.y
				when (event.actionMasked) {
					MotionEvent.ACTION_DOWN -> {
						offset = pos
						progress = zoomBar.progress
						return true
					}
					MotionEvent.ACTION_MOVE -> {
						if (prefs.zoomBySwiping) {
							v ?: return false
							val dist = offset - pos
							val maxValue = zoomBar.max
							val change = maxValue / v.height.toFloat() * 2f * dist
							zoomBar.progress = min(
								maxValue,
								max(progress + change.roundToInt(), 0)
							)
							return true
						}
					}
					MotionEvent.ACTION_UP -> {
						// Stop calling focusTo() as soon as it returns false
						// to avoid throwing and catching future exceptions.
						if (focus) {
							focus = cameraView.focusTo(v, event.x, event.y)
							if (focus) {
								v?.performClick()
								return true
							}
						}
					}
				}
				return false
			}
		})
		@Suppress("DEPRECATION")
		cameraView.setOnCameraListener(object : CameraView.OnCameraListener {
			override fun onConfigureParameters(
				parameters: Camera.Parameters
			) {
				zoomBar.visibility = if (parameters.isZoomSupported) {
					val max = parameters.maxZoom
					if (zoomBar.max != max) {
						zoomBar.max = max
						zoomBar.progress = max / 10
						saveZoom()
					}
					parameters.zoom = zoomBar.progress
					View.VISIBLE
				} else {
					View.GONE
				}
				val sceneModes = parameters.supportedSceneModes
				sceneModes?.let {
					for (mode in sceneModes) {
						if (mode == Camera.Parameters.SCENE_MODE_BARCODE) {
							parameters.sceneMode = mode
							break
						}
					}
				}
				CameraView.setAutoFocus(parameters)
				updateFlashFab(parameters.flashMode == null)
			}

			override fun onCameraError() {
				this@CameraActivity.toast(R.string.camera_error)
				finish()
			}

			override fun onCameraReady(camera: Camera) {
				frameMetrics = FrameMetrics(
					cameraView.frameWidth,
					cameraView.frameHeight,
					cameraView.frameOrientation
				)
				updateFrameRoiAndMappingMatrix()
				ignoreNext = null
				decoding = true
				camera.setPreviewCallback { frameData, _ ->
					if (decoding) {
						ZxingCpp.readByteArray(
							frameData,
							frameMetrics.width,
							frameRoi.left, frameRoi.top,
							frameRoi.width(), frameRoi.height(),
							frameMetrics.orientation,
							formats.joinToString(),
							prefs.tryHarder,
							prefs.autoRotate,
							tryInvert = true,
							tryDownscale = true
						)?.let { result ->
							if (result.text != ignoreNext) {
								postResult(result.redact())
								decoding = false
							}
						}
					}
				}
			}

			override fun onPreviewStarted(camera: Camera) {
			}

			override fun onCameraStopping(camera: Camera) {
				camera.setPreviewCallback(null)
			}
		})
	}

	private fun initZoomBar() {
		zoomBar.setOnSeekBarChangeListener(object :
			SeekBar.OnSeekBarChangeListener {
			override fun onProgressChanged(
				seekBar: SeekBar,
				progress: Int,
				fromUser: Boolean
			) {
				cameraView.camera?.setZoom(progress)
			}

			override fun onStartTrackingTouch(seekBar: SeekBar) {}

			override fun onStopTrackingTouch(seekBar: SeekBar) {}
		})
		restoreZoom()
	}

	@Suppress("DEPRECATION")
	private fun Camera.setZoom(zoom: Int) {
		try {
			val params = parameters
			params.zoom = zoom
			parameters = params
		} catch (e: RuntimeException) {
			// Ignore. There's nothing we can do.
		}
	}

	private fun saveZoom() {
		val editor = prefs.preferences.edit()
		editor.putInt(ZOOM_MAX, zoomBar.max)
		editor.putInt(ZOOM_LEVEL, zoomBar.progress)
		editor.apply()
	}

	private fun restoreZoom() {
		zoomBar.max = prefs.preferences.getInt(ZOOM_MAX, zoomBar.max)
		zoomBar.progress = prefs.preferences.getInt(
			ZOOM_LEVEL,
			zoomBar.progress
		)
	}

	private fun initDetectorView() {
		detectorView.onRoiChange = {
			decoding = false
		}
		detectorView.onRoiChanged = {
			decoding = true
			updateFrameRoiAndMappingMatrix()
		}
		detectorView.setPaddingFromWindowInsets()
		detectorView.restoreCropHandlePos()
	}

	private fun updateFrameRoiAndMappingMatrix() {
		val viewRect = cameraView.previewRect
		val viewRoi = if (detectorView.roi.width() < 1) {
			viewRect
		} else {
			detectorView.roi
		}
		frameRoi.setFrameRoi(frameMetrics, viewRect, viewRoi)
		matrix.setFrameToView(frameMetrics, viewRect, viewRoi)
	}

	private fun updateFlashFab(unavailable: Boolean) {
		if (unavailable) {
			flashFab.setImageResource(R.drawable.ic_action_create)
			flashFab.setOnClickListener { createBarcode() }
		} else {
			flashFab.setImageResource(R.drawable.ic_action_flash)
			flashFab.setOnClickListener { toggleTorchMode() }
		}
	}

	@Suppress("DEPRECATION")
	private fun toggleTorchMode() {
		val camera = cameraView.camera ?: return
		val parameters = camera.parameters ?: return
		parameters.flashMode = if (
			parameters.flashMode != Camera.Parameters.FLASH_MODE_OFF
		) {
			Camera.Parameters.FLASH_MODE_OFF
		} else {
			Camera.Parameters.FLASH_MODE_TORCH
		}
		try {
			camera.parameters = parameters
		} catch (e: RuntimeException) {
			toast(e.message ?: getString(R.string.error_flash))
		}
	}

	private fun postResult(result: Result) {
		cameraView.post {
			detectorView.update(
				matrix.mapPosition(
					result.position,
					detectorView.coordinates
				)
			)
			vibrator.vibrate()
			val returnUri = returnUrlTemplate?.let {
				try {
					completeUrl(it, result)
				} catch (e: Exception) {
					e.message?.let { message ->
						toast(message)
					}
					null
				}
			}
			when {
				returnResult -> {
					setResult(Activity.RESULT_OK, getReturnIntent(result))
					finish()
				}
				returnUri != null -> execShareIntent(
					Intent(Intent.ACTION_VIEW, returnUri)
				)
				else -> {
					showResult(
						this@CameraActivity,
						result,
						vibrator,
						bulkMode
					)
					// If this app was invoked via a deep link but without
					// a return URI, we probably don't want to return to
					// the camera screen after scanning, but to the caller.
					if (finishAfterShowingResult) {
						finish()
					}
				}
			}
			if (bulkMode) {
				ignoreNext = result.text
				if (prefs.showToastInBulkMode) {
					toast(result.text)
				}
				detectorView.postDelayed({
					decoding = true
				}, prefs.bulkModeDelay.toLong())
			}
		}
	}

	companion object {
		private const val PICK_FILE_RESULT_CODE = 1
		private const val ZOOM_MAX = "zoom_max"
		private const val ZOOM_LEVEL = "zoom_level"
		private const val FRONT_FACING = "front_facing"
		private const val BULK_MODE = "bulk_mode"
		private const val RESTRICT_FORMAT = "restrict_format"
	}
}

fun Result.redact() = if (
	format == Format.PDF_417.name &&
	text.isNotEmpty()
) {
	Result(
		format,
		contentType,
		String(
			text.toByteArray(Charset.forName("ISO-8859-1")),
			Charset.forName("UTF-8")
		),
		position,
		orientation,
		rawBytes,
		ecLevel,
		symbologyIdentifier,
		sequenceSize,
		sequenceIndex,
		sequenceId,
		readerInit,
		lineCount,
		versionNumber,
		gtin
	)
} else {
	this
}

fun showResult(
	activity: Activity,
	result: Result,
	vibrator: Vibrator,
	bulkMode: Boolean = false,
) {
	if (prefs.copyImmediately) {
		activity.copyToClipboard(result.text)
	}
	val scan = result.toScan()
	if (prefs.useHistory) {
		scan.id = db.insertScan(scan)
	}
	if (prefs.sendScanActive && prefs.sendScanUrl.isNotEmpty()) {
		if (prefs.sendScanType == "4") {
			activity.openUrl(
				prefs.sendScanUrl + scan.content.urlEncode()
			)
			return
		}
		scan.sendAsync(
			prefs.sendScanUrl,
			prefs.sendScanType
		) { code, body ->
			if (code == null || code < 200 || code > 299) {
				vibrator.error()
				if (!activity.isSilent()) {
					beepBeepBeep()
				}
			}
			if (body != null && body.isNotEmpty()) {
				activity.toast(body)
			} else if (code == null || code > 299) {
				activity.toast(R.string.background_request_failed)
			}
		}
	}
	if (!bulkMode) {
		activity.startActivity(
			MainActivity.getDecodeIntent(activity, scan)
		)
	}
}

private fun getReturnIntent(result: Result) = Intent().apply {
	putExtra("SCAN_RESULT", result.text)
	putExtra("SCAN_RESULT_FORMAT", result.format)
	putExtra("SCAN_RESULT_ORIENTATION", result.orientation)
	putExtra("SCAN_RESULT_ERROR_CORRECTION_LEVEL", result.ecLevel)
	if (result.rawBytes.isNotEmpty()) {
		putExtra("SCAN_RESULT_BYTES", result.rawBytes)
	}
}

private fun String.isReturnUrl() = listOf(
	"binaryeye://scan",
	"http://markusfisch.de/BinaryEye",
	"https://markusfisch.de/BinaryEye"
).firstOrNull { startsWith(it) } != null

private fun completeUrl(urlTemplate: String, result: Result) = Uri.parse(
	urlTemplate
		.replace("{RESULT}", result.text.urlEncode())
		.replace("{RESULT_BYTES}", result.rawBytes.toHexString())
		.replace(
			"{FORMAT}", result.format.urlEncode()
		)
		// And support {CODE} from the old ZXing app, too.
		.replace("{CODE}", result.text.urlEncode())
)

private fun Context.isSilent(): Boolean {
	val am = getSystemService(Context.AUDIO_SERVICE) as AudioManager
	return when (am.ringerMode) {
		AudioManager.RINGER_MODE_SILENT,
		AudioManager.RINGER_MODE_VIBRATE -> true
		else -> false
	}
}

private fun beepBeepBeep() {
	ToneGenerator(AudioManager.STREAM_ALARM, 100).startTone(
		ToneGenerator.TONE_CDMA_CONFIRM,
		1000
	)
}
		) { code, body ->
			if (code == null || code < 200 || code > 299) {
				vibrator.error()
				beepBeepBeep()
			}
			if (body != null && body.isNotEmpty()) {
				activity.toast(body)
		.replace("{CODE}", result.text.urlEncode())
)

private fun beepBeepBeep() {
	ToneGenerator(AudioManager.STREAM_ALARM, 100).startTone(
		ToneGenerator.TONE_CDMA_CONFIRM,

<MSG> Only play error peep when not in silent mode

The app shouldn't play any sound when the phone is set to silent.

<DFF> @@ -795,7 +795,9 @@ fun showResult(
 		) { code, body ->
 			if (code == null || code < 200 || code > 299) {
 				vibrator.error()
-				beepBeepBeep()
+				if (!activity.isSilent()) {
+					beepBeepBeep()
+				}
 			}
 			if (body != null && body.isNotEmpty()) {
 				activity.toast(body)
@@ -878,6 +880,15 @@ private fun completeUrl(urlTemplate: String, result: Result) = Uri.parse(
 		.replace("{CODE}", result.text.urlEncode())
 )
 
+private fun Context.isSilent(): Boolean {
+	val am = getSystemService(Context.AUDIO_SERVICE) as AudioManager
+	return when (am.ringerMode) {
+		AudioManager.RINGER_MODE_SILENT,
+		AudioManager.RINGER_MODE_VIBRATE -> true
+		else -> false
+	}
+}
+
 private fun beepBeepBeep() {
 	ToneGenerator(AudioManager.STREAM_ALARM, 100).startTone(
 		ToneGenerator.TONE_CDMA_CONFIRM,

{'commit': 'diff --git a/app/src/main/kotlin/de/markusfisch/android/binaryeye/activity/CameraActivity.kt b/app/src/main/kotlin/de/markusfisch/android/binaryeye/activity/CameraActivity.kt\nindex 91f84b8..4702fe4 100644\n--- a/app/src/main/kotlin/de/markusfisch/android/binaryeye/activity/CameraActivity.kt\n+++ b/app/src/main/kotlin/de/markusfisch/android/binaryeye/activity/CameraActivity.kt\n@@ -795,7 +795,9 @@ fun showResult(\n \t\t) { code, body ->\n \t\t\tif (code == null || code < 200 || code > 299) {\n \t\t\t\tvibrator.error()\n-\t\t\t\tbeepBeepBeep()\n+\t\t\t\tif (!activity.isSilent()) {\n+\t\t\t\t\tbeepBeepBeep()\n+\t\t\t\t}\n \t\t\t}\n \t\t\tif (body != null && body.isNotEmpty()) {\n \t\t\t\tactivity.toast(body)\n@@ -878,6 +880,15 @@ private fun completeUrl(urlTemplate: String, result: Result) = Uri.parse(\n \t\t.replace("{CODE}", result.text.urlEncode())\n )\n \n+private fun Context.isSilent(): Boolean {\n+\tval am = getSystemService(Context.AUDIO_SERVICE) as AudioManager\n+\treturn when (am.ringerMode) {\n+\t\tAudioManager.RINGER_MODE_SILENT,\n+\t\tAudioManager.RINGER_MODE_VIBRATE -> true\n+\t\telse -> false\n+\t}\n+}\n+\n private fun beepBeepBeep() {\n \tToneGenerator(AudioManager.STREAM_ALARM, 100).startTone(\n \t\tToneGenerator.TONE_CDMA_CONFIRM,\n', 'commit_subject': 'Only play error peep when not in silent mode', 'repo_name': 'markusfisch/BinaryEye', 'committer': {'name': 'Markus Fisch', 'email': '5d838ec8c2c344259f3c8540e98ed35f35d69aac@markusfisch.de', 'time_sec': '1647894822', 'tz_offset': '60', 'date': {'seconds': '1647894822'}}, 'commit_tree': '3223da6b8d1357bb8af0f4d8584411e5d63883b6', 'commit_message': "Only play error peep when not in silent mode\n\nThe app shouldn't play any sound when the phone is set to silent.\n", 'language_name': 'Kotlin', 'license': 'mit', 'hunks': '@@ -795,7 +795,9 @@ fun showResult(\n \t\t) { code, body ->\n \t\t\tif (code == null || code < 200 || code > 299) {\n \t\t\t\tvibrator.error()\n-\t\t\t\tbeepBeepBeep()\n+\t\t\t\tif (!activity.isSilent()) {\n+\t\t\t\t\tbeepBeepBeep()\n+\t\t\t\t}\n \t\t\t}\n \t\t\tif (body != null && body.isNotEmpty()) {\n \t\t\t\tactivity.toast(body)\n@@ -878,6 +880,15 @@ private fun completeUrl(urlTemplate: String, result: Result) = Uri.parse(\n \t\t.replace("{CODE}", result.text.urlEncode())\n )\n \n+private fun Context.isSilent(): Boolean {\n+\tval am = getSystemService(Context.AUDIO_SERVICE) as AudioManager\n+\treturn when (am.ringerMode) {\n+\t\tAudioManager.RINGER_MODE_SILENT,\n+\t\tAudioManager.RINGER_MODE_VIBRATE -> true\n+\t\telse -> false\n+\t}\n+}\n+\n private fun beepBeepBeep() {\n \tToneGenerator(AudioManager.STREAM_ALARM, 100).startTone(\n \t\tToneGenerator.TONE_CDMA_CONFIRM,\n', 'addition_count': 12, 'deletion_count': 1, 'src_file': 'a/app/src/main/kotlin/de/markusfisch/android/binaryeye/activity/CameraActivity.kt', 'tgt_file': 'b/app/src/main/kotlin/de/markusfisch/android/binaryeye/activity/CameraActivity.kt', 'file_extension': '.kt', 'before_file': 'package de.markusfisch.android.binaryeye.activity\n\nimport android.app.Activity\nimport android.app.AlertDialog\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.PackageManager\nimport android.graphics.Matrix\nimport android.graphics.Rect\nimport android.hardware.Camera\nimport android.media.AudioManager\nimport android.media.ToneGenerator\nimport android.net.Uri\nimport android.os.Bundle\nimport android.os.Vibrator\nimport android.support.design.widget.FloatingActionButton\nimport android.support.v7.app.AppCompatActivity\nimport android.support.v7.widget.Toolbar\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.MotionEvent\nimport android.view.View\nimport android.widget.SeekBar\nimport de.markusfisch.android.binaryeye.R\nimport de.markusfisch.android.binaryeye.adapter.prettifyFormatName\nimport de.markusfisch.android.binaryeye.app.*\nimport de.markusfisch.android.binaryeye.content.copyToClipboard\nimport de.markusfisch.android.binaryeye.content.execShareIntent\nimport de.markusfisch.android.binaryeye.content.openUrl\nimport de.markusfisch.android.binaryeye.content.toHexString\nimport de.markusfisch.android.binaryeye.database.toScan\nimport de.markusfisch.android.binaryeye.graphics.FrameMetrics\nimport de.markusfisch.android.binaryeye.graphics.mapPosition\nimport de.markusfisch.android.binaryeye.graphics.setFrameRoi\nimport de.markusfisch.android.binaryeye.graphics.setFrameToView\nimport de.markusfisch.android.binaryeye.net.sendAsync\nimport de.markusfisch.android.binaryeye.net.urlEncode\nimport de.markusfisch.android.binaryeye.os.error\nimport de.markusfisch.android.binaryeye.os.getVibrator\nimport de.markusfisch.android.binaryeye.os.vibrate\nimport de.markusfisch.android.binaryeye.view.initSystemBars\nimport de.markusfisch.android.binaryeye.view.setPaddingFromWindowInsets\nimport de.markusfisch.android.binaryeye.widget.DetectorView\nimport de.markusfisch.android.binaryeye.widget.toast\nimport de.markusfisch.android.cameraview.widget.CameraView\nimport de.markusfisch.android.zxingcpp.ZxingCpp\nimport de.markusfisch.android.zxingcpp.ZxingCpp.Format\nimport de.markusfisch.android.zxingcpp.ZxingCpp.Result\nimport java.nio.charset.Charset\nimport kotlin.math.max\nimport kotlin.math.min\nimport kotlin.math.roundToInt\n\nclass CameraActivity : AppCompatActivity() {\n\tprivate val frameRoi = Rect()\n\tprivate val matrix = Matrix()\n\n\tprivate lateinit var vibrator: Vibrator\n\tprivate lateinit var cameraView: CameraView\n\tprivate lateinit var detectorView: DetectorView\n\tprivate lateinit var zoomBar: SeekBar\n\tprivate lateinit var flashFab: FloatingActionButton\n\n\tprivate var formats = setOf<String>()\n\tprivate var frameMetrics = FrameMetrics()\n\tprivate var decoding = true\n\tprivate var returnResult = false\n\tprivate var returnUrlTemplate: String? = null\n\tprivate var finishAfterShowingResult = false\n\tprivate var frontFacing = false\n\tprivate var bulkMode = prefs.bulkMode\n\tprivate var restrictFormat: String? = null\n\tprivate var ignoreNext: String? = null\n\tprivate var fallbackBuffer: IntArray? = null\n\n\toverride fun onRequestPermissionsResult(\n\t\trequestCode: Int,\n\t\tpermissions: Array<String>,\n\t\tgrantResults: IntArray\n\t) {\n\t\twhen (requestCode) {\n\t\t\tPERMISSION_CAMERA -> if (grantResults.isNotEmpty() &&\n\t\t\t\tgrantResults[0] != PackageManager.PERMISSION_GRANTED\n\t\t\t) {\n\t\t\t\ttoast(R.string.no_camera_no_fun)\n\t\t\t\tfinish()\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onActivityResult(\n\t\trequestCode: Int,\n\t\tresultCode: Int,\n\t\tresultData: Intent?\n\t) {\n\t\twhen (requestCode) {\n\t\t\tPICK_FILE_RESULT_CODE -> {\n\t\t\t\tif (resultCode == Activity.RESULT_OK && resultData != null) {\n\t\t\t\t\tval pick = Intent(this, PickActivity::class.java)\n\t\t\t\t\tpick.action = Intent.ACTION_VIEW\n\t\t\t\t\tpick.setDataAndType(resultData.data, "image/*")\n\t\t\t\t\tstartActivity(pick)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun attachBaseContext(base: Context?) {\n\t\tbase?.applyLocale(prefs.customLocale)\n\t\tsuper.attachBaseContext(base)\n\t}\n\n\toverride fun onCreate(state: Bundle?) {\n\t\tsuper.onCreate(state)\n\t\tsetContentView(R.layout.activity_camera)\n\n\t\t// Necessary to get the right translation after setting a\n\t\t// custom locale.\n\t\tsetTitle(R.string.scan_code)\n\n\t\tvibrator = getVibrator()\n\n\t\tinitSystemBars(this)\n\t\tsetSupportActionBar(findViewById(R.id.toolbar) as Toolbar)\n\n\t\tcameraView = findViewById(R.id.camera_view) as CameraView\n\t\tdetectorView = findViewById(R.id.detector_view) as DetectorView\n\t\tzoomBar = findViewById(R.id.zoom) as SeekBar\n\t\tflashFab = findViewById(R.id.flash) as FloatingActionButton\n\n\t\tinitCameraView()\n\t\tinitZoomBar()\n\t\tinitDetectorView()\n\n\t\tif (intent?.action == Intent.ACTION_SEND &&\n\t\t\tintent.type == "text/plain"\n\t\t) {\n\t\t\thandleSendText(intent)\n\t\t}\n\t}\n\n\toverride fun onDestroy() {\n\t\tsuper.onDestroy()\n\t\tfallbackBuffer = null\n\t\tsaveZoom()\n\t\tdetectorView.saveCropHandlePos()\n\t}\n\n\toverride fun onResume() {\n\t\tsuper.onResume()\n\t\tSystem.gc()\n\t\tupdateHints()\n\t\tif (prefs.bulkMode && bulkMode != prefs.bulkMode) {\n\t\t\tbulkMode = prefs.bulkMode\n\t\t\tinvalidateOptionsMenu()\n\t\t\tignoreNext = null\n\t\t}\n\t\tsetReturnTarget(intent)\n\t\tif (hasCameraPermission()) {\n\t\t\topenCamera()\n\t\t}\n\t}\n\n\tprivate fun updateHints() {\n\t\tval restriction = restrictFormat\n\t\tformats = if (restriction != null) {\n\t\t\ttitle = getString(\n\t\t\t\tR.string.scan_format,\n\t\t\t\tprettifyFormatName(restriction)\n\t\t\t)\n\t\t\tsetOf(restriction)\n\t\t} else {\n\t\t\tsetTitle(R.string.scan_code)\n\t\t\tprefs.barcodeFormats\n\t\t}\n\t}\n\n\tprivate fun setReturnTarget(intent: Intent?) {\n\t\twhen {\n\t\t\tintent?.action == "com.google.zxing.client.android.SCAN" -> {\n\t\t\t\treturnResult = true\n\t\t\t}\n\t\t\tintent?.dataString?.isReturnUrl() == true -> {\n\t\t\t\tfinishAfterShowingResult = true\n\t\t\t\treturnUrlTemplate = intent.data?.getQueryParameter("ret")\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun openCamera() {\n\t\tcameraView.openAsync(\n\t\t\tCameraView.findCameraId(\n\t\t\t\t@Suppress("DEPRECATION")\n\t\t\t\tif (frontFacing) {\n\t\t\t\t\tCamera.CameraInfo.CAMERA_FACING_FRONT\n\t\t\t\t} else {\n\t\t\t\t\tCamera.CameraInfo.CAMERA_FACING_BACK\n\t\t\t\t}\n\t\t\t)\n\t\t)\n\t}\n\n\toverride fun onPause() {\n\t\tsuper.onPause()\n\t\tcloseCamera()\n\t}\n\n\tprivate fun closeCamera() {\n\t\tcameraView.close()\n\t}\n\n\toverride fun onRestoreInstanceState(savedState: Bundle) {\n\t\tsuper.onRestoreInstanceState(savedState)\n\t\tzoomBar.max = savedState.getInt(ZOOM_MAX)\n\t\tzoomBar.progress = savedState.getInt(ZOOM_LEVEL)\n\t\tfrontFacing = savedState.getBoolean(FRONT_FACING)\n\t\tbulkMode = savedState.getBoolean(BULK_MODE)\n\t\trestrictFormat = savedState.getString(RESTRICT_FORMAT)\n\t}\n\n\toverride fun onSaveInstanceState(outState: Bundle) {\n\t\toutState.putInt(ZOOM_MAX, zoomBar.max)\n\t\toutState.putInt(ZOOM_LEVEL, zoomBar.progress)\n\t\toutState.putBoolean(FRONT_FACING, frontFacing)\n\t\toutState.putBoolean(BULK_MODE, bulkMode)\n\t\toutState.putString(RESTRICT_FORMAT, restrictFormat)\n\t\tsuper.onSaveInstanceState(outState)\n\t}\n\n\toverride fun dispatchTouchEvent(ev: MotionEvent): Boolean {\n\t\t// Always give crop handle precedence over other controls\n\t\t// because it can easily overlap and would then be inaccessible.\n\t\tif (detectorView.onTouchEvent(ev)) {\n\t\t\treturn true\n\t\t}\n\t\treturn super.dispatchTouchEvent(ev)\n\t}\n\n\toverride fun onCreateOptionsMenu(menu: Menu): Boolean {\n\t\tmenuInflater.inflate(R.menu.activity_camera, menu)\n\t\tmenu.findItem(R.id.bulk_mode).isChecked = bulkMode\n\t\treturn true\n\t}\n\n\toverride fun onOptionsItemSelected(item: MenuItem): Boolean {\n\t\treturn when (item.itemId) {\n\t\t\tR.id.create -> {\n\t\t\t\tcreateBarcode()\n\t\t\t\ttrue\n\t\t\t}\n\t\t\tR.id.history -> {\n\t\t\t\tstartActivity(MainActivity.getHistoryIntent(this))\n\t\t\t\ttrue\n\t\t\t}\n\t\t\tR.id.pick_file -> {\n\t\t\t\tstartActivityForResult(\n\t\t\t\t\tIntent.createChooser(\n\t\t\t\t\t\tIntent(Intent.ACTION_GET_CONTENT).apply {\n\t\t\t\t\t\t\ttype = "image/*"\n\t\t\t\t\t\t},\n\t\t\t\t\t\tgetString(R.string.pick_file)\n\t\t\t\t\t),\n\t\t\t\t\tPICK_FILE_RESULT_CODE\n\t\t\t\t)\n\t\t\t\ttrue\n\t\t\t}\n\t\t\tR.id.switch_camera -> {\n\t\t\t\tswitchCamera()\n\t\t\t\ttrue\n\t\t\t}\n\t\t\tR.id.bulk_mode -> {\n\t\t\t\tbulkMode = bulkMode xor true\n\t\t\t\titem.isChecked = bulkMode\n\t\t\t\tignoreNext = null\n\t\t\t\ttrue\n\t\t\t}\n\t\t\tR.id.restrict_format -> {\n\t\t\t\tshowRestrictionDialog()\n\t\t\t\ttrue\n\t\t\t}\n\t\t\tR.id.preferences -> {\n\t\t\t\tstartActivity(MainActivity.getPreferencesIntent(this))\n\t\t\t\ttrue\n\t\t\t}\n\t\t\tR.id.info -> {\n\t\t\t\topenReadme()\n\t\t\t\ttrue\n\t\t\t}\n\t\t\telse -> super.onOptionsItemSelected(item)\n\t\t}\n\t}\n\n\tprivate fun createBarcode() {\n\t\tstartActivity(MainActivity.getEncodeIntent(this))\n\t}\n\n\tprivate fun switchCamera() {\n\t\tcloseCamera()\n\t\tfrontFacing = frontFacing xor true\n\t\topenCamera()\n\t}\n\n\tprivate fun showRestrictionDialog() {\n\t\tval names = resources.getStringArray(\n\t\t\tR.array.barcode_formats_names\n\t\t).toMutableList()\n\t\tval formats = resources.getStringArray(\n\t\t\tR.array.barcode_formats_values\n\t\t).toMutableList()\n\t\tif (restrictFormat != null) {\n\t\t\tnames.add(0, getString(R.string.remove_restriction))\n\t\t\tformats.add(0, null)\n\t\t}\n\t\tAlertDialog.Builder(this).apply {\n\t\t\tsetTitle(R.string.restrict_format)\n\t\t\tsetItems(names.toTypedArray()) { _, which ->\n\t\t\t\trestrictFormat = formats[which]\n\t\t\t\tupdateHints()\n\t\t\t}\n\t\t\tshow()\n\t\t}\n\t}\n\n\tprivate fun openReadme() {\n\t\tval intent = Intent(\n\t\t\tIntent.ACTION_VIEW,\n\t\t\tUri.parse(getString(R.string.project_url))\n\t\t)\n\t\texecShareIntent(intent)\n\t}\n\n\tprivate fun handleSendText(intent: Intent) {\n\t\tval text = intent.getStringExtra(Intent.EXTRA_TEXT)\n\t\tif (text?.isEmpty() == false) {\n\t\t\tstartActivity(MainActivity.getEncodeIntent(this, text, true))\n\t\t\tfinish()\n\t\t}\n\t}\n\n\tprivate fun initCameraView() {\n\t\tcameraView.setUseOrientationListener(true)\n\t\t@Suppress("ClickableViewAccessibility")\n\t\tcameraView.setOnTouchListener(object : View.OnTouchListener {\n\t\t\tvar focus = true\n\t\t\tvar offset = -1f\n\t\t\tvar progress = 0\n\n\t\t\toverride fun onTouch(v: View?, event: MotionEvent?): Boolean {\n\t\t\t\tevent ?: return false\n\t\t\t\tval pos = event.y\n\t\t\t\twhen (event.actionMasked) {\n\t\t\t\t\tMotionEvent.ACTION_DOWN -> {\n\t\t\t\t\t\toffset = pos\n\t\t\t\t\t\tprogress = zoomBar.progress\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t\tMotionEvent.ACTION_MOVE -> {\n\t\t\t\t\t\tif (prefs.zoomBySwiping) {\n\t\t\t\t\t\t\tv ?: return false\n\t\t\t\t\t\t\tval dist = offset - pos\n\t\t\t\t\t\t\tval maxValue = zoomBar.max\n\t\t\t\t\t\t\tval change = maxValue / v.height.toFloat() * 2f * dist\n\t\t\t\t\t\t\tzoomBar.progress = min(\n\t\t\t\t\t\t\t\tmaxValue,\n\t\t\t\t\t\t\t\tmax(progress + change.roundToInt(), 0)\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\treturn true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tMotionEvent.ACTION_UP -> {\n\t\t\t\t\t\t// Stop calling focusTo() as soon as it returns false\n\t\t\t\t\t\t// to avoid throwing and catching future exceptions.\n\t\t\t\t\t\tif (focus) {\n\t\t\t\t\t\t\tfocus = cameraView.focusTo(v, event.x, event.y)\n\t\t\t\t\t\t\tif (focus) {\n\t\t\t\t\t\t\t\tv?.performClick()\n\t\t\t\t\t\t\t\treturn true\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn false\n\t\t\t}\n\t\t})\n\t\t@Suppress("DEPRECATION")\n\t\tcameraView.setOnCameraListener(object : CameraView.OnCameraListener {\n\t\t\toverride fun onConfigureParameters(\n\t\t\t\tparameters: Camera.Parameters\n\t\t\t) {\n\t\t\t\tzoomBar.visibility = if (parameters.isZoomSupported) {\n\t\t\t\t\tval max = parameters.maxZoom\n\t\t\t\t\tif (zoomBar.max != max) {\n\t\t\t\t\t\tzoomBar.max = max\n\t\t\t\t\t\tzoomBar.progress = max / 10\n\t\t\t\t\t\tsaveZoom()\n\t\t\t\t\t}\n\t\t\t\t\tparameters.zoom = zoomBar.progress\n\t\t\t\t\tView.VISIBLE\n\t\t\t\t} else {\n\t\t\t\t\tView.GONE\n\t\t\t\t}\n\t\t\t\tval sceneModes = parameters.supportedSceneModes\n\t\t\t\tsceneModes?.let {\n\t\t\t\t\tfor (mode in sceneModes) {\n\t\t\t\t\t\tif (mode == Camera.Parameters.SCENE_MODE_BARCODE) {\n\t\t\t\t\t\t\tparameters.sceneMode = mode\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tCameraView.setAutoFocus(parameters)\n\t\t\t\tupdateFlashFab(parameters.flashMode == null)\n\t\t\t}\n\n\t\t\toverride fun onCameraError() {\n\t\t\t\tthis@CameraActivity.toast(R.string.camera_error)\n\t\t\t\tfinish()\n\t\t\t}\n\n\t\t\toverride fun onCameraReady(camera: Camera) {\n\t\t\t\tframeMetrics = FrameMetrics(\n\t\t\t\t\tcameraView.frameWidth,\n\t\t\t\t\tcameraView.frameHeight,\n\t\t\t\t\tcameraView.frameOrientation\n\t\t\t\t)\n\t\t\t\tupdateFrameRoiAndMappingMatrix()\n\t\t\t\tignoreNext = null\n\t\t\t\tdecoding = true\n\t\t\t\tcamera.setPreviewCallback { frameData, _ ->\n\t\t\t\t\tif (decoding) {\n\t\t\t\t\t\tZxingCpp.readByteArray(\n\t\t\t\t\t\t\tframeData,\n\t\t\t\t\t\t\tframeMetrics.width,\n\t\t\t\t\t\t\tframeRoi.left, frameRoi.top,\n\t\t\t\t\t\t\tframeRoi.width(), frameRoi.height(),\n\t\t\t\t\t\t\tframeMetrics.orientation,\n\t\t\t\t\t\t\tformats.joinToString(),\n\t\t\t\t\t\t\tprefs.tryHarder,\n\t\t\t\t\t\t\tprefs.autoRotate,\n\t\t\t\t\t\t\ttryInvert = true,\n\t\t\t\t\t\t\ttryDownscale = true\n\t\t\t\t\t\t)?.let { result ->\n\t\t\t\t\t\t\tif (result.text != ignoreNext) {\n\t\t\t\t\t\t\t\tpostResult(result.redact())\n\t\t\t\t\t\t\t\tdecoding = false\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\toverride fun onPreviewStarted(camera: Camera) {\n\t\t\t}\n\n\t\t\toverride fun onCameraStopping(camera: Camera) {\n\t\t\t\tcamera.setPreviewCallback(null)\n\t\t\t}\n\t\t})\n\t}\n\n\tprivate fun initZoomBar() {\n\t\tzoomBar.setOnSeekBarChangeListener(object :\n\t\t\tSeekBar.OnSeekBarChangeListener {\n\t\t\toverride fun onProgressChanged(\n\t\t\t\tseekBar: SeekBar,\n\t\t\t\tprogress: Int,\n\t\t\t\tfromUser: Boolean\n\t\t\t) {\n\t\t\t\tcameraView.camera?.setZoom(progress)\n\t\t\t}\n\n\t\t\toverride fun onStartTrackingTouch(seekBar: SeekBar) {}\n\n\t\t\toverride fun onStopTrackingTouch(seekBar: SeekBar) {}\n\t\t})\n\t\trestoreZoom()\n\t}\n\n\t@Suppress("DEPRECATION")\n\tprivate fun Camera.setZoom(zoom: Int) {\n\t\ttry {\n\t\t\tval params = parameters\n\t\t\tparams.zoom = zoom\n\t\t\tparameters = params\n\t\t} catch (e: RuntimeException) {\n\t\t\t// Ignore. There\'s nothing we can do.\n\t\t}\n\t}\n\n\tprivate fun saveZoom() {\n\t\tval editor = prefs.preferences.edit()\n\t\teditor.putInt(ZOOM_MAX, zoomBar.max)\n\t\teditor.putInt(ZOOM_LEVEL, zoomBar.progress)\n\t\teditor.apply()\n\t}\n\n\tprivate fun restoreZoom() {\n\t\tzoomBar.max = prefs.preferences.getInt(ZOOM_MAX, zoomBar.max)\n\t\tzoomBar.progress = prefs.preferences.getInt(\n\t\t\tZOOM_LEVEL,\n\t\t\tzoomBar.progress\n\t\t)\n\t}\n\n\tprivate fun initDetectorView() {\n\t\tdetectorView.onRoiChange = {\n\t\t\tdecoding = false\n\t\t}\n\t\tdetectorView.onRoiChanged = {\n\t\t\tdecoding = true\n\t\t\tupdateFrameRoiAndMappingMatrix()\n\t\t}\n\t\tdetectorView.setPaddingFromWindowInsets()\n\t\tdetectorView.restoreCropHandlePos()\n\t}\n\n\tprivate fun updateFrameRoiAndMappingMatrix() {\n\t\tval viewRect = cameraView.previewRect\n\t\tval viewRoi = if (detectorView.roi.width() < 1) {\n\t\t\tviewRect\n\t\t} else {\n\t\t\tdetectorView.roi\n\t\t}\n\t\tframeRoi.setFrameRoi(frameMetrics, viewRect, viewRoi)\n\t\tmatrix.setFrameToView(frameMetrics, viewRect, viewRoi)\n\t}\n\n\tprivate fun updateFlashFab(unavailable: Boolean) {\n\t\tif (unavailable) {\n\t\t\tflashFab.setImageResource(R.drawable.ic_action_create)\n\t\t\tflashFab.setOnClickListener { createBarcode() }\n\t\t} else {\n\t\t\tflashFab.setImageResource(R.drawable.ic_action_flash)\n\t\t\tflashFab.setOnClickListener { toggleTorchMode() }\n\t\t}\n\t}\n\n\t@Suppress("DEPRECATION")\n\tprivate fun toggleTorchMode() {\n\t\tval camera = cameraView.camera ?: return\n\t\tval parameters = camera.parameters ?: return\n\t\tparameters.flashMode = if (\n\t\t\tparameters.flashMode != Camera.Parameters.FLASH_MODE_OFF\n\t\t) {\n\t\t\tCamera.Parameters.FLASH_MODE_OFF\n\t\t} else {\n\t\t\tCamera.Parameters.FLASH_MODE_TORCH\n\t\t}\n\t\ttry {\n\t\t\tcamera.parameters = parameters\n\t\t} catch (e: RuntimeException) {\n\t\t\ttoast(e.message ?: getString(R.string.error_flash))\n\t\t}\n\t}\n\n\tprivate fun postResult(result: Result) {\n\t\tcameraView.post {\n\t\t\tdetectorView.update(\n\t\t\t\tmatrix.mapPosition(\n\t\t\t\t\tresult.position,\n\t\t\t\t\tdetectorView.coordinates\n\t\t\t\t)\n\t\t\t)\n\t\t\tvibrator.vibrate()\n\t\t\tval returnUri = returnUrlTemplate?.let {\n\t\t\t\ttry {\n\t\t\t\t\tcompleteUrl(it, result)\n\t\t\t\t} catch (e: Exception) {\n\t\t\t\t\te.message?.let { message ->\n\t\t\t\t\t\ttoast(message)\n\t\t\t\t\t}\n\t\t\t\t\tnull\n\t\t\t\t}\n\t\t\t}\n\t\t\twhen {\n\t\t\t\treturnResult -> {\n\t\t\t\t\tsetResult(Activity.RESULT_OK, getReturnIntent(result))\n\t\t\t\t\tfinish()\n\t\t\t\t}\n\t\t\t\treturnUri != null -> execShareIntent(\n\t\t\t\t\tIntent(Intent.ACTION_VIEW, returnUri)\n\t\t\t\t)\n\t\t\t\telse -> {\n\t\t\t\t\tshowResult(\n\t\t\t\t\t\tthis@CameraActivity,\n\t\t\t\t\t\tresult,\n\t\t\t\t\t\tvibrator,\n\t\t\t\t\t\tbulkMode\n\t\t\t\t\t)\n\t\t\t\t\t// If this app was invoked via a deep link but without\n\t\t\t\t\t// a return URI, we probably don\'t want to return to\n\t\t\t\t\t// the camera screen after scanning, but to the caller.\n\t\t\t\t\tif (finishAfterShowingResult) {\n\t\t\t\t\t\tfinish()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (bulkMode) {\n\t\t\t\tignoreNext = result.text\n\t\t\t\tif (prefs.showToastInBulkMode) {\n\t\t\t\t\ttoast(result.text)\n\t\t\t\t}\n\t\t\t\tdetectorView.postDelayed({\n\t\t\t\t\tdecoding = true\n\t\t\t\t}, prefs.bulkModeDelay.toLong())\n\t\t\t}\n\t\t}\n\t}\n\n\tcompanion object {\n\t\tprivate const val PICK_FILE_RESULT_CODE = 1\n\t\tprivate const val ZOOM_MAX = "zoom_max"\n\t\tprivate const val ZOOM_LEVEL = "zoom_level"\n\t\tprivate const val FRONT_FACING = "front_facing"\n\t\tprivate const val BULK_MODE = "bulk_mode"\n\t\tprivate const val RESTRICT_FORMAT = "restrict_format"\n\t}\n}\n\nfun Result.redact() = if (\n\tformat == Format.PDF_417.name &&\n\ttext.isNotEmpty()\n) {\n\tResult(\n\t\tformat,\n\t\tcontentType,\n\t\tString(\n\t\t\ttext.toByteArray(Charset.forName("ISO-8859-1")),\n\t\t\tCharset.forName("UTF-8")\n\t\t),\n\t\tposition,\n\t\torientation,\n\t\trawBytes,\n\t\tecLevel,\n\t\tsymbologyIdentifier,\n\t\tsequenceSize,\n\t\tsequenceIndex,\n\t\tsequenceId,\n\t\treaderInit,\n\t\tlineCount,\n\t\tversionNumber,\n\t\tgtin\n\t)\n} else {\n\tthis\n}\n\nfun showResult(\n\tactivity: Activity,\n\tresult: Result,\n\tvibrator: Vibrator,\n\tbulkMode: Boolean = false,\n) {\n\tif (prefs.copyImmediately) {\n\t\tactivity.copyToClipboard(result.text)\n\t}\n\tval scan = result.toScan()\n\tif (prefs.useHistory) {\n\t\tscan.id = db.insertScan(scan)\n\t}\n\tif (prefs.sendScanActive && prefs.sendScanUrl.isNotEmpty()) {\n\t\tif (prefs.sendScanType == "4") {\n\t\t\tactivity.openUrl(\n\t\t\t\tprefs.sendScanUrl + scan.content.urlEncode()\n\t\t\t)\n\t\t\treturn\n\t\t}\n\t\tscan.sendAsync(\n\t\t\tprefs.sendScanUrl,\n\t\t\tprefs.sendScanType\n\t\t) { code, body ->\n\t\t\tif (code == null || code < 200 || code > 299) {\n\t\t\t\tvibrator.error()\n\t\t\t\tif (!activity.isSilent()) {\n\t\t\t\t\tbeepBeepBeep()\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (body != null && body.isNotEmpty()) {\n\t\t\t\tactivity.toast(body)\n\t\t\t} else if (code == null || code > 299) {\n\t\t\t\tactivity.toast(R.string.background_request_failed)\n\t\t\t}\n\t\t}\n\t}\n\tif (!bulkMode) {\n\t\tactivity.startActivity(\n\t\t\tMainActivity.getDecodeIntent(activity, scan)\n\t\t)\n\t}\n}\n\nprivate fun getReturnIntent(result: Result) = Intent().apply {\n\tputExtra("SCAN_RESULT", result.text)\n\tputExtra("SCAN_RESULT_FORMAT", result.format)\n\tputExtra("SCAN_RESULT_ORIENTATION", result.orientation)\n\tputExtra("SCAN_RESULT_ERROR_CORRECTION_LEVEL", result.ecLevel)\n\tif (result.rawBytes.isNotEmpty()) {\n\t\tputExtra("SCAN_RESULT_BYTES", result.rawBytes)\n\t}\n}\n\nprivate fun String.isReturnUrl() = listOf(\n\t"binaryeye://scan",\n\t"http://markusfisch.de/BinaryEye",\n\t"https://markusfisch.de/BinaryEye"\n).firstOrNull { startsWith(it) } != null\n\nprivate fun completeUrl(urlTemplate: String, result: Result) = Uri.parse(\n\turlTemplate\n\t\t.replace("{RESULT}", result.text.urlEncode())\n\t\t.replace("{RESULT_BYTES}", result.rawBytes.toHexString())\n\t\t.replace(\n\t\t\t"{FORMAT}", result.format.urlEncode()\n\t\t)\n\t\t// And support {CODE} from the old ZXing app, too.\n\t\t.replace("{CODE}", result.text.urlEncode())\n)\n\nprivate fun Context.isSilent(): Boolean {\n\tval am = getSystemService(Context.AUDIO_SERVICE) as AudioManager\n\treturn when (am.ringerMode) {\n\t\tAudioManager.RINGER_MODE_SILENT,\n\t\tAudioManager.RINGER_MODE_VIBRATE -> true\n\t\telse -> false\n\t}\n}\n\nprivate fun beepBeepBeep() {\n\tToneGenerator(AudioManager.STREAM_ALARM, 100).startTone(\n\t\tToneGenerator.TONE_CDMA_CONFIRM,\n\t\t1000\n\t)\n}\n\t\t) { code, body ->\n\t\t\tif (code == null || code < 200 || code > 299) {\n\t\t\t\tvibrator.error()\n\t\t\t\tbeepBeepBeep()\n\t\t\t}\n\t\t\tif (body != null && body.isNotEmpty()) {\n\t\t\t\tactivity.toast(body)\n\t\t.replace("{CODE}", result.text.urlEncode())\n)\n\nprivate fun beepBeepBeep() {\n\tToneGenerator(AudioManager.STREAM_ALARM, 100).startTone(\n\t\tToneGenerator.TONE_CDMA_CONFIRM,\n', 'message': "Only play error peep when not in silent mode\n\nThe app shouldn't play any sound when the phone is set to silent.\n", 'author_name': 'Markus Fisch', 'lang': 'kt'}