Refactor: extract image processor

This commit is contained in:
Ming Ming 2023-09-01 02:34:48 +08:00
parent 70a234a9b4
commit dbea0e0a55
249 changed files with 5644 additions and 4139 deletions

View file

@ -121,6 +121,6 @@ dependencies {
// fix crash on sdk33, need investigation
implementation "androidx.window:window:1.0.0"
implementation 'com.google.android.gms:play-services-maps:18.1.0'
implementation 'com.nkming.nc_photos.np_android_log:np_android_log'
implementation 'com.nkming.nc_photos.np_android_core:np_android_core'
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.3"
}

View file

@ -1,14 +1,14 @@
package com.nkming.nc_photos
import com.nkming.nc_photos.np_android_log.LogConfig
import com.nkming.nc_photos.np_android_core.LogConfig
import io.flutter.BuildConfig
import io.flutter.app.FlutterApplication
class App : FlutterApplication() {
override fun onCreate() {
super.onCreate()
LogConfig.isShowInfo = BuildConfig.DEBUG
LogConfig.isShowDebug = BuildConfig.DEBUG
LogConfig.isShowVerbose = BuildConfig.DEBUG
}
override fun onCreate() {
super.onCreate()
LogConfig.isShowInfo = BuildConfig.DEBUG
LogConfig.isShowDebug = BuildConfig.DEBUG
LogConfig.isShowVerbose = BuildConfig.DEBUG
}
}

View file

@ -1,46 +1,44 @@
package com.nkming.nc_photos
import android.content.Context
import com.nkming.nc_photos.np_android_log.logI
import com.nkming.nc_photos.np_android_core.logI
import java.util.Locale
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLSession
class CustomHostnameVerifier(context: Context) : HostnameVerifier {
/**
* Allow host names allowed by user, for other hosts, revert to the default
* behavior
*/
override fun verify(hostname: String, session: SSLSession): Boolean {
return if (allowedHosts.contains(
hostname.lowercase(Locale.getDefault())
)) {
// good
logI(
"CustomHostnameVerifier::verify",
"Allowing registered host: $hostname"
)
true
} else {
defaultHostnameVerifier.verify(hostname, session)
}
}
/**
* Allow host names allowed by user, for other hosts, revert to the default
* behavior
*/
override fun verify(hostname: String, session: SSLSession): Boolean {
return if (allowedHosts.contains(hostname.lowercase(Locale.getDefault()))) {
// good
logI(
"CustomHostnameVerifier::verify",
"Allowing registered host: $hostname"
)
true
} else {
defaultHostnameVerifier.verify(hostname, session)
}
}
fun reload(context: Context) {
val certManager = SelfSignedCertManager()
val certs = certManager.readAllCerts(context)
allowedHosts.clear()
for (c in certs) {
allowedHosts.add(c.first.host.lowercase(Locale.getDefault()))
}
}
fun reload(context: Context) {
val certManager = SelfSignedCertManager()
val certs = certManager.readAllCerts(context)
allowedHosts.clear()
for (c in certs) {
allowedHosts.add(c.first.host.lowercase(Locale.getDefault()))
}
}
private val defaultHostnameVerifier: HostnameVerifier =
HttpsURLConnection.getDefaultHostnameVerifier()
private val allowedHosts: MutableList<String> = ArrayList()
private val defaultHostnameVerifier: HostnameVerifier =
HttpsURLConnection.getDefaultHostnameVerifier()
private val allowedHosts: MutableList<String> = ArrayList()
init {
reload(context)
}
init {
reload(context)
}
}

View file

@ -9,80 +9,84 @@ import javax.net.ssl.X509TrustManager
// See: https://stackoverflow.com/a/6378872
class CustomKeyStoresTrustManager(keyStore: KeyStore) : X509TrustManager {
/*
* Delegate to the default trust manager.
*/
@Throws(CertificateException::class)
override fun checkClientTrusted(chain: Array<X509Certificate>,
authType: String) {
val defaultX509TrustManager = x509TrustManagers[0]
defaultX509TrustManager.checkClientTrusted(chain, authType)
}
/*
* Delegate to the default trust manager.
*/
@Throws(CertificateException::class)
override fun checkClientTrusted(
chain: Array<X509Certificate>, authType: String
) {
val defaultX509TrustManager = x509TrustManagers[0]
defaultX509TrustManager.checkClientTrusted(chain, authType)
}
/*
* Loop over the trustmanagers until we find one that accepts our server
*/
@Throws(CertificateException::class)
override fun checkServerTrusted(chain: Array<X509Certificate>,
authType: String) {
var defaultException: Exception? = null
for (tm in x509TrustManagers) {
try {
tm.checkServerTrusted(chain, authType)
return
} catch (e: CertificateException) {
// ignore
if (defaultException == null) {
defaultException = e
}
}
}
if (defaultException != null) {
throw defaultException
}
}
/*
* Loop over the trustmanagers until we find one that accepts our server
*/
@Throws(CertificateException::class)
override fun checkServerTrusted(
chain: Array<X509Certificate>, authType: String
) {
var defaultException: Exception? = null
for (tm in x509TrustManagers) {
try {
tm.checkServerTrusted(chain, authType)
return
} catch (e: CertificateException) {
// ignore
if (defaultException == null) {
defaultException = e
}
}
}
if (defaultException != null) {
throw defaultException
}
}
override fun getAcceptedIssuers(): Array<X509Certificate> {
val list = ArrayList<X509Certificate>()
for (tm in x509TrustManagers) {
list.addAll(tm.acceptedIssuers.toList())
}
return list.toTypedArray()
}
override fun getAcceptedIssuers(): Array<X509Certificate> {
val list = ArrayList<X509Certificate>()
for (tm in x509TrustManagers) {
list.addAll(tm.acceptedIssuers.toList())
}
return list.toTypedArray()
}
fun setCustomKeyStore(keyStore: KeyStore) {
val factories = ArrayList<TrustManagerFactory>()
// The default Trustmanager with default keystore
val original = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm())
original.init(null as KeyStore?)
factories.add(original)
fun setCustomKeyStore(keyStore: KeyStore) {
val factories = ArrayList<TrustManagerFactory>()
// The default Trustmanager with default keystore
val original = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm()
)
original.init(null as KeyStore?)
factories.add(original)
// with custom keystore
val custom = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm())
custom.init(keyStore)
factories.add(custom)
// with custom keystore
val custom = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm()
)
custom.init(keyStore)
factories.add(custom)
/*
* Iterate over the returned trustmanagers, and hold on
* to any that are X509TrustManagers
*/
for (tmf in factories) {
for (tm in tmf.trustManagers) {
if (tm is X509TrustManager) {
x509TrustManagers.add(tm)
}
}
}
if (x509TrustManagers.isEmpty()) {
throw RuntimeException("Couldn't find any X509TrustManagers")
}
}
/*
* Iterate over the returned trustmanagers, and hold on
* to any that are X509TrustManagers
*/
for (tmf in factories) {
for (tm in tmf.trustManagers) {
if (tm is X509TrustManager) {
x509TrustManagers.add(tm)
}
}
}
if (x509TrustManagers.isEmpty()) {
throw RuntimeException("Couldn't find any X509TrustManagers")
}
}
private val x509TrustManagers: MutableList<X509TrustManager> = ArrayList()
private val x509TrustManagers: MutableList<X509TrustManager> = ArrayList()
init {
setCustomKeyStore(keyStore)
}
init {
setCustomKeyStore(keyStore)
}
}

View file

@ -15,90 +15,100 @@ import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManager
class CustomSSLSocketFactory(context: Context) : SSLSocketFactory() {
override fun getDefaultCipherSuites(): Array<String> {
return sslSocketFactory.defaultCipherSuites
}
override fun getDefaultCipherSuites(): Array<String> {
return sslSocketFactory.defaultCipherSuites
}
override fun getSupportedCipherSuites(): Array<String> {
return sslSocketFactory.supportedCipherSuites
}
override fun getSupportedCipherSuites(): Array<String> {
return sslSocketFactory.supportedCipherSuites
}
@Throws(IOException::class)
override fun createSocket(): Socket {
return enableProtocols(sslSocketFactory.createSocket())
}
@Throws(IOException::class)
override fun createSocket(): Socket {
return enableProtocols(sslSocketFactory.createSocket())
}
@Throws(IOException::class)
override fun createSocket(s: Socket, host: String, port: Int,
autoClose: Boolean): Socket {
return enableProtocols(
sslSocketFactory.createSocket(s, host, port, autoClose))
}
@Throws(IOException::class)
override fun createSocket(
s: Socket, host: String, port: Int, autoClose: Boolean
): Socket {
return enableProtocols(
sslSocketFactory.createSocket(s, host, port, autoClose)
)
}
@Throws(IOException::class)
override fun createSocket(host: String, port: Int): Socket {
return enableProtocols(sslSocketFactory.createSocket(host, port))
}
@Throws(IOException::class)
override fun createSocket(host: String, port: Int): Socket {
return enableProtocols(sslSocketFactory.createSocket(host, port))
}
@Throws(IOException::class)
override fun createSocket(host: String, port: Int, localHost: InetAddress,
localPort: Int): Socket {
return enableProtocols(
sslSocketFactory.createSocket(host, port, localHost, localPort))
}
@Throws(IOException::class)
override fun createSocket(
host: String, port: Int, localHost: InetAddress, localPort: Int
): Socket {
return enableProtocols(
sslSocketFactory.createSocket(host, port, localHost, localPort)
)
}
@Throws(IOException::class)
override fun createSocket(host: InetAddress, port: Int): Socket {
return enableProtocols(sslSocketFactory.createSocket(host, port))
}
@Throws(IOException::class)
override fun createSocket(host: InetAddress, port: Int): Socket {
return enableProtocols(sslSocketFactory.createSocket(host, port))
}
@Throws(IOException::class)
override fun createSocket(address: InetAddress, port: Int,
localAddress: InetAddress, localPort: Int): Socket {
return enableProtocols(
sslSocketFactory.createSocket(address, port, localAddress,
localPort))
}
@Throws(IOException::class)
override fun createSocket(
address: InetAddress,
port: Int,
localAddress: InetAddress,
localPort: Int
): Socket {
return enableProtocols(
sslSocketFactory.createSocket(
address, port, localAddress, localPort
)
)
}
fun reload(context: Context) {
val keyStore = makeCustomKeyStore(context)
trustManager.setCustomKeyStore(keyStore)
}
fun reload(context: Context) {
val keyStore = makeCustomKeyStore(context)
trustManager.setCustomKeyStore(keyStore)
}
private fun enableProtocols(socket: Socket): Socket {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// enable TLSv1.1 and TLSv1.2 Protocols for API level 19 and below
if (socket is SSLSocket) {
socket.enabledProtocols = arrayOf("TLSv1.1", "TLSv1.2")
}
}
return socket
}
private fun enableProtocols(socket: Socket): Socket {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// enable TLSv1.1 and TLSv1.2 Protocols for API level 19 and below
if (socket is SSLSocket) {
socket.enabledProtocols = arrayOf("TLSv1.1", "TLSv1.2")
}
}
return socket
}
private fun makeCustomKeyStore(context: Context): KeyStore {
// build key store with ca certificate
val keyStoreType = KeyStore.getDefaultType()
val keyStore = KeyStore.getInstance(keyStoreType)
keyStore.load(null, null)
private fun makeCustomKeyStore(context: Context): KeyStore {
// build key store with ca certificate
val keyStoreType = KeyStore.getDefaultType()
val keyStore = KeyStore.getInstance(keyStoreType)
keyStore.load(null, null)
val certManager = SelfSignedCertManager()
val certs = certManager.readAllCerts(context)
for (c in certs) {
keyStore.setCertificateEntry(c.first.host, c.second)
}
return keyStore
}
val certManager = SelfSignedCertManager()
val certs = certManager.readAllCerts(context)
for (c in certs) {
keyStore.setCertificateEntry(c.first.host, c.second)
}
return keyStore
}
private val sslSocketFactory: SSLSocketFactory
private val trustManager: CustomKeyStoresTrustManager
private val sslSocketFactory: SSLSocketFactory
private val trustManager: CustomKeyStoresTrustManager
init {
val keyStore = makeCustomKeyStore(context)
trustManager = CustomKeyStoresTrustManager(keyStore)
init {
val keyStore = makeCustomKeyStore(context)
trustManager = CustomKeyStoresTrustManager(keyStore)
// Create an SSLContext that uses our TrustManager
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, arrayOf<TrustManager>(trustManager), null)
sslSocketFactory = sslContext.socketFactory
}
// Create an SSLContext that uses our TrustManager
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, arrayOf<TrustManager>(trustManager), null)
sslSocketFactory = sslContext.socketFactory
}
}

View file

