From b91d9474d53636c414bd04f93ba3c4092b3c5910 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 4 Feb 2018 05:34:01 -0800 Subject: [PATCH] Support importing from file Now scanning QR codes on camera-less devices actually do sth. --- .github/faq.md | 5 -- .../com/github/shadowsocks/ScannerActivity.kt | 86 +++++++++++++++++-- .../src/main/res/drawable/ic_image_photo.xml | 10 +++ mobile/src/main/res/menu/app_manager_menu.xml | 6 +- mobile/src/main/res/menu/scanner_menu.xml | 10 +++ mobile/src/main/res/values/strings.xml | 1 + 6 files changed, 104 insertions(+), 14 deletions(-) create mode 100644 mobile/src/main/res/drawable/ic_image_photo.xml create mode 100644 mobile/src/main/res/menu/scanner_menu.xml diff --git a/.github/faq.md b/.github/faq.md index 58af541a..9292796a 100644 --- a/.github/faq.md +++ b/.github/faq.md @@ -12,11 +12,6 @@ Crash: [Submit an issue](https://github.com/shadowsocks/shadowsocks-android/issu Use [Tasker](http://tasker.dinglisch.net/) integration. -### How to add QR code from local gallery? - -Scan it with a third-party scanner like [QuickMark Barcode Scanner](https://play.google.com/store/apps/details?id=tw.com.quickmark) and click the `ss` url. - - ### Why is NAT mode deprecated? 1. Requiring ROOT permission; diff --git a/mobile/src/main/java/com/github/shadowsocks/ScannerActivity.kt b/mobile/src/main/java/com/github/shadowsocks/ScannerActivity.kt index 167a8822..d94ca422 100644 --- a/mobile/src/main/java/com/github/shadowsocks/ScannerActivity.kt +++ b/mobile/src/main/java/com/github/shadowsocks/ScannerActivity.kt @@ -20,8 +20,14 @@ package com.github.shadowsocks +import android.app.Activity +import android.content.Context +import android.content.Intent import android.content.pm.PackageManager import android.content.pm.ShortcutManager +import android.graphics.BitmapFactory +import android.hardware.camera2.CameraAccessException +import android.hardware.camera2.CameraManager import android.os.Build import android.os.Bundle import android.support.v4.app.ActivityCompat @@ -29,16 +35,24 @@ import android.support.v4.app.TaskStackBuilder import android.support.v4.content.ContextCompat import android.support.v7.app.AppCompatActivity import android.support.v7.widget.Toolbar +import android.view.MenuItem import android.widget.Toast +import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.ProfileManager import com.github.shadowsocks.utils.resolveResourceId +import com.google.zxing.BinaryBitmap +import com.google.zxing.MultiFormatReader +import com.google.zxing.RGBLuminanceSource import com.google.zxing.Result +import com.google.zxing.common.HybridBinarizer import me.dm7.barcodescanner.zxing.ZXingScannerView -class ScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler { +class ScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler, Toolbar.OnMenuItemClickListener { companion object { private const val MY_PERMISSIONS_REQUEST_CAMERA = 1 + private const val REQUEST_IMPORT = 2 + private const val REQUEST_IMPORT_OR_FINISH = 3 } private lateinit var scannerView: ZXingScannerView @@ -57,19 +71,36 @@ class ScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler { toolbar.title = title toolbar.setNavigationIcon(theme.resolveResourceId(R.attr.homeAsUpIndicator)) toolbar.setNavigationOnClickListener { navigateUp() } + toolbar.inflateMenu(R.menu.scanner_menu) + toolbar.setOnMenuItemClickListener(this) scannerView = findViewById(R.id.scanner) if (Build.VERSION.SDK_INT >= 25) getSystemService(ShortcutManager::class.java).reportShortcutUsed("scan") } - override fun onResume() { - super.onResume() + override fun onStart() { + super.onStart() + if (try { + (getSystemService(Context.CAMERA_SERVICE) as CameraManager).cameraIdList.isEmpty() + } catch (_: CameraAccessException) { + true + }) { + startImport() + return + } val permissionCheck = ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) + if (permissionCheck != PackageManager.PERMISSION_GRANTED) + ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CAMERA), + MY_PERMISSIONS_REQUEST_CAMERA) + } + + override fun onResume() { + super.onResume() + val permissionCheck = ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) if (permissionCheck == PackageManager.PERMISSION_GRANTED) { scannerView.setResultHandler(this) // Register ourselves as a handler for scan results. scannerView.startCamera() // Start camera on resume - } else ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CAMERA), - MY_PERMISSIONS_REQUEST_CAMERA) + } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { @@ -79,7 +110,7 @@ class ScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler { scannerView.startCamera() } else { Toast.makeText(this, R.string.add_profile_scanner_permission_required, Toast.LENGTH_SHORT).show() - finish() + startImport() } else super.onRequestPermissionsResult(requestCode, permissions, grantResults) } @@ -93,4 +124,47 @@ class ScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler { Profile.findAll(rawResult?.text).forEach { ProfileManager.createProfile(it) } navigateUp() } + + override fun onMenuItemClick(item: MenuItem) = when (item.itemId) { + R.id.action_import -> { + startImport(true) + true + } + else -> false + } + + private fun startImport(manual: Boolean = false) = startActivityForResult(Intent(Intent.ACTION_OPEN_DOCUMENT) + .addCategory(Intent.CATEGORY_OPENABLE) + .setType("image/*") + .putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true), if (manual) REQUEST_IMPORT else REQUEST_IMPORT_OR_FINISH) + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + when (requestCode) { + REQUEST_IMPORT, REQUEST_IMPORT_OR_FINISH -> { + if (resultCode == Activity.RESULT_OK) { + var list = listOfNotNull(data?.data) + val clipData = data?.clipData + if (clipData != null) list += (0 until clipData.itemCount).map { clipData.getItemAt(it).uri } + val resolver = contentResolver + val reader = MultiFormatReader() + var success = false + for (uri in list) try { + val bitmap = BitmapFactory.decodeStream(resolver.openInputStream(uri)) + val pixels = IntArray(bitmap.width * bitmap.height) + bitmap.getPixels(pixels, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height) + Profile.findAll(reader.decode(BinaryBitmap(HybridBinarizer( + RGBLuminanceSource(bitmap.width, bitmap.height, pixels)))).text).forEach { + ProfileManager.createProfile(it) + success = true + } + } catch (e: Exception) { + app.track(e) + } + Toast.makeText(this, if (success) R.string.action_import_msg else R.string.action_import_err, + Toast.LENGTH_SHORT).show() + finish() + } else if (requestCode == REQUEST_IMPORT_OR_FINISH) finish() + } + else -> super.onActivityResult(requestCode, resultCode, data) + } + } } diff --git a/mobile/src/main/res/drawable/ic_image_photo.xml b/mobile/src/main/res/drawable/ic_image_photo.xml new file mode 100644 index 00000000..722eadb9 --- /dev/null +++ b/mobile/src/main/res/drawable/ic_image_photo.xml @@ -0,0 +1,10 @@ + + + diff --git a/mobile/src/main/res/menu/app_manager_menu.xml b/mobile/src/main/res/menu/app_manager_menu.xml index 83b6eef2..bc8c5494 100644 --- a/mobile/src/main/res/menu/app_manager_menu.xml +++ b/mobile/src/main/res/menu/app_manager_menu.xml @@ -1,6 +1,6 @@ - + + + + \ No newline at end of file diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 43638d67..e25f9320 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -99,6 +99,7 @@ Apply Settings to All Profiles Export to Clipboard Import from Clipboard + Import from file… Successfully export! Failed to export. Successfully import!