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 // fix crash on sdk33, need investigation
implementation "androidx.window:window:1.0.0" implementation "androidx.window:window:1.0.0"
implementation 'com.google.android.gms:play-services-maps:18.1.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" coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.3"
} }

View file

@ -1,14 +1,14 @@
package com.nkming.nc_photos 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.BuildConfig
import io.flutter.app.FlutterApplication import io.flutter.app.FlutterApplication
class App : FlutterApplication() { class App : FlutterApplication() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
LogConfig.isShowInfo = BuildConfig.DEBUG LogConfig.isShowInfo = BuildConfig.DEBUG
LogConfig.isShowDebug = BuildConfig.DEBUG LogConfig.isShowDebug = BuildConfig.DEBUG
LogConfig.isShowVerbose = BuildConfig.DEBUG LogConfig.isShowVerbose = BuildConfig.DEBUG
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
package com.nkming.nc_photos package com.nkming.nc_photos
import android.app.Activity 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.MethodCall
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:nc_photos/stream_extension.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( Future<Map<String, int>> requestPermissionsForResult(
List<String> permissions) async { 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_async/np_async.dart';
import 'package:np_codegen/np_codegen.dart'; import 'package:np_codegen/np_codegen.dart';
import 'package:np_collection/np_collection.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'; import 'package:np_platform_util/np_platform_util.dart';
part 'enhanced_photo_browser.g.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/android_info.dart';
import 'package:nc_photos/mobile/android/permission_util.dart'; import 'package:nc_photos/mobile/android/permission_util.dart';
import 'package:nc_photos/snack_bar_manager.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'; import 'package:np_platform_util/np_platform_util.dart';
/// Handle platform permissions /// 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/crop_controller.dart';
import 'package:nc_photos/widget/image_editor/transform_toolbar.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/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'; import 'package:np_ui/np_ui.dart';
class ImageEditorArguments { class ImageEditorArguments {

View file

@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/object_extension.dart'; import 'package:nc_photos/object_extension.dart';
import 'package:nc_photos/widget/image_editor/toolbar_button.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_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_string/np_string.dart';
import 'package:np_ui/np_ui.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:logging/logging.dart';
import 'package:nc_photos/pixel_image_provider.dart'; import 'package:nc_photos/pixel_image_provider.dart';
import 'package:nc_photos/widget/image_editor/transform_toolbar.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_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'; part 'crop_controller.g.dart';

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/widget/image_editor/toolbar_button.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 { enum TransformToolType {
crop, 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/image_editor_persist_option_dialog.dart';
import 'package:nc_photos/widget/selectable.dart'; import 'package:nc_photos/widget/selectable.dart';
import 'package:nc_photos/widget/settings/enhancement_settings.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_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_platform_util/np_platform_util.dart';
import 'package:np_ui/np_ui.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/snack_bar_manager.dart';
import 'package:nc_photos/widget/page_visibility_mixin.dart'; import 'package:nc_photos/widget/page_visibility_mixin.dart';
import 'package:np_codegen/np_codegen.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:np_platform_util/np_platform_util.dart';
import 'package:to_string/to_string.dart'; import 'package:to_string/to_string.dart';

View file

@ -1011,6 +1011,13 @@ packages:
relative: true relative: true
source: path source: path
version: "1.0.0" 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: np_platform_lock:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1018,6 +1025,20 @@ packages:
relative: true relative: true
source: path source: path
version: "0.0.1" 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: np_platform_util:
dependency: "direct main" dependency: "direct main"
description: description:

View file

@ -109,8 +109,14 @@ dependencies:
path: ../np_log path: ../np_log
np_math: np_math:
path: ../np_math path: ../np_math
np_platform_image_processor:
path: ../np_platform_image_processor
np_platform_lock: np_platform_lock:
path: ../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: np_platform_util:
path: ../np_platform_util path: ../np_platform_util
np_string: 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' version '1.0-SNAPSHOT'
buildscript { buildscript {
@ -26,7 +26,7 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
android { android {
namespace 'com.nkming.nc_photos.np_android_log' namespace 'com.nkming.nc_photos.np_android_core'
compileSdk 31 compileSdk 31
defaultConfig { defaultConfig {
@ -51,4 +51,7 @@ android {
} }
dependencies { 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 { class LogConfig {
companion object { 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.ContentValues
import android.content.Context 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.content.Context
import android.net.Uri import android.net.Uri
import android.provider.MediaStore import android.provider.MediaStore
import com.nkming.nc_photos.np_android_log.logI
interface UriUtil { interface UriUtil {
companion object { 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.app.PendingIntent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import com.nkming.nc_photos.np_android_log.logI
import java.io.Serializable import java.io.Serializable
import java.net.HttpURLConnection 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. # Declares and names the project.
project("plugin") project("np_platform_image_processor")
# Creates and names a library, sets it as either STATIC # Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code. # 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 ) ${dependency_DIR}/renderscript-intrinsics-replacement-toolkit/jni/${ANDROID_ABI}/librenderscript-toolkit.so )
add_library( # Sets the name of the library. add_library( # Sets the name of the library.
plugin np_platform_image_processor
# Sets the library as a shared library. # Sets the library as a shared library.
SHARED SHARED
@ -62,7 +62,7 @@ add_library( # Sets the name of the library.
util.cpp util.cpp
zero_dce.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 # Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by # 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 ) find_library( android-lib android )
target_include_directories( plugin PRIVATE target_include_directories( np_platform_image_processor PRIVATE
${dependency_DIR}/tensorflowlite/headers ${dependency_DIR}/tensorflowlite/headers
${dependency_DIR}/renderscript-intrinsics-replacement-toolkit/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. # build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library. target_link_libraries( # Specifies the target library.
plugin np_platform_image_processor
# Links the target library to the log library # Links the target library to the log library
# included in the NDK. # included in the NDK.

View file

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

View file

@ -7,7 +7,7 @@ extern "C" {
#endif #endif
JNIEXPORT jbyteArray JNICALL 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, JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image,
jint width, jint height, jbyteArray style, jfloat weight); jint width, jint height, jbyteArray style, jfloat weight);

View file

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

View file

@ -7,12 +7,12 @@ extern "C" {
#endif #endif
JNIEXPORT jbyteArray JNICALL 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, JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image,
jint width, jint height, jint radius); jint width, jint height, jint radius);
JNIEXPORT jbyteArray JNICALL 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, JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image,
jint width, jint height, jfloat weight); jint width, jint height, jfloat weight);

View file

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

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