@ -8,40 +8,40 @@ import com.nkming.nc_photos.plugin.NcPhotosPlugin
import io.flutter.plugin.common.EventChannel
class DownloadEventCancelChannelHandler(context: Context) : BroadcastReceiver(),
EventChannel.StreamHandler {
companion object {
@JvmStatic
val CHANNEL =
"com.nkming.nc_photos/download_event/action_download_cancel"
}
EventChannel.StreamHandler {
companion object {
@JvmStatic
val CHANNEL =
"com.nkming.nc_photos/download_event/action_download_cancel"
}
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action != NcPhotosPlugin.ACTION_DOWNLOAD_CANCEL || !intent.hasExtra(
NcPhotosPlugin.EXTRA_NOTIFICATION_ID
)
) {
return
}
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action != NcPhotosPlugin.ACTION_DOWNLOAD_CANCEL || !intent.hasExtra(
NcPhotosPlugin.EXTRA_NOTIFICATION_ID
)
) {
return
}
val id = intent.getIntExtra(NcPhotosPlugin.EXTRA_NOTIFICATION_ID, 0)
_eventSink?.success(
mapOf(
"notificationId" to id
)
)
}
val id = intent.getIntExtra(NcPhotosPlugin.EXTRA_NOTIFICATION_ID, 0)
_eventSink?.success(
mapOf(
"notificationId" to id
)
)
}
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
_context.registerReceiver(
this, IntentFilter(NcPhotosPlugin.ACTION_DOWNLOAD_CANCEL)
)
_eventSink = events
}
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
_context.registerReceiver(
this, IntentFilter(NcPhotosPlugin.ACTION_DOWNLOAD_CANCEL)
)
_eventSink = events
}
override fun onCancel(arguments: Any?) {
_context.unregisterReceiver(this)
}
override fun onCancel(arguments: Any?) {
_context.unregisterReceiver(this)
}
private val _context = context
private var _eventSink: EventChannel.EventSink? = null
private val _context = context
private var _eventSink: EventChannel.EventSink? = null
}

View file

@ -1,6 +0,0 @@
package com.nkming.nc_photos
interface K {
companion object {
}
}

View file

@ -6,11 +6,11 @@ import android.os.Bundle
import androidx.annotation.NonNull
import com.google.android.gms.maps.MapsInitializer
import com.google.android.gms.maps.OnMapsSdkInitializedCallback
import com.nkming.nc_photos.np_android_log.logD
import com.nkming.nc_photos.np_android_log.logE
import com.nkming.nc_photos.np_android_log.logI
import com.nkming.nc_photos.plugin.NcPhotosPlugin
import com.nkming.nc_photos.plugin.UriUtil
import com.nkming.nc_photos.np_android_core.UriUtil
import com.nkming.nc_photos.np_android_core.logD
import com.nkming.nc_photos.np_android_core.logE
import com.nkming.nc_photos.np_android_core.logI
import com.nkming.nc_photos.np_platform_image_processor.NpPlatformImageProcessorPlugin
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
@ -19,117 +19,117 @@ import io.flutter.plugin.common.MethodChannel
import java.net.URLEncoder
class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler,
OnMapsSdkInitializedCallback {
companion object {
private const val METHOD_CHANNEL = "com.nkming.nc_photos/activity"
OnMapsSdkInitializedCallback {
companion object {
private const val METHOD_CHANNEL = "com.nkming.nc_photos/activity"
private const val TAG = "MainActivity"
}
private const val TAG = "MainActivity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (intent.action == NcPhotosPlugin.ACTION_SHOW_IMAGE_PROCESSOR_RESULT) {
val route = getRouteFromImageProcessorResult(intent) ?: return
logI(TAG, "Initial route: $route")
_initialRoute = route
}
MapsInitializer.initialize(
applicationContext, MapsInitializer.Renderer.LATEST, this
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (intent.action == NpPlatformImageProcessorPlugin.ACTION_SHOW_IMAGE_PROCESSOR_RESULT) {
val route = getRouteFromImageProcessorResult(intent) ?: return
logI(TAG, "Initial route: $route")
_initialRoute = route
}
MapsInitializer.initialize(
applicationContext, MapsInitializer.Renderer.LATEST, this
)
}
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
SelfSignedCertChannelHandler.CHANNEL
).setMethodCallHandler(
SelfSignedCertChannelHandler(this)
)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
ShareChannelHandler.CHANNEL
).setMethodCallHandler(
ShareChannelHandler(this)
)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger, METHOD_CHANNEL
).setMethodCallHandler(this)
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
SelfSignedCertChannelHandler.CHANNEL
).setMethodCallHandler(
SelfSignedCertChannelHandler(this)
)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
ShareChannelHandler.CHANNEL
).setMethodCallHandler(
ShareChannelHandler(this)
)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger, METHOD_CHANNEL
).setMethodCallHandler(this)
EventChannel(
flutterEngine.dartExecutor.binaryMessenger,
DownloadEventCancelChannelHandler.CHANNEL
).setStreamHandler(
DownloadEventCancelChannelHandler(this)
)
}
EventChannel(
flutterEngine.dartExecutor.binaryMessenger,
DownloadEventCancelChannelHandler.CHANNEL
).setStreamHandler(
DownloadEventCancelChannelHandler(this)
)
}
override fun onNewIntent(intent: Intent) {
when (intent.action) {
NcPhotosPlugin.ACTION_SHOW_IMAGE_PROCESSOR_RESULT -> {
val route = getRouteFromImageProcessorResult(intent) ?: return
logI(TAG, "Navigate to route: $route")
flutterEngine?.navigationChannel?.pushRoute(route)
}
override fun onNewIntent(intent: Intent) {
when (intent.action) {
NpPlatformImageProcessorPlugin.ACTION_SHOW_IMAGE_PROCESSOR_RESULT -> {
val route = getRouteFromImageProcessorResult(intent) ?: return
logI(TAG, "Navigate to route: $route")
flutterEngine?.navigationChannel?.pushRoute(route)
}
else -> {
super.onNewIntent(intent)
}
}
}
else -> {
super.onNewIntent(intent)
}
}
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"consumeInitialRoute" -> {
result.success(_initialRoute)
_initialRoute = null
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"consumeInitialRoute" -> {
result.success(_initialRoute)
_initialRoute = null
}
"isNewGMapsRenderer" -> {
result.success(_isNewGMapsRenderer)
}
"isNewGMapsRenderer" -> {
result.success(_isNewGMapsRenderer)
}
else -> result.notImplemented()
}
}
else -> result.notImplemented()
}
}
override fun onMapsSdkInitialized(renderer: MapsInitializer.Renderer) {
_isNewGMapsRenderer = when (renderer) {
MapsInitializer.Renderer.LATEST -> {
logD(TAG, "Using new map renderer")
true
}
override fun onMapsSdkInitialized(renderer: MapsInitializer.Renderer) {
_isNewGMapsRenderer = when (renderer) {
MapsInitializer.Renderer.LATEST -> {
logD(TAG, "Using new map renderer")
true
}
MapsInitializer.Renderer.LEGACY -> {
logD(TAG, "Using legacy map renderer")
false
}
}
}
MapsInitializer.Renderer.LEGACY -> {
logD(TAG, "Using legacy map renderer")
false
}
}
}
private fun getRouteFromImageProcessorResult(intent: Intent): String? {
val resultUri = intent.getParcelableExtra<Uri>(
NcPhotosPlugin.EXTRA_IMAGE_RESULT_URI
)
if (resultUri == null) {
logE(TAG, "Image result uri == null")
return null
}
return if (resultUri.scheme?.startsWith("http") == true) {
// remote uri
val encodedUrl = URLEncoder.encode(resultUri.toString(), "utf-8")
"/result-viewer?url=$encodedUrl"
} else {
val filename = UriUtil.resolveFilename(this, resultUri)?.let {
URLEncoder.encode(it, Charsets.UTF_8.toString())
}
StringBuilder().apply {
append("/enhanced-photo-browser?")
if (filename != null) append("filename=$filename")
}.toString()
}
}
private fun getRouteFromImageProcessorResult(intent: Intent): String? {
val resultUri = intent.getParcelableExtra<Uri>(
NpPlatformImageProcessorPlugin.EXTRA_IMAGE_RESULT_URI
)
if (resultUri == null) {
logE(TAG, "Image result uri == null")
return null
}
return if (resultUri.scheme?.startsWith("http") == true) {
// remote uri
val encodedUrl = URLEncoder.encode(resultUri.toString(), "utf-8")
"/result-viewer?url=$encodedUrl"
} else {
val filename = UriUtil.resolveFilename(this, resultUri)?.let {
URLEncoder.encode(it, Charsets.UTF_8.toString())
}
StringBuilder().apply {
append("/enhanced-photo-browser?")
if (filename != null) append("filename=$filename")
}.toString()
}
}
private var _initialRoute: String? = null
private var _isNewGMapsRenderer = false
private var _initialRoute: String? = null
private var _isNewGMapsRenderer = false
}

View file

@ -1,7 +1,7 @@
package com.nkming.nc_photos
import android.app.Activity
import com.nkming.nc_photos.np_android_log.logE
import com.nkming.nc_photos.np_android_core.logE
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import javax.net.ssl.HttpsURLConnection

View file

@ -2,8 +2,8 @@ package com.nkming.nc_photos
import android.content.Context
import android.util.Pair
import com.nkming.nc_photos.np_android_log.logE
import com.nkming.nc_photos.np_android_log.logI
import com.nkming.nc_photos.np_android_core.logE
import com.nkming.nc_photos.np_android_core.logI
import org.json.JSONObject
import java.io.File
import java.io.FileInputStream
@ -13,57 +13,63 @@ import java.time.OffsetDateTime
// Modifications to this class must also reflect on dart side
data class CertInfo(
val host: String, val sha1: String, val subject: String,
val issuer: String, val startValidity: OffsetDateTime,
val endValidity: OffsetDateTime
val host: String,
val sha1: String,
val subject: String,
val issuer: String,
val startValidity: OffsetDateTime,
val endValidity: OffsetDateTime
) {
companion object {
fun fromJson(json: JSONObject): CertInfo {
return CertInfo(
json.getString("host"), json.getString("sha1"),
json.getString("subject"), json.getString("issuer"),
OffsetDateTime.parse(json.getString("startValidity")),
OffsetDateTime.parse(json.getString("endValidity"))
)
}
}
companion object {
fun fromJson(json: JSONObject): CertInfo {
return CertInfo(
json.getString("host"),
json.getString("sha1"),
json.getString("subject"),
json.getString("issuer"),
OffsetDateTime.parse(json.getString("startValidity")),
OffsetDateTime.parse(json.getString("endValidity"))
)
}
}
}
class SelfSignedCertManager {
/**
* Read and return all persisted certificates
*
* @return List of certificates with the corresponding info
*/
fun readAllCerts(context: Context): List<Pair<CertInfo, Certificate>> {
val products = ArrayList<Pair<CertInfo, Certificate>>()
val certDir = openCertsDir(context)
val certFiles = certDir.listFiles()!!
val factory = CertificateFactory.getInstance("X.509")
for (f in certFiles) {
if (f.name.endsWith(".json")) {
// companion file
continue
}
try {
val c = factory.generateCertificate(FileInputStream(f))
val jsonFile = File(certDir, f.name + ".json")
val jsonStr = jsonFile.bufferedReader().use { it.readText() }
val info = CertInfo.fromJson(JSONObject(jsonStr))
logI(
"SelfSignedCertManager::readAllCerts",
"Found certificate: ${f.name} for host: ${info.host}"
)
products.add(Pair(info, c))
} catch (e: Exception) {
logE(
"SelfSignedCertManager::readAllCerts",
"Failed to read certificate file: ${f.name}", e
)
}
}
return products
}
/**
* Read and return all persisted certificates
*
* @return List of certificates with the corresponding info
*/
fun readAllCerts(context: Context): List<Pair<CertInfo, Certificate>> {
val products = ArrayList<Pair<CertInfo, Certificate>>()
val certDir = openCertsDir(context)
val certFiles = certDir.listFiles()!!
val factory = CertificateFactory.getInstance("X.509")
for (f in certFiles) {
if (f.name.endsWith(".json")) {
// companion file
continue
}
try {
val c = factory.generateCertificate(FileInputStream(f))
val jsonFile = File(certDir, f.name + ".json")
val jsonStr = jsonFile.bufferedReader().use { it.readText() }
val info = CertInfo.fromJson(JSONObject(jsonStr))
logI(
"SelfSignedCertManager::readAllCerts",
"Found certificate: ${f.name} for host: ${info.host}"
)
products.add(Pair(info, c))
} catch (e: Exception) {
logE(
"SelfSignedCertManager::readAllCerts",
"Failed to read certificate file: ${f.name}",
e
)
}
}
return products
}
// Outdated, don't use
// /**
@ -88,21 +94,21 @@ class SelfSignedCertManager {
// }
// }
private fun openCertsDir(context: Context): File {
val certDir = File(context.filesDir, "certs")
return if (!certDir.exists()) {
certDir.mkdir()
certDir
} else if (!certDir.isDirectory) {
logE(
"SelfSignedCertManager::openCertsDir",
"Removing certs file to make way for the directory"
)
certDir.delete()
certDir.mkdir()
certDir
} else {
certDir
}
}
private fun openCertsDir(context: Context): File {
val certDir = File(context.filesDir, "certs")
return if (!certDir.exists()) {
certDir.mkdir()
certDir
} else if (!certDir.isDirectory) {
logE(
"SelfSignedCertManager::openCertsDir",
"Removing certs file to make way for the directory"
)
certDir.delete()
certDir.mkdir()
certDir
} else {
certDir
}
}
}

View file

@ -8,130 +8,134 @@ import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
class ShareChannelHandler(activity: Activity) :
MethodChannel.MethodCallHandler {
companion object {
const val CHANNEL = "com.nkming.nc_photos/share"
}
MethodChannel.MethodCallHandler {
companion object {
const val CHANNEL = "com.nkming.nc_photos/share"
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"shareItems" -> {
try {
shareItems(
call.argument("fileUris")!!,
call.argument("mimeTypes")!!, result
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"shareItems" -> {
try {
shareItems(
call.argument("fileUris")!!,
call.argument("mimeTypes")!!,
result
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
"shareText" -> {
try {
shareText(
call.argument("text")!!, call.argument("mimeType"),
result
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
"shareText" -> {
try {
shareText(
call.argument("text")!!,
call.argument("mimeType"),
result
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
"shareAsAttachData" -> {
try {
shareAsAttachData(
call.argument("fileUri")!!, call.argument("mimeType"),
result
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
"shareAsAttachData" -> {
try {
shareAsAttachData(
call.argument("fileUri")!!,
call.argument("mimeType"),
result
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
else -> {
result.notImplemented()
}
}
}
else -> {
result.notImplemented()
}
}
}
private fun shareItems(
fileUris: List<String>, mimeTypes: List<String?>,
result: MethodChannel.Result
) {
assert(fileUris.isNotEmpty())
assert(fileUris.size == mimeTypes.size)
val uris = fileUris.map { Uri.parse(it) }
private fun shareItems(
fileUris: List<String>,
mimeTypes: List<String?>,
result: MethodChannel.Result
) {
assert(fileUris.isNotEmpty())
assert(fileUris.size == mimeTypes.size)
val uris = fileUris.map { Uri.parse(it) }
val shareIntent = if (uris.size == 1) Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM, uris[0])
// setting clipdata is needed for FLAG_GRANT_READ_URI_PERMISSION to
// work, see: https://developer.android.com/reference/android/content/Intent#ACTION_SEND
clipData =
ClipData.newUri(_context.contentResolver, "Share", uris[0])
type = mimeTypes[0] ?: "*/*"
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
} else Intent().apply {
action = Intent.ACTION_SEND_MULTIPLE
putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(uris))
clipData =
ClipData.newUri(_context.contentResolver, "Share", uris[0])
.apply {
for (uri in uris.subList(1, uris.size)) {
addItem(ClipData.Item(uri))
}
}
type = if (mimeTypes.all {
it?.startsWith("image/") == true
}) "image/*" else "*/*"
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
val shareChooser = Intent.createChooser(
shareIntent, _context.getString(
R.string.download_successful_notification_action_share_chooser
)
)
_context.startActivity(shareChooser)
result.success(null)
}
val shareIntent = if (uris.size == 1) Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM, uris[0])
// setting clipdata is needed for FLAG_GRANT_READ_URI_PERMISSION to
// work, see: https://developer.android.com/reference/android/content/Intent#ACTION_SEND
clipData =
ClipData.newUri(_context.contentResolver, "Share", uris[0])
type = mimeTypes[0] ?: "*/*"
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
} else Intent().apply {
action = Intent.ACTION_SEND_MULTIPLE
putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(uris))
clipData =
ClipData.newUri(_context.contentResolver, "Share", uris[0])
.apply {
for (uri in uris.subList(1, uris.size)) {
addItem(ClipData.Item(uri))
}
}
type = if (mimeTypes.all {
it?.startsWith("image/") == true
}) "image/*" else "*/*"
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
val shareChooser = Intent.createChooser(
shareIntent, _context.getString(
R.string.download_successful_notification_action_share_chooser
)
)
_context.startActivity(shareChooser)
result.success(null)
}
private fun shareText(
text: String, mimeType: String?, result: MethodChannel.Result
) {
val shareIntent = Intent().apply {
action = Intent.ACTION_SEND
type = mimeType ?: "text/plain"
putExtra(Intent.EXTRA_TEXT, text)
}
val shareChooser = Intent.createChooser(
shareIntent, _context.getString(
R.string.download_successful_notification_action_share_chooser
)
)
_context.startActivity(shareChooser)
result.success(null)
}
private fun shareText(
text: String, mimeType: String?, result: MethodChannel.Result
) {
val shareIntent = Intent().apply {
action = Intent.ACTION_SEND
type = mimeType ?: "text/plain"
putExtra(Intent.EXTRA_TEXT, text)
}
val shareChooser = Intent.createChooser(
shareIntent, _context.getString(
R.string.download_successful_notification_action_share_chooser
)
)
_context.startActivity(shareChooser)
result.success(null)
}
private fun shareAsAttachData(
fileUri: String, mimeType: String?, result: MethodChannel.Result
) {
val intent = Intent().apply {
action = Intent.ACTION_ATTACH_DATA
if (mimeType == null) {
data = Uri.parse(fileUri)
} else {
setDataAndType(Uri.parse(fileUri), mimeType)
}
addCategory(Intent.CATEGORY_DEFAULT)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
val chooser = Intent.createChooser(
intent, _context.getString(R.string.attach_data_chooser_title)
)
_context.startActivity(chooser)
result.success(null)
}
private fun shareAsAttachData(
fileUri: String, mimeType: String?, result: MethodChannel.Result
) {
val intent = Intent().apply {
action = Intent.ACTION_ATTACH_DATA
if (mimeType == null) {
data = Uri.parse(fileUri)
} else {
setDataAndType(Uri.parse(fileUri), mimeType)
}
addCategory(Intent.CATEGORY_DEFAULT)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
val chooser = Intent.createChooser(
intent, _context.getString(R.string.attach_data_chooser_title)
)
_context.startActivity(chooser)
result.success(null)
}
private val _activity = activity
private val _context get() = _activity
private val _activity = activity
private val _context get() = _activity
}

View file

@ -1,5 +1,5 @@
include ':app'
includeBuild '../../np_android_log'
includeBuild '../../np_android_core'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()

View file

@ -1,6 +1,6 @@
import 'package:logging/logging.dart';
import 'package:nc_photos/stream_extension.dart';
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
import 'package:np_platform_permission/np_platform_permission.dart';
Future<Map<String, int>> requestPermissionsForResult(
List<String> permissions) async {

View file

@ -27,6 +27,7 @@ import 'package:nc_photos_plugin/nc_photos_plugin.dart';
import 'package:np_async/np_async.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:np_collection/np_collection.dart';
import 'package:np_platform_permission/np_platform_permission.dart';
import 'package:np_platform_util/np_platform_util.dart';
part 'enhanced_photo_browser.g.dart';

View file

@ -4,7 +4,7 @@ import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/mobile/android/android_info.dart';
import 'package:nc_photos/mobile/android/permission_util.dart';
import 'package:nc_photos/snack_bar_manager.dart';
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
import 'package:np_platform_permission/np_platform_permission.dart';
import 'package:np_platform_util/np_platform_util.dart';
/// Handle platform permissions

View file

@ -23,7 +23,8 @@ import 'package:nc_photos/widget/image_editor/color_toolbar.dart';
import 'package:nc_photos/widget/image_editor/crop_controller.dart';
import 'package:nc_photos/widget/image_editor/transform_toolbar.dart';
import 'package:nc_photos/widget/image_editor_persist_option_dialog.dart';
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
import 'package:np_platform_image_processor/np_platform_image_processor.dart';
import 'package:np_platform_raw_image/np_platform_raw_image.dart';
import 'package:np_ui/np_ui.dart';
class ImageEditorArguments {

View file

@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/object_extension.dart';
import 'package:nc_photos/widget/image_editor/toolbar_button.dart';
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
import 'package:np_collection/np_collection.dart';
import 'package:np_platform_image_processor/np_platform_image_processor.dart';
import 'package:np_string/np_string.dart';
import 'package:np_ui/np_ui.dart';

View file

@ -4,8 +4,9 @@ import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos/pixel_image_provider.dart';
import 'package:nc_photos/widget/image_editor/transform_toolbar.dart';
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:np_platform_image_processor/np_platform_image_processor.dart';
import 'package:np_platform_raw_image/np_platform_raw_image.dart';
part 'crop_controller.g.dart';

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/widget/image_editor/toolbar_button.dart';
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
import 'package:np_platform_image_processor/np_platform_image_processor.dart';
enum TransformToolType {
crop,

View file

@ -28,8 +28,8 @@ import 'package:nc_photos/widget/handler/permission_handler.dart';
import 'package:nc_photos/widget/image_editor_persist_option_dialog.dart';
import 'package:nc_photos/widget/selectable.dart';
import 'package:nc_photos/widget/settings/enhancement_settings.dart';
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:np_platform_image_processor/np_platform_image_processor.dart';
import 'package:np_platform_util/np_platform_util.dart';
import 'package:np_ui/np_ui.dart';

View file

@ -17,7 +17,6 @@ import 'package:nc_photos/mobile/self_signed_cert_manager.dart';
import 'package:nc_photos/snack_bar_manager.dart';
import 'package:nc_photos/widget/page_visibility_mixin.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:np_platform_lock/np_platform_lock.dart';
import 'package:np_platform_util/np_platform_util.dart';
import 'package:to_string/to_string.dart';

View file

@ -1011,6 +1011,13 @@ packages:
relative: true
source: path
version: "1.0.0"
np_platform_image_processor:
dependency: "direct main"
description:
path: "../np_platform_image_processor"
relative: true
source: path
version: "0.0.1"
np_platform_lock:
dependency: "direct main"
description:
@ -1018,6 +1025,20 @@ packages:
relative: true
source: path
version: "0.0.1"
np_platform_permission:
dependency: "direct main"
description:
path: "../np_platform_permission"
relative: true
source: path
version: "0.0.1"
np_platform_raw_image:
dependency: "direct main"
description:
path: "../np_platform_raw_image"
relative: true
source: path
version: "0.0.1"
np_platform_util:
dependency: "direct main"
description:

View file

@ -109,8 +109,14 @@ dependencies:
path: ../np_log
np_math:
path: ../np_math
np_platform_image_processor:
path: ../np_platform_image_processor
np_platform_lock:
path: ../np_platform_lock
np_platform_permission:
path: ../np_platform_permission
np_platform_raw_image:
path: ../np_platform_raw_image
np_platform_util:
path: ../np_platform_util
np_string:

View file

@ -1,4 +1,4 @@
group 'com.nkming.nc_photos.np_android_log'
group 'com.nkming.nc_photos.np_android_core'
version '1.0-SNAPSHOT'
buildscript {
@ -26,7 +26,7 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
namespace 'com.nkming.nc_photos.np_android_log'
namespace 'com.nkming.nc_photos.np_android_core'
compileSdk 31
defaultConfig {
@ -51,4 +51,7 @@ android {
}
dependencies {
implementation "androidx.annotation:annotation:1.6.0"
implementation "androidx.core:core-ktx:1.10.1"
implementation "androidx.exifinterface:exifinterface:1.3.6"
}

View file

@ -0,0 +1 @@
rootProject.name = "np_android_core"

View file

@ -0,0 +1,244 @@
package com.nkming.nc_photos.np_android_core
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.net.Uri
import androidx.exifinterface.media.ExifInterface
import java.io.InputStream
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
fun Bitmap.aspectRatio() = width / height.toFloat()
/**
* Recycle the bitmap after @c block returns.
*
* @param block
* @return
*/
@OptIn(ExperimentalContracts::class)
inline fun <T> Bitmap.use(block: (Bitmap) -> T): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
try {
return block(this)
} finally {
recycle()
}
}
enum class BitmapResizeMethod {
FIT, FILL,
}
interface BitmapUtil {
companion object {
fun loadImageFixed(
context: Context, uri: Uri, targetW: Int, targetH: Int
): Bitmap {
val opt = loadImageBounds(context, uri)
val subsample = calcBitmapSubsample(
opt.outWidth,
opt.outHeight,
targetW,
targetH,
BitmapResizeMethod.FILL
)
if (subsample > 1) {
logD(
TAG,
"Subsample image to fixed: $subsample ${opt.outWidth}x${opt.outHeight} -> ${targetW}x$targetH"
)
}
val outOpt = BitmapFactory.Options().apply {
inSampleSize = subsample
}
val bitmap = loadImage(context, uri, outOpt)
if (subsample > 1) {
logD(TAG, "Bitmap subsampled: ${bitmap.width}x${bitmap.height}")
}
return Bitmap.createScaledBitmap(bitmap, targetW, targetH, true)
}
/**
* Load a bitmap
*
* If @c resizeMethod == FIT, make sure the size of the bitmap can fit
* inside the bound defined by @c targetW and @c targetH, i.e.,
* bitmap.w <= @c targetW and bitmap.h <= @c targetH
*
* If @c resizeMethod == FILL, make sure the size of the bitmap can
* completely fill the bound defined by @c targetW and @c targetH, i.e.,
* bitmap.w >= @c targetW and bitmap.h >= @c targetH
*
* If bitmap is smaller than the bound and @c shouldUpscale == true, it
* will be upscaled
*
* @param context
* @param uri
* @param targetW
* @param targetH
* @param resizeMethod
* @param isAllowSwapSide
* @param shouldUpscale
* @return
*/
fun loadImage(
context: Context,
uri: Uri,
targetW: Int,
targetH: Int,
resizeMethod: BitmapResizeMethod,
isAllowSwapSide: Boolean = false,
shouldUpscale: Boolean = true,
shouldFixOrientation: Boolean = false,
): Bitmap {
val opt = loadImageBounds(context, uri)
val shouldSwapSide =
isAllowSwapSide && opt.outWidth != opt.outHeight && (opt.outWidth >= opt.outHeight) != (targetW >= targetH)
val dstW = if (shouldSwapSide) targetH else targetW
val dstH = if (shouldSwapSide) targetW else targetH
val subsample = calcBitmapSubsample(
opt.outWidth, opt.outHeight, dstW, dstH, resizeMethod
)
if (subsample > 1) {
logD(
TAG,
"Subsample image to ${resizeMethod.name}: $subsample ${opt.outWidth}x${opt.outHeight} -> ${dstW}x$dstH" + (if (shouldSwapSide) " (swapped)" else "")
)
}
val outOpt = BitmapFactory.Options().apply {
inSampleSize = subsample
}
val bitmap = loadImage(context, uri, outOpt)
if (subsample > 1) {
logD(TAG, "Bitmap subsampled: ${bitmap.width}x${bitmap.height}")
}
if (bitmap.width < dstW && bitmap.height < dstH && !shouldUpscale) {
return if (shouldFixOrientation) {
fixOrientation(context, uri, bitmap)
} else {
bitmap
}
}
val result = when (resizeMethod) {
BitmapResizeMethod.FIT -> Bitmap.createScaledBitmap(
bitmap,
minOf(dstW, (dstH * bitmap.aspectRatio()).toInt()),
minOf(dstH, (dstW / bitmap.aspectRatio()).toInt()),
true
)
BitmapResizeMethod.FILL -> Bitmap.createScaledBitmap(
bitmap,
maxOf(dstW, (dstH * bitmap.aspectRatio()).toInt()),
maxOf(dstH, (dstW / bitmap.aspectRatio()).toInt()),
true
)
}
return if (shouldFixOrientation) {
fixOrientation(context, uri, result)
} else {
result
}
}
/**
* Rotate the image to its visible orientation
*/
private fun fixOrientation(
context: Context, uri: Uri, bitmap: Bitmap
): Bitmap {
return try {
openUriInputStream(context, uri)!!.use {
val iExif = ExifInterface(it)
val orientation =
iExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1)
logI(
TAG,
"[fixOrientation] Input file orientation: $orientation"
)
val rotate = when (orientation) {
ExifInterface.ORIENTATION_ROTATE_90, ExifInterface.ORIENTATION_TRANSPOSE -> 90f
ExifInterface.ORIENTATION_ROTATE_180, ExifInterface.ORIENTATION_FLIP_VERTICAL -> 180f
ExifInterface.ORIENTATION_ROTATE_270, ExifInterface.ORIENTATION_TRANSVERSE -> 270f
ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> 0f
else -> return bitmap
}
val mat = Matrix()
mat.postRotate(rotate)
if (orientation == ExifInterface.ORIENTATION_FLIP_HORIZONTAL || orientation == ExifInterface.ORIENTATION_TRANSVERSE || orientation == ExifInterface.ORIENTATION_FLIP_VERTICAL || orientation == ExifInterface.ORIENTATION_TRANSPOSE) {
mat.postScale(-1f, 1f)
}
Bitmap.createBitmap(
bitmap, 0, 0, bitmap.width, bitmap.height, mat, true
)
}
} catch (e: Throwable) {
logE(
TAG,
"[fixOrientation] Failed fixing, assume normal orientation",
e
)
bitmap
}
}
private fun openUriInputStream(
context: Context, uri: Uri
): InputStream? {
return if (UriUtil.isAssetUri(uri)) {
context.assets.open(UriUtil.getAssetUriPath(uri))
} else {
context.contentResolver.openInputStream(uri)
}
}
private fun loadImageBounds(
context: Context, uri: Uri
): BitmapFactory.Options {
openUriInputStream(context, uri)!!.use {
val opt = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeStream(it, null, opt)
return opt
}
}
private fun loadImage(
context: Context, uri: Uri, opt: BitmapFactory.Options
): Bitmap {
openUriInputStream(context, uri)!!.use {
return BitmapFactory.decodeStream(it, null, opt)!!
}
}
private fun calcBitmapSubsample(
originalW: Int,
originalH: Int,
targetW: Int,
targetH: Int,
resizeMethod: BitmapResizeMethod
): Int {
return when (resizeMethod) {
BitmapResizeMethod.FIT -> maxOf(
originalW / targetW, originalH / targetH
)
BitmapResizeMethod.FILL -> minOf(
originalW / targetW, originalH / targetH
)
}
}
private const val TAG = "BitmapUtil"
}
}

View file

@ -0,0 +1,3 @@
package com.nkming.nc_photos.np_android_core
class PermissionException(message: String) : Exception(message)

View file

@ -0,0 +1,7 @@
package com.nkming.nc_photos.np_android_core
internal interface K {
companion object {
const val PERMISSION_REQUEST_CODE = 11011
}
}

View file

@ -1,4 +1,4 @@
package com.nkming.nc_photos.np_android_log
package com.nkming.nc_photos.np_android_core
class LogConfig {
companion object {

View file

@ -1,4 +1,4 @@
package com.nkming.nc_photos.plugin
package com.nkming.nc_photos.np_android_core
import android.content.ContentValues
import android.content.Context

View file

@ -0,0 +1,42 @@
package com.nkming.nc_photos.np_android_core
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
interface PermissionUtil {
companion object {
const val REQUEST_CODE = K.PERMISSION_REQUEST_CODE
fun request(
activity: Activity, vararg permissions: String
) {
ActivityCompat.requestPermissions(
activity, permissions, REQUEST_CODE
)
}
fun hasReadExternalStorage(context: Context): Boolean {
return ContextCompat.checkSelfPermission(
context, Manifest.permission.READ_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
}
fun requestReadExternalStorage(activity: Activity) = request(
activity, Manifest.permission.READ_EXTERNAL_STORAGE
)
fun hasWriteExternalStorage(context: Context): Boolean {
return ContextCompat.checkSelfPermission(
context, Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
}
fun requestWriteExternalStorage(activity: Activity) = request(
activity, Manifest.permission.WRITE_EXTERNAL_STORAGE
)
}
}

View file

@ -0,0 +1,44 @@
package com.nkming.nc_photos.np_android_core
import android.graphics.Bitmap
import java.nio.ByteBuffer
/**
* Container of pixel data stored in RGBA format
*/
class Rgba8Image(
val pixel: ByteArray, val width: Int, val height: Int
) {
companion object {
fun fromJson(json: Map<String, Any>) = Rgba8Image(
json["pixel"] as ByteArray,
json["width"] as Int,
json["height"] as Int
)
fun fromBitmap(src: Bitmap): Rgba8Image {
assert(src.config == Bitmap.Config.ARGB_8888)
val buffer = ByteBuffer.allocate(src.width * src.height * 4).also {
src.copyPixelsToBuffer(it)
}
return Rgba8Image(buffer.array(), src.width, src.height)
}
}
fun toJson() = mapOf<String, Any>(
"pixel" to pixel,
"width" to width,
"height" to height,
)
fun toBitmap(): Bitmap {
return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
.apply {
copyPixelsFromBuffer(ByteBuffer.wrap(pixel))
}
}
init {
assert(pixel.size == width * height * 4)
}
}

View file

@ -1,9 +1,8 @@
package com.nkming.nc_photos.plugin
package com.nkming.nc_photos.np_android_core
import android.content.Context
import android.net.Uri
import android.provider.MediaStore
import com.nkming.nc_photos.np_android_log.logI
interface UriUtil {
companion object {

View file

@ -1,9 +1,8 @@
package com.nkming.nc_photos.plugin
package com.nkming.nc_photos.np_android_core
import android.app.PendingIntent
import android.os.Build
import android.os.Bundle
import com.nkming.nc_photos.np_android_log.logI
import java.io.Serializable
import java.net.HttpURLConnection

View file

@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -1 +0,0 @@
rootProject.name = "np_android_log"

30
np_platform_image_processor/.gitignore vendored Normal file
View file

@ -0,0 +1,30 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/

View file

@ -0,0 +1,33 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled.
version:
revision: f72efea43c3013323d1b95cff571f3c1caa37583
channel: stable
project_type: plugin
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: f72efea43c3013323d1b95cff571f3c1caa37583
base_revision: f72efea43c3013323d1b95cff571f3c1caa37583
- platform: android
create_revision: f72efea43c3013323d1b95cff571f3c1caa37583
base_revision: f72efea43c3013323d1b95cff571f3c1caa37583
- platform: ios
create_revision: f72efea43c3013323d1b95cff571f3c1caa37583
base_revision: f72efea43c3013323d1b95cff571f3c1caa37583
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View file

@ -0,0 +1 @@
include: package:np_lints/np.yaml

View file

@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx

View file

@ -0,0 +1,76 @@
group 'com.nkming.nc_photos.np_platform_image_processor'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.8.20'
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
namespace 'com.nkming.nc_photos.np_platform_image_processor'
compileSdk 31
ndkVersion "23.2.8568313"
defaultConfig {
minSdk 21
ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86_64"
}
consumerProguardFiles "consumer-rules.pro"
}
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.18.1'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
lintOptions {
disable 'LongLogTag'
}
}
dependencies {
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.3"
implementation "androidx.annotation:annotation:1.6.0"
implementation "androidx.core:core-ktx:1.10.1"
implementation "androidx.exifinterface:exifinterface:1.3.6"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'com.nkming.nc_photos.np_android_core:np_android_core'
}

View file

@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true

View file

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip

View file

@ -0,0 +1,2 @@
rootProject.name = 'np_platform_image_processor'
includeBuild '../../np_android_core'

View file

@ -0,0 +1,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.nkming.nc_photos.np_platform_image_processor">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application>
<service
android:name=".ImageProcessorService"
android:exported="false" />
</application>
</manifest>

View file

@ -8,7 +8,7 @@ cmake_minimum_required(VERSION 3.18.1)
# Declares and names the project.
project("plugin")
project("np_platform_image_processor")
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
@ -27,7 +27,7 @@ set_target_properties( renderscript-intrinsics-replacement-toolkit PROPERTIES IM
${dependency_DIR}/renderscript-intrinsics-replacement-toolkit/jni/${ANDROID_ABI}/librenderscript-toolkit.so )
add_library( # Sets the name of the library.
plugin
np_platform_image_processor
# Sets the library as a shared library.
SHARED
@ -62,7 +62,7 @@ add_library( # Sets the name of the library.
util.cpp
zero_dce.cpp
)
set_target_properties( plugin PROPERTIES COMPILE_OPTIONS -fopenmp )
set_target_properties( np_platform_image_processor PROPERTIES COMPILE_OPTIONS -fopenmp )
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
@ -79,7 +79,7 @@ find_library( # Sets the name of the path variable.
find_library( android-lib android )
target_include_directories( plugin PRIVATE
target_include_directories( np_platform_image_processor PRIVATE
${dependency_DIR}/tensorflowlite/headers
${dependency_DIR}/renderscript-intrinsics-replacement-toolkit/headers )
@ -88,7 +88,7 @@ target_include_directories( plugin PRIVATE
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
plugin
np_platform_image_processor
# Links the target library to the log library
# included in the NDK.

View file

@ -15,7 +15,7 @@
#include "tflite_wrapper.h"
#include "util.h"
using namespace plugin;
using namespace im_proc;
using namespace std;
using namespace tflite;
@ -60,7 +60,7 @@ private:
} // namespace
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_nkming_nc_1photos_plugin_image_1processor_ArbitraryStyleTransfer_inferNative(
Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_ArbitraryStyleTransfer_inferNative(
JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image,
jint width, jint height, jbyteArray style, jfloat weight) {
try {

View file

@ -7,7 +7,7 @@ extern "C" {
#endif
JNIEXPORT jbyteArray JNICALL
Java_com_nkming_nc_1photos_plugin_image_1processor_ArbitraryStyleTransfer_inferNative(
Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_ArbitraryStyleTransfer_inferNative(
JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image,
jint width, jint height, jbyteArray style, jfloat weight);

View file

@ -18,7 +18,7 @@
#include "util.h"
using namespace core;
using namespace plugin;
using namespace im_proc;
using namespace renderscript;
using namespace std;
using namespace tflite;
@ -151,7 +151,7 @@ private:
} // namespace
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_nkming_nc_1photos_plugin_image_1processor_DeepLab3Portrait_inferNative(
Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_DeepLab3Portrait_inferNative(
JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image,
jint width, jint height, jint radius) {
try {
@ -176,7 +176,7 @@ Java_com_nkming_nc_1photos_plugin_image_1processor_DeepLab3Portrait_inferNative(
}
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_nkming_nc_1photos_plugin_image_1processor_DeepLab3ColorPop_inferNative(
Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_DeepLab3ColorPop_inferNative(
JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image,
jint width, jint height, jfloat weight) {
try {

View file

@ -7,12 +7,12 @@ extern "C" {
#endif
JNIEXPORT jbyteArray JNICALL
Java_com_nkming_nc_1photos_plugin_image_1processor_DeepLab3Portrait_inferNative(
Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_DeepLab3Portrait_inferNative(
JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image,
jint width, jint height, jint radius);
JNIEXPORT jbyteArray JNICALL
Java_com_nkming_nc_1photos_plugin_image_1processor_DeepLab3ColorPop_inferNative(
Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_DeepLab3ColorPop_inferNative(
JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image,
jint width, jint height, jfloat weight);

View file

@ -16,7 +16,7 @@
#include "tflite_wrapper.h"
#include "util.h"
using namespace plugin;
using namespace im_proc;
using namespace std;
using namespace tflite;
@ -47,7 +47,7 @@ private:
} // namespace
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_nkming_nc_1photos_plugin_image_1processor_Esrgan_inferNative(
Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_Esrgan_inferNative(
JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image,
jint width, jint height) {
try {

Some files were not shown because too many files have changed in this diff Show more