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 ...@@ -12,11 +12,6 @@ Crash: [Submit an issue](https://github.com/shadowsocks/shadowsocks-android/issu
Use [Tasker](http://tasker.dinglisch.net/) integration. 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? ### Why is NAT mode deprecated?
1. Requiring ROOT permission; 1. Requiring ROOT permission;
......
...@@ -20,8 +20,14 @@ ...@@ -20,8 +20,14 @@
package com.github.shadowsocks 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.PackageManager
import android.content.pm.ShortcutManager 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.Build
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.ActivityCompat import android.support.v4.app.ActivityCompat
...@@ -29,16 +35,24 @@ import android.support.v4.app.TaskStackBuilder ...@@ -29,16 +35,24 @@ import android.support.v4.app.TaskStackBuilder
import android.support.v4.content.ContextCompat import android.support.v4.content.ContextCompat
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar import android.support.v7.widget.Toolbar
import android.view.MenuItem
import android.widget.Toast import android.widget.Toast
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.utils.resolveResourceId 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.Result
import com.google.zxing.common.HybridBinarizer
import me.dm7.barcodescanner.zxing.ZXingScannerView import me.dm7.barcodescanner.zxing.ZXingScannerView
class ScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler { class ScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler, Toolbar.OnMenuItemClickListener {
companion object { companion object {
private const val MY_PERMISSIONS_REQUEST_CAMERA = 1 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 private lateinit var scannerView: ZXingScannerView
...@@ -57,19 +71,36 @@ class ScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler { ...@@ -57,19 +71,36 @@ class ScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
toolbar.title = title toolbar.title = title
toolbar.setNavigationIcon(theme.resolveResourceId(R.attr.homeAsUpIndicator)) toolbar.setNavigationIcon(theme.resolveResourceId(R.attr.homeAsUpIndicator))
toolbar.setNavigationOnClickListener { navigateUp() } toolbar.setNavigationOnClickListener { navigateUp() }
toolbar.inflateMenu(R.menu.scanner_menu)
toolbar.setOnMenuItemClickListener(this)
scannerView = findViewById(R.id.scanner) scannerView = findViewById(R.id.scanner)
if (Build.VERSION.SDK_INT >= 25) getSystemService(ShortcutManager::class.java).reportShortcutUsed("scan") if (Build.VERSION.SDK_INT >= 25) getSystemService(ShortcutManager::class.java).reportShortcutUsed("scan")
} }
override fun onResume() { override fun onStart() {
super.onResume() super.onStart()
if (try {
(getSystemService(Context.CAMERA_SERVICE) as CameraManager).cameraIdList.isEmpty()
} catch (_: CameraAccessException) {
true
}) {
startImport()
return
}
val permissionCheck = ContextCompat.checkSelfPermission(this, val permissionCheck = ContextCompat.checkSelfPermission(this,
android.Manifest.permission.CAMERA) 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) { if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
scannerView.setResultHandler(this) // Register ourselves as a handler for scan results. scannerView.setResultHandler(this) // Register ourselves as a handler for scan results.
scannerView.startCamera() // Start camera on resume 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) { override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
...@@ -79,7 +110,7 @@ class ScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler { ...@@ -79,7 +110,7 @@ class ScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
scannerView.startCamera() scannerView.startCamera()
} else { } else {
Toast.makeText(this, R.string.add_profile_scanner_permission_required, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.add_profile_scanner_permission_required, Toast.LENGTH_SHORT).show()
finish() startImport()
} }
else super.onRequestPermissionsResult(requestCode, permissions, grantResults) else super.onRequestPermissionsResult(requestCode, permissions, grantResults)
} }
...@@ -93,4 +124,47 @@ class ScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler { ...@@ -93,4 +124,47 @@ class ScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
Profile.findAll(rawResult?.text).forEach { ProfileManager.createProfile(it) } Profile.findAll(rawResult?.text).forEach { ProfileManager.createProfile(it) }
navigateUp() 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" <menu
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_apply_all" <item android:id="@+id/action_apply_all"
android:title="@string/action_apply_all" android:title="@string/action_apply_all"
android:alphabeticShortcut="a" 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 @@ ...@@ -99,6 +99,7 @@
<string name="action_apply_all">Apply Settings to All Profiles</string> <string name="action_apply_all">Apply Settings to All Profiles</string>
<string name="action_export">Export to Clipboard</string> <string name="action_export">Export to Clipboard</string>
<string name="action_import">Import from 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_msg">Successfully export!</string>
<string name="action_export_err">Failed to export.</string> <string name="action_export_err">Failed to export.</string>
<string name="action_import_msg">Successfully import!</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