Commit b91d9474 authored by Mygod's avatar Mygod

Support importing from file

Now scanning QR codes on camera-less devices actually do sth.
parent f0140c00
......@@ -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;
......
......@@ -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<out String>, 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)
}
}
}
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="#FF000000"
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
</vector>
<menu xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_apply_all"
android:title="@string/action_apply_all"
android:alphabeticShortcut="a"
......
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_import"
android:title="@string/action_import_file"
android:icon="@drawable/ic_image_photo"
android:alphabeticShortcut="p"
android:numericShortcut="1"
app:showAsAction="always"/>
</menu>
\ No newline at end of file
......@@ -99,6 +99,7 @@
<string name="action_apply_all">Apply Settings to All Profiles</string>
<string name="action_export">Export to Clipboard</string>
<string name="action_import">Import from Clipboard</string>
<string name="action_import_file">Import from file…</string>
<string name="action_export_msg">Successfully export!</string>
<string name="action_export_err">Failed to export.</string>
<string name="action_import_msg">Successfully import!</string>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment